diff --git a/deploy/sitting-admission.json b/deploy/sitting-admission.json index 49fa642..c168ab7 100644 --- a/deploy/sitting-admission.json +++ b/deploy/sitting-admission.json @@ -1,10 +1,10 @@ { - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:8f55bcecf37a8d65f96e073510b1ffb4636c0a91d75e1ee7d582ad4bce8b953a", + "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:ef6fdd61209863654be3adba070661645a2714e86cb4b7f4e847540291b55f07", "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", + "version": "v2", + "package_digest": "sha256:24a524785740e60df70e0ced4d884f1a6197bf3fbf7b003471947ce76b763ead", "pod_name": "flex-auth-informed-decision-sitting" }, "keycape_egress_ips": [ diff --git a/docs/batches/2026-09-21/bound/index.json b/docs/batches/2026-09-21/bound/index.json new file mode 100644 index 0000000..46da707 --- /dev/null +++ b/docs/batches/2026-09-21/bound/index.json @@ -0,0 +1,15 @@ +{ + "kind": "informed-decision-bound-sitting", + "sitting_id": "infd-sitting-2026-09-21", + "principal": "uid=platform-root,ou=people,dc=netkingdom,dc=local", + "submitted": false, + "agent_disposition_forbidden": true, + "memos": [ + { + "memo_id": "infd-20260921-b01", + "approval_id": "ba5ce2d8-8b6d-40be-af89-2e8c147029a3", + "memo": "infd-20260921-b01.memo.json", + "packet": "infd-20260921-b01.packet.md" + } + ] +} diff --git a/docs/batches/2026-09-21/bound/infd-20260921-b01.memo.json b/docs/batches/2026-09-21/bound/infd-20260921-b01.memo.json new file mode 100644 index 0000000..ab96c99 --- /dev/null +++ b/docs/batches/2026-09-21/bound/infd-20260921-b01.memo.json @@ -0,0 +1 @@ +{"approval_binding_digest":"sha256:e9eaf454e94871ec2060855fcfbd327bcaec873d2c61cae031bec9f95a34bcae","approval_id":"ba5ce2d8-8b6d-40be-af89-2e8c147029a3","binding":{"justification":null,"principal":{"display_name":"uid=platform-root,ou=people,dc=netkingdom,dc=local","id":"uid=platform-root,ou=people,dc=netkingdom,dc=local","identifiers":[],"kind":"person","role":"reviewer"},"target":{"environment":"prod","id":"spend-envelope:hfact-glas-anthropic-2026-09","kind":"spend-envelope","label":"Glas metered runs on the Anthropic key (railiance01)","requires_new_bind":false},"terms":null},"binding_level":"organizational","brief":"Sets the spending authority that rein-aharness MessagesOwner enforces before each metered Glas run on railiance01. Accepting it does not deliver the key, run a model or create an OpenBao policy; those are three separate SECRETS-WP-0009-T03 approvals. The EUR 100 monthly limit is recorded here but not enforced by the software.","highlights":[{"id":"infd-20260921-b01-h1","item_id":"infd-20260921-b01-packet","locator":{"kind":"work_record","task":"SECRETS-WP-0009-T03","workplan":"SECRETS-WP-0009"},"note":"The EUR 100 per month limit is NOT enforced: SpendPolicy has no monthly ceiling. Only EUR 5 per run, EUR 20 per day and EUR 500 total are enforced. Every completed run is charged its full reservation (EUR 4.9938), not its actual cost.","required_ack":true,"severity":"critical"}],"id":"infd-20260921-b01","locale":"en","packet":[{"hash":"sha256:a3d3f4e33d75579795c0e407971c0d472432569ef68a9408e9897587a58f0deb","item_id":"infd-20260921-b01-packet","label":"infd-20260921-b01-packet"}],"question":"Accept the spend envelope for metered Glas runs on the Anthropic key (EUR 5 per run, 20 per day, 500 total, until 2027-01-31)?","requested_act":"approve","sealed":false,"step_kind":"approve","ui_release":"informed-decision@0.2.0","version":1} diff --git a/docs/batches/2026-09-21/bound/infd-20260921-b01.packet.md b/docs/batches/2026-09-21/bound/infd-20260921-b01.packet.md new file mode 100644 index 0000000..5d1a9e4 --- /dev/null +++ b/docs/batches/2026-09-21/bound/infd-20260921-b01.packet.md @@ -0,0 +1,51 @@ +# SECRETS-WP-0009-T03 — spend envelope for metered Glas runs + +Blocking workplan: SECRETS-WP-0009 +Blocking task: SECRETS-WP-0009-T03 (hub prefix f8069c8a) +Related: HFACT-WP-0001-T01/T04 (SpendPolicy and owner config), GLAS-WP-0012 + +## Question +Accept the spend envelope for metered Glas runs on the Anthropic key (EUR 5 per run, 20 per day, 500 total, until 2027-01-31)? + +## One act +Accept this envelope as the spend authority for rein-aharness MessagesOwner +`metered-once` runs on railiance01 using the Anthropic key of catalog lane +`glas-claude-agent-dev-anthropic`. The accepted approval id becomes the +SpendPolicy `authority_ref` for envelope `hfact-glas-anthropic-2026-09`. + +## Enforced values (rein-aharness SpendPolicy, decimal strings) + +| Field | Value | Note | +| --- | --- | --- | +| `per_run_eur` | 5 | Operator-stated | +| `daily_eur` | 20 | Operator-stated; timezone Europe/Berlin | +| `total_eur` | 500 | Operator-stated; over the whole envelope | +| `eur_per_usd` | 0.87 | Operator rate 1 EUR = 1.15 USD (1/1.15 = 0.8696), rounded up | +| `max_liability_usd` | 5.74 | Per run. Reserves ceil(5.74 x 0.87) = EUR 4.9938, inside EUR 5 | +| `max_budget_usd` | 5.00 | Per run. Claude CLI stop threshold, inside liability | +| `valid_from` | acceptance time | | +| `expires_at` | 2027-01-31T23:59:59+01:00 | Operator-stated | + +Resulting capacity: at most 4 runs per day and 100 runs in total. A completed run +is charged its full reservation, not its reported cost. A failed, cancelled or +unaccounted run keeps its reservation and blocks further runs until reconciled. +Cost above liability permanently marks the envelope breached. + +The operator's overall USD 600 budget and USD 800 liability caps are dominated +by `total_eur` 500 (= USD 575) and need no separate field. + +## Recorded, not enforced +- EUR 100 per calendar month. SpendPolicy has no monthly ceiling; a rein-aharness + follow-up adds `monthly_eur`. Until then the operator reviews monthly use. + +## Scope bound by the SpendPolicy, filled in before the private file is written +`worker_id`, `activity_definition_id`, `target_repo`, `project`, `profile_ref`, +`profile_sha256`, `descriptor_sha256`, `repository_grant_id`, `max_turns` come from +the admitted factory profile (HFACT-WP-0001-T01). They narrow this envelope; they +cannot widen the amounts above. + +## Must not +- Deliver, read or display the Anthropic key +- Authorize the OpenBao apply, verify or exec actions (separate T03 approvals) +- Cover llm-connect or its DeepSeek default; that path is not this envelope +- Raise any amount or extend the expiry without a new memo diff --git a/tools/attach_compact_bindings.py b/tools/attach_compact_bindings.py index 227a0ad..5c36d31 100644 --- a/tools/attach_compact_bindings.py +++ b/tools/attach_compact_bindings.py @@ -45,13 +45,13 @@ def _approval(row: dict, memo_id: str) -> dict: return approval -def attach(principal: str, receipt: dict, root: Path = ROOT) -> dict: +def attach(principal: str, receipt: dict, root: Path = ROOT, batches: tuple[str, ...] = ("credentials", "decisions")) -> 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"): + for name in batches: index = json.loads((root / name / "index.json").read_text()) for row in index["ordinal"]: expected.append((name, row["memo_id"], row["memo"], row["packet"])) @@ -108,9 +108,14 @@ 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") + parser.add_argument("--sitting", help="batch date directory under docs/batches; default 2026-09-14") + parser.add_argument("--batches", default="credentials,decisions", help="comma-separated batch directories") args = parser.parse_args() receipt = json.loads(args.receipt.read_text()) - index = attach(args.principal, receipt) + root = ROOT.parent / args.sitting if args.sitting else ROOT + if args.sitting and "/" in args.sitting: + raise SystemExit("sitting must be a directory name") + index = attach(args.principal, receipt, root, tuple(b for b in args.batches.split(",") if b)) print(json.dumps({"status": "bound_copies_written", "count": len(index["memos"])}, indent=2)) print("No presentations, dispositions, or approval entries created.")