Implement reproducible S1 handoff contracts
Some checks failed
CI Smoke / source-contract (push) Failing after 2s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
This commit is contained in:
codex 2026-08-23 12:02:23 +02:00
parent c8cb1c8edf
commit b93af8cc78
44 changed files with 2035 additions and 342 deletions

View file

@ -4,7 +4,7 @@ type: workplan
title: "Make the S1 declaration reproducible and the handoff verifiably green"
domain: financials
repo: railiance-infra
status: ready
status: active
owner: codex
topic_slug: railiance
created: "2026-08-23"
@ -39,7 +39,7 @@ independently. T07 depends on T02 and T05. T08 depends on T06.
```task
id: RAIL-HO-WP-0011-T01
status: todo
status: done
priority: high
```
@ -60,11 +60,17 @@ resources, a complete Hetzner fixture validates, incomplete or contradictory
records fail with actionable errors, and no provider command is needed to run
the validation.
**Done 2026-08-23.** `scripts/inventory_contract.py` validates schema version,
provider, lifecycle mode, connection identity, baseline profile, and nested
Hetzner provisioning fields. Both current hosts are explicit adopted Host
Europe records; valid Hetzner and invalid mixed fixtures have negative/positive
unit coverage.
## T02 — Make Hetzner planning select only managed Hetzner resources
```task
id: RAIL-HO-WP-0011-T02
status: todo
status: done
priority: high
```
@ -81,11 +87,19 @@ 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.
**Done 2026-08-23.** Terraform filters on `provider: hetzner` plus
`lifecycle_mode: provider-managed`; the shared SSH-key resource is also absent
when selection is empty. No local Terraform state exists in the module. In an
isolated Terraform 1.9.8 container with mocked providers, the adopted-only plan
selected zero resources and the managed fixture selected exactly its named
host (2/2 native Terraform tests passed). Apply/destroy Make targets now refuse
before init without exact approval variables; no provider mutation occurred.
## T03 — Model host-specific baseline profiles and reconcile declared state
```task
id: RAIL-HO-WP-0011-T03
status: todo
status: done
priority: high
```
@ -103,11 +117,18 @@ UFW-managed and externally-filtered postures have explicit assertions, and the
spec no longer claims properties that convergence neither establishes nor
deliberately delegates.
**Done 2026-08-23.** `spec/server-baseline.yaml` v2 defines shared defaults and
the `ufw-managed` / `external-firewall` profiles. Dynamic inventory resolves
the model into both consumers. The external profile carries its replacement
control, owner, removal condition, and an iptables INPUT default-drop check.
Both rendered Goss files parse as YAML and contain their distinct firewall
assertions. No live firewall change occurred.
## T04 — Add automated baseline contract-parity tests
```task
id: RAIL-HO-WP-0011-T04
status: todo
status: progress
priority: high
```
@ -124,11 +145,17 @@ requiring host access or secrets.
firewall rule, service, or user property fails locally and in CI, while all
declared profiles pass from a clean checkout.
**Implemented locally 2026-08-23.** The baseline validator enforces governed
minimum packages, services, SSH directives, user/sudo posture, both required
profiles, and consumer markers. Unit tests prove deliberate `htop` removal and
SSH weakening fail. Forgejo workflow coverage is committed but still needs its
post-push green run before this task is done.
## T05 — Provide a fresh green S1 handoff gate
```task
id: RAIL-HO-WP-0011-T05
status: todo
status: wait
priority: high
```
@ -146,11 +173,19 @@ 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.
**Implemented; live gate pending 2026-08-23.** `scripts/s1_handoff.py` and
`make s1-handoff` validate source contracts, require a clean revision, run each
host separately, fail the aggregate on any non-zero result, require TAP hashes
for a pass, and record a freshness boundary. Dry-run output is forcibly
`not-run`. The attended all-host run waits for an environment with Ansible and
reviewed permission to refresh the on-host Goss surface; this workstation has
no `ansible-playbook`. No host was contacted.
## T06 — Repair and enforce the secret-source contract
```task
id: RAIL-HO-WP-0011-T06
status: todo
status: done
priority: high
```
@ -168,11 +203,17 @@ but does not distribute an age private key.
the committed tree contains no file falsely presented as encrypted input, and
the documented controller and host secret flows match the executable paths.
**Done 2026-08-23.** The plaintext placeholder was removed and bootstrap no
longer loads it. `scripts/check_secret_paths.py` protects both `secrets/` and
inventory `secrets*` paths in the pre-commit hook, Make target, and CI. Docs,
helpers, and Make consistently use `secrets/hetzner-token.yaml` field
`hetzner.token`; tests reject plaintext and empty encrypted-file fixtures.
## T07 — Emit non-secret provisioning and handoff receipts
```task
id: RAIL-HO-WP-0011-T07
status: todo
status: done
priority: medium
```
@ -189,11 +230,17 @@ and redaction behavior.
tokens cannot be serialized, incomplete phases fail closed, and a synthetic
end-to-end example is consumable without prose interpretation.
**Done 2026-08-23.** `schemas/s1-receipt.schema.json` documents the v1 shape
and `scripts/s1_receipt.py` enforces phase-specific completeness plus recursive
secret-shaped key/value rejection. The committed synthetic chain separates all
five phases and validates; tests reject an incomplete passing chain, a passing
verification without host evidence, and a token-shaped field.
## T08 — Automate bounded SOPS recipient rotation
```task
id: RAIL-HO-WP-0011-T08
status: todo
status: wait
priority: medium
```
@ -211,15 +258,22 @@ recovery-key custody are documented.
run identifies the exact files and before/after recipient set, rollback is
documented, and a sample receipt proves verification without exposing values.
**Implemented; attended verification pending 2026-08-23.**
`scripts/sops_rotation.py` reports zero metadata drift for the current file,
supports null-output decryption verification, exact approval-file binding, and
suppressed-output `sops updatekeys`; CI runs metadata-only `--check`. The local
workstation has no `sops` executable or approved age-key session, so no passing
decryption receipt or recipient change was attempted.
## Acceptance
- [ ] Adopted Host Europe records and provider-managed Hetzner records cannot
- [x] 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
- [x] 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
- [x] 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.
@ -228,7 +282,14 @@ documented, and a sample receipt proves verification without exposing values.
## 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.
Current evidence (2026-08-23):
- Python unit suite: 25 tests pass.
- Terraform 1.9.8 mock-provider tests: 2 pass.
- Ansible 2.17.13 syntax checks: bootstrap, verify, and firewall pass in a
disposable environment.
- Both profile-specific Goss templates render and parse as YAML.
- Inventory, baseline, secret metadata, receipt, shell syntax, Python compile,
and whitespace checks pass.
- Pending before finish: green Forgejo CI, an attended fresh all-host handoff
receipt, and an attended non-printing SOPS decryption receipt.