Add companion lanes to catalog-bound exec owners (SECRETS-WP-0011 T01-T03)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

A configured exec owner may receive fields from other consenting kv lanes.
Each lane is gated, consumed and read through its own AppRole; any refusal
starts no child. Companions are part of the owner digest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 226514@bnt-lap001
Assistant-Session: 26ba103d-05fe-45a1-9cd7-9475bf239df6
This commit is contained in:
tegwick 2026-09-23 17:27:09 +02:00
parent 452203b19b
commit bc58184d71
10 changed files with 449 additions and 29 deletions

View file

@ -546,32 +546,61 @@ def cmd_wrap(cfg: Config, args) -> int:
def cmd_exec(cfg: Config, args) -> int:
from contextlib import ExitStack
from secrets_engine.exec_delivery import exec_with_secret
from secrets_engine.exec_owner import validate_delivery_target
from secrets_engine.exec_owner import resolve_companions, validate_delivery_target
entry = get_entry(cfg.catalog_dir, args.catalog)
field = args.field or (entry.fields[0] if entry.fields else "")
session_detail: dict[str, object] = {}
companion_sessions: dict[str, dict[str, object]] = {}
# Refuse a substituted recipient before consuming approval or opening Bao.
owner_digest = validate_delivery_target(entry, field, args.command, args.mode)
companions = resolve_companions(entry, lambda cid: get_entry(cfg.catalog_dir, cid))
command_name = args.command[0] if args.command else ""
with _privileged_evidence(
cfg,
entry,
"exec",
detail={
"command": command_name,
"mode": args.mode,
"field": field,
"session": session_detail,
"exec_owner_sha256": owner_digest,
},
) as evidence:
with ExitStack() as stack:
evidence = stack.enter_context(_privileged_evidence(
cfg,
entry,
"exec",
detail={
"command": command_name,
"mode": args.mode,
"field": field,
"session": session_detail,
"exec_owner_sha256": owner_digest,
"companions": [lane.id for lane, _, _ in companions],
},
))
# require approval + readiness before running.
decision = _require_lane_approval(
cfg, entry, "exec", evidence, fields=(field,) if field else ()
)
evidence.mark_approved(decision)
require_delivery_state(cfg.evidence_dir, entry.id, "exec")
# Each companion read is its own protected action: its own evidence,
# stance, approval and consume. No lane's decision covers another.
companion_evidence = []
for lane, lane_field, _env in companions:
lane_evidence = stack.enter_context(_privileged_evidence(
cfg,
lane,
"exec",
detail={
"command": command_name,
"mode": "exec-env",
"field": lane_field,
"companion_of": entry.id,
"session": companion_sessions.setdefault(lane.id, {}),
"exec_owner_sha256": owner_digest,
},
))
lane_decision = _require_lane_approval(
cfg, lane, "exec", lane_evidence, fields=(lane_field,)
)
lane_evidence.mark_approved(lane_decision)
require_delivery_state(cfg.evidence_dir, lane.id, "exec")
companion_evidence.append(lane_evidence)
if not args.command:
from secrets_engine.errors import DeliveryError
@ -585,7 +614,11 @@ def cmd_exec(cfg: Config, args) -> int:
mode=args.mode,
session_evidence=session_detail,
expected_owner_digest=owner_digest,
companions=companions,
companion_sessions=companion_sessions,
)
for lane_evidence in companion_evidence:
lane_evidence.finish(f"exit-{rc}")
evidence.finish(f"exit-{rc}")
return rc