Add companion lanes to catalog-bound exec owners (SECRETS-WP-0011 T01-T03)
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:
parent
452203b19b
commit
bc58184d71
10 changed files with 449 additions and 29 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue