NK-WP-0056-T21 done: request phase presents the approval through informed-decision
All checks were successful
All checks were successful
admission_memo composes the present_existing envelope (admission YAML as packet, three required highlights, intent = binding without actor, key nk-bind-executor.<approval_id>); InformedDecision.submit posts it with the projected intake token and checks the receipt. Exit 7 when the memo is refused (approval stays usable). Envelope validated by informed-decision's own validate_submission with the present_existing profile. Service-identity declaration notes key-cape's prepared approval:read. 61 executor tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5502@bnt-lap001 Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
This commit is contained in:
parent
45feaa13cc
commit
e6252bc0e4
6 changed files with 200 additions and 4 deletions
|
|
@ -25,7 +25,9 @@ Request (NK-WP-0056-T18): file the approval for one admission as the executor it
|
|||
binding.actor == the token's sub). Binding: action bounded-execution, actor = executor_identity, principal = the
|
||||
first listed approver's subject, target {admission_id, admission_digest}, validity = the admission window,
|
||||
human_control on, required_count = number of listed approvers. Prints the approval id; consumes nothing.
|
||||
Exit codes: 0 requested, 4 refused.
|
||||
With informed_decision{url, token_file} configured (T21), the approval is then presented to the founder: one memo
|
||||
through informed-decision's present_existing intake, carrying the admission YAML. Exit codes: 0 requested (and
|
||||
presented, if configured), 4 refused, 7 requested but the memo was not accepted (resubmitting is idempotent).
|
||||
|
||||
approval_engine.client{token_url, client_id, k8s_role, kv, host?} selects the executor's own KeyCape client
|
||||
(SecurityCaseType oidc-service-client, case nk-bind-executor-approval): the client secret is read once through its
|
||||
|
|
@ -151,6 +153,76 @@ def request_binding(doc: dict, cfg: dict) -> tuple[dict, dict, int]:
|
|||
return binding, validity, len(approvers)
|
||||
|
||||
|
||||
MEMO_CLASS = "bounded-execution"
|
||||
REVIEW_GROUP = "net-kingdom-admins"
|
||||
|
||||
|
||||
def admission_memo(doc: dict, admission_text: str, binding: dict, approval: dict) -> dict:
|
||||
"""The informed-decision intake envelope that presents this executor-filed approval (present_existing profile,
|
||||
informed-decision docs/decision-services-alignment-answer.md S1). The packet carries the admission YAML itself;
|
||||
required highlights point at the scope ceiling, the window and budget, and the revert. The intent restates the
|
||||
approval's binding without the actor (informed-decision takes the actor from its registry)."""
|
||||
spec = doc["spec"]
|
||||
approval_id = approval["id"]
|
||||
memo_id = (spec["id"].lower() + "-" + approval_id.split("-")[0])[:63]
|
||||
item = memo_id + "-admission"
|
||||
digest = "sha256:" + X.hashlib.sha256(admission_text.encode("utf-8")).hexdigest()
|
||||
ceiling = "; ".join(f"{c['mode']} {c['scope']}" for c in spec["scope_ceiling"])
|
||||
budget = spec["budget"]
|
||||
|
||||
def highlight(n, severity, section, note):
|
||||
return {"id": f"{memo_id}-h{n}", "item_id": item, "severity": severity, "required_ack": True,
|
||||
"locator": {"kind": "admission", "admission_id": spec["id"], "section": section}, "note": note}
|
||||
|
||||
return {
|
||||
"kind": "informed-decision.memo-intake",
|
||||
"version": 1,
|
||||
"idempotency_key": f"nk-bind-executor.{approval_id}",
|
||||
"memo_class": MEMO_CLASS,
|
||||
"review_group": REVIEW_GROUP,
|
||||
"drafted_by": "net-kingdom bounded executor (service:nk-bind-executor)",
|
||||
"approval_intent": {k: binding[k] for k in ("action", "principal", "purpose", "target")},
|
||||
"memo": {
|
||||
"id": memo_id,
|
||||
"version": 1,
|
||||
"question": f"Admit {spec['id']}: {spec['title']}?"[:400],
|
||||
"requested_act": "approve",
|
||||
"binding_level": "organizational",
|
||||
"brief": (f"Bounded Execution Admission {spec['id']} (risk {spec['risk_class']}). "
|
||||
f"Executor {spec['executor']['identity']} may act once, inside "
|
||||
f"{spec['window']['not_before']} .. {spec['window']['not_after']}, within: {ceiling}. "
|
||||
f"Budget {budget['wall_time_seconds']} s, {budget['attempts']} attempt(s). "
|
||||
f"Purpose: {spec['purpose']}")[:20000],
|
||||
"binding": {
|
||||
"justification": None,
|
||||
"principal": {"display_name": f"Named reviewer in {REVIEW_GROUP}", "id": "pending-human-session",
|
||||
"identifiers": [], "kind": "person", "role": "approver"},
|
||||
"target": {"environment": "prod", "id": spec["id"], "kind": "bounded-execution-admission",
|
||||
"label": spec["title"][:200], "requires_new_bind": False},
|
||||
"terms": None,
|
||||
},
|
||||
"step_kind": "approve",
|
||||
"packet": [{"item_id": item, "label": f"Admission {spec['id']} (YAML)", "hash": digest}],
|
||||
"highlights": [
|
||||
highlight(1, "critical", "scope_ceiling",
|
||||
"Approving lets the executor act once, unattended, within exactly this scope ceiling: "
|
||||
+ ceiling),
|
||||
highlight(2, "warning", "window_budget",
|
||||
f"Window {spec['window']['not_before']} .. {spec['window']['not_after']}; budget "
|
||||
f"{budget['wall_time_seconds']} s, {budget['attempts']} attempt(s); the executor stops "
|
||||
"and reverts when a check fails or the budget is exhausted."),
|
||||
highlight(3, "warning", "plan_revert",
|
||||
"Each mutating step names its own checks and revert; read the plan before approving."),
|
||||
],
|
||||
"locale": "en",
|
||||
"approval_id": approval_id,
|
||||
"approval_binding_digest": approval["binding"]["digest"],
|
||||
"sealed": False,
|
||||
},
|
||||
"packet": [{"item_id": item, "media_type": "text/plain", "content": admission_text}],
|
||||
}
|
||||
|
||||
|
||||
def run_request(admission_path: str, cfg: dict) -> tuple[dict, int]:
|
||||
logins: list = []
|
||||
try:
|
||||
|
|
@ -168,6 +240,17 @@ def run_request(admission_path: str, cfg: dict) -> tuple[dict, int]:
|
|||
"admission_digest": binding["target"]["admission_digest"], "required_count": required,
|
||||
"effects": "approval requested"}
|
||||
code = 0
|
||||
if "informed_decision" in cfg: # present it to the founder (T21); the approval stays usable either way
|
||||
idd = cfg["informed_decision"]
|
||||
envelope = admission_memo(doc, pathlib.Path(admission_path).read_text(), binding, created)
|
||||
try:
|
||||
receipt = C.InformedDecision(idd["url"], idd["token_file"], idd.get("ca_file")).submit(envelope)
|
||||
result.update(outcome="presented", memo_id=receipt["memo_id"],
|
||||
effects="approval requested; memo submitted to informed-decision")
|
||||
except Exception as error: # the approval exists; the memo can be resubmitted with the same key
|
||||
result.update(outcome="requested_not_presented", memo_id=envelope["memo"]["id"],
|
||||
reason="memo:" + type(error).__name__)
|
||||
code = 7
|
||||
except Exception as error: # nothing was consumed; at most an unapproved request exists
|
||||
result = {"mode": "request", "outcome": "refused", "reason": "request:" + type(error).__name__}
|
||||
code = 4
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue