NK-WP-0056-T21 done: request phase presents the approval through informed-decision
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 17s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Identity provider journey acceptance / provider (push) Successful in 24s

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:
tegwick 2026-11-01 00:39:17 +01:00
parent 45feaa13cc
commit e6252bc0e4
6 changed files with 200 additions and 4 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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