From fab81f00867fdf714e5cbbc0e01eadf9e22e6bc5 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 5 Nov 2026 22:50:58 +0100 Subject: [PATCH] Record functional pilot walkthrough and prepare attended inventory handoff Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790 --- ...-11-05-vergabe-functional-flow-return.json | 29 ++++ .../README.md | 30 ++++ docs/vergabe-demo-company-acceptance.md | 12 +- tools/vergabe_pilot_inventory_attended.py | 135 ++++++++++++++++++ .../RAPPS-WP-0014-vergabe-invited-pilot.md | 21 ++- 5 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 docs/evidence/2026-11-05-vergabe-functional-flow-return.json create mode 100644 tools/vergabe_pilot_inventory_attended.py diff --git a/docs/evidence/2026-11-05-vergabe-functional-flow-return.json b/docs/evidence/2026-11-05-vergabe-functional-flow-return.json new file mode 100644 index 0000000..a458856 --- /dev/null +++ b/docs/evidence/2026-11-05-vergabe-functional-flow-return.json @@ -0,0 +1,29 @@ +{ + "schema": "railiance.vergabe-pilot-user-return.v1", + "reported_on": "2026-11-05", + "workplan": "RAPPS-WP-0014", + "task": "RAPPS-WP-0014-T03", + "evidence_source": "User confirmation in the active conversation", + "evidence_kind": "user_reported", + "fixture": {"ausschreibung_id": 1, "aufgabe_id": 1, "dokument_id": 1}, + "cross_account_task_editing_works": true, + "user_reports_flow_completed": true, + "final_recorded_phase_user_label": "Zuschlag/Nachlese", + "user_assessment": "The flow needs work, but the functionality present seems to work quite well.", + "functional_walkthrough_status": "confirmed_with_ui_followup", + "ui_review_status": "open", + "individual_approval_records_independently_verified": false, + "native_inventory_complete": false, + "populated_restart_or_recovery_verified": false, + "session_or_otp_policy_changed": false, + "runtime_mutations_by_agent": false, + "remaining_work": [ + "Attended native release/storage inventory", + "File checksum and direct anonymous-download refusal", + "Owner-run inactive-session denial", + "Populated pod-replacement verification", + "Coherent PostgreSQL/media/issue-state off-host recovery point", + "Isolated restored workflow and compatible rollback", + "Backup and incident/support ownership, cadence, retention and recovery objectives" + ] +} diff --git a/docs/proposals/2026-11-05-vergabe-recovery-preflight/README.md b/docs/proposals/2026-11-05-vergabe-recovery-preflight/README.md index 43125b6..e55336a 100644 --- a/docs/proposals/2026-11-05-vergabe-recovery-preflight/README.md +++ b/docs/proposals/2026-11-05-vergabe-recovery-preflight/README.md @@ -36,6 +36,36 @@ new standing agent grant is requested by the packet. The result must report `native_inventory_complete: true` before using its expected release as the baseline of a separately reviewed capture/restart operation. +## Founder-terminal handoff + +The user has now confirmed cross-account task edits and the functional flow +through Zuschlag/Nachlese, with UI follow-up. The remaining first step is native +inventory. A restricted SSH check as `custodian-agt` was refused at authentication; +the agent did not try an elevated account or issue its own certificate. + +From a synced `railiance-apps` checkout on **railiance01**, run: + +```bash +python3 tools/vergabe_pilot_inventory_attended.py --submit +``` + +The launcher asks for the **path** to the existing gexec approver key. The normal +gexec review/signature step follows. It stages the fixed proposal in the existing +exchange directory, then uses `sudo -u custodian-agt` only to enqueue the signed +request under the required UID and read its sanitized result. Kubernetes access +runs through gexec's configured `kube:railiance01` target. The founder needs the +existing exchange access and ability to use that restricted account via sudo. +No new key, standing grant or privileged kubeconfig is installed. Keep the +approver key in its existing custody. If it is held only on the workstation, +sign the action there with the existing gexec CLI and supply that public signed +approval on the node with `--submit --approval /path/to/approval.json`; do not +copy the private key to the node. Gexec verifies the signature and its expiry. + +Share the final result JSON. A pending result retains its request ID; inspect +that request before submitting again. The launcher defaults to local packet +review when `--submit` is omitted and refuses a changed action digest. The +existing gated executor remains responsible for signature/nonce/profile checks. + Validation: source compilation and synthetic command responses cover success, immediate wrong-cluster refusal, image drift and exclusion of a private canary from the receipt. Ruff is unavailable in the current shell. No native pass, diff --git a/docs/vergabe-demo-company-acceptance.md b/docs/vergabe-demo-company-acceptance.md index 6180e75..415ab23 100644 --- a/docs/vergabe-demo-company-acceptance.md +++ b/docs/vergabe-demo-company-acceptance.md @@ -10,8 +10,12 @@ the [document return](evidence/2026-11-05-vergabe-document-return.json). The document UI still needs user review. File hash equality and signed-out download refusal are unverified. The user also confirms the **LOKAL** issue badge is visible in both accounts; the [issue return](evidence/2026-11-05-vergabe-local-issue-return.json) -binds Ausschreibung 1, task 1 and document 1 from their supplied URLs. Task -edits, domain approval/submission and recovery remain pending. +binds Ausschreibung 1, task 1 and document 1 from their supplied URLs. The user +now confirms cross-account task editing and the flow through documented +**Zuschlag/Nachlese**, with further UI work needed; see the +[functional-flow return](evidence/2026-11-05-vergabe-functional-flow-return.json). +The functional walkthrough is confirmed with UI follow-up. The instructions +below remain the reproduction sequence for the populated restart/restore drill. The other session does not update automatically, and a fresh login was needed after a short interval. The user wants a full working day without renewed login @@ -107,8 +111,8 @@ and [deployment binding](vergabe-demo-company-binding.md). Its receipt must cove Existing database-only backup/restore targets default to historical `vergabe_db` and omit both file stores. They cannot establish this three-store recovery claim. -Two-account sign-in and cross-account visibility of newly created records are -user-confirmed; the remaining shared workflow is pending. The attended restart, +The two-account functional walkthrough is user-confirmed with UI follow-up. +The attended restart, off-host capture, isolated restore and rollback action has not been prepared or executed. Logged-in account deactivation remains a separate owner-run access-control check from the pilot contract. No account is disabled as part of these walkthrough steps. diff --git a/tools/vergabe_pilot_inventory_attended.py b/tools/vergabe_pilot_inventory_attended.py new file mode 100644 index 0000000..31c9afc --- /dev/null +++ b/tools/vergabe_pilot_inventory_attended.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Founder-terminal review, signing and submission of the fixed pilot inventory. + +Run on railiance01 from a synced railiance-apps checkout. Default only reviews +the packet. --submit uses the existing approver key and gated executor; the +request is staged as custodian-agt so the executor's submitter check is retained. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import stat +import subprocess +import uuid +from pathlib import Path + +EXPECTED = "sha256:2014e62a84b0875fec155dbfd1be26963d9a39ab931092d26c5dfb9e627ffbd7" +PACKET = (Path(__file__).resolve().parents[1] + / "docs/proposals/2026-11-05-vergabe-recovery-preflight/action.json") +GEXEC = Path("/usr/local/bin/gexec") +EXCHANGE = Path("/var/spool/gexec/exchange") +SPOOL = "/var/spool/gexec" + +READ_RESULT = """ +import json, pathlib, re, sys, time +request_id = sys.argv[1] +if not re.fullmatch(r'vergabe-inventory-[0-9a-f]{16}', request_id): + raise SystemExit(1) +directory = pathlib.Path('/var/spool/gexec/results') +for attempt in range(60): + matches = list(directory.glob(request_id + '.*.json')) + if len(matches) == 1: + result = json.loads(matches[0].read_text()) + print(json.dumps(result, indent=2)) + raise SystemExit(0 if result.get('status') == 'executed' + and result.get('exit_code') == 0 else 1) + if len(matches) > 1: + print(json.dumps({'status': 'ambiguous_result', 'request_id': request_id})) + raise SystemExit(1) + time.sleep(1) +print(json.dumps({'status': 'result_pending', 'request_id': request_id, + 'instruction': 'Preserve this request id; inspect its result before resubmitting.'})) +raise SystemExit(2) +""" + + +def validated_packet(path=PACKET): + packet = json.loads(path.read_text()) + encoded = json.dumps(packet["action"], sort_keys=True, separators=(",", ":"), + ensure_ascii=False, allow_nan=False).encode() + digest = "sha256:" + hashlib.sha256(encoded).hexdigest() + if digest != EXPECTED or packet.get("digest") != EXPECTED: + raise ValueError("packet_digest_mismatch") + return packet + + +def submit(packet, key=None, approval_file=None): + info = GEXEC.resolve(strict=True).stat() + if info.st_uid != 0 or not stat.S_ISREG(info.st_mode) or info.st_mode & 0o022: + raise ValueError("admitted_root_owned_gexec_required") + if EXCHANGE.is_symlink() or not EXCHANGE.is_dir(): + raise ValueError("admitted_exchange_required") + request_id = "vergabe-inventory-" + uuid.uuid4().hex[:16] + proposal = EXCHANGE / (request_id + ".action.json") + approval = EXCHANGE / (request_id + ".approval.json") + fd = os.open(proposal, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o644) + with os.fdopen(fd, "w") as output: + json.dump(packet, output, indent=2) + if approval_file is not None: + signed = json.loads(approval_file.read_text()) + if signed.get("payload", {}).get("digest") != EXPECTED: + raise ValueError("approval_digest_mismatch") + fd = os.open(approval, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o644) + with os.fdopen(fd, "w") as output: + json.dump(signed, output) + else: + # The existing CLI displays the exact action and asks the founder to sign. + approved = subprocess.run([ + str(GEXEC), "approve", str(proposal), "--key", str(key), + "--approver", "bernd", "--ttl", "900", "--out", str(approval), + ], check=False) + if approved.returncode: + print(json.dumps({"status": "not_submitted", "request_id": request_id})) + return 1 + queued = subprocess.run([ + "/usr/bin/sudo", "-u", "custodian-agt", str(GEXEC), "submit", + "--spool", SPOOL, "--proposal", str(proposal), "--approval", str(approval), + "--request-id", request_id, + ], check=False) + if queued.returncode: + print(json.dumps({"status": "submission_unconfirmed", "request_id": request_id, + "instruction": "Inspect this request before creating another."})) + return 1 + return subprocess.run([ + "/usr/bin/sudo", "-u", "custodian-agt", "/usr/bin/python3", "-c", + READ_RESULT, request_id, + ], check=False).returncode + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--submit", action="store_true") + signing = parser.add_mutually_exclusive_group() + signing.add_argument("--key", type=Path, help="Path to the existing gexec approver key") + signing.add_argument("--approval", type=Path, help="Existing signed approval; keeps key in its custody") + args = parser.parse_args(argv) + packet = validated_packet() + print(json.dumps({"status": "prepared", "digest": EXPECTED, + "scope": "Read-only Vergabe deployment/storage inventory", + "target": "kube:railiance01", "runtime_mutations": False}), flush=True) + if not args.submit: + return 0 + if not GEXEC.is_file(): + raise ValueError("admitted_gexec_missing") + if args.key is None and args.approval is None: + args.key = Path(input("Path to your existing gexec approver key: ").strip()).expanduser() + return submit(packet, args.key, args.approval) + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except ValueError as error: + failure = str(error) + if failure not in {"packet_digest_mismatch", "approval_digest_mismatch", "admitted_gexec_missing", + "admitted_root_owned_gexec_required", "admitted_exchange_required"}: + failure = "invalid_packet_or_runtime" + print(json.dumps({"status": "launcher_failed", "failure": failure})) + raise SystemExit(1) + except Exception: + print(json.dumps({"status": "launcher_failed", "failure": "attended_runtime_unavailable"})) + raise SystemExit(1) diff --git a/workplans/RAPPS-WP-0014-vergabe-invited-pilot.md b/workplans/RAPPS-WP-0014-vergabe-invited-pilot.md index 4d0142c..3a1868a 100644 --- a/workplans/RAPPS-WP-0014-vergabe-invited-pilot.md +++ b/workplans/RAPPS-WP-0014-vergabe-invited-pilot.md @@ -196,10 +196,10 @@ id: RAPPS-WP-0014-T03 status: wait needs_human: true decision_id: d06307e5-2737-461a-a2f4-a12c3b492a9a -intervention_note: "Both accounts sign in and see created records, the uploaded document and LOKAL issue (2026-11-05). Fixture: Ausschreibung/task/document 1. Remaining shared task edit, approval/submission and access checks precede populated restart/recovery. Exact metadata-only inventory packet is prepared; current caller lacks native reads. Off-host recovery, isolated restore, rollback and ownership remain open. Document UI review stays pending; session/OTP refinement is deferred." +intervention_note: "Two-account functional walkthrough through Zuschlag/Nachlese is user-confirmed with UI follow-up (2026-11-05); task edits, document and LOKAL issue work. Fixture: Ausschreibung/task/document 1. Next: run the prepared attended native inventory; current caller lacks native reads and restricted agent SSH authentication was refused. Populated restart/off-host recovery, isolated restore, rollback, access evidence and operating ownership remain open." priority: high assignee: the-custodian -blocking_reason: "Await populated two-user/document workflow, coherent PostgreSQL/media/issue-state off-host recovery point, isolated restored workflow, rollback evidence and support ownership. September 25 proves database restore and restart only." +blocking_reason: "Two-account functional walkthrough is confirmed with UI follow-up. Await attended native inventory, populated restart, coherent PostgreSQL/media/issue-state off-host recovery, isolated restored workflow, rollback/access evidence and operating ownership. September 25 proves database-only restore and restart." state_hub_task_id: "dd069c6d-fcc1-5bac-b233-976f2f0d5cd1" ``` @@ -321,6 +321,23 @@ the platform primary-archive helper currently has no Vergabe application profile Off-host publication/readback still needs its owner-bound storage/custody path. T03 remains wait for the remaining workflow and operational recovery evidence. +2026-11-05 functional-flow return: the user confirms task editing works and +reports completing the flow through documented Zuschlag/Nachlese. Existing +functionality works quite well in their review, while UI work remains. Receipt: +`docs/evidence/2026-11-05-vergabe-functional-flow-return.json`. The two-account +functional walkthrough is now confirmed with UI follow-up; no repeat baseline +walkthrough is requested. Native recovery, byte/access checks and operating +ownership remain open, and the broader decision/task are not closed. + +The routed restricted SSH check as custodian-agt was refused at authentication; +no elevated account was attempted. Route `agt-custodian-ssh-cert` identifies +supervisor-only issuance. Prepared +`tools/vergabe_pilot_inventory_attended.py` for the founder terminal on +railiance01: it validates the exact existing proposal, obtains the normal gexec +signature, submits as custodian-agt to preserve the request UID check, and +returns the sanitized result. Default mode only reviews the packet. This +prepares an attended read-only inventory, not permission for a snapshot/restart. + ## Validate synthetic sizing against the exact release and shared contention