From d2eefb371dbda5ad26623b4b9122bbb92d769dd3 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 5 Nov 2026 23:02:01 +0100 Subject: [PATCH] Use existing founder execution lane for read-only pilot inventory Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790 --- ...-05-vergabe-inventory-launcher-return.json | 16 ++ .../README.md | 58 +++---- tools/vergabe_pilot_inventory_attended.py | 143 ------------------ .../RAPPS-WP-0014-vergabe-invited-pilot.md | 20 ++- 4 files changed, 64 insertions(+), 173 deletions(-) create mode 100644 docs/evidence/2026-11-05-vergabe-inventory-launcher-return.json delete mode 100644 tools/vergabe_pilot_inventory_attended.py diff --git a/docs/evidence/2026-11-05-vergabe-inventory-launcher-return.json b/docs/evidence/2026-11-05-vergabe-inventory-launcher-return.json new file mode 100644 index 0000000..c746b30 --- /dev/null +++ b/docs/evidence/2026-11-05-vergabe-inventory-launcher-return.json @@ -0,0 +1,16 @@ +{ + "schema": "railiance.vergabe-pilot-inventory-launcher-return.v1", + "reported_on": "2026-11-05", + "evidence_kind": "user_reported_terminal_output", + "workplan": "RAPPS-WP-0014", + "task": "RAPPS-WP-0014-T03", + "prepared_digest": "sha256:2014e62a84b0875fec155dbfd1be26963d9a39ab931092d26c5dfb9e627ffbd7", + "status": "launcher_failed", + "failure": "admitted_gexec_missing", + "execution_host": "not_reported", + "proposal_staging_reached": false, + "approval_or_submission_reached": false, + "native_inventory_executed": false, + "runtime_mutations": false, + "followup": "Founder executes the reviewed read-only inventory on railiance01 under the existing supervisor-execution lane; no executor installation or agent privilege grant is required." +} 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 5ee4613..d87750a 100644 --- a/docs/proposals/2026-11-05-vergabe-recovery-preflight/README.md +++ b/docs/proposals/2026-11-05-vergabe-recovery-preflight/README.md @@ -28,45 +28,45 @@ reads are forbidden and Helm listing fails. This caller did not execute a privileged proposal. The workstation has no installed `gexec` command; proposal preparation used the existing gated-executor source CLI/action format only. -For attended execution, review this exact JSON with `gexec show` in the admitted -founder runtime, sign it with that runtime's existing approver key and submit -through its configured spool. The environment orientation ยง6 requires the -gated executor for privileged actions. No signing key, elevated kubeconfig or -new standing agent grant is requested by the packet. The result must report +The gated executor is the agent's privileged execution path. CUST-WP-0073-T01 +also explicitly permits **supervisor execution**, with admin credentials kept +outside the agent. The founder can run this reviewed read-only inventory in +their own terminal on railiance01. 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 +## Founder-executed inventory -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. +The first launcher attempt returned `admitted_gexec_missing` before any +staging, signature or submission. That establishes only that the fixed gexec +path was unavailable in the invoking terminal; its host was not reported. +The founder does not need an executor installation to perform their own +reviewed read-only check. -From a synced `railiance-apps` checkout on **railiance01**, run: +From a synced `railiance-apps` checkout in the founder's own terminal on +**railiance01**, run: ```bash -python3 tools/vergabe_pilot_inventory_attended.py --submit +sudo env KUBECONFIG=/etc/rancher/k3s/k3s.yaml /usr/bin/python3 tools/vergabe_pilot_inventory.py ``` -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 resulting JSON. This is the same inventory source pinned above. +The kubeconfig remains in the founder's privileged execution path. The agent +receives the helper's sanitized metadata receipt, not administrative credentials. +No snapshot, restart or resource change is included. -Share the final result JSON. If it is pending, retrieve the same request with -`python3 tools/vergabe_pilot_inventory_attended.py --result REQUEST_ID` before -submitting again. This mode only reads the result and never enqueues an action. -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. +Authority: `the-custodian/workplans/CUST-WP-0073-agent-credential-separation.md`, +T01: exact privileged actions go through "supervisor approval or supervisor +execution". This uses the existing supervisor lane; it does not add an agent +permission, change a control or install a service. + +## Agent execution + +The original action JSON remains available for the agent's existing admitted +gexec show/approve/submit procedure. Restricted agent SSH authentication was +refused; no elevated account was attempted. The custom founder launcher was +retired because it unnecessarily required gexec for the founder's own read-only +check. The standing agent boundary and executor signature checks stay in force. Validation: source compilation and synthetic command responses cover success, immediate wrong-cluster refusal, image drift and exclusion of a private canary diff --git a/tools/vergabe_pilot_inventory_attended.py b/tools/vergabe_pilot_inventory_attended.py deleted file mode 100644 index 4e5ef92..0000000 --- a/tools/vergabe_pilot_inventory_attended.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/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 read_result(request_id): - return subprocess.run([ - "/usr/bin/sudo", "-u", "custodian-agt", "/usr/bin/python3", "-c", - READ_RESULT, request_id, - ], check=False).returncode - - -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 read_result(request_id) - - -def main(argv=None): - parser = argparse.ArgumentParser(description=__doc__) - mode = parser.add_mutually_exclusive_group() - mode.add_argument("--submit", action="store_true") - mode.add_argument("--result", help="Retrieve an existing request result without resubmitting") - 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) - if args.result: - return read_result(args.result) - 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 3a1868a..f138162 100644 --- a/workplans/RAPPS-WP-0014-vergabe-invited-pilot.md +++ b/workplans/RAPPS-WP-0014-vergabe-invited-pilot.md @@ -196,7 +196,7 @@ id: RAPPS-WP-0014-T03 status: wait needs_human: true decision_id: d06307e5-2737-461a-a2f4-a12c3b492a9a -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." +intervention_note: "Two-account functional walkthrough through Zuschlag/Nachlese is user-confirmed with UI follow-up. Next: founder runs the reviewed read-only inventory on railiance01 using the existing supervisor-execution lane. Gexec launcher stopped before submission because its binary was missing in the invoking terminal; host is unconfirmed. Agent privileges stay unchanged. Populated restart/off-host recovery, isolated restore, rollback, access evidence and ownership remain open." priority: high assignee: the-custodian 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." @@ -338,6 +338,24 @@ 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. +2026-11-05 launcher return: the user reports `admitted_gexec_missing` after +the prepared line. Source ordering proves this stopped before proposal staging, +signature, submission or native inventory. The invoking host is unreported; +no node installation failure is inferred. Receipt: +`docs/evidence/2026-11-05-vergabe-inventory-launcher-return.json`. + +Corrected the handoff after checking CUST-WP-0073-T01's existing "supervisor +approval or supervisor execution" policy. The founder can run the unchanged, +reviewed read-only inventory helper in their own terminal on railiance01 with +their existing privileged context and return only its sanitized JSON. This +does not grant the agent access to admin credentials or alter a control. +Gexec remains the agent's privileged path; an executor installation is not +required for this founder-executed inventory. T03 still awaits native evidence. + +The unnecessary custom founder launcher is retired from current source; its +earlier preparation and failed attempt remain historical evidence. The pinned +inventory helper and original agent gexec action remain unchanged. + ## Validate synthetic sizing against the exact release and shared contention