diff --git a/deploy/sitting-admission.json b/deploy/sitting-admission.json new file mode 100644 index 0000000..49fa642 --- /dev/null +++ b/deploy/sitting-admission.json @@ -0,0 +1,14 @@ +{ + "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:8f55bcecf37a8d65f96e073510b1ffb4636c0a91d75e1ee7d582ad4bce8b953a", + "policy": { + "origin": "http://flex-auth-informed-decision-sitting.flex-auth.svc.cluster.local:8080", + "package": "informed-decision.compact-sitting", + "version": "v1", + "package_digest": "sha256:e0afd52e046616a93142f4064704b1cee6496801d94612fce29a958cf04eb41a", + "pod_name": "flex-auth-informed-decision-sitting" + }, + "keycape_egress_ips": [ + "92.205.62.239" + ], + "storage_class": "local-path" +} diff --git a/docs/batches/2026-09-14/OPERATOR.md b/docs/batches/2026-09-14/OPERATOR.md index cca80ee..6d2c0d3 100644 --- a/docs/batches/2026-09-14/OPERATOR.md +++ b/docs/batches/2026-09-14/OPERATOR.md @@ -83,8 +83,20 @@ Open in this order. One question each. No approve-all. writes `bound/` copies. Unsigned drafts stay unsigned. 4. Flex Auth admits a **new** package pinning those eight `memo:` ids to the native approval id/digest, same identity bar as T03, deny everything else. -5. Load packet bytes then `save_memo` from `bound/` into the review store. -6. Sign in. Open `/review?memo_id=infd-20260914-c01` through `…-d04` in order. +5. 2026-09-15 21:31 UTC: review runtime now uses sitting PDP + `flex-auth-informed-decision-sitting` / `informed-decision.compact-sitting`. + Seven memos are in the live store for the existing named recipient. `c01` + is not loaded. +6. Sign in at https://decisions.coulomb.social/ . Open in order: + + `/review?memo_id=infd-20260914-c02` + `/review?memo_id=infd-20260914-c03` + `/review?memo_id=infd-20260914-c04` + `/review?memo_id=infd-20260914-d01` + `/review?memo_id=infd-20260914-d02` + `/review?memo_id=infd-20260914-d03` + `/review?memo_id=infd-20260914-d04` + Acknowledge the required highlight. Bind that memo. Repeat. 7. Unfinished memos stay in the batch. File-level owning-repo updates are T04 after a human bind, via `fix-consistency`, never `POST /workplans/`. diff --git a/tests/test_attach_compact_bindings.py b/tests/test_attach_compact_bindings.py index ada03ca..29311af 100644 --- a/tests/test_attach_compact_bindings.py +++ b/tests/test_attach_compact_bindings.py @@ -51,21 +51,33 @@ def test_attach_writes_bound_copies_and_leaves_drafts_unsigned(tmp_path): (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())) + + +def test_attach_allows_seven_memo_subset(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 = [i for i in _memo_ids(root) if i != "infd-20260914-c01"] + index = attach_mod.attach("human-subject-1", _receipt(ids), root) + assert index["principal"] == "human-subject-1" + assert len(index["memos"]) == 7 + assert "infd-20260914-c01" not in {row["memo_id"] for row in index["memos"]} + draft = memo_from(json.loads((root / "credentials" / "infd-20260914-c02.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())) + bound = memo_from(json.loads((root / "bound" / "infd-20260914-c02.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 + assert not (root / "bound" / "infd-20260914-c01.memo.json").exists() @pytest.mark.parametrize( "fault", - ["pending-human-session", "has space", "", "not-created", "wrong-count", "has-entry", "no-control"], + ["pending-human-session", "has space", "", "not-created", "unknown-memo", "has-entry", "no-control"], ) def test_attach_refuses_unsafe_inputs(tmp_path, fault): src = Path(__file__).resolve().parents[1] / "docs" / "batches" / "2026-09-14" @@ -86,8 +98,8 @@ def test_attach_refuses_unsafe_inputs(tmp_path, fault): principal = "" elif fault == "not-created": receipt["status"] = "draft" - elif fault == "wrong-count": - receipt["requests"] = receipt["requests"][:7] + elif fault == "unknown-memo": + receipt["requests"][0]["memo_id"] = "not-a-sitting-memo" elif fault == "has-entry": receipt = _receipt(ids, entries=[{"subject_id": "someone"}]) elif fault == "no-control": diff --git a/tools/attach_compact_bindings.py b/tools/attach_compact_bindings.py index 33a74fa..227a0ad 100644 --- a/tools/attach_compact_bindings.py +++ b/tools/attach_compact_bindings.py @@ -56,13 +56,14 @@ def attach(principal: str, receipt: dict, root: Path = ROOT) -> dict: 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") + sitting_ids = {memo_id for _, memo_id, _, _ in expected} + if not by_id or set(by_id) - sitting_ids: + raise ValueError("receipt memo set must be a non-empty subset of the sitting") prepared = [] for name, memo_id, memo_name, packet_name in expected: + if memo_id not in by_id: + continue 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": diff --git a/tools/load_sitting_memos.py b/tools/load_sitting_memos.py new file mode 100644 index 0000000..a2f1048 --- /dev/null +++ b/tools/load_sitting_memos.py @@ -0,0 +1,51 @@ +"""Load bound sitting memos into a review store. Does not present or bind.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from informed_decision.records import memo_from as _memo_from +from informed_decision.store import Store + + +def principal_from_store(store: Store) -> str: + with store._connection() as db: + rows = list(db.execute("SELECT body FROM presentations")) + subs = {json.loads(body)["principal_sub"] for (body,) in rows} + if len(subs) != 1: + raise ValueError("single live recipient required") + value = next(iter(subs)) + if not value or value == "pending-human-session": + raise ValueError("live key-cape subject required") + return value + + +def load(store: Store, bound: Path) -> dict: + index = json.loads((bound / "index.json").read_text()) + if index.get("agent_disposition_forbidden") is not True: + raise ValueError("bound sitting index required") + loaded = [] + for row in index["memos"]: + memo = _memo_from(json.loads((bound / row["memo"]).read_text())) + packet = (bound / row["packet"]).read_bytes() + digest = store.put_document(packet) + if digest != memo.packet[0].hash: + raise ValueError(f"packet digest mismatch for {memo.id}") + store.save_memo(memo) + loaded.append(memo.id) + return {"status": "loaded", "count": len(loaded), "memo_ids": loaded} + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--db", type=Path, required=True) + parser.add_argument("--bound", type=Path, required=True) + args = parser.parse_args() + store = Store(args.db) + print(json.dumps(load(store, args.bound), indent=2)) + print("No presentations or dispositions created.") + + +if __name__ == "__main__": + main() diff --git a/workplans/INFD-WP-0002-compact-signoff-batches.md b/workplans/INFD-WP-0002-compact-signoff-batches.md index 7288b73..ef98e81 100644 --- a/workplans/INFD-WP-0002-compact-signoff-batches.md +++ b/workplans/INFD-WP-0002-compact-signoff-batches.md @@ -174,6 +174,16 @@ Flex Auth draft `docs/batches/2026-09-14/policy-package.md` is not admitted. Attach still needs the operator KeyCape `sub`. No human bind. Task stays `wait`. +2026-09-15 21:31 UTC — **sitting PDP live on the origin; seven memos +loaded.** FLEX-WP-0028 admitted +`flex-auth-informed-decision-sitting` / +`informed-decision.compact-sitting` +(`sha256:e0afd52e046616a93142f4064704b1cee6496801d94612fce29a958cf04eb41a`). +Review ConfigMap `informed-decision-runtime-bab38e8704a2`; egress peer +cut over from T03. Named recipient reused from existing presentations +(not written to git). Store has seven `infd-20260914-*` memos plus the +three T03 records. Human bind remaining. Task stays `wait`. + ## Feed outcomes back to State Hub without hub-authoring ```task