From 46eb8bea9ecb1c68df9bec84a9688c6030d175ad Mon Sep 17 00:00:00 2001 From: codex Date: Sun, 4 Oct 2026 00:27:00 +0200 Subject: [PATCH] Prepare offline attended provisioning checklists and route remaining custody gates Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a10391-6355-7b41-b0fa-3e14e874f091 --- docs/attended-provisioning-window.md | 13 +++ scripts/credential-change.py | 6 ++ scripts/provisioning_window.py | 80 +++++++++++++++++++ tests/test_provisioning_window.py | 55 +++++++++++++ ...-WP-0035-credential-lane-implementation.md | 9 ++- ...-0050-attended-provisioning-preparation.md | 62 ++++++++++++++ 6 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 docs/attended-provisioning-window.md create mode 100644 scripts/provisioning_window.py create mode 100644 tests/test_provisioning_window.py create mode 100644 workplans/RPF-WP-0050-attended-provisioning-preparation.md diff --git a/docs/attended-provisioning-window.md b/docs/attended-provisioning-window.md new file mode 100644 index 0000000..8b0c924 --- /dev/null +++ b/docs/attended-provisioning-window.md @@ -0,0 +1,13 @@ +# Attended provisioning preparation contract + +Owner: railiance-platform, RPF-WP-0050. Origin: CUST-WP-0075 P2/P3. + +`python3 scripts/credential-change.py window-checklist --window-file --workplan-root ` joins open non-residual task blocks carrying the exact decision UUID to explicit CCR, lane and owner-receipt contracts. Repeat `--workplan-root` for each repo in scope. Roots are the authoritative file snapshot; Hub qualifiers are independently verified before any attended window. No implicit completeness claim exists for omitted roots. + +The metadata manifest has `decision_id`, `date` (YYYY-MM-DD), and `entries`; each entry has `task_id`, `ccr`, `lane_doc`, `receipt_contract`. The three file references are relative to this platform repo and must exist; the receipt contract names the owner’s required positive/negative/revocation/cleanup evidence, never values. Empty manifests/decisions, missing joins or any non-approved CCR produce exit 2 and NOT READY. Duplicate IDs and stale task joins are refused. Applied CCRs are never replay candidates. Arbitrary CCR content is never printed. + +Each item must additionally bind the exact reviewed revision/digest, expected result, native preflight, abort owner, verification and rollback in the existing CCR/supervision receipt. REVIEW PREPARED is a source-join result, not execution permission. Upstream owner gates and live readiness must be checked again in the approved dated window. The Custodian owns cadence; this renderer schedules nothing and executes nothing. A resolved packet calls for owner re-check, not unconditional task completion. + +## P3 draft for founder ruling + +The current rule remains attended value custody. Choose explicitly between (a) batched attended custody, (b) response-wrapped issuer delivery with one-time unwrap, and (c) a scoped MFA writer through a silent contained helper. Review exact actor/path/audience, TTL/single use, audit boundary, child-output suppression, crash cleanup, replay refusal and rollback before adopting b/c. Wrapped tokens are credentials and stay out of files, Hub, chat and logs. No option grants broad write or changes existing lanes. RPF-WP-0050-T02 is routed to D3; a generic D3 resolution still requires a specific value-path disposition, so lack of an explicit answer does not unblock it. diff --git a/scripts/credential-change.py b/scripts/credential-change.py index 9c28c9a..38f6682 100755 --- a/scripts/credential-change.py +++ b/scripts/credential-change.py @@ -2240,6 +2240,12 @@ def build_parser() -> argparse.ArgumentParser: ) applier_apply.set_defaults(func=command_applier_apply) + from provisioning_window import command_window + window = sub.add_parser("window-checklist", help="Render an offline attended-window preparation checklist; never execute") + window.add_argument("--window-file", required=True) + window.add_argument("--workplan-root", action="append", required=True) + window.set_defaults(func=command_window) + runbook = sub.add_parser( "runbook", help="Render or execute the attended operator apply/verify runbook", diff --git a/scripts/provisioning_window.py b/scripts/provisioning_window.py new file mode 100644 index 0000000..63496e5 --- /dev/null +++ b/scripts/provisioning_window.py @@ -0,0 +1,80 @@ +"""Offline, metadata-only attended-window checklist; never executes a CCR.""" +from pathlib import Path +import re +from uuid import UUID +import yaml + + +def task_sources(roots, decision_id): + selected = {} + for root in roots: + for path in sorted(Path(root).glob("*.md")): + text = path.read_text() + front = re.match(r"---\n(.*?)\n---", text, re.S) + meta = yaml.safe_load(front.group(1)) if front else {} + if meta and (meta.get("flavor") == "residual" or meta.get("status") in {"finished", "archived"}): + continue + for block in re.findall(r"```task\s*\n(.*?)\n```", text, re.S): + task = yaml.safe_load(block) + if str(task.get("decision_id", "")) == decision_id and task.get("status") not in {"done", "cancel"}: + task_id = task["id"] + if task_id in selected: + raise ValueError("duplicate task id: " + task_id) + selected[task_id] = task + return selected + + +def local_file(repo, value): + path = (repo / value).resolve() + if not path.is_relative_to(repo.resolve()) or not path.is_file(): + raise ValueError("missing or outside-repository preparation file") + return path + + +def render_window(window_file, roots, repo): + window = yaml.safe_load(Path(window_file).read_text()) + decision = str(UUID(str(window["decision_id"]))) + date = str(window["date"]) + # A dated manifest is preparation, never a scheduled event or approval. + if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", date): + raise ValueError("window date must be YYYY-MM-DD") + tasks = task_sources(roots, decision) + entries = window.get("entries", []) + by_task = {} + for entry in entries: + tid = entry["task_id"] + if tid in by_task: + raise ValueError("duplicate window task") + by_task[tid] = entry + stale = set(by_task) - set(tasks) + if stale: + raise ValueError("manifest tasks absent from current decision: " + ", ".join(sorted(stale))) + lines = ["# Attended provisioning preparation: " + date, + "Decision: " + decision, + "Preparation only. Founder window approval and each exact CCR revision remain required."] + ready = bool(tasks) + for index, tid in enumerate(sorted(tasks), 1): + entry = by_task.get(tid) + if not entry: + lines.append(f"{index}. {tid}: NOT READY — missing CCR/lane/receipt join.") + ready = False + continue + ccr = yaml.safe_load(local_file(repo, entry["ccr"]).read_text()) + local_file(repo, entry["lane_doc"]) + local_file(repo, entry["receipt_contract"]) + eligible = ccr.get("status") == "approved" + ready &= eligible + label = "REVIEW PREPARED" if eligible else "NOT READY (CCR is not approved; never replay applied custody)" + # Deliberately avoid rendering arbitrary CCR contents or secret-bearing values. + lines.extend([f"{index}. {tid}: {label}", + " CCR: " + entry["ccr"], " Lane: " + entry["lane_doc"], + " Owner receipt contract: " + entry["receipt_contract"], + " Revalidate exact digest, upstream gates and native readiness before attended execution."]) + lines.append("Checklist complete: " + str(ready).lower()) + return "\n".join(lines) + "\n", ready + + +def command_window(args): + output, ready = render_window(args.window_file, args.workplan_root, Path(__file__).resolve().parents[1]) + print(output, end="") + return 0 if ready else 2 diff --git a/tests/test_provisioning_window.py b/tests/test_provisioning_window.py new file mode 100644 index 0000000..441609b --- /dev/null +++ b/tests/test_provisioning_window.py @@ -0,0 +1,55 @@ +import importlib.util +from pathlib import Path +import tempfile +import unittest +import yaml + +SPEC = importlib.util.spec_from_file_location("window", Path(__file__).resolve().parents[1] / "scripts/provisioning_window.py") +module = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(module) +D = "62aa95e4-3ccc-4867-8806-5e3db005b483" + +class WindowTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) + self.plans = self.root / "workplans" + self.plans.mkdir() + self.task = "```task\nid: EX-WP-0001-T01\nstatus: wait\ndecision_id: " + D + "\n```\n" + (self.plans / "plan.md").write_text(self.task) + for name in ("lane.md", "receipt.md"): + (self.root / name).write_text("non-secret contract") + self.window = {"decision_id": D, "date": "2026-10-03", "entries": [{"task_id": "EX-WP-0001-T01", "ccr": "ccr.yaml", "lane_doc": "lane.md", "receipt_contract": "receipt.md"}]} + self.ccr("approved") + def ccr(self, status): + (self.root / "ccr.yaml").write_text(yaml.safe_dump({"status": status, "secret": "NEVER_PRINT_THIS"})) + def render(self): + p = self.root / "window.yaml" + p.write_text(yaml.safe_dump(self.window)) + return module.render_window(p, [self.plans], self.root) + def test_approved_join_and_no_values(self): + output, ready = self.render() + self.assertTrue(ready) + self.assertNotIn("NEVER_PRINT_THIS", output) + def test_applied_custody_cannot_replay(self): + self.ccr("applied") + self.assertFalse(self.render()[1]) + def test_missing_join_stays_not_ready(self): + self.window["entries"] = [] + self.assertFalse(self.render()[1]) + def test_stale_join_refused(self): + (self.plans / "plan.md").write_text(self.task.replace("status: wait", "status: done")) + with self.assertRaises(ValueError): self.render() + def test_duplicate_authority_refused(self): + (self.plans / "duplicate.md").write_text(self.task) + with self.assertRaises(ValueError): self.render() + def test_outside_contract_refused(self): + self.window["entries"][0]["lane_doc"] = "../outside.md" + with self.assertRaises(ValueError): self.render() + def test_residual_not_selected(self): + (self.plans / "plan.md").write_text("---\nflavor: residual\n---\n" + self.task) + self.window["entries"] = [] + self.assertFalse(self.render()[1]) + +if __name__ == "__main__": unittest.main() diff --git a/workplans/RPF-WP-0035-credential-lane-implementation.md b/workplans/RPF-WP-0035-credential-lane-implementation.md index 3ae808f..7d09028 100644 --- a/workplans/RPF-WP-0035-credential-lane-implementation.md +++ b/workplans/RPF-WP-0035-credential-lane-implementation.md @@ -71,7 +71,7 @@ issuer/audience/subject/claims and unrelated secret access fail, bounded TTL and revocation are proven, the consumer explicitly opts into the verified contract, and rollback/cleanup receipts contain no secret material. -2026-10-03: human gate routed to hub decision 62aa95e4-3ccc-4867-8806-5e3db005b483 (D1: Attended OpenBao provisioning window: one session clears the pending credential lanes) by CUST-WP-0075; resolving that decision raises C-40 here. +2026-10-03: the historical D1 routing was withdrawn because provision/delivery already completed. Owner routing now uses decision `ab85ae8a-12a0-4945-ae64-1d3d86d5b26f`, specifically independent archive readback and coordinated interim audit bearer revocation acceptance. `needs_human` and the wait remain; resolving a decision requires owner receipt re-check, not automatic completion. 2026-10-03 (CUST-WP-0075): gate check. SECRETS-WP-0007-T04 is done (2026-09-27), so the gate named in the design is open. It is not the last one: the design says to confirm the exact live client registration and protected client-side custody before writing, and positive-login proof needs a real KeyCape service JWT. KeyCape's contract (`openbao-service-auth-contract.md`) and KEY-WP-0009 claim source registration only; the live registration of `secrets-engine-openbao` and its client-credential custody are "a separate approved custody action" that no workplan task owns yet (SECRETS-WP-0008-T06 waits on this task). Re-qualified with `blocked_on: message-from:key-cape`; key-cape was asked for the owning task id, which will replace it as `depends_on`. The design stays proposed and no child command was written. Not approvable in D1 yet. @@ -108,7 +108,7 @@ only the exact matrix, negative/expiry/revocation checks pass, custody is seeded through the separate writer, and the verified route has a safe handoff receipt. The unattended adapter remains a separate demand and gets no operator session. -2026-10-03: human gate routed to hub decision 62aa95e4-3ccc-4867-8806-5e3db005b483 (D1: Attended OpenBao provisioning window: one session clears the pending credential lanes) by CUST-WP-0075; resolving that decision raises C-40 here. +2026-10-03: the historical D1 routing was withdrawn because provision/delivery already completed. Owner routing now uses decision `ab85ae8a-12a0-4945-ae64-1d3d86d5b26f`, specifically independent archive readback and coordinated interim audit bearer revocation acceptance. `needs_human` and the wait remain; resolving a decision requires owner receipt re-check, not automatic completion. ## Accept the needed signing lane and deliver it to the owning runtime @@ -432,7 +432,7 @@ receipts to HFACT-WP-0001-T03, SECRETS-WP-0009-T03 and APPROVAL-WP-0002-T01/T05. Audit receiver/sender custody remains AUDIT-WP-0009-T09 and must not be folded into this client-identity grant. -2026-10-03: human gate routed to hub decision 62aa95e4-3ccc-4867-8806-5e3db005b483 (D1: Attended OpenBao provisioning window: one session clears the pending credential lanes) by CUST-WP-0075; resolving that decision raises C-40 here. +2026-10-03: the historical D1 routing was withdrawn because provision/delivery already completed. Owner routing now uses decision `ab85ae8a-12a0-4945-ae64-1d3d86d5b26f`, specifically independent archive readback and coordinated interim audit bearer revocation acceptance. `needs_human` and the wait remain; resolving a decision requires owner receipt re-check, not automatic completion. ## Admit the two factory audit-sender custody lanes @@ -440,6 +440,7 @@ into this client-identity grant. id: RPF-WP-0035-T08 status: wait needs_human: true +decision_id: "ab85ae8a-12a0-4945-ae64-1d3d86d5b26f" blocking_reason: "Provisioned and delivered 2026-09-11 (CCR-2026-0021/0022 applied); do not repeat. Receiver rollout with evidence_kind and native producer acceptance passed 2026-09-11. Remaining human acceptance: independent archive readback and revocation of the interim audit bearer tokens." priority: high assignee: railiance-platform @@ -469,7 +470,7 @@ Approval Engine's missing namespace remains its owner prerequisite. [Preparation evidence](../docs/evidence/2026-09-11-factory-audit-custody.json). Do not fold these senders into CCR-2026-0019 or the cancelled CCR-2026-0020. -2026-10-03: human gate routed to hub decision 62aa95e4-3ccc-4867-8806-5e3db005b483 (D1: Attended OpenBao provisioning window: one session clears the pending credential lanes) by CUST-WP-0075; resolving that decision raises C-40 here. +2026-10-03: the historical D1 routing was withdrawn because provision/delivery already completed. Owner routing now uses decision `ab85ae8a-12a0-4945-ae64-1d3d86d5b26f`, specifically independent archive readback and coordinated interim audit bearer revocation acceptance. `needs_human` and the wait remain; resolving a decision requires owner receipt re-check, not automatic completion. ## Dependency review — 2026-09-06 diff --git a/workplans/RPF-WP-0050-attended-provisioning-preparation.md b/workplans/RPF-WP-0050-attended-provisioning-preparation.md new file mode 100644 index 0000000..bf7c295 --- /dev/null +++ b/workplans/RPF-WP-0050-attended-provisioning-preparation.md @@ -0,0 +1,62 @@ +--- +id: RPF-WP-0050 +type: workplan +title: "Prepare generated attended windows and explicit value-custody choice" +domain: financials +repo: railiance-platform +status: active +owner: codex +topic_slug: platform-services +created: "2026-10-03" +updated: "2026-10-03" +flavor: implementation +--- + +Origin: Custodian proposal f84637fa-2355-4971-a2dc-7f1fd01710a8, CUST-WP-0075 P2/P3. Platform accepts bounded preparation; Custodian owns cadence, founder owns custody policy and live authorization. No recurring event is enabled. + +## Render offline decision-task-CCR preparation checklist + +```task +id: RPF-WP-0050-T01 +status: done +priority: high +``` + +Implemented `credential-change.py window-checklist` and `scripts/provisioning_window.py`. Reads authoritative open non-residual task blocks and explicit CCR/lane/receipt contracts. Missing/stale joins and non-approved/applied CCRs prevent a complete checklist. No execution, scheduling, credential reads or printing arbitrary CCR contents. Contract: `docs/attended-provisioning-window.md`. Synthetic regression suite verifies actual decision joins and replay refusal. This completes the bounded source renderer, not a real attended session. + +## Obtain exact founder value-custody choice + +```task +id: RPF-WP-0050-T02 +status: wait +priority: high +needs_human: true +decision_id: "1591daa1-6e85-4dfd-aadd-5a9fe1ef47fa" +blocking_reason: "Founder must explicitly choose batched attended custody, response-wrapped issuer delivery, or scoped MFA silent writer, and approve its exact boundary. Generic D3 resolution without this disposition is insufficient." +``` + +P3 options and required review boundaries are drafted in the contract. Existing attended custody remains in force. No delivery helper, delegated value role or policy change is admitted by preparation. + +## Bind a real dated window and owner receipt manifests + +```task +id: RPF-WP-0050-T03 +status: wait +priority: medium +blocked_on: message-from:the-custodian +blocking_reason: "Custodian must return the reviewed cadence/window date and current owner task scope before a real metadata manifest is assembled; exact founder execution authorization remains separate." +``` + +Generate a checklist from all named authoritative roots, verified Hub decision joins and owner CCR/lane/receipt mappings; revalidate each exact revision, upstream acceptance, native readiness and rollback. Applied custody such as RPF-WP-0035-T08 is excluded. Each session returns exact receipts for each owner; missing receipts leave waits intact. Do not activate schedules in this workplan. + +## Accept platform seam for catalog lane scaffold proposal + +```task +id: RPF-WP-0050-T04 +status: todo +priority: medium +``` + +P4 accepted for platform-side source review and bounded scaffolding after Warden returns its contract. Catalog entry/scaffold never supplies credentials, reviews, admission or live authority. Keep owner acceptance explicit before implementation. + +2026-10-03: Platform source review accepts the delivered Warden seam at `ops-warden/wiki/CatalogLaneScaffoldContract.md` (`ac112d8`). T04 is now a ready source implementation task for the platform-owned rendering and schema fixture. Warden T02 may progress independently; no mutual owner-return wait remains. This is source contract acceptance, not credential admission or production authorization.