From 305650224970983bedeccbec72d0b1df659586f2 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Mon, 28 Sep 2026 03:17:41 +0800 Subject: [PATCH 1/3] fix(quota): bound plan observations with explicit full detail Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- loopx/cli_commands/quota.py | 3 + loopx/cli_commands/quota_context.py | 17 ++-- loopx/cli_commands/quota_registration.py | 3 +- loopx/cli_commands/quota_request.py | 11 ++- loopx/control_plane/quota/cli_projection.py | 37 ++++++- .../test_quota_plan_observation_payload.py | 98 +++++++++++++++++++ 6 files changed, 150 insertions(+), 19 deletions(-) create mode 100644 tests/control_plane/test_quota_plan_observation_payload.py diff --git a/loopx/cli_commands/quota.py b/loopx/cli_commands/quota.py index 2ba1351da7..dda586df6d 100644 --- a/loopx/cli_commands/quota.py +++ b/loopx/cli_commands/quota.py @@ -20,6 +20,7 @@ from ..control_plane.capability_hooks import InteractionProjectionHookRegistration from ..control_plane.quota.cli_projection import ( compact_quota_monitor_poll_cli_payload, + compact_quota_plan_cli_payload, compact_quota_should_run_cli_payload, ) from ..control_plane.quota.effective_action import EffectiveAction @@ -318,6 +319,8 @@ def _project_quota_cli_payload( instead of masking it with a crash (issue #3687). """ if not bool(getattr(args, "turn_envelope", False)): + if args.quota_command in {"status", "plan"}: + return compact_quota_plan_cli_payload(payload, detail_sections=detail_sections) if args.quota_command == "should-run": return compact_quota_should_run_cli_payload( payload, diff --git a/loopx/cli_commands/quota_context.py b/loopx/cli_commands/quota_context.py index e54a098c66..41393af0be 100644 --- a/loopx/cli_commands/quota_context.py +++ b/loopx/cli_commands/quota_context.py @@ -30,8 +30,7 @@ from ..status import AUTONOMOUS_REPLAN_PERIODIC_LOOKBACK, collect_status from ..turn_identity import mint_turn_instance_id, normalize_turn_instance_id from .quota_request import ( - QUOTA_MONITOR_POLL_DETAIL_SECTIONS, - QUOTA_SHOULD_RUN_DETAIL_SECTIONS, + QUOTA_COMMAND_DETAIL_SECTIONS, quota_detail_sections_from_args, validate_quota_command_request, ) @@ -124,17 +123,13 @@ def validate_quota_command_context_request( "--turn-envelope is only valid with `quota should-run`" ) requested_details = set(getattr(args, "include_details", None) or ()) - if requested_details and command not in {"should-run", "monitor-poll"}: + if requested_details and command not in QUOTA_COMMAND_DETAIL_SECTIONS: raise QuotaCommandValidationError( - "--include-detail is only valid with `quota should-run` or " - "`quota monitor-poll`" - ) - if requested_details and "all" not in requested_details: - allowed_details = set( - QUOTA_MONITOR_POLL_DETAIL_SECTIONS - if command == "monitor-poll" - else QUOTA_SHOULD_RUN_DETAIL_SECTIONS + "--include-detail is only valid with `quota status`, `quota plan`, " + "`quota should-run` or `quota monitor-poll`" ) + if requested_details: + allowed_details = {*QUOTA_COMMAND_DETAIL_SECTIONS[command], "all"} unsupported_details = sorted(requested_details - allowed_details) if unsupported_details: raise QuotaCommandValidationError( diff --git a/loopx/cli_commands/quota_registration.py b/loopx/cli_commands/quota_registration.py index e3b5a77d00..4c91e36a18 100644 --- a/loopx/cli_commands/quota_registration.py +++ b/loopx/cli_commands/quota_registration.py @@ -69,7 +69,8 @@ def register_quota_command( action="append", choices=[*QUOTA_DETAIL_SECTIONS, "all"], help=( - "Include one command-specific cold-path detail section. For `quota " + "Include one command-specific cold-path detail section. Status/plan default " + "to bounded Todo summaries; use agent-todos or user-todos for full lists. For `quota " "should-run`: scheduler, agent-todos, user-todos, goal-boundary, or " "vision. For `quota monitor-poll`: decisions. Repeat for multiple " "sections or use `all`." diff --git a/loopx/cli_commands/quota_request.py b/loopx/cli_commands/quota_request.py index 59259bc935..a807644451 100644 --- a/loopx/cli_commands/quota_request.py +++ b/loopx/cli_commands/quota_request.py @@ -13,6 +13,13 @@ "vision", ) QUOTA_MONITOR_POLL_DETAIL_SECTIONS = ("decisions",) +QUOTA_PLAN_DETAIL_SECTIONS = ("agent-todos", "user-todos") +QUOTA_COMMAND_DETAIL_SECTIONS = { + "status": QUOTA_PLAN_DETAIL_SECTIONS, + "plan": QUOTA_PLAN_DETAIL_SECTIONS, + "should-run": QUOTA_SHOULD_RUN_DETAIL_SECTIONS, + "monitor-poll": QUOTA_MONITOR_POLL_DETAIL_SECTIONS, +} QUOTA_DETAIL_SECTIONS = ( *QUOTA_SHOULD_RUN_DETAIL_SECTIONS, *QUOTA_MONITOR_POLL_DETAIL_SECTIONS, @@ -178,9 +185,7 @@ def quota_detail_sections_from_args(args: argparse.Namespace) -> frozenset[str]: sections.add("scheduler") if "all" in sections: sections.update( - QUOTA_MONITOR_POLL_DETAIL_SECTIONS - if args.quota_command == "monitor-poll" - else QUOTA_SHOULD_RUN_DETAIL_SECTIONS + QUOTA_COMMAND_DETAIL_SECTIONS.get(args.quota_command, ()) ) sections.discard("all") return frozenset(sections) diff --git a/loopx/control_plane/quota/cli_projection.py b/loopx/control_plane/quota/cli_projection.py index 10b8c4998c..c5e11ec471 100644 --- a/loopx/control_plane/quota/cli_projection.py +++ b/loopx/control_plane/quota/cli_projection.py @@ -293,7 +293,9 @@ def _compact_nested_item_lists( return compact -def _compact_agent_todo_summary(summary: dict[str, Any]) -> dict[str, Any]: +def _compact_agent_todo_summary( + summary: dict[str, Any], *, detail_command: str = QUOTA_CLI_TODO_SUMMARY_DETAIL_COMMAND +) -> dict[str, Any]: compact: dict[str, Any] = {} omitted_lanes: dict[str, int] = {} for key, value in summary.items(): @@ -327,12 +329,14 @@ def _compact_agent_todo_summary(summary: dict[str, Any]) -> dict[str, Any]: if lane != "current_agent_blocker_items" or summary.get(lane) ), "omitted_lanes": omitted_lanes, - "full_detail_cold_path": QUOTA_CLI_TODO_SUMMARY_DETAIL_COMMAND, + "full_detail_cold_path": detail_command, } return compact -def _compact_user_todo_summary(summary: dict[str, Any]) -> dict[str, Any]: +def _compact_user_todo_summary( + summary: dict[str, Any], *, detail_command: str = QUOTA_CLI_USER_TODO_SUMMARY_DETAIL_COMMAND +) -> dict[str, Any]: compact: dict[str, Any] = {} omitted_lanes: dict[str, int] = {} for key, value in summary.items(): @@ -359,7 +363,7 @@ def _compact_user_todo_summary(summary: dict[str, Any]) -> dict[str, Any]: "schema_version": QUOTA_CLI_USER_TODO_SUMMARY_COMPACTION_SCHEMA_VERSION, "retained_item_lanes": sorted(_RETAINED_USER_ITEM_LANES), "omitted_lanes": omitted_lanes, - "full_detail_cold_path": QUOTA_CLI_USER_TODO_SUMMARY_DETAIL_COMMAND, + "full_detail_cold_path": detail_command, } return compact @@ -790,3 +794,28 @@ def compact_quota_should_run_cli_payload( return _promote_interaction_contract( _promote_runtime_capability_reentry(compact) ) + + +def compact_quota_plan_cli_payload( + payload: dict[str, Any], *, detail_sections: frozenset[str] = frozenset() +) -> dict[str, Any]: + """Bound read-only CLI summaries after complete planning; retain full opt-ins.""" + if payload.get("mode") not in {"status", "plan"} or not isinstance(payload.get("groups"), dict): + return payload + + def project_row(row: dict[str, Any]) -> dict[str, Any]: + result = dict(row) + for role, compact_summary in (("agent", _compact_agent_todo_summary), ("user", _compact_user_todo_summary)): + key = f"{role}_todos" + if f"{role}-todos" not in detail_sections and isinstance(row.get(key), dict): + result[key] = compact_summary(row[key], detail_command=( + f"quota {payload['mode']} --include-detail {role}-todos" + )) + return result + + result = dict(payload) + result["groups"] = {state: [project_row(row) for row in rows] + for state, rows in payload["groups"].items()} + if isinstance(payload.get("next_automatic_turn"), dict): + result["next_automatic_turn"] = project_row(payload["next_automatic_turn"]) + return result diff --git a/tests/control_plane/test_quota_plan_observation_payload.py b/tests/control_plane/test_quota_plan_observation_payload.py new file mode 100644 index 0000000000..e64ad2db00 --- /dev/null +++ b/tests/control_plane/test_quota_plan_observation_payload.py @@ -0,0 +1,98 @@ +"""Read-only plans are bounded displays with an explicit lossless detail path.""" +from __future__ import annotations + +import json +import subprocess +import sys +from copy import deepcopy +from pathlib import Path + +import pytest +from canonical_authority_fixture import initialize_canonical_authority, isolate_sqlite_runtime + +from loopx.cli_commands.quota_request import quota_detail_sections_from_args +from loopx.cli_runtime import _build_selected_parser +from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection +from loopx.control_plane.effect_runtime import restart_effect_runtime +from loopx.control_plane.quota.cli_projection import compact_quota_plan_cli_payload +from loopx.control_plane.testing.canary_harness import write_fixture_registry + +REPO = Path(__file__).resolve().parents[2] + + +def test_plan_projection_retains_decisions_health_and_input_without_mutation(): + items = [{"todo_id": str(i), "status": "open", "text": "Work", "note": "detail" * 100} for i in range(40)] + agent = {"total_count": 40, "open_count": 40, "items": items, + "first_executable_items": items[:4], "monitor_due_count": 5, + "monitor_due_items": items[:5], "blocker_count": 2} + row = {"goal_id": "goal", "quota": {"state": "eligible", "allowed_slots": 2}, + "agent_todos": agent, "user_todos": {"items": items, "open_count": 40}} + payload = {"mode": "status", "ok": False, "groups": {"eligible": [row]}, + "next_automatic_turn": row, "health_items": [{"severity": "error"}], + "summary": {"registered_goals": 1}, "status_projection_envelope": {"coverage": {"scope": "goal"}}} + original = deepcopy(payload) + compact = compact_quota_plan_cli_payload(payload) + assert payload == original + result = compact["groups"]["eligible"][0] + assert "items" not in result["agent_todos"] + assert result["agent_todos"]["open_count"] == 40 + assert result["agent_todos"]["monitor_due_count"] == 5 + assert result["agent_todos"]["blocker_count"] == 2 + assert result["agent_todos"]["payload_compaction"]["omitted_lanes"]["items"] == 40 + assert "quota status --include-detail agent-todos" == result["agent_todos"]["payload_compaction"]["full_detail_cold_path"] + assert compact["next_automatic_turn"] == result + for key in ("health_items", "summary", "status_projection_envelope", "ok"): + assert compact[key] == payload[key] + assert result["quota"] == row["quota"] + assert compact_quota_plan_cli_payload(payload, detail_sections=frozenset({"agent-todos", "user-todos"})) == original + assert compact_quota_plan_cli_payload({"mode": "should-run", "agent_todo_summary": agent}) == {"mode": "should-run", "agent_todo_summary": agent} + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_real_cli_compact_and_full_detail_preserve_canonical_todos(tmp_path, monkeypatch, provider): + isolate_sqlite_runtime(tmp_path, monkeypatch) + runtime, registry, state = tmp_path / "runtime", tmp_path / "registry.json", tmp_path / "state.md" + state.write_text("---\nstatus: active\nwaiting_on: codex\n---\n# Example\n\n## Agent Todo\n") + write_fixture_registry(project=tmp_path, runtime_root=runtime, registry_path=registry, + goal_id="example", domain="engineering", adapter_kind="generic_project_goal_v0", + state_file=str(state), registered_agents=["worker"]) + records = [{"schema_version": "todo_item_v0", "todo_id": f"work-{i:03}", + "role": "agent" if i < 40 else "user", "status": "open" if i % 3 else "done", + "done": i % 3 == 0, "text": f"Retained work {i}", "note": "exact metadata🙂" * 100, + "archive_state": "active", "source_section": "Agent Todo" if i < 40 else "User Todo", + "index": i + 1, "task_class": "advancement_task"} for i in range(60)] + projection = build_todo_runtime_shadow_projection(goal_id="example", todos=records, leases=[], handoff_mode="soft_claim") + initialize_canonical_authority(runtime, "example", projection, state_path=state, provider=provider) + + def call(mode, *details): + process = subprocess.run([sys.executable, "-m", "loopx.entrypoint", "--registry", str(registry), + "--runtime-root", str(runtime), "--format", "json", "quota", mode, "--goal-id", "example", + "--scan-root", str(tmp_path), *details], cwd=REPO, text=True, capture_output=True, timeout=60) + value = json.loads(process.stdout) + assert process.returncode == 0, value + return value + + try: + for mode in ("status", "plan"): + compact, full = call(mode), call(mode, "--include-detail", "all") + compact_row = next(row for group in compact["groups"].values() for row in group) + full_row = next(row for group in full["groups"].values() for row in group) + assert compact["summary"] == full["summary"] + for role, count in (("agent", 40), ("user", 20)): + c, f = compact_row[f"{role}_todos"], full_row[f"{role}_todos"] + assert c["total_count"] == f["total_count"] == count + assert "items" not in c + actual = {item["todo_id"]: item for item in f["items"]} + for record in records: + if record["role"] == role: + assert actual[record["todo_id"]]["note"] == record["note"] + assert actual[record["todo_id"]]["status"] == record["status"] + assert len(json.dumps(compact)) < len(json.dumps(full)) / 2 + assert not list((runtime / "goals" / "example" / "runs").glob("*.json*")) + finally: + assert restart_effect_runtime()["status"] in {"stopped", "not_running"} + + +def test_plan_all_only_expands_observation_sections(): + args = _build_selected_parser("quota").parse_args(["quota", "plan", "--include-detail", "all"]) + assert quota_detail_sections_from_args(args) == frozenset({"agent-todos", "user-todos"}) From 9bf5b18741c9096802e1a62ae5719ef95bb4c456 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Mon, 28 Sep 2026 03:17:41 +0800 Subject: [PATCH 2/3] perf(authority): retain a bounded File proof working set Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../coordination/file_authority_store.ts | 40 +++++++++---- .../control_plane_ts/authority_store.test.ts | 60 +++++++++++++++++++ 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/loopx/control_plane/coordination/file_authority_store.ts b/loopx/control_plane/coordination/file_authority_store.ts index 0fc1be38ce..f44dbf30df 100644 --- a/loopx/control_plane/coordination/file_authority_store.ts +++ b/loopx/control_plane/coordination/file_authority_store.ts @@ -28,13 +28,15 @@ import {AuthorityJournalScan} from "./authority_journal_scan.ts"; const STORE_IDENTITY_PATTERN = /^file:[0-9a-f]{32}$/; // File retains a checkpoint/delta journal in one durable envelope. A managed Effect server -// opens a new store handle for each request. Keep one verified read view across -// handles, keyed by exact bytes and store identity. Large journals retain only +// opens a new store handle for each request. Retain a bounded working set across +// handles so alternating Goals do not evict each other on every observation. +// Every lookup still reads and hashes the full file and checks store identity. Large journals retain only // the head and receipt index in memory; commits and scans still load and verify // the complete history. This is a bounded read optimization, not a new source // of authority or a substitute for the SQLite long-goal profile. const MAX_CACHED_DOCUMENT_BYTES = 128 * 1024 * 1024; const MAX_CACHED_READ_VIEW_BYTES = 16 * 1024 * 1024; +const MAX_CACHED_STORES = 4; interface VerifiedDocument { path: string; identity: string; @@ -49,7 +51,7 @@ interface VerifiedDocument { }>; document?: FileAuthorityJournal; } -let verifiedDocument: VerifiedDocument | null = null; +const verifiedDocuments = new Map(); // Only identical immutable input bytes share in-flight verification. Failed // proofs are removed too; neither a path nor a pending promise grants authority. const pendingVerification = new Map>(); @@ -76,10 +78,22 @@ function rememberVerifiedDocument(path: string, identity: string, raw: Uint8Arra const view: VerifiedDocument = {path, identity, digest, head: document.head, providerRevision: document.provider_revision, cursor: document.cursor, receipts, document}; - verifiedDocument = raw.byteLength <= maxDocumentBytes ? view - : viewBytes <= MAX_CACHED_READ_VIEW_BYTES - ? {...view, document: undefined} - : null; + // Account for serialized history and the separate head/receipt index. This + // is a retained-byte bound, not a claim about the JS heap or process RSS. + const fullBytes = raw.byteLength + viewBytes; + const retainHistory = raw.byteLength <= maxDocumentBytes && fullBytes <= MAX_CACHED_DOCUMENT_BYTES; + const retained = retainHistory ? view : {...view, document: undefined}; + const bytes = retainHistory ? fullBytes : viewBytes; + verifiedDocuments.delete(path); + if (retainHistory || viewBytes <= MAX_CACHED_READ_VIEW_BYTES) { + verifiedDocuments.set(path, {view: retained, bytes}); + let total = [...verifiedDocuments.values()].reduce((sum, entry) => sum + entry.bytes, 0); + while (verifiedDocuments.size > MAX_CACHED_STORES || total > MAX_CACHED_DOCUMENT_BYTES) { + const oldest = verifiedDocuments.keys().next().value!; + total -= verifiedDocuments.get(oldest)!.bytes; + verifiedDocuments.delete(oldest); + } + } return view; } @@ -274,10 +288,12 @@ export class FileAuthorityStore implements AuthorityStore { const identity = knownIdentity ?? await this.readStoreIdentity(); try { const digest = documentDigest(raw); - if (verifiedDocument?.path === this.path && - verifiedDocument.identity === identity && verifiedDocument.digest === digest && - (!requireHistory || verifiedDocument.document !== undefined)) { - return verifiedDocument; + const cached = verifiedDocuments.get(this.path); + if (cached?.view.identity === identity && cached.view.digest === digest && + (!requireHistory || cached.view.document !== undefined)) { + verifiedDocuments.delete(this.path); + verifiedDocuments.set(this.path, cached); + return cached.view; } const key = JSON.stringify([this.path, identity, digest]); let proof = pendingVerification.get(key); @@ -420,7 +436,7 @@ export class FileAuthorityStore implements AuthorityStore { // A failure after rename may already have published the new bytes. // The next read must prove the actual file rather than reuse either // the previous or attempted document. - verifiedDocument = null; + verifiedDocuments.delete(this.path); return { status: "ambiguous", reason_code: "commit_outcome_unknown", diff --git a/tests/control_plane_ts/authority_store.test.ts b/tests/control_plane_ts/authority_store.test.ts index 318743a33d..bd4994c50f 100644 --- a/tests/control_plane_ts/authority_store.test.ts +++ b/tests/control_plane_ts/authority_store.test.ts @@ -284,3 +284,63 @@ test("concurrent cold reads share only the same exact-byte proof and recover aft assert.equal((await readers[0]!.loadAuthority()).status, "loaded"); assert.equal(CountingStore.validations, 4); }); + +test("alternating File stores reuse their own exact-byte proofs across handles", async t => { + const fixtures = await Promise.all([fixture(t), fixture(t)]); + for (const {store} of fixtures) { + assert.equal((await store.commitAuthority(commit(null, "alternating", 1, 1))).status, "applied"); + await writeFile(store.path, (await readFile(store.path, "utf8")) + "\n"); + } + class CountingStore extends FileAuthorityStore { + static validations = 0; + protected override decodeStoredDocument(value: unknown, identity: string) { + CountingStore.validations++; + return super.decodeStoredDocument(value, identity); + } + } + for (let round = 0; round < 3; round++) { + for (const {root} of fixtures) { + const result = await new CountingStore(root, "goal-a").loadAuthority(); + assert.equal(result.status, "loaded"); + if (result.status === "loaded") { + assert.equal(result.head.authority_revision, 1); + result.head.authority_revision = "caller mutation"; + } + } + } + assert.equal(CountingStore.validations, 2, "each unchanged store proves its history once"); + const first = fixtures[0]!; + const document = JSON.parse(await readFile(first.store.path, "utf8")); + document.committed[0].provider_revision = "tampered"; + await writeFile(first.store.path, JSON.stringify(document)); + assert.equal((await new CountingStore(first.root, "goal-a").loadAuthority()).status, "failed"); + assert.equal((await new CountingStore(fixtures[1]!.root, "goal-a").loadAuthority()).status, "loaded"); + assert.equal(CountingStore.validations, 3, "a bad store cannot invalidate an unrelated valid proof"); +}); + +test("File proof working set evicts least-recently used stores rather than growing with Goal count", async t => { + class CountingStore extends FileAuthorityStore { + static validations = 0; + protected override decodeStoredDocument(value: unknown, identity: string) { + CountingStore.validations++; + return super.decodeStoredDocument(value, identity); + } + } + const roots: string[] = []; + for (let index = 0; index < 5; index++) { + const {root, store} = await fixture(t); + roots.push(root); + assert.equal((await store.commitAuthority(commit(null, "working-set", 1, 1))).status, "applied"); + await writeFile(store.path, (await readFile(store.path, "utf8")) + "\n"); + assert.equal((await new CountingStore(root, "goal-a").loadAuthority()).status, "loaded"); + } + assert.equal(CountingStore.validations, 5); + for (const index of [4, 2, 3, 1]) { + assert.equal((await new CountingStore(roots[index]!, "goal-a").loadAuthority()).status, "loaded"); + } + assert.equal(CountingStore.validations, 5, "four recent stores remain reusable"); + assert.equal((await new CountingStore(roots[0]!, "goal-a").loadAuthority()).status, "loaded"); + assert.equal(CountingStore.validations, 6, "the evicted store must prove history again"); + assert.equal((await new CountingStore(roots[4]!, "goal-a").loadAuthority()).status, "loaded"); + assert.equal(CountingStore.validations, 7, "access order, not insertion identity, determines eviction"); +}); From 8b1e25849bdadccd366cd67732378e7f1489f56a Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Mon, 28 Sep 2026 03:26:56 +0800 Subject: [PATCH 3/3] test(quota): qualify compact observation and record read-cost limits Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../2026-09-28-retirement-cadence.md | 30 +++++++++++++++++++ .../2026-09-28-retirement-cadence.zh-CN.md | 24 +++++++++++++++ docs/quota-allocation.md | 18 +++++++++++ .../contracts/interface-budget-contract.md | 12 +++++--- docs/reference/file-authority-state-log.md | 9 ++++-- .../test_quota_plan_observation_payload.py | 21 +++++++++++++ 6 files changed, 108 insertions(+), 6 deletions(-) diff --git a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.md b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.md index 39f5f1ce70..e8a325e0c9 100644 --- a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.md +++ b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.md @@ -132,3 +132,33 @@ capture of current production state, or D2 qualification. Raw private snapshots and diagnostics remain outside the repository. No production code is deleted by this planning PR; it establishes the deletion exits and records their actual validation boundary. + +### Read-cost qualification update + +After #4931 and #5215 integrated, matched detached File/SQLite copies retained +379 original commits and the same final projection hash. On Node 24.21.0, +three fresh processes per provider measured File head reads at 5.98–6.32 s +versus SQLite at 34.5–36.0 ms; repeated reads were 9.1–10.2 ms and 25.7–28.2 ms +respectively. This is process-cold, not OS-cache-cold: File proves its entire +retained journal, whereas SQLite reads current state without making the same +full-history proof. It is evidence for a long-history SQLite candidate, not +equivalent integrity-work throughput or release-default acceptance. + +Alternating two unchanged File stores exposed singleton proof-cache eviction: +every read cost 6.30–6.49 s. A bounded four-store working set keeps the first +proof for each store (6.15–6.16 s) and subsequent alternation at 9.8–11.2 ms, +with identical cursors/hashes. Exact-byte and identity checks remain mandatory; +eviction and corruption regressions cover the changed cache boundary. + +Quota observation reused the existing should-run compactors: a captured single +Goal row serialized from 1,252,747 to 78,688 UTF-8 bytes, with explicit full +detail restoring the original row. This is a display measurement; collection, +decision inputs and first-read verification are not reduced by it. + +A separate 148-second isolated run appended 12 commits per provider through +fresh processes, crossing a checkpoint and checking original-receipt replay, +changed-intent rejection and projection/hash parity at every step. It qualifies +that bounded storage journey, **not** Host execution, live Goal adoption or D2's +ten-day soak. No active authority, release default or legacy-writer deletion +decision changes. B still needs sustained workload/platform/capacity evidence; +C still needs consumer/onboarding and supported upgrade acceptance. diff --git a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.zh-CN.md b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.zh-CN.md index 7045afbde8..7c4055dbc6 100644 --- a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.zh-CN.md +++ b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.zh-CN.md @@ -104,3 +104,27 @@ CLI 全部 drain,原 Todo JSON 完整相等。所得四笔事务恢复/审 活跃 Goal。这证明有界 drain 和逻辑 archive 连续性,**不是**全部原始 224 笔历史重放、 live selector cutover、重新捕获当前生产状态或 D2 验收。私有快照和原始诊断不入库。 本规划 PR 不删除生产代码,只确定删除出口并记录实际验证边界。 + +### 读取成本验收更新 + +#4931、#5215 集成后,配对的 File/SQLite 隔离副本保留 379 笔原始提交,最终 +projection hash 相同。Node 24.21.0 下,每个 provider 分别启动三个新进程, +File 首次 head 读取为 5.98–6.32 秒,SQLite 为 34.5–36.0 毫秒;后续读取分别为 +9.1–10.2 毫秒、25.7–28.2 毫秒。这是进程冷读,没有清空 OS 文件缓存;File +验证全部保留历史,SQLite 读取当前状态,不承担相同的全历史证明。这支持将 SQLite +作为长历史候选,但不是同等完整性工作量的吞吐比较,也不构成发布默认值验收。 + +交替读取两个未变化的 File 存储,暴露了单份证明缓存互相淘汰的问题:每次都要 +6.30–6.49 秒。改为有总容量上限的四份缓存后,各存储首次验证仍为 6.15–6.16 秒, +后续交替读取为 9.8–11.2 毫秒,cursor/hash 相同。每次仍检查实际字节摘要和存储 +身份;淘汰与损坏回归覆盖缓存边界。 + +Quota 观察复用既有 should-run 摘要:捕获的单 Goal 行序列化由 1,252,747 降至 +78,688 UTF-8 字节,显式明细恢复原行。这是展示体积测量,未减少采集、决策输入或 +首次读取的验证成本。 + +另一项 148 秒隔离演练通过新进程为两种 provider 各追加 12 笔提交,跨越 checkpoint, +逐轮验证原回执重放、变更意图拒绝及 projection/hash 一致性。它证明这段有界存储 +流程,**不代表** Host 执行、活跃 Goal 采用或 D2 的十天 soak 已完成。本次不改变活跃 +authority、发布默认值或旧 writer 删除决定。B 仍缺持续负载/平台/容量证据;C 仍需 +consumer/新建入口及受支持升级验收。 diff --git a/docs/quota-allocation.md b/docs/quota-allocation.md index 27ab1bde7c..58627a3ff2 100644 --- a/docs/quota-allocation.md +++ b/docs/quota-allocation.md @@ -866,6 +866,24 @@ The first screen should make it obvious why a project is quiet: ## CLI Surface +`quota status` and `quota plan` now default to bounded Todo summaries in JSON, +reusing the summaries already used by `quota should-run`. Previously these two +observation commands returned full Todo lists. Counts, quota decisions, ordering +and health remain intact; `payload_compaction` identifies omitted lists and their +detail command. Planning still consumes complete input before this CLI projection. +Consumers that read individual Todo metadata or every item must opt into detail: + +```bash +loopx --format json quota status --include-detail all +loopx --format json quota plan --include-detail agent-todos --include-detail user-todos +``` + +Keep the original registry, runtime and Goal selection when following a detail +command. `all` expands only the sections supported by that command. Detail reads +do not acquire a Turn or spend quota. Markdown plan rendering and standalone +`status`/`todo list` are unchanged. This bounds Todo-list display growth, not the +cost of gathering and verifying the input or the total number of Goals returned. + The first read-only or preview commands are: ```bash diff --git a/docs/reference/contracts/interface-budget-contract.md b/docs/reference/contracts/interface-budget-contract.md index deec4a3629..8e69749c6b 100644 --- a/docs/reference/contracts/interface-budget-contract.md +++ b/docs/reference/contracts/interface-budget-contract.md @@ -87,11 +87,15 @@ removed without a separately validated caller migration. | `evidence-log --thin --limit 5` | explicit-limit cold path | returned-evidence bound | referenced run-history and rollout-event artifacts | `quota should-run` uses one repeatable cold-path selector: -`--include-detail scheduler`, `agent-todos`, `user-todos`, or -`goal-boundary`; `--include-detail all` expands every section. Public docs, +`--include-detail scheduler`, `agent-todos`, `user-todos`, `vision`, or +`goal-boundary`. `quota status` and `quota plan` accept `agent-todos` and +`user-todos`; `quota monitor-poll` accepts `decisions`. +`--include-detail all` expands the selected command's sections. Public docs, emitted `detail_ref` commands, and internal callers use only this selector. -Unknown sections and selectors attached to another quota command fail before -status collection. +Unknown or unsupported sections fail before status collection, including when +combined with `all`. Status/plan summaries preserve counts and decisions and +declare omitted lists; explicit detail preserves the full Todo metadata. These +are CLI display projections after full planning, not truncated provider inputs. The canonical emitted-output inventory and current characterization ceilings live in `loopx.control_plane.testing.cli_output_budget`. Those ceilings are diff --git a/docs/reference/file-authority-state-log.md b/docs/reference/file-authority-state-log.md index 15d9b8bf86..d9df08bcc8 100644 --- a/docs/reference/file-authority-state-log.md +++ b/docs/reference/file-authority-state-log.md @@ -22,8 +22,13 @@ append-only even though File atomically replaces its physical envelope. Cold reads verify every retained transaction and the final head; a valid head cannot hide a corrupt old delta or receipt. Verified pagination reconstructs at -most 63 predecessor deltas plus the requested page. The exact-byte cache remains -bounded. File still reads/hashes and rewrites one retained file: this reduces +most 63 predecessor deltas plus the requested page. The exact-byte cache retains +at most four store paths in least-recently-used order, with a shared 128 MiB +serialized history/read-view budget. A large journal can retain only its head +and receipt index (up to 16 MiB per read view); scans and writes still verify its +history. Every cache hit requires matching file digest and store identity, not +only file timestamps. These are encoded-data bounds, not a heap/RSS limit. +File still reads/hashes and rewrites one retained file: this reduces repeated data, not asymptotic growth. Cold verification can be slower. Measure upgrade, cold verification, warm reads and steady writes separately. diff --git a/tests/control_plane/test_quota_plan_observation_payload.py b/tests/control_plane/test_quota_plan_observation_payload.py index e64ad2db00..c6550d827b 100644 --- a/tests/control_plane/test_quota_plan_observation_payload.py +++ b/tests/control_plane/test_quota_plan_observation_payload.py @@ -10,12 +10,15 @@ import pytest from canonical_authority_fixture import initialize_canonical_authority, isolate_sqlite_runtime +from loopx.cli_commands.quota_context import validate_quota_command_context_request from loopx.cli_commands.quota_request import quota_detail_sections_from_args from loopx.cli_runtime import _build_selected_parser from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection from loopx.control_plane.effect_runtime import restart_effect_runtime from loopx.control_plane.quota.cli_projection import compact_quota_plan_cli_payload +from loopx.control_plane.quota.error_codes import QuotaCommandValidationError from loopx.control_plane.testing.canary_harness import write_fixture_registry +from loopx.presentation.renderers.quota_markdown import render_quota_markdown REPO = Path(__file__).resolve().parents[2] @@ -44,6 +47,10 @@ def test_plan_projection_retains_decisions_health_and_input_without_mutation(): for key in ("health_items", "summary", "status_projection_envelope", "ok"): assert compact[key] == payload[key] assert result["quota"] == row["quota"] + selective = compact_quota_plan_cli_payload(payload, detail_sections=frozenset({"agent-todos"})) + selected_row = selective["groups"]["eligible"][0] + assert selected_row["agent_todos"] == agent + assert "items" not in selected_row["user_todos"] assert compact_quota_plan_cli_payload(payload, detail_sections=frozenset({"agent-todos", "user-todos"})) == original assert compact_quota_plan_cli_payload({"mode": "should-run", "agent_todo_summary": agent}) == {"mode": "should-run", "agent_todo_summary": agent} @@ -78,6 +85,7 @@ def call(mode, *details): compact_row = next(row for group in compact["groups"].values() for row in group) full_row = next(row for group in full["groups"].values() for row in group) assert compact["summary"] == full["summary"] + assert render_quota_markdown(compact) == render_quota_markdown(full) for role, count in (("agent", 40), ("user", 20)): c, f = compact_row[f"{role}_todos"], full_row[f"{role}_todos"] assert c["total_count"] == f["total_count"] == count @@ -96,3 +104,16 @@ def call(mode, *details): def test_plan_all_only_expands_observation_sections(): args = _build_selected_parser("quota").parse_args(["quota", "plan", "--include-detail", "all"]) assert quota_detail_sections_from_args(args) == frozenset({"agent-todos", "user-todos"}) + + +@pytest.mark.parametrize("command,sections", [ + ("status", ["decisions"]), ("plan", ["all", "scheduler"]), + ("monitor-poll", ["all", "agent-todos"]), +]) +def test_detail_selector_rejects_foreign_sections_even_with_all(command, sections): + argv = ["quota", command] + for section in sections: + argv.extend(["--include-detail", section]) + args = _build_selected_parser("quota").parse_args(argv) + with pytest.raises(QuotaCommandValidationError, match="does not accept --include-detail"): + validate_quota_command_context_request(args)