Skip to content

core.soul

← Module catalog

keeper-sidesoulstack.orchestrationorchestration

Register a Soul by SID in the Keeper's registry and bind it to stable Coven labels.

core.soul.registered binds a Soul (by SID) to a set of stable Coven labels in the Keeper's registries (Postgres souls + coven) — the step runs on the Keeper itself, and the on: keeper dispatcher is required (otherwise a scenario validation error). It is idempotent by design: changed=true only if the record was created by the module or the resulting coven set changed (order-independent comparison). It accepts a single SID or a list (via CEL register.<step>.hosts) and optionally carries the blocking await_online onboarding barrier; the module does not issue bootstrap tokens or SoulSeed — that is the onboarding's responsibility.

Requirements

  • Rootnot required
  • Sidekeeper-side
  • Collectionsoulstack.orchestration
  • Categoryorchestration

States

core.soul.registered — The Soul with the given sid is in the Keeper's registry and bound to a set of Coven labels (per mode); it optionally carries the await_online onboarding barrier.

Changes when

The souls record was created by the module, or the resulting coven set differs from the current one (order-independent comparison).

Stays unchanged when

The souls record already existed and the coven set matched.

Parameters

ParamTypeRequired / defaultDescription
sidstringrequiredSID (FQDN) of the target Soul: a single string OR a list (via CEL `${ register.<step>.hosts }`) — register N hosts in one barrier step, ADR-061.
covenlist<string>requiredA set of Coven labels (kebab-case), applied to all SIDs.
modestringoptionalappend (default) | replace | remove.
refresh_soulprintbooloptionalMid-run re-resolve of the run roster (ADR-061; stratification/re-resolve — slices S2/S3, echo refreshed:false).
await_onlinebooloptionalOnboarding barrier (ADR-061): after registration, block until the created Souls come online (Redis SID-lease).
await_timeoutstring
format: duration
optionalUpper bound on the barrier wait. Required when await_online: true. Ceiling — keeper.yml::max_await_timeout.
await_min_countintoptionalMinimum online hosts for the barrier to succeed. Default — the number of registered SIDs (all).
await_poll_intervalstring
format: duration
optionalPresence poll interval (default ~2s).

Example — Bind a new Soul to the incarnation's root coven (append by default)

- name: Bind new replica to the incarnation root coven
on: keeper
module: core.soul.registered
params:
sid: "${ vars.new_sid }"
coven: ["${ incarnation.name }"]

Example — Register a list of provisioned VMs with an onboarding barrier (ADR-061)

- name: Register provisioned shards and await onboarding
on: keeper
module: core.soul.registered
register: shards
params:
sid: "${ register.provision.hosts }"
coven: ["${ incarnation.name }"]
await_online: true
await_timeout: 10m

Output

FieldTypeDescription
sidstring | array<string>echo of the input: a string for a single sid, an array for a list
covenarray<string>the resulting coven set after applying mode (not the passed argument)
modestringthe applied mode
createdbooltrue if at least one souls record was created by the module
refreshedboolecho of refresh_soulprint
removedarray<string>only with mode: remove: the labels actually removed
onlinearray<string>only with await_online: the SIDs that became online by the time of success/timeout
pendingarray<string>only with await_online: the SIDs that did not reach online by the timeout (B1-strict diagnostics)
satisfiedboolonly with await_online: whether await_min_count was reached

Reference

mode semantics

The strategy for applying the passed coven set to the host's already-bound labels.

modeResulting coven setEdge behavior
append (default)existing ∪ passedrepeat with the same set — no-op (changed=false)
replaceonly the passed ones (unmentioned are removed)empty coven: [] — error (the host must keep at least one label)
removeexisting minus passedlabels not present on the host are skipped without error

Notes

  • Keeper-side, does not touch the host: all side effects are in the Keeper's registries (Postgres souls + coven), not in the file system or the Soul's processes. The module has no required_capabilities manifest — this is a keeper-internal operation on Postgres, and the host's root/capability do not apply here (access is the operator's keeper permission, see secure_defaults).
  • If there is no souls record for the SID yet, the module creates one with status: pending (transport: agent, empty coven, CreatedByAID/LastSeenAt = null): a new host from a scenario or a host after a cloud-create. It does not issue bootstrap tokens or SoulSeed.
  • list-SID: sid accepts a string OR a list. The target case is one create-scenario creating N VMs (their sids arrive as a list in register.<provision>.hosts from core.cloud.created), and this step registers them and (with await_online) waits for onboarding with a single barrier; the passed coven is applied to each SID. A literal list sid: [a, b] does not pass soul-lint statically (the manifest declares type: string) — the list always arrives as a CEL expression register.*. The output form follows the input: a single sid → a string, a list → an array.
  • The await_online onboarding barrier: after registering all SIDs, the step blocks and polls presence (period await_poll_interval, default ~2s) under a shared await_timeout until the number of ready hosts is ≥ await_min_count (default — all SIDs). The source of online is the Redis SID-lease (a live EventStream), not PG souls.status. await_timeout is required when await_online: true and is capped from above by keeper.yml::max_await_timeout. A quorum shortfall at the timeout → the step fails → fail-stop of the run (B1-strict). await_online without a configured presence-checker on the keeper → failed (a silent success is not allowed).
  • refresh_soulprint (ADR-061, S2/S3): true makes the step a stratification boundary — after it succeeds the scenario-runner re-resolves the run's roster before the next Passage (a live snapshot), and the refreshed output echoes the flag. Together with await_online it tightens the barrier to a facts-wait: a SID is ready = online AND a typed soulprint is written to PG (souls.soulprint_facts IS NOT NULL) — this removes the render_failed race on provision-from-zero.

Security & defaults

  • Keeper-side, not Soul-side: the step runs in the Keeper's process (the on: keeper dispatcher), not by a soul agent on the host, so root/capability semantics do not apply. Access to running such a scenario is governed by the operator's RBAC at the run level (a keeper permission); the core module itself declares no separate permission, and the created souls record is written with CreatedByAID: null (a keeper-internal action).
  • Input validation against injecting junk into the registry: sid is checked as an FQDN (keepersoul.ValidSID), each coven label as kebab-case 1..63 (keepersoul.ValidCoven); anything invalid — the step fails and nothing reaches the registry. This mirrors the POST /v1/souls API boundary, so the scenario path is not a back door around the checks.
  • Footgun protection for mode: replace: an empty coven: [] is rejected with an error — the host must keep at least one Coven label, otherwise it would lose the incarnation's root coven and drop out of targeting.
  • It does not issue bootstrap tokens or SoulSeed — the module neither produces nor discloses secrets; issuing bootstrap credentials remains the onboarding's responsibility.
  • DoS guard for the onboarding barrier: await_timeout is capped from above by the operator ceiling keeper.yml::max_await_timeout (default 30m, fail-closed) — a step with an inflated await_timeout is rejected as failed BEFORE polling, rather than silently clamped, so a mistaken await_timeout does not keep a run goroutine / Acolyte worker busy.

See also