Implement reproducible S1 handoff contracts
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
This commit is contained in:
parent
c8cb1c8edf
commit
b93af8cc78
44 changed files with 2035 additions and 342 deletions
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue