# Changelog

All notable changes to `mgf-secrets` are documented here. Format loosely follows
Keep a Changelog; versions follow SemVer.

## [Unreleased]

## [0.3.0] — 2026-07-22

### Added
- **Phase 3 — the `sync` reconciler + `retire` + doctor determinism hardening.**
  - `mgf.secrets.sync` — plan-first reconciler (`sync --plan/--apply
    [--surface woodpecker|forge|hosts|local] [--json]`). Per surface: create/update
    Woodpecker org/repo secrets to the exact declared event scope (a create injects
    the store value via the API body — never printed); the **CIR-34 forge deploy-key
    coverage sweep** (ensure every repo the `repos` glob / `ALL_MANAGED` covers
    carries the declared key — matched by **public-key fingerprint, not title**, so a
    repo carrying the same key under a different title is not a false gap; the
    registered key is PUBLIC material **sourced from a repo that already carries it**,
    never the store private half); host-file mode+owner over SSH (never contents in
    P3); local via `materialize`.
  - **D-7 image restriction — evaluated and dropped.** Court found it both
    unenforceable (Woodpecker's image-restriction regex rejects dotted tags →
    `python:3.12-slim` 422s) and ineffective (the whole fleet runs one image, so any
    restriction that keeps publishing working provides no isolation). The real
    `devpi_password` hardening (dedicated publish image + tag signing, DECISION-06)
    is a separate founder decision. `sync` reconciles event scopes only; a declared
    `images` is ignored and live images are preserved.
    Properties (tested): **never deletes silently** (forward-only; removal is
    `retire`'s job), **held is inviolable** (never armed), **idempotent**
    (plan→apply→plan empty), and the **contract-surface playbook** (an org-secret
    apply runs a re-plan scope audit → the event-class reminder → the sooth canary).
  - `mgf.secrets.retire` + `retire <id>` — completion-gated `active→retiring` state
    flip (surgical `SECRETS.toml` edit) + store entry/manifest removal on
    `--apply --allow-retire`; **refuses** an entry still referenced by a live
    pipeline (the mgf-hrb lesson) or founder-held (→ ⚖). The `my_stuff/` file prune
    stays a separate deliberate `materialize` concern.
  - **Doctor determinism (the cron prerequisite):** a check that could not be
    performed (forge 429/5xx, host unreachable) now emits an **`unverifiable`**
    record — distinct from clean AND from a real finding (exit 1; a `--json`
    `unverifiable` list) — never silently-clean. `ALL_MANAGED` is resolved **once**
    per run as the defined union (MGF_LIBRARIES ∪ SOOTH_LIBRARIES ∪
    Woodpecker-activated), logged with count + per-source provenance, stable across
    runs. Surface clients gained the read+write admin protocols (`WoodpeckerAdmin`/
    `ForgeAdmin`/`HostAdmin`); `SurfaceUnavailableError` carries the "couldn't-look"
    signal.
  - Extended suite: fake-surface contract tests (plan→apply→plan empty, injected
    scope/image/key/perm drift fixed, never-delete, held-inviolable), determinism
    tests (429→unverifiable, ALL_MANAGED stable + union), retire refusals + surgical
    flip, redaction over sync/retire, a seeded idempotence property, the playbook
    audit+canary path, and the real store + `ssh-keygen` provider paths. Coverage
    stays above the 80% floor.
- **Phase 2 — encrypted store + value CLI + materialized cache.**
  - `mgf.secrets.store` — the age-encrypted store engine (dep: `pyrage`):
    `encrypt`/`decrypt` (public-key multi-recipient; either secret key decrypts),
    `store_put`/`store_get`, `manifest_load`/`manifest_verify`, the no-plaintext
    `plaintext_guard`, and `recipients.txt` parsing. Values are `bytes` in-process
    only — never logged, never in an exception, never on argv (AG-18). The encrypt
    path is deterministic-by-argument (the CLI stamps `encrypted_at`).
  - `mgf.secrets.resolve(id)` — the in-process value API for sibling tools (the
    AG-18-native replacement for `$(cat my_stuff/…)`); its docstring forbids logging.
  - `mgf.secrets.materialize` — key-free `plan` + `apply` that decrypt declared
    `local-file` surfaces into bare-value files at the exact registry paths,
    enforcing 0600 files / 0700 parent dirs, refusing `held` entries, **never
    deleting** anything (leftovers reported, not pruned) — the doc-form leak class dies.
  - CLI verbs (SPEC §4): `show --value` (refuses a TTY without `--tty-ok`, never
    `--json`), `add`/`set` (value via `--value-file PATH`, stdin `-`, or hidden
    prompt — never argv), `exec --with ID[:ENV] -- CMD` (env-injected child),
    `materialize --plan/--apply`, `export-offline` (founder-only bundle), `history`
    (git audit trail). Global `--store`/`--identity`; the P1 exit-code contract holds.
  - Extended suite: roundtrip property (both recipients), redaction over every value
    verb (canary absent everywhere but `show --value`/`exec`), argv/TTY guards,
    materialize idempotence + safety, manifest consistency, doc-form elimination,
    no-plaintext guard. Coverage stays above the 80% floor.
  - Internal: `mgf.secrets.resolve` (the `ALL_MANAGED` helper) renamed to
    `mgf.secrets.managed` so the public `resolve(id)` value API owns that name.
- **Phase 1 — registry + read-only doctor.**
  - `SECRETS.toml` schema, loader, and fail-closed validator (family-namespaced;
    duplicate ids across families and armed `held` entries are load errors).
  - Read-only doctor with checks C1–C8 (Woodpecker declared↔actual, consumed↔
    declared parity with `audit_secret_scopes.py`, unregistered-actual, held
    discipline, forge deploy-key coverage, local hygiene, host-file perms,
    policy), human table + `--json`.
  - Per-kind liveness probes (one handler per registered kind).
  - CLI `mgf-secrets` with `list` / `show` / `doctor` / `probe`, `--json`, and
    the exit-code contract (0 clean · 1 findings · 2 usage/credential/load error).
  - Test suite: schema fails-closed, ported scope-logic corpus, incident replays
    (CIR-02/33/34, expired JWT, unregistered-actor, doc-form), the AG-18 redaction
    property test, the no-ungoverned gate, and the kind-extension meta-test.
