Skip to content

status

status

One vault-state picture for every surface — CLI status, TUI pill, sickbay.

VaultStatus.load computes everything the frontends used to derive independently (and with independently-drifting wording): the lock classification, the per-tier chain table with shadowing, the session-shadow comparison, and a catalog of VaultWarnings whose text is authored exactly once. A renderer's whole job is picking which fields to show — never re-deciding what they mean.

"Locked" alone hides three different operator problems with three different remedies, so the classification keeps them apart:

  • VaultState.UNPROVISIONED — fresh install: no DB and no tier holds anything. The remedy is the provisioning flow, not an unlock prompt.
  • VaultState.LOCKED — a passphrase problem; lock_reason says which one (empty chain, wrong key, or a broken fail-closed tier).
  • VaultState.ERROR — the DB failed for a non-passphrase reason (schema drift, permissions); db_error carries it verbatim.
  • VaultState.UNLOCKED — a tier resolves and (when the DB exists) opens it.

__all__ = ['ChainRow', 'SessionShadow', 'VaultState', 'VaultStatus', 'VaultWarning', 'VaultWarningKind', 'active_durable_source', 'clear_redundant_session_file', 'session_shadow_state'] module-attribute

SessionShadow(durable_source, redundant) dataclass

A session-file tier sitting on top of a durable tier.

redundant is the actionable bit:

  • True — the session copy is byte-identical to what the durable tier resolves to, so it's pure residue (a past unlock on a box that already auto-unlocks). Safe to delete; nothing is lost.
  • False — the two differ. Either a deliberate re-key in progress or a stale unlock masking the durable value; never auto-removed.
  • None — the durable tier is present but couldn't be read to compare (broken seal, dead helper), so the session file may be doing real work. Left alone.

durable_source instance-attribute

redundant instance-attribute

VaultState

Bases: StrEnum

The four operator-distinct answers to "can I use the vault?".

UNPROVISIONED = 'unprovisioned' class-attribute instance-attribute

Fresh install — no credentials DB and no tier holds a passphrase. The remedy is the provisioning flow (setup / the TUI tier chooser), not an unlock prompt keying a brand-new vault to a typo.

LOCKED = 'locked' class-attribute instance-attribute

A passphrase problem — lock_reason names which of the three: empty chain, wrong key, or a broken fail-closed tier.

UNLOCKED = 'unlocked' class-attribute instance-attribute

A tier resolves the passphrase and, when the DB exists, opens it.

ERROR = 'error' class-attribute instance-attribute

The DB failed for a non-passphrase reason — db_error has it verbatim.

ChainRow(tier, present, active, shadowed, detail) dataclass

One tier of the resolution chain, annotated for display.

active marks the tier the resolver would use right now; shadowed marks a durable tier outranked by a volatile one (the "why is my TPM2 box reading a RAM-backed file?" diagnostic).

tier instance-attribute

present instance-attribute

active instance-attribute

shadowed instance-attribute

detail instance-attribute

VaultWarningKind

Bases: StrEnum

Stable identifiers for the warning catalog — frontends dispatch on these.

BROKEN_TIER = 'broken-tier' class-attribute instance-attribute

RECOVERY_UNCONFIRMED = 'recovery-unconfirmed' class-attribute instance-attribute

RECOVERY_VOLATILE = 'recovery-volatile' class-attribute instance-attribute

SHADOW_REDUNDANT = 'shadow-redundant' class-attribute instance-attribute

SHADOW_OVERRIDE = 'shadow-override' class-attribute instance-attribute

SHADOW_UNREADABLE = 'shadow-unreadable' class-attribute instance-attribute

VaultWarning(kind, severity, brief, message) dataclass

One vault warning, authored here and only here.

brief is the compact form for pills / one-line summaries; message the full sentence for notifications and status pages. kind is the semantic identifier — a renderer that wants to attach a remedy (a CLI verb, a TUI button) maps it per frontend rather than reading command strings out of the library layer.

kind instance-attribute

severity instance-attribute

brief instance-attribute

message instance-attribute

VaultStatus(state, lock_reason, db_error, source, chain, shadow, recovery, db_path, db_exists, providers, credential_types, ssh_keys, warnings) dataclass

Everything a frontend needs to render the vault — loaded in one call.

state instance-attribute

lock_reason instance-attribute

Why the vault counts as locked, in operator language. None unless state is LOCKED.

db_error instance-attribute

Verbatim non-passphrase DB failure. None unless state is ERROR.

source instance-attribute

The tier the resolver would use right now, or None.

chain instance-attribute

shadow instance-attribute

recovery instance-attribute

db_path instance-attribute

db_exists instance-attribute

False on a fresh install — the DB is created encrypted on first use, so "unlocked" then means "the key is ready", not "the DB opened".

providers instance-attribute

Sorted provider slugs stored in the vault; None when the DB couldn't be read (locked / error).

credential_types instance-attribute

provider → type (api_key / oauth_token / …), read in the same DB pass as providers so no renderer ever pays a second SQLCipher key derivation just to label a row. None exactly when providers is.

ssh_keys instance-attribute

Count of stored SSH keypairs, from the same DB pass. None exactly when providers is.

warnings instance-attribute

load(cfg=None) classmethod

Compute the full vault picture for cfg (defaults if None).

Read-only by construction: unlike a bare cfg.open_credential_db() this never creates the DB — a fresh install stays fresh no matter how often status is rendered. Never prompts (a status read must not block on stdin) and pays the one durable-tier unseal only when a session-shadow actually needs comparing.

Source code in src/terok_sandbox/vault/store/status.py
@classmethod
def load(cls, cfg: SandboxConfig | None = None) -> VaultStatus:
    """Compute the full vault picture for *cfg* (defaults if ``None``).

    Read-only by construction: unlike a bare
    ``cfg.open_credential_db()`` this never *creates* the DB — a
    fresh install stays fresh no matter how often status is
    rendered.  Never prompts (a status read must not block on
    stdin) and pays the one durable-tier unseal only when a
    session-shadow actually needs comparing.
    """
    cfg = _resolve_cfg(cfg)
    recovery = RecoveryStatus.load(cfg)
    chain = _encryption.probe_passphrase_chain(
        passphrase_file=cfg.vault_passphrase_file,
        systemd_creds_file=cfg.vault_systemd_creds_file,
        use_keyring=cfg.credentials_use_keyring,
        passphrase_command=cfg.credentials_passphrase_command,
    )
    active_index = next((i for i, tier in enumerate(chain) if tier.present), None)
    active_source = chain[active_index].source if active_index is not None else None
    # Shadowing only matters when a *volatile* tier (the session file)
    # sits on top of a durable one.  A durable active tier legitimately
    # outranks the tiers below it — that's just the resolution order.
    active_is_durable = active_source in DURABLE_TIERS
    rows = tuple(
        ChainRow(
            tier=presence.source,
            present=presence.present,
            active=(i == active_index),
            shadowed=(
                presence.present
                and active_index is not None
                and i > active_index
                and presence.source in DURABLE_TIERS
                and not active_is_durable
            ),
            detail=presence.detail,
        )
        for i, presence in enumerate(chain)
    )
    shadow = session_shadow_state(cfg) if any(row.shadowed for row in rows) else None

    db_exists = cfg.db_path.exists()
    access = _classify_db_access(cfg, recovery, db_exists=db_exists)
    if access.db_error is not None:
        state = VaultState.ERROR
    elif access.lock_reason is None:
        state = VaultState.UNLOCKED
    elif not db_exists and recovery.source is None and recovery.resolve_error is None:
        state = VaultState.UNPROVISIONED
    else:
        state = VaultState.LOCKED

    return cls(
        state=state,
        lock_reason=access.lock_reason,
        db_error=access.db_error,
        source=recovery.source,
        chain=rows,
        shadow=shadow,
        recovery=recovery,
        db_path=cfg.db_path,
        db_exists=db_exists,
        providers=access.providers,
        credential_types=access.credential_types,
        ssh_keys=access.ssh_keys,
        warnings=_build_warnings(recovery, shadow),
    )

active_durable_source(cfg)

Name the durable tier that already resolves the vault, or None.

Probes the chain minus the session file (file presence only — no unseal, no command exec): if a reboot-surviving tier is present, a session write would merely shadow it. The single source of truth for the no-shadow guard, shared by the session writer and the CLI's skip-the-prompt early-out.

Source code in src/terok_sandbox/vault/store/status.py
def active_durable_source(cfg: SandboxConfig) -> PassphraseTier | None:
    """Name the durable tier that already resolves the vault, or ``None``.

    Probes the chain *minus* the session file (file presence only — no
    unseal, no command exec): if a reboot-surviving tier is present, a
    session write would merely shadow it.  The single source of truth
    for the no-shadow guard, shared by the session writer and the CLI's
    skip-the-prompt early-out.
    """
    for tier in _encryption.probe_passphrase_chain(
        systemd_creds_file=cfg.vault_systemd_creds_file,
        use_keyring=cfg.credentials_use_keyring,
        passphrase_command=cfg.credentials_passphrase_command,
    ):
        if tier.present and tier.source in DURABLE_TIERS:
            return tier.source
    return None

session_shadow_state(cfg)

Describe a session-file-over-durable-tier shadow, or None if there is none.

Returns None in the common cases — no session file, or no durable tier beneath it — without resolving anything. Only when both are present does it resolve the durable chain (omitting the session file) to compare values; that one comparison is the only place status / remediation pay an unseal. The session secret never leaves the process — this reads two tiers and compares, exactly what the resolver already does internally.

Source code in src/terok_sandbox/vault/store/status.py
def session_shadow_state(cfg: SandboxConfig) -> SessionShadow | None:
    """Describe a session-file-over-durable-tier shadow, or ``None`` if there is none.

    Returns ``None`` in the common cases — no session file, or no durable
    tier beneath it — without resolving anything.  Only when both are
    present does it resolve the *durable* chain (omitting the session
    file) to compare values; that one comparison is the only place
    status / remediation pay an unseal.  The session secret never leaves
    the process — this reads two tiers and compares, exactly what the
    resolver already does internally.
    """
    session_value = _encryption.load_passphrase_from_file(cfg.vault_passphrase_file)
    if not session_value:
        return None
    durable_source = active_durable_source(cfg)
    if durable_source is None:
        return None
    try:
        durable_value, _ = _encryption.resolve_passphrase_with_source(
            systemd_creds_file=cfg.vault_systemd_creds_file,
            use_keyring=cfg.credentials_use_keyring,
            passphrase_command=cfg.credentials_passphrase_command,
            # ``passphrase_file`` omitted on purpose — resolve the durable
            # chain *under* the session file so we can compare against it.
        )
    except (WrongPassphraseError, NoPassphraseError):
        durable_value = None
    if not durable_value:
        return SessionShadow(durable_source, redundant=None)
    return SessionShadow(durable_source, redundant=(durable_value == session_value))

clear_redundant_session_file(cfg)

Remove the session-unlock file iff it merely duplicates a durable tier.

Same-key residue only: a session file whose passphrase differs from the durable tier is a deliberate override (or a re-key mid-flight) and is kept. Re-verifies state at call time rather than trusting a caller's stale read. Returns the durable tier it deduplicated against, or None when there was nothing safe to remove.

Source code in src/terok_sandbox/vault/store/status.py
def clear_redundant_session_file(cfg: SandboxConfig) -> PassphraseTier | None:
    """Remove the session-unlock file iff it merely duplicates a durable tier.

    Same-key residue only: a session file whose passphrase differs from
    the durable tier is a deliberate override (or a re-key mid-flight)
    and is kept.  Re-verifies state at call time rather than trusting a
    caller's stale read.  Returns the durable tier it deduplicated
    against, or ``None`` when there was nothing safe to remove.
    """
    shadow = session_shadow_state(cfg)
    if shadow is None or shadow.redundant is not True:
        return None
    cfg.vault_passphrase_file.unlink(missing_ok=True)
    return shadow.durable_source