Orchestrate missing-secret detection on top of axm-vault.
This module ORCHESTRATES; it never POSSESSES a secret. :func:missing_secrets
reads the vault catalog (:func:axm_vault.load_catalog) and the value-free
resolver provenance (:func:axm_vault.doctor.doctor_data) to surface the specs
that resolve to "missing" — without ever reading a secret value.
:func:provision_missing delegates to vault's :func:axm_vault.setup.run_setup
only on confirmation; doctor never writes a secret itself (every write goes
through vault's API — the SRP invariant).
Unlike :mod:axm_doctor.detect (bootstrap-sensitive, no AXM import), this is
the orchestration seam, so it depends on axm-vault directly.
MissingSecret
Bases: BaseModel
A credential spec that resolves to "missing" across every layer.
Value-less by construction: it carries only the coordinates of the spec
and a copy-pasteable recovery hint (setup_hint). The secret value
itself NEVER transits axm_doctor.
Source code in packages/axm-doctor/src/axm_doctor/orchestrate.py
| Python |
|---|
| class MissingSecret(BaseModel, frozen=True): # type: ignore[explicit-any]
"""A credential spec that resolves to ``"missing"`` across every layer.
Value-less by construction: it carries only the coordinates of the spec
and a copy-pasteable recovery hint (``setup_hint``). The secret value
itself NEVER transits axm_doctor.
"""
group: str
name: str
package: str
setup_hint: str
|
ProvisionResult
Bases: BaseModel
Outcome of :func:provision_missing.
On a dry-run (confirm=False) provisioned is False and groups
lists the groups it WOULD prompt for. On a confirmed run provisioned
is True ONLY when a post-setup re-scan confirms every previously-missing
spec now resolves — delegating to vault's setup driver is not proof the
user actually supplied the secrets (they may skip/empty the prompts).
still_missing lists the specs that remain unresolved after the run
(always empty on a dry-run), so a partial provisioning is reported truthfully
rather than as a false green.
Source code in packages/axm-doctor/src/axm_doctor/orchestrate.py
| Python |
|---|
| class ProvisionResult(BaseModel, frozen=True): # type: ignore[explicit-any]
"""Outcome of :func:`provision_missing`.
On a dry-run (``confirm=False``) ``provisioned`` is False and ``groups``
lists the groups it WOULD prompt for. On a confirmed run ``provisioned``
is True ONLY when a post-setup re-scan confirms every previously-missing
spec now resolves — delegating to vault's setup driver is not proof the
user actually supplied the secrets (they may skip/empty the prompts).
``still_missing`` lists the specs that remain unresolved after the run
(always empty on a dry-run), so a partial provisioning is reported truthfully
rather than as a false green.
"""
provisioned: bool
groups: list[str]
still_missing: list[str] = []
reason: str | None = None
|
missing_secrets()
Return the catalog specs that resolve to "missing", value-free.
Reads the vault catalog and the value-free provenance report; a spec is
reported when no resolver layer supplies it. An empty catalog (the
nominal state for vault today) yields [] gracefully.
Source code in packages/axm-doctor/src/axm_doctor/orchestrate.py
| Python |
|---|
| def missing_secrets() -> list[MissingSecret]:
"""Return the catalog specs that resolve to ``"missing"``, value-free.
Reads the vault catalog and the value-free provenance report; a spec is
reported when no resolver layer supplies it. An empty catalog (the
nominal state for vault today) yields ``[]`` gracefully.
"""
catalog = load_catalog()
provenance = doctor_data(catalog=catalog)
missing: list[MissingSecret] = []
for group in catalog.groups():
for spec in group.specs:
entry = provenance.get(f"{group.id}.{spec.name}")
if entry is None or entry.get("layer") != _MISSING:
continue
missing.append(
MissingSecret(
group=group.id,
name=spec.name,
package=group.package,
setup_hint=f"axm-vault set {group.id}.{spec.name}",
)
)
return missing
|
provision_missing(*, confirm=False)
Plan (and on confirm execute) provisioning of missing secrets.
Collects the distinct groups owning at least one missing spec. With
confirm=False it returns the plan without prompting or storing. With
confirm=True it delegates to vault's :func:run_setup (one call per
group, restricted via only=); doctor never stores a secret itself.
Source code in packages/axm-doctor/src/axm_doctor/orchestrate.py
| Python |
|---|
| def provision_missing(*, confirm: bool = False) -> ProvisionResult:
"""Plan (and on ``confirm`` execute) provisioning of missing secrets.
Collects the distinct groups owning at least one missing spec. With
``confirm=False`` it returns the plan without prompting or storing. With
``confirm=True`` it delegates to vault's :func:`run_setup` (one call per
group, restricted via ``only=``); doctor never stores a secret itself.
"""
groups: list[str] = []
for secret in missing_secrets():
if secret.group not in groups:
groups.append(secret.group)
if confirm and not sys.stdin.isatty():
return ProvisionResult(
provisioned=False,
groups=groups,
reason="non-interactive shell: cannot prompt for secrets",
)
if not confirm:
return ProvisionResult(provisioned=False, groups=groups)
for group in groups:
try:
run_setup(only=group)
except SystemExit as exc: # vault's setup driver aborts via SystemExit
return ProvisionResult(
provisioned=False,
groups=groups,
reason=f"vault setup aborted for {group} (exit {exc.code})",
)
# Re-scan: delegating to run_setup is NOT proof a secret was supplied (the
# user may skip/empty a prompt). Truth comes from re-resolving the catalog.
still_missing = [f"{s.group}.{s.name}" for s in missing_secrets()]
provisioned = bool(groups) and not still_missing
reason = None if provisioned else "some secrets remain unresolved after setup"
return ProvisionResult(
provisioned=provisioned,
groups=groups,
still_missing=still_missing,
reason=reason if still_missing else None,
)
|