diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 244bb39..3a049b2 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -14,6 +14,7 @@ | workplan | RAIL-HO-WP-0008 | finished | — | workplans/RAIL-HO-WP-0008-railiance01-resource-and-commercial-evidence.md | | workplan | RAIL-HO-WP-0009 | finished | — | workplans/RAIL-HO-WP-0009-firewall-declared-state-and-api-exposure.md | | workplan | RAIL-HO-WP-0010 | finished | — | workplans/RAIL-HO-WP-0010-new-reef-ports-need-a-grant.md | +| workplan | RAIL-HO-WP-0011 | ready | — | workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md | | task | ADHOC-2026-08-22-T01 | done | — | workplans/ADHOC-2026-08-22.md | | task | ADHOC-2026-08-22-T02 | done | — | workplans/ADHOC-2026-08-22.md | | task | ADHOC-2026-08-22-T03 | done | — | workplans/ADHOC-2026-08-22.md | @@ -40,3 +41,11 @@ | task | RAIL-HO-WP-0009-T05 | done | — | workplans/RAIL-HO-WP-0009-firewall-declared-state-and-api-exposure.md | | task | RAIL-HO-WP-0009-T06 | done | — | workplans/RAIL-HO-WP-0009-firewall-declared-state-and-api-exposure.md | | task | RAIL-HO-WP-0010-T01 | done | — | workplans/RAIL-HO-WP-0010-new-reef-ports-need-a-grant.md | +| task | RAIL-HO-WP-0011-T01 | todo | — | workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md | +| task | RAIL-HO-WP-0011-T02 | todo | — | workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md | +| task | RAIL-HO-WP-0011-T03 | todo | — | workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md | +| task | RAIL-HO-WP-0011-T04 | todo | — | workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md | +| task | RAIL-HO-WP-0011-T05 | todo | — | workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md | +| task | RAIL-HO-WP-0011-T06 | todo | — | workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md | +| task | RAIL-HO-WP-0011-T07 | todo | — | workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md | +| task | RAIL-HO-WP-0011-T08 | todo | — | workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md | diff --git a/workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md b/workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md new file mode 100644 index 0000000..b07e131 --- /dev/null +++ b/workplans/RAIL-HO-WP-0011-reproducible-s1-declaration-and-handoff.md @@ -0,0 +1,234 @@ +--- +id: RAIL-HO-WP-0011 +type: workplan +title: "Make the S1 declaration reproducible and the handoff verifiably green" +domain: financials +repo: railiance-infra +status: ready +owner: codex +topic_slug: railiance +created: "2026-08-23" +updated: "2026-08-23" +related: + - RAIL-HO-WP-0002 + - RAIL-HO-WP-0009 +--- + +# RAIL-HO-WP-0011 — reproducible S1 declaration and handoff + +## Goal + +Close the highest-leverage gaps identified in +`history/2026-08-23-scope-against-intent.md`: distinguish adopted resources +from provider-managed resources, make the host baseline contract executable, +produce a green per-host handoff gate, and make every declared secret input +fail closed. Then add the evidence and rotation mechanics needed for an +auditable provisioning path. + +This workplan improves the source and validation path. It does **not** authorize +a Terraform apply or destroy, a live firewall change, a credential rotation, +or any other live-host mutation. Those actions require their normal reviewed +plan and operator approval. + +## Delivery Order + +The critical path is T01 → T02 and T03 → T04 → T05. T06 can proceed +independently. T07 depends on T02 and T05. T08 depends on T06. + +## T01 — Define and validate adopted-versus-managed inventory + +```task +id: RAIL-HO-WP-0011-T01 +status: todo +priority: high +``` + +Define one explicit inventory contract for common host identity and +provider-specific lifecycle fields. At minimum it must distinguish: + +- provider (`hosteurope`, `hetzner`, or a documented extension value) +- lifecycle mode (`adopted` versus provider-managed) +- connection identity used by Ansible +- provider-specific provisioning fields used by Terraform + +Add a side-effect-free validator and fixtures for the two current adopted Host +Europe hosts, a valid provisionable Hetzner host, and invalid mixed records. +Preserve stable host names and connection behavior. + +**Done when:** the current inventory validates as adopted Host Europe +resources, a complete Hetzner fixture validates, incomplete or contradictory +records fail with actionable errors, and no provider command is needed to run +the validation. + +## T02 — Make Hetzner planning select only managed Hetzner resources + +```task +id: RAIL-HO-WP-0011-T02 +status: todo +priority: high +``` + +Update the Terraform and helper path to consume the T01 contract and exclude +adopted or non-Hetzner records. Audit any existing Terraform state before +changing resource addresses or selection logic. Correct provisioning docs and +Make targets so they name the provider-token source actually used. + +Add an isolated plan test or equivalent deterministic validation that proves +the current Host Europe records cannot become Hetzner creates or destroys. + +**Done when:** current adopted records pass through the inventory-to-Terraform +boundary without missing-field errors or managed resources, a provisionable +Hetzner fixture produces the expected resource shape, and a reviewed plan shows +no unintended create, replace, or destroy. Do not apply the plan in this task. + +## T03 — Model host-specific baseline profiles and reconcile declared state + +```task +id: RAIL-HO-WP-0011-T03 +status: todo +priority: high +``` + +Replace the permanent `CoulombCore` expected failure with an explicit baseline +profile or declared exception. Reconcile `spec/server-baseline.yaml`, Ansible, +and the intended live posture for packages, users/sudo, SSH, firewall, +fail2ban, SOPS tooling, swap, and resource limits. + +Exceptions must say which control replaces the default, why it is accepted, +and who owns removing it. An unmanaged control must not silently count as +verified. + +**Done when:** both current hosts resolve to complete declared profiles, the +UFW-managed and externally-filtered postures have explicit assertions, and the +spec no longer claims properties that convergence neither establishes nor +deliberately delegates. + +## T04 — Add automated baseline contract-parity tests + +```task +id: RAIL-HO-WP-0011-T04 +status: todo +priority: high +``` + +Add repository tests that fail when the human baseline, convergence roles, and +Goss assertions diverge on governed properties. Prefer a single +machine-readable model or generated artifacts where that reduces duplicated +declarations; otherwise implement explicit parity checks with clear failure +messages. + +Cover the profiles introduced by T03 and run the tests in Forgejo CI without +requiring host access or secrets. + +**Done when:** a deliberate mismatch in a governed package, SSH setting, +firewall rule, service, or user property fails locally and in CI, while all +declared profiles pass from a clean checkout. + +## T05 — Provide a fresh green S1 handoff gate + +```task +id: RAIL-HO-WP-0011-T05 +status: todo +priority: high +``` + +Provide one operator-facing, non-ambiguous command that checks inventory and +contract validity, runs the applicable profile for every selected host, +collects evidence, and exits non-zero if any required control is failed, +unknown, stale, or skipped without an accepted declaration. + +The receipt must identify the source revision, inventory digest, host/profile, +check time, and result without including credentials or sensitive host output. +Define freshness and the exact interface S2 can consume. + +**Done when:** both current hosts can return green against their declared +profiles in an attended verification run, a failing or stale host makes the +aggregate gate fail, and the evidence is sufficient for an S2 handoff without +interpreting an expected-red exception. + +## T06 — Repair and enforce the secret-source contract + +```task +id: RAIL-HO-WP-0011-T06 +status: todo +priority: high +``` + +Remove, relocate, or SOPS-encrypt the plaintext +`inventory/group_vars/secrets.sops.yaml` placeholder. Inventory every path that +documentation or automation describes as secret-bearing, extend the +pre-commit/CI check to those paths, and make playbooks fail clearly when an +expected encrypted input is absent or malformed. + +Align README, provisioning, convergence, Make targets, and playbooks on the +actual Hetzner token source and on the fact that the host role installs tools +but does not distribute an age private key. + +**Done when:** plaintext fixtures at any declared secret path are rejected, +the committed tree contains no file falsely presented as encrypted input, and +the documented controller and host secret flows match the executable paths. + +## T07 — Emit non-secret provisioning and handoff receipts + +```task +id: RAIL-HO-WP-0011-T07 +status: todo +priority: medium +``` + +Define a versioned receipt linking inventory and source revisions, provider +plan/apply identity, provider resource ID, cloud-init completion, Ansible +convergence, and the T05 handoff result. Separate plan, apply, and verification +events so a plan receipt cannot be mistaken for proof of a live change. + +Exercise the format with a dry-run or synthetic fixture first. A live provider +apply remains separately approved and is not required merely to prove schema +and redaction behavior. + +**Done when:** the receipt schema validates, sensitive fields and provider +tokens cannot be serialized, incomplete phases fail closed, and a synthetic +end-to-end example is consumable without prose interpretation. + +## T08 — Automate bounded SOPS recipient rotation + +```task +id: RAIL-HO-WP-0011-T08 +status: todo +priority: medium +``` + +Build a dry-run-first rotation workflow for the repository's SOPS files. It +must inventory affected files and recipients, detect recipient drift, require a +reviewed change set, verify decryption through an approved non-printing check, +and emit a metadata-only receipt. Never print a decrypted value or private age +key. + +Live recipient removal or credential replacement requires explicit operator +approval after the dry run. Scheduling may be proposed only after rollback and +recovery-key custody are documented. + +**Done when:** CI can detect recipient drift without secrets, an attended dry +run identifies the exact files and before/after recipient set, rollback is +documented, and a sample receipt proves verification without exposing values. + +## Acceptance + +- [ ] Adopted Host Europe records and provider-managed Hetzner records cannot + be confused by validation or Terraform selection. +- [ ] The declared baseline, Ansible convergence, and Goss verification have an + automated parity contract. +- [ ] Every current host can produce a fresh green result against an explicit + profile, and the aggregate handoff fails closed. +- [ ] Every documented secret-bearing path is encrypted or deliberately absent + and protected by local and CI checks. +- [ ] Provisioning/handoff and rotation paths emit metadata-only receipts with + reviewed redaction behavior. +- [ ] No live provider, firewall, credential, or host mutation occurs without a + separately reviewed plan and the required operator approval. + +## Completion Evidence + +Record the final inventory schema and examples, Terraform plan safety result, +baseline parity tests, CI run, attended all-host gate receipt, secret-path +negative tests, provisioning receipt fixture, and SOPS rotation dry-run receipt +here before marking the workplan finished.