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
|
|
@ -363,7 +363,7 @@
|
|||
| task | NK-WP-0056-T18 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
|
||||
| task | NK-WP-0056-T19 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md |
|
||||
| task | NK-WP-0056-T20 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
|
||||
| task | NK-WP-0056-T21 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md |
|
||||
| task | NK-WP-0056-T21 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
|
||||
| intake | NK-IN-0001 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |
|
||||
| intake | NK-IN-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |
|
||||
| intake | NET-IN-0001 | open | — | intakes/intakes.md |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -142,6 +142,24 @@ class ApprovalEngine:
|
|||
scope="approval:consume")[1]
|
||||
|
||||
|
||||
class InformedDecision:
|
||||
"""POST /v1/memos on informed-decision's private workload listener (informed-decision INFD-WP-0010,
|
||||
present_existing). The bearer is the executor's projected ServiceAccount token, audience informed-decision-intake;
|
||||
informed-decision checks it with TokenReview and joins it to service:nk-bind-executor in its registry."""
|
||||
|
||||
def __init__(self, base: str, token_file: str, ca_file: str | None = None):
|
||||
self.http = _Http(base, token_file, ca_file)
|
||||
|
||||
def submit(self, envelope: dict) -> dict:
|
||||
status, receipt = self.http.call("POST", "/v1/memos", envelope)
|
||||
memo = envelope["memo"]
|
||||
if status not in (200, 201) or receipt.get("memo_id") != memo["id"] \
|
||||
or receipt.get("approval_id") != memo["approval_id"] \
|
||||
or receipt.get("approval_binding_digest") != memo["approval_binding_digest"]:
|
||||
raise ClientError(f"memo receipt {status}")
|
||||
return receipt
|
||||
|
||||
|
||||
class AuditCore:
|
||||
"""POST /v1/events with Idempotency-Key = event id (audit-core docs/event-envelope.md)."""
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ class World:
|
|||
self.create_status = 201
|
||||
self.create_override = {}
|
||||
self.kv = {"CLIENT_SECRET": SECRET}
|
||||
self.memos = []
|
||||
self.memo_status = 201
|
||||
|
||||
|
||||
def serve(w: World):
|
||||
|
|
@ -89,6 +91,14 @@ def serve(w: World):
|
|||
scope = urllib.parse.parse_qs(raw.decode())["scope"][0]
|
||||
return self._send(200, w.token_body or {"access_token": "at-" + scope.replace(":", "-"),
|
||||
"token_type": "Bearer", "expires_in": 900})
|
||||
if self.path == "/v1/memos":
|
||||
env = json.loads(raw)
|
||||
w.memos.append((dict(self.headers), env))
|
||||
if w.memo_status != 201:
|
||||
return self._send(w.memo_status, {"error": "approval_not_requested"})
|
||||
m = env["memo"]
|
||||
return self._send(201, {"memo_id": m["id"], "approval_id": m["approval_id"],
|
||||
"approval_binding_digest": m["approval_binding_digest"]})
|
||||
if self.path == "/v1/approvals":
|
||||
body = json.loads(raw)
|
||||
created = {"id": "apr-1", "status": "requested",
|
||||
|
|
@ -185,6 +195,62 @@ class ScopesPerCall(Base):
|
|||
C.ApprovalEngine(self.addr, token_file="/x", token_provider=lambda s: "t")
|
||||
|
||||
|
||||
INFD = pathlib.Path("/home/worsch/informed-decision")
|
||||
|
||||
|
||||
class PresentToFounder(Base):
|
||||
"""T21: after creating the approval the executor presents it through informed-decision (present_existing)."""
|
||||
|
||||
def run_cli(self, cfg):
|
||||
(self.root / "cfg.json").write_text(json.dumps(cfg))
|
||||
(self.root / "intake-token").write_text("synthetic-intake-token\n")
|
||||
with contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
code = CLI.main(["--request", "--admission", str(ADMISSION), "--config", str(self.root / "cfg.json")])
|
||||
return code, json.loads(out.getvalue()), out.getvalue()
|
||||
|
||||
def cfg(self, **kw):
|
||||
return {**super().cfg(), "informed_decision": {"url": self.addr,
|
||||
"token_file": str(self.root / "intake-token")}}
|
||||
|
||||
def test_files_then_presents_with_the_admission_as_packet(self):
|
||||
code, result, text = self.run_cli(self.cfg())
|
||||
self.assertEqual((code, result["outcome"]), (0, "presented"))
|
||||
headers, env = self.w.memos[0]
|
||||
self.assertEqual(headers["Authorization"], "Bearer synthetic-intake-token")
|
||||
self.assertEqual(env["memo"]["approval_id"], "apr-1")
|
||||
self.assertEqual(env["memo"]["approval_binding_digest"], "sha256:" + "ab" * 32)
|
||||
self.assertEqual(env["packet"][0]["content"], ADMISSION.read_text())
|
||||
body = json.loads(self.calls("/v1/approvals")[0][3])
|
||||
self.assertEqual(env["approval_intent"], {k: body["binding"][k] for k in ("action", "principal", "purpose", "target")})
|
||||
self.assertNotIn("actor", env["approval_intent"])
|
||||
self.assertEqual(result["memo_id"], env["memo"]["id"])
|
||||
self.assertNotIn(SECRET, text)
|
||||
|
||||
def test_refused_memo_keeps_the_approval_and_exits_7(self):
|
||||
self.w.memo_status = 409
|
||||
code, result, _ = self.run_cli(self.cfg())
|
||||
self.assertEqual((code, result["outcome"], result["approval_id"]), (7, "requested_not_presented", "apr-1"))
|
||||
|
||||
def test_envelope_passes_informed_decisions_own_validator(self):
|
||||
if not (INFD / "informed_decision" / "intake.py").exists():
|
||||
self.skipTest("informed-decision checkout absent")
|
||||
sys.path.insert(0, str(INFD))
|
||||
try:
|
||||
from informed_decision import intake
|
||||
finally:
|
||||
sys.path.remove(str(INFD))
|
||||
doc = X.bea.load(ADMISSION)
|
||||
binding, _, _ = CLI.request_binding(doc, super().cfg())
|
||||
approval = {"id": "0f1e2d3c-4b5a-4968-8776-655443322110", "binding": {**binding, "digest": "sha256:" + "b" * 64}}
|
||||
env = CLI.admission_memo(doc, ADMISSION.read_text(), binding, approval)
|
||||
grant = intake.SubmitterGrant("nk-bind-executor", frozenset({"bounded-execution"}),
|
||||
frozenset({"net-kingdom-admins"}))
|
||||
profile = intake.PresentExisting("service:nk-bind-executor", frozenset({"bounded-execution"}))
|
||||
validated = intake.validate_submission(env, submitter_id="nk-bind-executor", grant=grant,
|
||||
present_existing=profile)
|
||||
self.assertEqual(validated.memo.id, env["memo"]["id"])
|
||||
|
||||
|
||||
class RequestPhase(Base):
|
||||
def run_cli(self, cfg, admission=ADMISSION):
|
||||
(self.root / "cfg.json").write_text(json.dumps(cfg))
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ identities:
|
|||
purpose: "Confidential OIDC client (client_credentials); token scopes limited to its registered scopes"
|
||||
grants:
|
||||
- {mode: USE, scope: keycape, activation: STANDING, environments: [PRODUCTION], note: "client credentials"}
|
||||
- {mode: USE, scope: "approval-engine: approval:create", activation: STANDING, environments: [PRODUCTION], note: "key-cape a1ab8ff L122-135"}
|
||||
- {mode: USE, scope: "approval-engine: approval:create", activation: STANDING, environments: [PRODUCTION], note: "key-cape a1ab8ff L122-135; approval:read prepared in key-cape 82b471f (founder decision 2026-11-01, informed-decision present_existing), live only after the attended registry change"}
|
||||
interactive_login: false
|
||||
second_factor: not_applicable
|
||||
custody: [delivered-secret]
|
||||
|
|
|
|||
|
|
@ -953,7 +953,7 @@ Next for the founder: queue memo r2 through the State Hub memo dispatcher and ac
|
|||
|
||||
```task
|
||||
id: NK-WP-0056-T21
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
assignee: claude-code
|
||||
depends_on: [NK-WP-0056-T18]
|
||||
|
|
@ -979,6 +979,35 @@ Outside net-kingdom:
|
|||
**Done when:** the request phase files the approval and the memo, tested against informed-decision's real intake
|
||||
validator.
|
||||
|
||||
**Done 2026-11-01.** After creating the approval, `cli.py --request` composes one present_existing memo:
|
||||
|
||||
- the packet is the admission YAML itself (informed-decision's choice);
|
||||
- three required highlights cover the scope ceiling, the window and budget, and the plan with its revert;
|
||||
- the intent restates the binding without the actor;
|
||||
- the idempotency key is `nk-bind-executor.<approval_id>`.
|
||||
|
||||
It is submitted to informed-decision's private intake with the projected `informed-decision-intake` token. Exit
|
||||
codes: 0 presented; 7 requested but the memo was refused (the approval stays usable, and resubmitting is idempotent).
|
||||
|
||||
Tests:
|
||||
|
||||
- the envelope passes informed-decision's own `validate_submission` with the present_existing profile;
|
||||
- the receipt is checked against the memo;
|
||||
- a refused memo exits 7.
|
||||
|
||||
61 executor tests.
|
||||
|
||||
rapp (T21 commit): the request Job's projected token, the exact config section, and egress to informed-decision:8081.
|
||||
|
||||
The service-identity declaration notes key-cape's prepared `approval:read` for informed-decision's requester.
|
||||
|
||||
Live (founder-run, after the owners' releases):
|
||||
|
||||
- access-engine policy v14;
|
||||
- key-cape `approval:read`;
|
||||
- informed-decision's registry row and its ingress for the executor pod;
|
||||
- the executor image pin.
|
||||
|
||||
## Pilot: unattended bind rotation under one admission
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue