Re-open INFD-WP-0002-T03 live accept; keep the sitting unsigned.
audit-core rollout made origin /readyz 200. Remaining gates are the T03-only Flex Auth package, no approval:create requester for these eight acts, and a live KeyCape subject. Attach writes bound copies from a created receipt; it does not bind. Assistant: grok Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
This commit is contained in:
parent
6a386dd787
commit
6289ecbb68
9 changed files with 367 additions and 51 deletions
|
|
@ -10,27 +10,33 @@ Do not mark INFD-WP-0002 finished on a partial sitting.
|
||||||
## What is already proven
|
## What is already proven
|
||||||
|
|
||||||
On 2026-09-14 around 01:06 UTC this origin took **three real human accepts**
|
On 2026-09-14 around 01:06 UTC this origin took **three real human accepts**
|
||||||
for `SECRETS-WP-0010-T03-{apply,verify,exec}`: presentations, required-highlight
|
for `SECRETS-WP-0010-T03-{apply,verify,exec}`. That is T08 history, not this
|
||||||
acknowledgments, `accept` dispositions, and confirmed approval-engine
|
batch. `infd-20260914-c01` must not reuse those approval ids.
|
||||||
submissions are in the review store. That discharges “can a human bind through
|
|
||||||
this surface?” for T08 historically.
|
|
||||||
|
|
||||||
Those three memos are **not** this sitting. `infd-20260914-c01` asks a different
|
## Live accept reopened (2026-09-14 22:16 UTC)
|
||||||
question (admit native delivery) and must not reuse those approval ids.
|
|
||||||
|
|
||||||
## What is closed right now (2026-09-14 16:56 UTC)
|
audit-core `/readyz` was hanging; the Service had no ready endpoints; this
|
||||||
|
origin refused accept (`/readyz` 503). audit-core `b0e6792` rolled out. Now:
|
||||||
|
|
||||||
Live accept is refused:
|
- Origin `/healthz` 200, `/readyz` **200 ready**, `/auth/start` 303 to KeyCape
|
||||||
|
- Review pod reaches `audit-core.audit-core.svc:8080` healthz/readyz 200
|
||||||
|
- Preflight: `live_accept=open`, still not `ready_to_sit`
|
||||||
|
|
||||||
- Origin `/healthz` 200; `/auth/start` 303 to KeyCape (`informed-decision-approver`, PKCE S256).
|
Evidence: `docs/evidence/2026-09-14-infd-0002-t03-accept-reopened.json`.
|
||||||
- Origin `/readyz` **503** `approval_path_not_connected`.
|
|
||||||
- `audit-core` pod Ready=false; `/readyz` hangs; Service has no ready endpoints;
|
|
||||||
the review pod gets connection refused talking to `audit-core.audit-core.svc:8080`.
|
|
||||||
- Accept requires recent audit delivery health. Until that Service has a ready
|
|
||||||
endpoint, a new sitting cannot submit entries.
|
|
||||||
|
|
||||||
Evidence: `docs/evidence/2026-09-14-infd-0002-t03-bind-path-probe.json`.
|
## What still blocks this sitting
|
||||||
Audit-core owns the hang; this packet does not change that deployment.
|
|
||||||
|
1. **Flex Auth** still admits only the three T03 memo ids
|
||||||
|
(`FLEX-WP-0027`, `examples/informed-decision-t03`). Compact ids would be
|
||||||
|
denied. Request shape: `policy-request.md`. Do not expand the T03 mandate
|
||||||
|
in place.
|
||||||
|
2. **No `approval:create` requester** whose `binding.actor` matches these
|
||||||
|
acts. Do **not** reuse `secrets-engine-requester` for WSL2, clock
|
||||||
|
ownership, mason plan, or warden seal.
|
||||||
|
3. Drafts still have `approval_id: null` and principal
|
||||||
|
`pending-human-session`. Live review refuses `missing_act_binding` /
|
||||||
|
`wrong_recipient`.
|
||||||
|
4. A human must bind. An agent must not.
|
||||||
|
|
||||||
## Eight unsigned memos (this sitting)
|
## Eight unsigned memos (this sitting)
|
||||||
|
|
||||||
|
|
@ -47,28 +53,18 @@ Open in this order. One question each. No approve-all.
|
||||||
| 7 | `infd-20260914-d03` | MASON-WP-0005 plan |
|
| 7 | `infd-20260914-d03` | MASON-WP-0005 plan |
|
||||||
| 8 | `infd-20260914-d04` | RCLK-WP-0002-T01 |
|
| 8 | `infd-20260914-d04` | RCLK-WP-0002-T01 |
|
||||||
|
|
||||||
Drafts still have `approval_id: null` and principal `pending-human-session`.
|
## After requester, receipts, and policy admission
|
||||||
The live controller refuses those with `missing_act_binding` / `wrong_recipient`.
|
|
||||||
They are not in the live store.
|
|
||||||
|
|
||||||
## After audit-core is Ready
|
1. `uv run python tools/sitting_bind_preflight.py --origin https://decisions.coulomb.social`
|
||||||
|
— `live_accept` must stay `open`.
|
||||||
1. Confirm `GET https://decisions.coulomb.social/readyz` returns 200, or run
|
2. Owning requester creates eight `human_control=true` objects, required_count 1,
|
||||||
`uv run python tools/sitting_bind_preflight.py --origin https://decisions.coulomb.social`.
|
no entries. Carry the native id and `binding.digest`; do not invent a digest.
|
||||||
2. For **each** memo, the owning requester creates one `approval-engine` object
|
3. `uv run python tools/attach_compact_bindings.py --principal <exact-keycape-sub> --receipt <created.json>`
|
||||||
with `human_control=true`, required_count 1, no entries. Do **not** reuse
|
writes `bound/` copies. Unsigned drafts stay unsigned.
|
||||||
`secrets-engine-requester` for unrelated acts (WSL2 retirement, clock
|
4. Flex Auth admits a **new** package pinning those eight `memo:` ids to the
|
||||||
ownership, mason plan, …).
|
native approval id/digest, same identity bar as T03, deny everything else.
|
||||||
3. Substitute the operator’s live KeyCape `sub` for `pending-human-session`.
|
5. Load packet bytes then `save_memo` from `bound/` into the review store.
|
||||||
Carry the native `approval_id` and `binding.digest` on the memo. Do not
|
6. Sign in. Open `/review?memo_id=infd-20260914-c01` through `…-d04` in order.
|
||||||
invent a digest.
|
Acknowledge the required highlight. Bind that memo. Repeat.
|
||||||
4. Load packet bytes then `save_memo` into the review store (named-memo form,
|
7. Unfinished memos stay in the batch. File-level owning-repo updates are T04
|
||||||
no inbox).
|
|
||||||
5. Sign in at the origin. Open `/review?memo_id=infd-20260914-c01` through
|
|
||||||
`…-d04` in order. Acknowledge the required highlight. Bind that memo. Repeat.
|
|
||||||
6. Unfinished memos stay in the batch. File-level owning-repo updates are T04
|
|
||||||
after a human bind, via `fix-consistency`, never `POST /workplans/`.
|
after a human bind, via `fix-consistency`, never `POST /workplans/`.
|
||||||
|
|
||||||
A recorded desktop sitting (same order, same highlights, `view_hash` from
|
|
||||||
`presentation.render` only) is allowed if the Stage 1 UI still cannot take the
|
|
||||||
group. It is still a human bind. An agent must not complete the form.
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ Preflight: `uv run python tools/sitting_bind_preflight.py --origin https://decis
|
||||||
Operator packet: `OPERATOR.md`.
|
Operator packet: `OPERATOR.md`.
|
||||||
|
|
||||||
T08 already took three human accepts on this origin for `SECRETS-WP-0010-T03-*`.
|
T08 already took three human accepts on this origin for `SECRETS-WP-0010-T03-*`.
|
||||||
Those memos are not this sitting. Live accept is currently closed
|
Those memos are not this sitting. Live accept reopened 2026-09-14 22:16 UTC
|
||||||
(`/readyz` 503) because audit-core has no ready Service endpoints.
|
(`/readyz` 200). This sitting stays `draft-unsigned` until native receipts,
|
||||||
This sitting stays `draft-unsigned` until a human binds through the surface.
|
a live KeyCape subject, a new Flex Auth package, and a human bind.
|
||||||
|
Attach (does not bind): `uv run python tools/attach_compact_bindings.py`.
|
||||||
|
|
|
||||||
37
docs/batches/2026-09-14/policy-request.md
Normal file
37
docs/batches/2026-09-14/policy-request.md
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Flex Auth policy request — compact sitting
|
||||||
|
|
||||||
|
Not admitted. Not a local allow. Not an expansion of the T03 three-record
|
||||||
|
mandate (`FLEX-WP-0027`, `examples/informed-decision-t03`).
|
||||||
|
|
||||||
|
`net-kingdom-admins` may review these eight Decision Memos **only after**
|
||||||
|
the operator admits a new package that pins exact `memo_id`, `approval_id`
|
||||||
|
and native `binding.digest`. Until those approval objects exist, this file
|
||||||
|
is a request shape, not a compilable package.
|
||||||
|
|
||||||
|
## Intended allow (same identity bar as T03)
|
||||||
|
|
||||||
|
- caller: `system:serviceaccount:informed-decision:review` via TokenReview
|
||||||
|
- subject: verified human, `tenant:platform`, group `net-kingdom-admins`,
|
||||||
|
KeyCape AAL2 MFA facts as in the T03 package
|
||||||
|
- actions: `read`, `acknowledge`, `accept`, `return`, `discuss`, `decline`
|
||||||
|
- deny every other resource id
|
||||||
|
- no consume, no approval create, no presentation claim as policy input
|
||||||
|
|
||||||
|
## Exact resource ids (approval ids still unknown)
|
||||||
|
|
||||||
|
| resource.id | Blocking record |
|
||||||
|
| --- | --- |
|
||||||
|
| `memo:infd-20260914-c01` | SECRETS-WP-0010 native delivery |
|
||||||
|
| `memo:infd-20260914-c02` | RPF-WP-0035-T02 |
|
||||||
|
| `memo:infd-20260914-c03` | NK-WP-0032-T03 |
|
||||||
|
| `memo:infd-20260914-c04` | WARDEN-WP-0027-T02 |
|
||||||
|
| `memo:infd-20260914-d01` | CUST-WP-0038-T08 |
|
||||||
|
| `memo:infd-20260914-d02` | HFACT-WP-0001-T03 |
|
||||||
|
| `memo:infd-20260914-d03` | MASON-WP-0005 plan |
|
||||||
|
| `memo:infd-20260914-d04` | RCLK-WP-0002-T01 |
|
||||||
|
|
||||||
|
Do not copy T03 approval ids into this table. Do not serve this list as
|
||||||
|
policy until a created-receipt supplies `approval_id` and `binding.digest`
|
||||||
|
for every row.
|
||||||
|
|
||||||
|
Owner: flex-auth. This repository drafts; it does not evaluate authorization.
|
||||||
|
|
@ -8,6 +8,6 @@
|
||||||
"infd-batch-2026-09-14-decisions"
|
"infd-batch-2026-09-14-decisions"
|
||||||
],
|
],
|
||||||
"memo_count": 8,
|
"memo_count": 8,
|
||||||
"bind_path": "INFD-WP-0001-T08 historically proven; live accept closed 2026-09-14T16:56:55Z",
|
"bind_path": "INFD-WP-0001-T08 historically proven; live accept open 2026-09-14T22:16:07Z; sitting not admitted",
|
||||||
"note": "Unsigned drafts. Do not submit until a human sitting is opened under T03. Live bind substitutes pending-human-session with the authenticated key-cape subject. Probe: docs/evidence/2026-09-14-infd-0002-t03-bind-path-probe.json. Operator packet: OPERATOR.md."
|
"note": "Unsigned drafts. Live accept reopened after audit-core rollout. Remaining: native approval receipts, live KeyCape subject, new Flex Auth package (not the T03 three-record mandate), then a human sitting. Operator packet: OPERATOR.md."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
51
docs/evidence/2026-09-14-infd-0002-t03-accept-reopened.json
Normal file
51
docs/evidence/2026-09-14-infd-0002-t03-accept-reopened.json
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"kind": "infd-wp-0002-t03-bind-path-probe",
|
||||||
|
"observed_at": "2026-09-14T22:16:07Z",
|
||||||
|
"workplan": "INFD-WP-0002",
|
||||||
|
"task": "INFD-WP-0002-T03",
|
||||||
|
"scope": "Re-probe after audit-core /readyz fix rollout. No human login, no presentation, no disposition, no approval create, no secret read.",
|
||||||
|
"precedes": "docs/evidence/2026-09-14-infd-0002-t03-bind-path-probe.json",
|
||||||
|
"origin": {
|
||||||
|
"url": "https://decisions.coulomb.social/",
|
||||||
|
"healthz": {"http": 200, "status": "ok"},
|
||||||
|
"readyz": {"http": 200, "status": "ready"},
|
||||||
|
"auth_start": {"http": 303, "location_host": "kc.coulomb.social"}
|
||||||
|
},
|
||||||
|
"cluster": {
|
||||||
|
"audit-core": {
|
||||||
|
"pod": "audit-core-6f6545dcf5-xklmd",
|
||||||
|
"ready": true,
|
||||||
|
"age": "73m at probe",
|
||||||
|
"classic_endpoints": "10.42.0.4:8080",
|
||||||
|
"from_informed_decision_pod": {
|
||||||
|
"healthz": 200,
|
||||||
|
"readyz": 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"informed-decision": {
|
||||||
|
"pod": "informed-decision-b7f6fc6c9-kd4bl",
|
||||||
|
"ready": true
|
||||||
|
},
|
||||||
|
"approval-engine": {
|
||||||
|
"pod": "approval-engine-0",
|
||||||
|
"ready": true,
|
||||||
|
"in_cluster_healthz": 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preflight": {
|
||||||
|
"live_accept": "open",
|
||||||
|
"ready_to_sit": false,
|
||||||
|
"gates": ["missing_act_binding", "principal_not_live_subject"],
|
||||||
|
"agent_disposition": "forbidden"
|
||||||
|
},
|
||||||
|
"remaining_external_gates": [
|
||||||
|
"flex-auth T03 package still admits only memo:SECRETS-WP-0010-T03-{apply,verify,exec}",
|
||||||
|
"no approval:create requester whose actor matches these eight acts; secrets-engine-requester must not be reused for unrelated acts",
|
||||||
|
"unsigned drafts still have approval_id null and principal pending-human-session",
|
||||||
|
"human bind still required; agent may not accept"
|
||||||
|
],
|
||||||
|
"audit_core_ack": {
|
||||||
|
"message_id": "676f123d-ea3d-47f0-bf86-568635c4b3e5",
|
||||||
|
"fix": "audit-core b0e6792 ADHOC-2026-09-14-T01; live rollout observed ready"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -52,9 +52,12 @@ decision/assent memos. Indexes name the blocking workplan/task. Do not
|
||||||
submit until a human sitting is opened under T03.
|
submit until a human sitting is opened under T03.
|
||||||
|
|
||||||
2026-09-14 16:56 UTC: Stage 1 historically bound three other memos
|
2026-09-14 16:56 UTC: Stage 1 historically bound three other memos
|
||||||
(`SECRETS-WP-0010-T03-*`) on this origin. New accepts are closed while
|
(`SECRETS-WP-0010-T03-*`) on this origin. New accepts were closed while
|
||||||
`GET /readyz` returns 503 (`approval_path_not_connected`) because
|
`GET /readyz` returned 503 because audit-core had no ready endpoints.
|
||||||
audit-core is not Ready and its Service has no ready endpoints. Compact
|
|
||||||
memos still have `approval_id: null`. Operator packet:
|
2026-09-14 22:16 UTC: live accept reopened (`/readyz` 200) after the
|
||||||
`docs/batches/2026-09-14/OPERATOR.md`. Preflight:
|
audit-core `/readyz` fix rolled out. Compact memos still have
|
||||||
`tools/sitting_bind_preflight.py`.
|
`approval_id: null`. Flex Auth still admits only the three T03 records.
|
||||||
|
Operator packet: `docs/batches/2026-09-14/OPERATOR.md`. Preflight:
|
||||||
|
`tools/sitting_bind_preflight.py`. Attach (no bind):
|
||||||
|
`tools/attach_compact_bindings.py`.
|
||||||
|
|
|
||||||
97
tests/test_attach_compact_bindings.py
Normal file
97
tests/test_attach_compact_bindings.py
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from informed_decision.records import memo_from
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"attach_compact_bindings",
|
||||||
|
Path(__file__).resolve().parents[1] / "tools" / "attach_compact_bindings.py",
|
||||||
|
)
|
||||||
|
attach_mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(attach_mod)
|
||||||
|
|
||||||
|
|
||||||
|
def _receipt(memo_ids, *, human_control=True, entries=None, status="requested"):
|
||||||
|
requests = []
|
||||||
|
for i, memo_id in enumerate(memo_ids, start=1):
|
||||||
|
digest = "sha256:" + f"{i:064x}"
|
||||||
|
requests.append(
|
||||||
|
{
|
||||||
|
"memo_id": memo_id,
|
||||||
|
"approval": {
|
||||||
|
"id": f"approval-{i:02d}",
|
||||||
|
"status": status,
|
||||||
|
"entries": entries or [],
|
||||||
|
"binding": {"digest": digest, "human_control": human_control},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return {"status": "created", "requests": requests}
|
||||||
|
|
||||||
|
|
||||||
|
def _memo_ids(root):
|
||||||
|
ids = []
|
||||||
|
for name in ("credentials", "decisions"):
|
||||||
|
index = json.loads((root / name / "index.json").read_text())
|
||||||
|
ids.extend(row["memo_id"] for row in index["ordinal"])
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
def test_attach_writes_bound_copies_and_leaves_drafts_unsigned(tmp_path):
|
||||||
|
src = Path(__file__).resolve().parents[1] / "docs" / "batches" / "2026-09-14"
|
||||||
|
root = tmp_path / "sitting"
|
||||||
|
for name in ("credentials", "decisions"):
|
||||||
|
(root / name).mkdir(parents=True)
|
||||||
|
for item in (src / name).iterdir():
|
||||||
|
(root / name / item.name).write_bytes(item.read_bytes())
|
||||||
|
(root / "sitting.json").write_bytes((src / "sitting.json").read_bytes())
|
||||||
|
ids = _memo_ids(root)
|
||||||
|
index = attach_mod.attach("human-subject-1", _receipt(ids), root)
|
||||||
|
assert index["principal"] == "human-subject-1"
|
||||||
|
assert len(index["memos"]) == 8
|
||||||
|
draft = memo_from(json.loads((root / "credentials" / "infd-20260914-c01.memo.json").read_text()))
|
||||||
|
assert draft.approval_id is None
|
||||||
|
assert draft.binding.principal.id == "pending-human-session"
|
||||||
|
bound = memo_from(json.loads((root / "bound" / "infd-20260914-c01.memo.json").read_text()))
|
||||||
|
assert bound.approval_id == "approval-01"
|
||||||
|
assert bound.approval_binding_digest.startswith("sha256:")
|
||||||
|
assert bound.binding.principal.id == "human-subject-1"
|
||||||
|
assert bound.question == draft.question
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"fault",
|
||||||
|
["pending-human-session", "has space", "", "not-created", "wrong-count", "has-entry", "no-control"],
|
||||||
|
)
|
||||||
|
def test_attach_refuses_unsafe_inputs(tmp_path, fault):
|
||||||
|
src = Path(__file__).resolve().parents[1] / "docs" / "batches" / "2026-09-14"
|
||||||
|
root = tmp_path / "sitting"
|
||||||
|
for name in ("credentials", "decisions"):
|
||||||
|
(root / name).mkdir(parents=True)
|
||||||
|
for item in (src / name).iterdir():
|
||||||
|
(root / name / item.name).write_bytes(item.read_bytes())
|
||||||
|
(root / "sitting.json").write_bytes((src / "sitting.json").read_bytes())
|
||||||
|
ids = _memo_ids(root)
|
||||||
|
principal = "human-subject-1"
|
||||||
|
receipt = _receipt(ids)
|
||||||
|
if fault == "pending-human-session":
|
||||||
|
principal = "pending-human-session"
|
||||||
|
elif fault == "has space":
|
||||||
|
principal = "human subject"
|
||||||
|
elif fault == "":
|
||||||
|
principal = ""
|
||||||
|
elif fault == "not-created":
|
||||||
|
receipt["status"] = "draft"
|
||||||
|
elif fault == "wrong-count":
|
||||||
|
receipt["requests"] = receipt["requests"][:7]
|
||||||
|
elif fault == "has-entry":
|
||||||
|
receipt = _receipt(ids, entries=[{"subject_id": "someone"}])
|
||||||
|
elif fault == "no-control":
|
||||||
|
receipt = _receipt(ids, human_control=False)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
attach_mod.attach(principal, receipt, root)
|
||||||
|
assert not (root / "bound").exists()
|
||||||
118
tools/attach_compact_bindings.py
Normal file
118
tools/attach_compact_bindings.py
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
"""Attach native approval ids to compact-sitting drafts (INFD-WP-0002-T03).
|
||||||
|
|
||||||
|
Requires an exact live KeyCape subject and eight unapproved human-control
|
||||||
|
receipts. Writes bound memo copies; does not present, bind, create approvals,
|
||||||
|
or load the live store.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from dataclasses import replace
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from informed_decision.memo import Principal
|
||||||
|
from informed_decision.records import dumps, memo_from
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1] / "docs" / "batches" / "2026-09-14"
|
||||||
|
DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
|
||||||
|
APPROVAL_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$")
|
||||||
|
|
||||||
|
|
||||||
|
def _principal(value: str) -> Principal:
|
||||||
|
if not value or any(c.isspace() for c in value) or len(value) > 256:
|
||||||
|
raise ValueError("exact human subject required")
|
||||||
|
if value == "pending-human-session":
|
||||||
|
raise ValueError("live key-cape subject required")
|
||||||
|
return Principal(id=value, kind="person", display_name=value, role="reviewer")
|
||||||
|
|
||||||
|
|
||||||
|
def _approval(row: dict, memo_id: str) -> dict:
|
||||||
|
if row.get("memo_id") != memo_id:
|
||||||
|
raise ValueError(f"receipt memo_id mismatch for {memo_id}")
|
||||||
|
approval = row.get("approval") or {}
|
||||||
|
binding = approval.get("binding") or {}
|
||||||
|
digest = binding.get("digest")
|
||||||
|
if approval.get("status") != "requested" or approval.get("entries"):
|
||||||
|
raise ValueError(f"unapproved human-control request required for {memo_id}")
|
||||||
|
if binding.get("human_control") is not True:
|
||||||
|
raise ValueError(f"human_control required for {memo_id}")
|
||||||
|
if not isinstance(approval.get("id"), str) or not APPROVAL_ID.fullmatch(approval["id"]):
|
||||||
|
raise ValueError(f"native approval id required for {memo_id}")
|
||||||
|
if not isinstance(digest, str) or not DIGEST.fullmatch(digest):
|
||||||
|
raise ValueError(f"native binding digest required for {memo_id}")
|
||||||
|
return approval
|
||||||
|
|
||||||
|
|
||||||
|
def attach(principal: str, receipt: dict, root: Path = ROOT) -> dict:
|
||||||
|
actor = _principal(principal)
|
||||||
|
if receipt.get("status") != "created":
|
||||||
|
raise ValueError("created native receipt required")
|
||||||
|
sitting = json.loads((root / "sitting.json").read_text())
|
||||||
|
expected = []
|
||||||
|
for name in ("credentials", "decisions"):
|
||||||
|
index = json.loads((root / name / "index.json").read_text())
|
||||||
|
for row in index["ordinal"]:
|
||||||
|
expected.append((name, row["memo_id"], row["memo"], row["packet"]))
|
||||||
|
requests = receipt.get("requests") or []
|
||||||
|
if len(requests) != len(expected):
|
||||||
|
raise ValueError("receipt must cover every sitting memo once")
|
||||||
|
by_id = {row.get("memo_id"): row for row in requests}
|
||||||
|
if set(by_id) != {memo_id for _, memo_id, _, _ in expected}:
|
||||||
|
raise ValueError("receipt memo set must match the sitting")
|
||||||
|
prepared = []
|
||||||
|
for name, memo_id, memo_name, packet_name in expected:
|
||||||
|
approval = _approval(by_id[memo_id], memo_id)
|
||||||
|
memo = memo_from(json.loads((root / name / memo_name).read_text()))
|
||||||
|
if memo.approval_id is not None or memo.binding.principal.id != "pending-human-session":
|
||||||
|
raise ValueError(f"unsigned draft required for {memo_id}")
|
||||||
|
bound = replace(
|
||||||
|
memo,
|
||||||
|
binding=replace(memo.binding, principal=actor),
|
||||||
|
approval_id=approval["id"],
|
||||||
|
approval_binding_digest=approval["binding"]["digest"],
|
||||||
|
)
|
||||||
|
prepared.append((name, memo_id, memo_name, packet_name, bound))
|
||||||
|
bound_root = root / "bound"
|
||||||
|
bound_root.mkdir(parents=True, exist_ok=True)
|
||||||
|
written = []
|
||||||
|
for name, memo_id, memo_name, packet_name, bound in prepared:
|
||||||
|
path = bound_root / memo_name
|
||||||
|
path.write_text(dumps(bound) + "\n", encoding="utf-8")
|
||||||
|
packet_src = root / name / packet_name
|
||||||
|
packet_dst = bound_root / packet_name
|
||||||
|
packet_dst.write_bytes(packet_src.read_bytes())
|
||||||
|
written.append(
|
||||||
|
{
|
||||||
|
"memo_id": memo_id,
|
||||||
|
"approval_id": bound.approval_id,
|
||||||
|
"memo": path.name,
|
||||||
|
"packet": packet_dst.name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
index = {
|
||||||
|
"kind": "informed-decision-bound-sitting",
|
||||||
|
"sitting_id": sitting["id"],
|
||||||
|
"principal": actor.id,
|
||||||
|
"submitted": False,
|
||||||
|
"agent_disposition_forbidden": True,
|
||||||
|
"memos": written,
|
||||||
|
}
|
||||||
|
(bound_root / "index.json").write_text(json.dumps(index, indent=2, ensure_ascii=False) + "\n")
|
||||||
|
return index
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--principal", required=True, help="exact live KeyCape subject")
|
||||||
|
parser.add_argument("--receipt", type=Path, required=True, help="native created-approval receipt")
|
||||||
|
args = parser.parse_args()
|
||||||
|
receipt = json.loads(args.receipt.read_text())
|
||||||
|
index = attach(args.principal, receipt)
|
||||||
|
print(json.dumps({"status": "bound_copies_written", "count": len(index["memos"])}, indent=2))
|
||||||
|
print("No presentations, dispositions, or approval entries created.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -11,7 +11,7 @@ flavor: planning
|
||||||
depends_on:
|
depends_on:
|
||||||
- INFD-WP-0001
|
- INFD-WP-0001
|
||||||
created: "2026-09-14"
|
created: "2026-09-14"
|
||||||
updated: "2026-09-14"
|
updated: "2026-09-15"
|
||||||
related:
|
related:
|
||||||
- INFD-WP-0001
|
- INFD-WP-0001
|
||||||
- STATE-WP-0092
|
- STATE-WP-0092
|
||||||
|
|
@ -131,6 +131,19 @@ refused talking to the audit ClusterIP. Operator packet
|
||||||
`tools/sitting_bind_preflight.py`. No agent disposition. Task stays
|
`tools/sitting_bind_preflight.py`. No agent disposition. Task stays
|
||||||
`wait`.
|
`wait`.
|
||||||
|
|
||||||
|
2026-09-14 22:16 UTC — **live accept reopened; sitting still not
|
||||||
|
admitted.** audit-core `b0e6792` is Ready; origin `/readyz` 200;
|
||||||
|
preflight `live_accept=open`. Remaining gates: (1) Flex Auth T03
|
||||||
|
package still allows only `memo:SECRETS-WP-0010-T03-*` —
|
||||||
|
`docs/batches/2026-09-14/policy-request.md` is a request, not an
|
||||||
|
admission; (2) no `approval:create` requester for these eight acts;
|
||||||
|
(3) drafts still `approval_id: null` / `pending-human-session`;
|
||||||
|
(4) human bind. Attach tool `tools/attach_compact_bindings.py` writes
|
||||||
|
bound copies from a created receipt and live subject; it does not
|
||||||
|
create approvals or dispositions. Evidence:
|
||||||
|
`docs/evidence/2026-09-14-infd-0002-t03-accept-reopened.json`.
|
||||||
|
Task stays `wait`.
|
||||||
|
|
||||||
## Feed outcomes back to State Hub without hub-authoring
|
## Feed outcomes back to State Hub without hub-authoring
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue