Use existing founder execution lane for read-only pilot inventory
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
This commit is contained in:
tegwick 2026-11-05 23:02:01 +01:00
parent 560589e154
commit d2eefb371d
4 changed files with 64 additions and 173 deletions

View file

@ -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."
}

View file

@ -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

View file

@ -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)

View file

@ -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