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