From babae80c8277ae8e583d812925dfb1f975a60414 Mon Sep 17 00:00:00 2001 From: codex Date: Wed, 28 Oct 2026 09:56:57 +0100 Subject: [PATCH] RAIL-HO-WP-0015-T01: owner answer for the S1 execution-attribution receipt (NK-WP-0040-T02) No S1 runner exists: operator runs ansible-playbook through make converge. Records the future converge-wrapper emitter, the proposed audit-core sender name, per-field fillability, and open items. Emission not implemented. Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 308701@bnt-lap001 Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56 --- WORK-RECORDS.md | 3 + ...cution-attribution-receipt-owner-answer.md | 132 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 workplans/RAIL-HO-WP-0015-s1-execution-attribution-receipt-owner-answer.md diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 2d62de2..76c3f79 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -18,6 +18,7 @@ | workplan | RAIL-HO-WP-0012 | blocked | — | workplans/RAIL-HO-WP-0012-s1-backup-recovery-loop.md | | workplan | RAIL-HO-WP-0013 | blocked | — | workplans/RAIL-HO-WP-0013-host-time-baseline.md | | workplan | RAIL-HO-WP-0014 | active | — | workplans/RAIL-HO-WP-0014-custodian-agt-restricted-host-account.md | +| workplan | RAIL-HO-WP-0015 | active | — | workplans/RAIL-HO-WP-0015-s1-execution-attribution-receipt-owner-answer.md | | task | RAIL-HO-WP-ADHOC-2026-08-22-T01 | done | — | workplans/ADHOC-2026-08-22.md | | task | RAIL-HO-WP-ADHOC-2026-08-22-T02 | done | — | workplans/ADHOC-2026-08-22.md | | task | RAIL-HO-WP-ADHOC-2026-08-22-T03 | done | — | workplans/ADHOC-2026-08-22.md | @@ -66,3 +67,5 @@ | task | RAIL-HO-WP-0014-T01 | done | — | workplans/RAIL-HO-WP-0014-custodian-agt-restricted-host-account.md | | task | RAIL-HO-WP-0014-T02 | done | — | workplans/RAIL-HO-WP-0014-custodian-agt-restricted-host-account.md | | task | RAIL-HO-WP-0014-T03 | wait | — | workplans/RAIL-HO-WP-0014-custodian-agt-restricted-host-account.md | +| task | RAIL-HO-WP-0015-T01 | done | — | workplans/RAIL-HO-WP-0015-s1-execution-attribution-receipt-owner-answer.md | +| task | RAIL-HO-WP-0015-T02 | wait | — | workplans/RAIL-HO-WP-0015-s1-execution-attribution-receipt-owner-answer.md | diff --git a/workplans/RAIL-HO-WP-0015-s1-execution-attribution-receipt-owner-answer.md b/workplans/RAIL-HO-WP-0015-s1-execution-attribution-receipt-owner-answer.md new file mode 100644 index 0000000..7b2ce52 --- /dev/null +++ b/workplans/RAIL-HO-WP-0015-s1-execution-attribution-receipt-owner-answer.md @@ -0,0 +1,132 @@ +--- +id: RAIL-HO-WP-0015 +type: workplan +title: "S1 playbook runner: owner answer for the execution-attribution receipt" +domain: financials +repo: railiance-infra +status: active +flavor: decision +owner: railiance-infra +topic_slug: railiance +created: "2026-10-28" +updated: "2026-10-28" +related_workplans: + - NK-WP-0040 + - RPF-WP-0067 + - AUDIT-WP-0014 + - RAIL-HO-WP-0013 +--- + +Owner record for the net-kingdom request (State Hub message +`848ef70a-992e-4834-8d8a-a5ba3c67f96f`, NK-WP-0040-T02) to name the component +that emits the "Execution Attribution Receipt (proposed)" of +`net-kingdom/canon/standards/playbook-capability-contract_v0.2.md` (lines +292-337) for the reference declaration +`capabilities/playbooks/railiance-infra.bootstrap-host.yaml`. Answered from +source only. Emission is **not** implemented here. + +## T01 — Answer the four owner questions from source + +```task +id: RAIL-HO-WP-0015-T01 +status: done +priority: high +``` + +**(1) What runs S1 playbooks today.** There is no runner component. S1 +playbooks are run by a human operator calling `ansible-playbook` directly +through Make targets: + +- The declaration names `invocation: make converge` for + `ansible/playbooks/bootstrap.yaml` + (`capabilities/playbooks/railiance-infra.bootstrap-host.yaml:16-19`). +- `make converge` is `cd ansible && ansible-playbook $(PLAY) $(ANSIBLE_USER_FLAG)` + with `PLAY := ansible/playbooks/bootstrap.yaml` (`Makefile:284`, `Makefile:361-362`); + `converge-host` / `converge-tags` / `ansible-bootstrap` are variants + (`Makefile:193-194`, `Makefile:364-370`). `SSH_USER` is optional; the + remote user otherwise comes from inventory (`Makefile:285-288`). +- The documented live path is the operator on the host: + `ssh tegwick@92.205.62.239 '... ansible-playbook playbooks/bootstrap.yaml -c local --become -l Railiance01'` + (`Makefile:234`, printed by `make deploy-stack`). +- `ansible/ansible.cfg:7` uses `stdout_callback = default`; there is no + callback plugin, wrapper or service that records a run. + +Started by: the founder/operator (`tegwick`) from a shell, on the +workstation or on Railiance01. Not a cluster workload, CI job or agent +service. The only existing receipt-producing wrapper is +`scripts/s1_handoff.py` (`make s1-handoff`, `Makefile:84-85`), and it wraps +the **read-only** `verify.yaml`, not the converge playbook +(`scripts/s1_handoff.py:133-139`). It writes a local JSON file under +`reports/` (`scripts/s1_handoff.py:153-159`) and sends nothing to audit-core. + +Owner decision: the emitter will be a railiance-infra **converge wrapper** +modelled on `scripts/s1_handoff.py` (Python, invoked by `make converge` +in place of the bare `ansible-playbook` call). It does not exist yet. + +**(2) Emission at every terminal outcome keyed by run_id.** Not today. +`make converge` emits nothing. `s1_handoff.py` emits only on its normal +path: it returns 2 without a receipt on a dirty checkout +(`scripts/s1_handoff.py:119-123`) or a missing `ansible-playbook` +(`:129-131`), and an inventory/spec validation exception exits without a +receipt (`:111-117`). Owner commitment for the future wrapper: one +`run_id` (UUID4, as `s1_handoff.py:63`) generated before anything else, and +a receipt emitted from a `finally` path for every terminal outcome: +`succeeded`, `failed` (non-zero ansible exit), `refused` (pre-flight +refusal, e.g. dirty tree) and `interrupted` (signal/exception). A receipt +that cannot be delivered is kept locally under `reports/` and retried with +the same `run_id` as Idempotency-Key. This is a commitment, gated on the +receipt schema (NK-WP-0040-T02) and the sender credential below. + +**(3) audit-core sender identity.** Proposed sender name: +`railiance-infra-s1-runner` (envelope `source` = that name, per the pattern +in `railiance-platform/workplans/RPF-WP-0067-*.md:49-67`). No sender exists +(RPF-WP-0067:66-67); the credential is a CCR to railiance-platform, not done +here. **Open, needs founder/railiance-platform:** the documented delivery is +a per-sender bearer projected by ESO into a Kubernetes Secret. The S1 runner +runs in an operator shell, not a pod, so that projection does not reach it. +How an operator-run process gets that bearer (for example a short-lived +fetch from OpenBao by the operator, or sending through an in-cluster relay) +is not decided in source. + +**(4) Per-field fillability today** (for a wrapper built from current +source; "no" fields must be recorded as `unknown`, or omitted where the +contract says so, never inferred): + +| Field | Truthful today? | Basis / gap | +| --- | --- | --- | +| `initiator.iss` / `initiator.sub` | No | Operator starts the run from a shell; no IAM Profile token is presented or checked anywhere on the path (`Makefile:361-362`, `:234`). | +| `delegation` | No | The runner cannot tell whether an agent invoked `make` for the operator. The contract allows "absent" only when there was no delegation (`playbook-capability-contract_v0.2.md:315`), which the runner cannot assert. Needs an `unknown` value in the schema (NK). | +| `executing_workload` | No | No workload binding: the run is a shell process, not a Tenancy Posture workload. | +| `runtime_principal` | Partly | The local OS user and the remote SSH user / `become` root can be read at run time. These are OS accounts, not IAM principals. A warden SSH cert principal, if used, is not visible to the wrapper. | +| `tenant` | No | `inventory/servers.yaml` has no tenant field. S1 acts for the platform; the value to record needs an NK convention. | +| `environment` | No | `inventory/servers.yaml` has no environment field (hosts carry `baseline_profile` only, `:9`, `:15`). | +| `run_id` | Yes | UUID4 per run (pattern `scripts/s1_handoff.py:63`). | +| `source_commit` | Yes | `git rev-parse HEAD` (`Makefile:9`, `s1_handoff.py:119`), but only truthful with the clean-tree refusal (`s1_handoff.py:120-123`); `make converge` has no such check today. | +| `artifact_digest` | No | No built artifact. A digest of the playbook + roles tree and `inventory_sha256` (`s1_handoff.py:68`) is possible, but the Galaxy collections are unpinned (`ansible/requirements.yaml:2-4`), so the executed code is not fully pinned. What the digest covers for an Ansible tree needs an NK definition. | +| `target` | Yes | Inventory host names from `--limit` / inventory plus inventory SHA-256 (`s1_handoff.py:38-46`, `:68-70`). | +| `action` | Yes | Playbook path + tags/limit, e.g. `ansible/playbooks/bootstrap.yaml` / `converge`. | +| `decision_refs` | No | `make converge` has no approval gate. The gated targets use approval tokens (`APPROVE_VERIFY_REFRESH`, `Makefile:352-356`; tf-apply "exact approval", `:101`), not decision record ids. | +| `time_interval.start` / `.end` | Yes | Controller UTC wall clock around the `ansible-playbook` call (`s1_handoff.py:34-35`, `:125`). | +| `time_interval.clock_source` | Partly | Can name "controller system clock"; its sync state is not verified by the runner. | +| `time_interval.bound` | No (omit) | No bound established: `railiance_clock_enabled: false` by default (`ansible/roles/railiance_clock/defaults/main.yaml:3`) and RAIL-HO-WP-0013 is `blocked`. The `max_error_ns` default (`:16`) is a policy target, not a measured bound. | + +Side observation (not investigated further): `bootstrap.yaml:14` names a +`swapfile` role that is neither under `ansible/roles/` nor in +`ansible/requirements.yaml`, so `make converge` as declared may not run +cleanly. Should be checked before any wrapper work. + +## T02 — Build the converge wrapper and emit receipts + +```task +id: RAIL-HO-WP-0015-T02 +status: wait +priority: medium +``` + +Waits on: the receipt schema and validator in net-kingdom `canon/schemas/` +(NK-WP-0040-T02), including an `unknown` value for `delegation` and +definitions for `tenant`, `environment` and `artifact_digest` for S1; the +`railiance-infra-s1-runner` sender in the audit-core registry plus a decided +bearer delivery path for an operator-run process (railiance-platform CCR, +founder decision). Wrapper scope: replace the bare `ansible-playbook` call in +`make converge*` with the wrapper. Do not start before these are settled.