supervision
supervision
¶
Post-start supervision check — did the per-container supervisor come up?
A container start deliberately survives a broken supervisor: the OCI hook soft-fails so a spawn failure never blocks the container, and terok-shield's egress firewall is fail-closed on its own hook. What must not happen is that the degradation stays silent — a container whose vault-routed providers and git gate are all dead, behaving normally at the shell, with nothing said (issue #458).
verify_supervision closes
that gap on the API launch path. After the container starts, it reads the
same sidecar the supervisor reads, and — when the sidecar declares
socket-mode services — polls briefly for the sockets the supervisor binds.
On timeout it returns a SupervisionStatus
naming the container, the unbound socket(s), and the hook diary to read;
the caller shouts it but the launch still succeeds (soft-fail preserved),
and an orchestrator may escalate on the structured result.
Both transports are covered. Socket-mode wiring binds sockets under
/run/terok; TCP-mode wiring binds per-container loopback ports, and
/proc/net/tcp says which ports are listening — one read answers for
every service at once, and it contacts none of them (an accept-and-close
probe on each start would put a stray connection in three service logs to
learn what a file already says). The pure-Python file poll never runs a
subprocess and never raises.
Every service the sidecar wires is checked, the SSH signer included. A dead signer costs the task its git access, and it is the child most likely to die alone: it and the vault are the only two that must open the credential store, so a passphrase the supervisor cannot resolve takes out exactly those two and leaves the rest of the bundle looking healthy.
outdated_container_warning
closes the mirror-image gap. A container's environment is frozen when it is
created. A container that outlives a change to the /run/terok layout
therefore keeps naming sockets this sandbox no longer binds. The supervisor is
healthy and the bridges start; they connect to nothing. The symptom arrives
half a minute later as an empty reply, not as a refused connection. The check
reads the container's protocol stamp and reports the gap. It never enforces —
an operator can keep using the parts of the container that still work, and
recreate it when that suits them.
MIN_RUNTIME_PROTOCOL = 3
module-attribute
¶
Lowest TEROK_CONTAINER_PROTOCOL whose socket layout this sandbox still binds.
Each service's socket moved into its own /run/terok subdirectory, which
changed TEROK_VAULT_SOCKET, TEROK_SSH_SIGNER_SOCKET and
TEROK_GATE_SOCKET. A container stamped below this value predates that
move. terok-executor stamps the containers, so this value tracks
CONTAINER_PROTOCOL there.
__all__ = ['MIN_RUNTIME_PROTOCOL', 'ServiceEndpoint', 'SupervisionStatus', 'outdated_container_warning', 'verify_supervision', 'warn_unsupervised']
module-attribute
¶
ServiceEndpoint(service, socket=None, port=None)
dataclass
¶
One supervisor service and the address its child binds.
Exactly one of socket and port is set — the sidecar's transport decides which — so the poll can test either kind and the warning can name the address the operator will look for.
SupervisionStatus(container_name, checked, missing, hook_log, skipped=False, hook_fired=None, supervisor_log=None)
dataclass
¶
The result of a post-start supervision check for one container.
missing is the subset of checked endpoints still unbound when
the poll gave up — empty on a healthy start. skipped marks the
cases with nothing to verify (no sidecar, or a host whose listening
ports cannot be read), which is not a failure.
container_name
instance-attribute
¶
checked
instance-attribute
¶
missing
instance-attribute
¶
hook_log
instance-attribute
¶
skipped = False
class-attribute
instance-attribute
¶
hook_fired = None
class-attribute
instance-attribute
¶
Whether the hook diary has an entry for this container; None when unknown.
supervisor_log = None
class-attribute
instance-attribute
¶
The per-container supervisor log, when the container id was known.
ok
property
¶
True when every required endpoint was bound (or nothing needed checking).
warning()
¶
A loud, multi-line operator warning naming the failure and where to look.
Source code in src/terok_sandbox/supervision.py
verify_supervision(cfg, container_name, *, find_container_id=None, timeout=_DEFAULT_TIMEOUT_S)
¶
Poll for the supervisor's sockets after container_name has started.
Reads <state>/sidecar/<container_name>.json — the same bundle the
supervisor reads — and, in socket mode, waits up to timeout seconds
for the vault socket (always bound) and the gate socket (when the
sidecar wired a gate). Returns a
SupervisionStatus; a
missing socket means the supervisor is not up. find_container_id
is asked for the container's id only when something is missing; with
it the status also says whether the hook diary saw this container, so
the warning points at the log that has the answer. Never raises and
never blocks a healthy start beyond the time the sockets take to
appear.
Source code in src/terok_sandbox/supervision.py
warn_unsupervised(status)
¶
Print the loud warning for a failed check to stderr; no-op when healthy.
outdated_container_warning(container_name, env)
¶
Return the warning for a container older than the current socket layout.
env is the environment recorded on the container at creation — see
Container.env. Returns
None for a current container, and for one with no usable stamp.
Sidecar tool containers are built from a minimal environment that never
carried the stamp, so its absence says nothing about age. Pure: no
filesystem, no subprocess, never raises.