Fix rotation review evidence and reconcile blocked S1 workplans
Some checks failed
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
CI Smoke / source-contract (push) Has been cancelled

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e3b9-b19e-7ba1-9eb4-4faea76af3ea
This commit is contained in:
codex 2026-09-27 18:47:55 +02:00
parent 41f25fe42d
commit 9886567b40
10 changed files with 255 additions and 18 deletions

View file

@ -0,0 +1,54 @@
# Existing workplan closeout review — 2026-09-27
Reviewed all root and archived workplans. The only unfinished plans are
RAIL-HO-WP-0011, 0012 and 0013; all now have state `blocked`, with eight
remaining tasks in `wait`. No new task or workplan was opened. No residual task
has been marked done without its required live acceptance evidence.
## Implemented under WP-0011 T06/T08
- Rotation dry-run output exposes the exact metadata-only review plan.
- Approval binds each changed ciphertext's SHA-256 as well as recipients/path.
- Applied receipts retain the original and resulting recipient/hash evidence.
- Unrelated Make targets no longer eagerly decrypt the Hetzner token or read
and export the local age private key.
Validation: 54 Python unit tests pass, including three new rotation regression
tests. Inventory, baseline parity, read-only handoff contract, protected secret
paths, SOPS recipient metadata and whitespace checks pass. Ansible-core 2.17.13
host-time playbook syntax check passes in a disposable controller environment.
No production decryption, recipient rotation or credential retrieval occurred.
## Read-only host verification
`ansible-playbook playbooks/verify.yaml`, Ansible-core 2.17.13, reached both hosts:
| Host | Checks completed | Blocking assertion | Changes |
| --- | --- | --- | --- |
| CoulombCore | Executable and baseline stat | `/usr/local/bin/goss` absent | 0 |
| Railiance01 | Executable and baseline stat | Installed baseline digest differs from source render | 0 |
The initial sandboxed attempt failed writing Ansible's connection cache; the
rerun with that access produced the host findings above. Neither attempt is a
passing handoff. Host refresh includes installation/configuration and an hourly
timer, so the concrete rendered diff must be reviewed before that separate
mutation; subsequent baseline failures must also be resolved before T05 closes.
## Backup dependency correction
The exact S1 offsite contract remains pending:
`d150eb3e6a19d658aa76c930b32fc20ef75ffa399558fc193fbde0738551ee62`.
Warden's route reports unknown execution workload identity. Platform WP-0029's
September 15 closure resolves the old upload-share incident, but does not accept
this contract or prove S1 transfer/restore. Keep WP-0012 T05/T06 waiting for those
specific owner and recovery receipts.
## Clock dependency check
The existing railiance-clock collector produced
`2026-09-27-railiance01-clock-inventory.json`. Read-only `dpkg-query -W systemd
systemd-timesyncd` returned `255.4-1ubuntu8.17` for both. Effective source/poll
observations and configuration hashes are recorded in the receipt and WP-0013.
RCLK-WP-0002 still lacks completed policy review; no disposable Ubuntu VM is
declared here for reboot/outage/rollback testing. The already deployed authority
does not close those gates. No clocks or services were changed.

View file

@ -0,0 +1,48 @@
{
"schema_version": 1,
"host_alias": "railiance01",
"collector_reported_at": "2026-09-27T16:46:14.477816+00:00",
"read_only": true,
"authority_usable": null,
"synchronized_reported": true,
"limitations": [
"OS synchronization flag is not an independent UTC error bound",
"host and collector timestamps are observations, not authenticated time samples",
"SSH uses existing caller identity/trust; no sudo, installation or time mutation"
],
"collector_sha256": "3aadb6fb888d87cefb63e94ac87c9c640ef63ef6f404b09a01e8e8642b26784e",
"remote_script_sha256": "597e08b95855c4bd1fee40ad8e7c1a8359312c1583ac8363e5bd922b493f074a",
"inventory": {
"remote_reported_at": "2026-09-27T16:46:16.708853+00:00",
"commands": {
"clock": {
"returncode": 0,
"stdout": "Timezone=Etc/UTC\nLocalRTC=no\nNTP=yes\nNTPSynchronized=yes\n"
},
"upstream": {
"returncode": 0,
"stdout": "LinkNTPServers=\nSystemNTPServers=ntp.ubuntu.com\nRuntimeNTPServers=\nFallbackNTPServers=ntp.ubuntu.com\nServerName=ntp.ubuntu.com\nServerAddress=2620:2d:4000:1::41\nRootDistanceMaxUSec=500ms\nPollIntervalMinUSec=32s\nPollIntervalMaxUSec=1min 4s\nPollIntervalUSec=1min 4s\nNTPMessage={ Leap=0, Version=4, Mode=4, Stratum=2, Precision=-25, RootDelay=6.454ms, RootDispersion=183us, Reference=1D586304, OriginateTimestamp=Sun 2026-09-27 16:45:18 UTC, ReceiveTimestamp=Sun 2026-09-27 16:45:18 UTC, TransmitTimestamp=Sun 2026-09-27 16:45:18 UTC, DestinationTimestamp=Sun 2026-09-27 16:45:18 UTC, Ignored=no, PacketCount=15904, Jitter=3.125ms }\nFrequency=1174453\n"
},
"timesyncd": {
"returncode": 0,
"stdout": "User=systemd-timesync\nActiveState=active\nFragmentPath=/usr/lib/systemd/system/systemd-timesyncd.service\nDropInPaths=\nUnitFileState=enabled\n"
},
"other_daemons": {
"returncode": 4,
"stdout": "inactive\ninactive\ninactive\n"
},
"virtualization": {
"returncode": 0,
"stdout": "kvm\n"
},
"ntp_listeners": {
"returncode": 0,
"stdout": ""
}
},
"config_sha256": {
"/etc/systemd/timesyncd.conf": "e6734751f8aaf19fddfff891ad246387f5f59bd9ff1a5f0cac2c34bc81941c62",
"/etc/systemd/timesyncd.conf.d/60-railiance-clock.conf": "c787da5279c983dc6284ed16258fa2ff069cc62b53f360f4f3fed5bb94f79d5f"
}
}
}

View file

@ -1,9 +1,10 @@
# Metadata-only example. Copy outside Git for an attended approved rotation.
approved: false
approved_by: "operator-name"
approved_by: "reviewer-name"
approved_at: "2026-08-23T00:00:00Z"
changes:
- path: secrets/hetzner-token.yaml
sha256: "replace-with-exact-ciphertext-sha256-from-plan"
before_recipients:
- age1old-example-not-valid
after_recipients:

View file

@ -8,6 +8,8 @@ python3 scripts/sops_rotation.py --check
It compares each protected file's public age-recipient metadata with the first
matching rule in `.sops.yaml`. CI runs this check to detect recipient drift.
The JSON output includes the exact file paths, ciphertext SHA-256 hashes, and
before/after recipient sets. Run without `--check` to inspect proposed drift.
An attended non-printing decryption check may emit a receipt:
@ -21,7 +23,10 @@ receipt or command output.
Actual key updates require `--apply` and an approval YAML containing
`approved: true`, `approved_by`, `approved_at`, and an exact `changes` list from
the current plan. The command fails if that list differs from current metadata.
the current plan (including each changed file's `sha256`). The command fails if
that list differs from current metadata or the reviewed ciphertext has changed.
Applied receipts retain the reviewed before/after recipient sets and original
ciphertext hash, plus `after_sha256` for the resulting ciphertext.
Review and preserve recovery-key custody before approving recipient removal.
Start from `docs/sops-rotation-approval.example.yaml`; the committed example is
deliberately unapproved and contains no usable recipient.