diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 932a519dc..cdfe80b9a 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -476,6 +476,7 @@ The same explicitly authorized rollover contract applies to the later supported | `acceptance-revalidation-v10` | V9 | | `acceptance-revalidation-v11` | V10 | | `acceptance-revalidation-v12` | V11 | +| `acceptance-revalidation-v13` | V12, including its retained unknown request | Before preparation, retain the predecessor ledger, binding and guard at their pinned fingerprints and preserve the entire earlier history chain. The selected policy's @@ -494,6 +495,14 @@ development passes, within its cumulative and per-purpose limits. Earlier period halted. A halted replacement is not resumed or reset; this runbook grants no automatic replacement, retries or paid calls. +V13 requires explicit owner authorization to continue DeepSeek under the cumulative +USD200 ceiling. Its sealed V12 history retains every earlier attempt, known charge, +conservative commitment and all three unknown liabilities. These are cumulative +liabilities, never new acceptance receipts or verified provider charges. +V12 remains permanently halted; V13 starts +its complete fresh prefix on the newly bound candidate. Unknown settlement needs +verifiable provider usage and must not be inferred from a balance change. + Issue the evidence-bound U02 gate only after those inputs validate. Gate artifact references are relative to the private directory containing the gate; place the referenced artifacts there: diff --git a/services/agent/data/repository_corpus_manifest.json b/services/agent/data/repository_corpus_manifest.json index 04f87db4e..5c6487feb 100644 --- a/services/agent/data/repository_corpus_manifest.json +++ b/services/agent/data/repository_corpus_manifest.json @@ -29,8 +29,8 @@ }, { "doc_id": "repository-development", - "version": "sha256-59aa819bc1779cf6339402f83d18ea34efbf772e6dbafa4b9e905148e4204e62", - "chunk_id": "repository-development:sha256-59aa819bc1779cf6339402f83d18ea34efbf772e6dbafa4b9e905148e4204e62:1", + "version": "sha256-1b5bcaa942bff557c70c49a7eaa5f6d6437e7bf98ab6b4b2e64797828664ba32", + "chunk_id": "repository-development:sha256-1b5bcaa942bff557c70c49a7eaa5f6d6437e7bf98ab6b4b2e64797828664ba32:1", "source_path": "docs/DEVELOPMENT.md", "access_scope": "repository-public", "sample_kind": "real", diff --git a/services/agent/src/authorized_budget_period.py b/services/agent/src/authorized_budget_period.py index 7591f24df..2db336999 100644 --- a/services/agent/src/authorized_budget_period.py +++ b/services/agent/src/authorized_budget_period.py @@ -72,6 +72,7 @@ REVALIDATION_V10_POLICY_ID = "acceptance-revalidation-v10" REVALIDATION_V11_POLICY_ID = "acceptance-revalidation-v11" REVALIDATION_V12_POLICY_ID = "acceptance-revalidation-v12" +REVALIDATION_V13_POLICY_ID = "acceptance-revalidation-v13" REVALIDATION_POLICY_IDS = frozenset({REVALIDATION_POLICY_ID, REVALIDATION_V2_POLICY_ID, REVALIDATION_V3_POLICY_ID, REVALIDATION_V4_POLICY_ID, REVALIDATION_V5_POLICY_ID, REVALIDATION_V6_POLICY_ID, REVALIDATION_V7_POLICY_ID, REVALIDATION_V8_POLICY_ID, REVALIDATION_V9_POLICY_ID, REVALIDATION_V10_POLICY_ID, REVALIDATION_V11_POLICY_ID, REVALIDATION_V12_POLICY_ID}) REVALIDATION_V2_HISTORY = MappingProxyType({ "attempts": 164, "known_actual_micro_usd": 42_307, @@ -202,6 +203,18 @@ **REVALIDATION_V11_POLICY, "history": REVALIDATION_V12_HISTORY, }) +# Explicitly authorized successor; all three unknown liabilities remain reserved. +REVALIDATION_V13_HISTORY = MappingProxyType({ + "attempts": 988, "known_actual_micro_usd": 262_915, + "known_committed_micro_usd": 8_691_314, + "unknown_attempts": 3, "unknown_encumbrance_micro_usd": 2_359_296, + "cumulative_attempt_limit": 1229, "cumulative_limit_micro_usd": 200_000_000, +}) +REVALIDATION_V13_POLICY = MappingProxyType({ + **REVALIDATION_V12_POLICY, "history": REVALIDATION_V13_HISTORY, +}) +REVALIDATION_POLICY_IDS = REVALIDATION_POLICY_IDS | {REVALIDATION_V13_POLICY_ID} + def policy_for(policy_id: str): if policy_id == POLICY_ID: @@ -230,6 +243,8 @@ def policy_for(policy_id: str): return REVALIDATION_V11_POLICY if policy_id == REVALIDATION_V12_POLICY_ID: return REVALIDATION_V12_POLICY + if policy_id == REVALIDATION_V13_POLICY_ID: + return REVALIDATION_V13_POLICY raise PeriodError("unsupported policy") diff --git a/services/agent/src/revalidation_history.py b/services/agent/src/revalidation_history.py index f4971c48c..b9ec7b65a 100644 --- a/services/agent/src/revalidation_history.py +++ b/services/agent/src/revalidation_history.py @@ -41,16 +41,23 @@ } -def _validate_unknown_rollover_history(sources): - from authorized_budget_period import PeriodError, PeriodIdentity, REVALIDATION_V3_HISTORY, _parent, _file +def _validate_unknown_rollover_history(sources, *, predecessor="v2"): + from authorized_budget_period import PeriodError, PeriodIdentity, REVALIDATION_V3_HISTORY, REVALIDATION_V13_HISTORY, _parent, _file from model_budget import ModelBudget, authorization_slot from dav58_live_guard import IncrementalGuard - previous = _validate_rollover_history(sources) - expected = PeriodIdentity(**UNKNOWN_ROLLOVER_IDENTITY) + if predecessor == "v12": + previous = _validate_rollover_history(sources, settled_policy="v11") + identity, fingerprints, baseline = V12_ROLLOVER_IDENTITY, V12_ROLLOVER_SHA256, REVALIDATION_V13_HISTORY + elif predecessor == "v2": + previous = _validate_rollover_history(sources) + identity, fingerprints, baseline = UNKNOWN_ROLLOVER_IDENTITY, UNKNOWN_ROLLOVER_SHA256, REVALIDATION_V3_HISTORY + else: + raise PeriodError("unsupported unknown rollover predecessor") + expected = PeriodIdentity(**identity) accounting = authorization_slot(expected) / "accounting" hashes, files = {}, {} - for name, digest in UNKNOWN_ROLLOVER_SHA256.items(): + for name, digest in fingerprints.items(): path = accounting / name with _parent(path) as directory: fd = _file(directory, path.name, os.O_RDONLY) @@ -68,7 +75,7 @@ def _validate_unknown_rollover_history(sources): budget = ModelBudget._sealed_history_view(expected, previous) snapshot = budget.snapshot() guard = json.loads(files[f"dav58-increment-{expected.identity}.json"]) - _check_unknown_rollover(snapshot, guard) + _check_unknown_rollover(snapshot, guard, predecessor=predecessor) # Validate only the settled historical prefix; this view never authorizes resume. known = {**guard, "halted": False, "pending_micro_usd": 0, "receipts": guard["receipts"][:-1]} try: @@ -87,25 +94,36 @@ def _validate_unknown_rollover_history(sources): if rows != [(last["attempt_id"], last["lane"], None, 0, 1, last["request_sha256"])]: raise PeriodError("retained unknown request binding differs") return {"sources": sources, "sha256": {**previous["sha256"], **hashes}, - "baseline": dict(REVALIDATION_V3_HISTORY), "acceptance_evidence": False, + "baseline": dict(baseline), "acceptance_evidence": False, "unknown_released": False} -def _check_unknown_rollover(snapshot, guard): +def _check_unknown_rollover(snapshot, guard, *, predecessor="v2"): from authorized_budget_period import PeriodError + if predecessor == "v12": + attempts, actual, committed = 5, 697, 48_000 + attempt_id = "d7026201-c803-4d20-8296-5632575bd0f9" + request_sha256 = "97915b2819a4cfa4aba4c4b979a7514dc49e93412ba3efc49e87473b722dc0ac" + elif predecessor == "v2": + attempts, actual, committed = 25, 5928, 240_000 + attempt_id = "630b0d60-4270-47ab-b42c-4e407df78a6a" + request_sha256 = "26a76e0d7c1289b4a3b8c17a18aae3ca8f2015ec857c213782372ab8cf426dec" + else: + raise PeriodError("unsupported unknown rollover predecessor") + if (snapshot["state"] != "halted" or snapshot["sql_gate"] != "halted" - or snapshot["halted"] != 1 or snapshot["attempts"] != 25 - or snapshot["actual_micro_usd"] != 5928 or snapshot["committed_micro_usd"] != 240_000 + or snapshot["halted"] != 1 or snapshot["attempts"] != attempts + or snapshot["actual_micro_usd"] != actual or snapshot["committed_micro_usd"] != committed or snapshot["unknown_usage_attempts"] != 1 or snapshot["unsettled_attempts"] != 0 or guard.get("halted") is not True or guard.get("pending_micro_usd") != 786_432 - or not isinstance(guard.get("receipts"), list) or len(guard["receipts"]) != 25): + or not isinstance(guard.get("receipts"), list) or len(guard["receipts"]) != attempts): raise PeriodError("previous unknown liability must remain sealed") last = guard["receipts"][-1] if (last.get("status") != "unknown_or_unsafe" or last.get("reason") != "network_or_read_failure" - or last.get("attempt_id") != "630b0d60-4270-47ab-b42c-4e407df78a6a" + or last.get("attempt_id") != attempt_id or last.get("lane") != "prior_development" or last.get("reserved_micro_usd") != 786_432 - or last.get("request_sha256") != "26a76e0d7c1289b4a3b8c17a18aae3ca8f2015ec857c213782372ab8cf426dec"): + or last.get("request_sha256") != request_sha256): raise PeriodError("retained unknown identity differs") @@ -225,6 +243,19 @@ def _check_unknown_rollover(snapshot, guard): } +V12_ROLLOVER_IDENTITY = { + "period_id": "revalidation-v12-20261010", + "identity": "0e14f56bfed6461b9e9555584da59c62", + "config_sha256": "3687c43437f1e55f91b9db5b68d2ad299c10f2dee421224991841ff22ad3b317", + "policy_id": "acceptance-revalidation-v12", +} +V12_ROLLOVER_SHA256 = { + "budget.sqlite3": "ab39e29fc9827b505699079aed830ca79a22149ac7613d1c0b33998b3bd13664", + "binding.json": "17d1aacf803182ed6924bda9a76d374ded3b3056f2e3b43315edf69608654876", + "dav58-increment-0e14f56bfed6461b9e9555584da59c62.json": "5eb9edc046b185cb03dc29193a2e2ec3bc7a834be9737046b4a78dd690a077ef", +} + + def _validate_rollover_history(sources, *, settled_policy="v1"): from authorized_budget_period import PeriodError, PeriodIdentity, REVALIDATION_V2_HISTORY, REVALIDATION_V4_HISTORY, REVALIDATION_V5_HISTORY, REVALIDATION_V6_HISTORY, REVALIDATION_V7_HISTORY, REVALIDATION_V8_HISTORY, REVALIDATION_V9_HISTORY, REVALIDATION_V10_HISTORY, REVALIDATION_V11_HISTORY, REVALIDATION_V12_HISTORY, _parent, _file from model_budget import ModelBudget, authorization_slot @@ -309,9 +340,12 @@ def _validate_rollover_history(sources, *, settled_policy="v1"): def validate_history(sources, *, policy_id="acceptance-revalidation-v1"): + from authorized_budget_period import REVALIDATION_V13_POLICY_ID from authorized_budget_period import PeriodError, REVALIDATION_HISTORY, _parent, _file from authorized_budget_period import REVALIDATION_POLICY_ID, REVALIDATION_V2_POLICY_ID, REVALIDATION_V3_POLICY_ID, REVALIDATION_V4_POLICY_ID, REVALIDATION_V5_POLICY_ID, REVALIDATION_V6_POLICY_ID, REVALIDATION_V7_POLICY_ID, REVALIDATION_V8_POLICY_ID, REVALIDATION_V9_POLICY_ID, REVALIDATION_V10_POLICY_ID, REVALIDATION_V11_POLICY_ID, REVALIDATION_V12_POLICY_ID + if policy_id == REVALIDATION_V13_POLICY_ID: + return _validate_unknown_rollover_history(sources, predecessor="v12") if policy_id == REVALIDATION_V2_POLICY_ID: return _validate_rollover_history(sources) if policy_id == REVALIDATION_V3_POLICY_ID: diff --git a/services/agent/tests/test_v13_unknown_history.py b/services/agent/tests/test_v13_unknown_history.py new file mode 100644 index 000000000..2fe8c0458 --- /dev/null +++ b/services/agent/tests/test_v13_unknown_history.py @@ -0,0 +1,55 @@ +import copy + +import pytest + +from authorized_budget_period import PeriodError, policy_for +from revalidation_history import _check_unknown_rollover + + +def test_v13_retains_full_history_and_three_unknown_liabilities(): + previous = policy_for("acceptance-revalidation-v12") + policy = policy_for("acceptance-revalidation-v13") + history = policy["history"] + assert history["attempts"] == previous["history"]["attempts"] + 5 == 988 + assert history["known_actual_micro_usd"] == 262_218 + 697 + assert history["known_committed_micro_usd"] == 8_643_314 + 48_000 + assert history["unknown_attempts"] == 3 + assert history["unknown_encumbrance_micro_usd"] == 3 * 786_432 + assert history["cumulative_limit_micro_usd"] == 200_000_000 + assert history["cumulative_attempt_limit"] == 988 + policy["attempts"] + assert policy["lanes"] == previous["lanes"] + assert (history["known_committed_micro_usd"] + history["unknown_encumbrance_micro_usd"] + + policy["limit_micro_usd"] <= history["cumulative_limit_micro_usd"]) + + +@pytest.mark.parametrize("mutation", [None, "unknown", "pending", "request", "active", "unsettled", "actual", "commit"]) +def test_v12_unknown_remains_sealed(mutation): + snapshot = {"state": "halted", "sql_gate": "halted", "halted": 1, + "attempts": 5, "actual_micro_usd": 697, "committed_micro_usd": 48_000, + "unknown_usage_attempts": 1, "unsettled_attempts": 0} + guard = {"halted": True, "pending_micro_usd": 786_432, + "receipts": [{"status": "settled"} for _ in range(4)] + [{ + "status": "unknown_or_unsafe", "reason": "network_or_read_failure", + "attempt_id": "d7026201-c803-4d20-8296-5632575bd0f9", + "lane": "prior_development", "reserved_micro_usd": 786_432, + "request_sha256": "97915b2819a4cfa4aba4c4b979a7514dc49e93412ba3efc49e87473b722dc0ac"}]} + altered = copy.deepcopy(guard) + if mutation == "unknown": + snapshot["unknown_usage_attempts"] = 0 + elif mutation == "pending": + altered["pending_micro_usd"] = 0 + elif mutation == "request": + altered["receipts"][-1]["request_sha256"] = "0" * 64 + elif mutation == "active": + snapshot["state"] = "active" + elif mutation == "unsettled": + snapshot["unsettled_attempts"] = 1 + elif mutation == "actual": + snapshot["actual_micro_usd"] = 0 + elif mutation == "commit": + snapshot["committed_micro_usd"] = 697 + if mutation: + with pytest.raises(PeriodError): + _check_unknown_rollover(snapshot, altered, predecessor="v12") + else: + _check_unknown_rollover(snapshot, altered, predecessor="v12")