From 80c8479e1ed35fb279e2c27585eeec6ce9fc448b Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Tue, 3 Nov 2026 13:39:15 +0800 Subject: [PATCH 1/3] fix(control-plane): retain archived todos on include-archived query and prevent single-goal stale registry false positives Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../goals/global_registry_health.py | 2 +- loopx/control_plane/todos/read_transaction.ts | 4 +- ...test_sqlite_archive_and_registry_health.py | 85 +++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/loopx/control_plane/goals/global_registry_health.py b/loopx/control_plane/goals/global_registry_health.py index ab80dfd693..f2e3e1ebc2 100644 --- a/loopx/control_plane/goals/global_registry_health.py +++ b/loopx/control_plane/goals/global_registry_health.py @@ -47,7 +47,7 @@ def _multi_goal_source_entry_unchanged( except Exception: # noqa: BLE001 source_goals_cache[cache_key] = None source_goals = source_goals_cache[cache_key] - if not source_goals or len(source_goals) <= 1: + if not source_goals: return False candidates = [ row for row in source_goals if str(row.get("id") or "") == goal_id diff --git a/loopx/control_plane/todos/read_transaction.ts b/loopx/control_plane/todos/read_transaction.ts index 98c65abb8f..a99f2cc388 100644 --- a/loopx/control_plane/todos/read_transaction.ts +++ b/loopx/control_plane/todos/read_transaction.ts @@ -18,7 +18,9 @@ export async function readCanonicalTodoProjection(input: JsonObject): Promise None: + runtime = tmp_path / "runtime" + project = tmp_path / "project" + source_registry_path = project / ".loopx" / "registry.json" + source_registry_path.parent.mkdir(parents=True) + + state_rel = ".codex/goals/goal-single/ACTIVE_GOAL_STATE.md" + state_path = project / state_rel + state_path.parent.mkdir(parents=True) + state_path.write_text("# Goal\n", encoding="utf-8") + single_goal = { + "id": "goal-single", + "status": "active", + "repo": str(project), + "state_file": state_rel, + } + + old_synced_at = "2026-01-01T00:00:00+00:00" + global_goals = [ + sanitize_goal_for_global( + single_goal, + source_registry=source_registry_path, + synced_at=old_synced_at, + ) + ] + global_path = global_registry_path(runtime) + global_path.parent.mkdir(parents=True, exist_ok=True) + global_path.write_text( + json.dumps( + { + "schema_version": 1, + "common_runtime_root": str(runtime), + "goals": global_goals, + }, + indent=2, + ) + + "\n", + encoding="utf-8", + ) + + source_registry = { + "schema_version": 1, + "common_runtime_root": str(runtime), + "goals": [single_goal], + } + source_registry_path.write_text( + json.dumps(source_registry, indent=2) + "\n", encoding="utf-8" + ) + + now = time.time() + 100 + os.utime(source_registry_path, (now, now)) + + health = status_module.collect_global_registry_health( + registry_path=source_registry_path, + runtime_root=runtime, + current_registry=source_registry, + ) + stale_goal_ids = [ + finding.get("goal_id") + for finding in health["findings"] + if finding.get("kind") == "stale_source_registry" + ] + assert stale_goal_ids == [] + + source_registry["goals"][0]["status"] = "paused" + source_registry_path.write_text( + json.dumps(source_registry, indent=2) + "\n", encoding="utf-8" + ) + os.utime(source_registry_path, (now + 10, now + 10)) + + health_mutated = status_module.collect_global_registry_health( + registry_path=source_registry_path, + runtime_root=runtime, + current_registry=source_registry, + ) + stale_mutated_ids = [ + finding.get("goal_id") + for finding in health_mutated["findings"] + if finding.get("kind") == "stale_source_registry" + ] + assert stale_mutated_ids == ["goal-single"] + + @pytest.mark.parametrize( ("scenario", "todo_payload", "expected_omitted_fields", "expect_inline_note"), [ From f2fc6d2c1df79b4d96a59f7f117748de697631ad Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Tue, 3 Nov 2026 16:36:06 +0800 Subject: [PATCH 2/3] fix(quota): omit unsupported max-page-output-bytes on non-POSIX settlement Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/control_plane/quota/effect_program.py | 9 +++++++- tests/control_plane/test_quota_settlement.py | 22 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/loopx/control_plane/quota/effect_program.py b/loopx/control_plane/quota/effect_program.py index 221352aa8b..ba363a775b 100644 --- a/loopx/control_plane/quota/effect_program.py +++ b/loopx/control_plane/quota/effect_program.py @@ -31,6 +31,8 @@ from ..work_items.delivery_batch_scale import DELIVERY_BATCH_SCALE_INPUT_CHOICES from ..turn_driver.native_context_profile import NATIVE_CONTEXT_PAGE_OUTPUT_BYTES +_PLATFORM_IS_POSIX = os.name == "posix" + __all__ = [ "SETTLEMENT_IDENTITY_SCHEMA_VERSION", "SETTLEMENT_PLAN_SCHEMA_VERSION", @@ -265,10 +267,15 @@ def writeback_command(boundary: str) -> str: writeback_command(" --delivery-boundary semantic_closeout") + " --agent-vision-json " ) + max_output_arg = ( + f" --max-page-output-bytes {NATIVE_CONTEXT_PAGE_OUTPUT_BYTES}" + if _PLATFORM_IS_POSIX + else "" + ) spend = ( f"{cli_prefix} quota spend-slot --goal-id {shlex.quote(goal_id)} --slots 1 " f"--source {quota_spend_source} --execute{binding_arg}{turn_arg}{goal_ref_arg}" - f"{scoped_cli_args} --max-page-output-bytes {NATIVE_CONTEXT_PAGE_OUTPUT_BYTES}" + f"{scoped_cli_args}{max_output_arg}" ) payload = effect_runtime_result("settlement.turn_scoped_cli_plan", { "identity": identity.as_dict(), "delivery_boundary": delivery_boundary, diff --git a/tests/control_plane/test_quota_settlement.py b/tests/control_plane/test_quota_settlement.py index 5c15742ee7..ed70156e50 100644 --- a/tests/control_plane/test_quota_settlement.py +++ b/tests/control_plane/test_quota_settlement.py @@ -805,6 +805,28 @@ def quote(value: object) -> str: assert '"${LOOPX_TURN:?}"' not in command +def test_windows_settlement_command_omits_unsupported_max_page_output_bytes() -> None: + # Non-POSIX platforms fail closed on private capture validation; settlement + # spend recipes must omit --max-page-output-bytes on Windows. + with patch.object(quota_effect_program, "_PLATFORM_IS_POSIX", False): + plan = quota_effect_program.build_turn_scoped_cli_settlement_plan( + goal_id=GOAL_ID, agent_id=AGENT_ID, todo_id=TODO_ID, + turn_instance_id=TURN_ID, scoped_cli_args="", lifecycle_actor_args="", + ).as_dict() + command = settlement_step_command(plan, SettlementStepKind.QUOTA_SPEND) + assert command is not None + assert "--max-page-output-bytes" not in command + + with patch.object(quota_effect_program, "_PLATFORM_IS_POSIX", True): + plan = quota_effect_program.build_turn_scoped_cli_settlement_plan( + goal_id=GOAL_ID, agent_id=AGENT_ID, todo_id=TODO_ID, + turn_instance_id=TURN_ID, scoped_cli_args="", lifecycle_actor_args="", + ).as_dict() + command = settlement_step_command(plan, SettlementStepKind.QUOTA_SPEND) + assert command is not None + assert "--max-page-output-bytes" in command + + def test_cli_validation_projects_ordinary_completion_not_terminal_intent() -> None: plan = build_codex_app_settlement_plan( goal_id=GOAL_ID, agent_id=AGENT_ID, todo_id=TODO_ID, From b52d549f72103b97aca7382cdedc17aba295f641 Mon Sep 17 00:00:00 2001 From: huangruiteng Date: Tue, 3 Nov 2026 22:18:16 +0800 Subject: [PATCH 3/3] fix(todos): preserve active guards when including archived rows Signed-off-by: huangruiteng --- loopx/control_plane/todos/read_projection.ts | 3 +- loopx/control_plane/todos/read_transaction.ts | 10 +- tests/control_plane_ts/todo_read.test.ts | 99 +++++++++++++++++++ 3 files changed, 108 insertions(+), 4 deletions(-) diff --git a/loopx/control_plane/todos/read_projection.ts b/loopx/control_plane/todos/read_projection.ts index 2da9d71f26..e7dfa88de0 100644 --- a/loopx/control_plane/todos/read_projection.ts +++ b/loopx/control_plane/todos/read_projection.ts @@ -202,12 +202,13 @@ export async function projectCanonicalTodoFields(input: JsonObject): Promise requireJsonObject(value, "Todo lineage")) : all; const lineageFacts = input.lineage_facts ?? lineage.map(successionFacts); + const includeArchived = input.include_archived === true || input.retained === true; const query = input.kind === "query" && input.selection == null ? object(input.query) : {}; const detailId = query.agent_id == null && query.limit == null ? query.todo_id : null; const fields: JsonObject = {}, evaluations: JsonObject = {}; for (const role of ["user", "agent"]) { const section = role === "agent" ? "Agent Todo" : "User Todo / Owner Review Reading Queue"; - const items = all.filter(row => (row.role === "user" ? "user" : "agent") === role && (input.retained === true || !archived.has(row.todo_id))) + const items = all.filter(row => (row.role === "user" ? "user" : "agent") === role && (includeArchived || !archived.has(row.todo_id))) .map(row => structureCanonicalTodo({...row, ...(input.retained !== true && checksConfigured && guards[text(row.todo_id)] ? {goal_acceptance_guard: guards[text(row.todo_id)]} : {})}, role, section, input.retained === true ? null : 500, input.retained !== true)); diff --git a/loopx/control_plane/todos/read_transaction.ts b/loopx/control_plane/todos/read_transaction.ts index a99f2cc388..53e4dea6f4 100644 --- a/loopx/control_plane/todos/read_transaction.ts +++ b/loopx/control_plane/todos/read_transaction.ts @@ -12,6 +12,9 @@ import {compactTodoReadSummary, compactThinTodoRead, compactTodoQueueSummary, to import {projectTodoContextPage} from "./context_projection.ts"; import {sealProjectionEnvelope, PROJECTION_ENVELOPE_SEAL_REQUEST} from "../projection_envelope.ts"; +const includesArchivedRows = (query: JsonObject): boolean => + query.read_scope === "completed_history" || query.include_archived === true; + export async function readCanonicalTodoProjection(input: JsonObject): Promise { const canonical = await collectCanonicalTodoSource(requireJsonObject(input.source, "canonical Todo read source")); if (canonical.status !== "loaded") return {schema_version: TODO_READ_RESULT, canonical}; @@ -19,8 +22,9 @@ export async function readCanonicalTodoProjection(input: JsonObject): Promise { const query = requireJsonObject(input.query, "Todo list query"); + const includeArchived = includesArchivedRows(query); const fullText = query.full_text === undefined ? false : requireBoolean(query.full_text, "full_text"); const summary = query.summary === undefined ? false : requireBoolean(query.summary, "Todo summary view"); if (fullText && (query.thin === true || query.json_references === true || summary)) @@ -120,7 +125,6 @@ async function todoListPayload(input: JsonObject, original: JsonObject[], fields item.text = String(matching[0]!.text ?? ""); } const matchedCount = todos.length; - const includeArchived = query.read_scope === "completed_history" || query.include_archived === true; const hot = !fullText && !includeArchived && query.agent_id && query.limit == null && query.role == null && query.status == null && query.todo_id == null; if (hot && !summary) { diff --git a/tests/control_plane_ts/todo_read.test.ts b/tests/control_plane_ts/todo_read.test.ts index 46864030d0..2d1a2b8a06 100644 --- a/tests/control_plane_ts/todo_read.test.ts +++ b/tests/control_plane_ts/todo_read.test.ts @@ -11,6 +11,8 @@ import {engageLegacyCoordinationWriterFence} from "../../loopx/control_plane/coo import {canonicalAuthoritySha256} from "../../loopx/control_plane/coordination/authority_store_codec.ts"; import {coordinationTodoReadModel} from "../../loopx/control_plane/coordination/coordination_projection.ts"; import {TODO_CANONICAL_READ_RECORD_SCHEMA} from "../../loopx/control_plane/coordination/coordination_state_contract.ts"; +import {authorityProjectionFixture} from "./authority_projection_fixture.ts"; +import {goalAcceptanceTodoDigest, normalizeGoalAcceptanceDocument} from "../../loopx/control_plane/goals/acceptance_contract.ts"; const now = "2026-10-19T07:00:00Z"; const row = (todo_id: string, extra: JsonObject = {}): JsonObject => ({schema_version: "todo_item_v0", todo_id, @@ -117,6 +119,103 @@ for (const provider of ["file", "sqlite"] as const) { }); } +for (const provider of ["file", "sqlite"] as const) { + test(`${provider} include-archived keeps current acceptance limits on active Todos`, async t => { + const root = await mkdtemp(join(tmpdir(), "loopx-todo-mixed-archive-")); + t.after(() => rm(root, {recursive: true, force: true})); + const state = join(root, "state.md"), goal = "goal-a"; + await writeFile(state, "# Synthetic Goal\n"); + if (provider === "sqlite") await selectLocalSqliteAuthority(root, goal, true); + const store = await openLocalAuthorityStore(root, goal); + const bound = row("todo_bound", {text: "Original bound requirement"}); + const unbound = row("todo_unbound", {index: 2, text: "Unbound active requirement"}); + const archived = row("todo_archived", {index: 3, status: "done", done: true, + task_class: "advancement_task", archive_state: "archive", source_section: "Completed Work Archive"}); + const document = normalizeGoalAcceptanceDocument({ + objective: "Preserve current acceptance limits while reading history", + non_goals: [], + criteria: [{id: "criterion-a", description: "The bound work remains guarded", + validation_argv: ["true"]}], + bindings: [{todo_id: "todo_bound", criterion_ids: ["criterion-a"]}], + }); + const acceptance = { + schema_version: "loopx_goal_acceptance_v0", + enabled: true, + revision: 1, + digest: canonicalAuthoritySha256(document), + document, + verification: null, + bindings: [{todo_id: "todo_bound", todo_semantic_digest: goalAcceptanceTodoDigest(bound), + revision: 1, criterion_ids: ["criterion-a"], confirmed_by: "owner"}], + }; + const projection = authorityProjectionFixture(goal, [ + {...bound, text: "Changed bound requirement"}, + unbound, + archived, + ], [], "native", {goal_acceptance: acceptance}); + const committed = await store.commitAuthority({ + expected_provider_revision: null, + operation_id: "seed-mixed-archive", + events: [], + next_projection: projection, + receipts: [], + }); + assert.equal(committed.status, "applied"); + if (committed.status !== "applied") return; + const fence = await engageLegacyCoordinationWriterFence({ + schema_version: "loopx_legacy_coordination_writer_fence_engage_request_v0", + runtime_root: root, + goal_id: goal, + state_path: state, + fence: { + schema_version: "loopx_legacy_coordination_writer_fence_v0", + state: "engaged", + goal_id: goal, + fence_id: "mixed-archive-fixture", + source_version: "fixture", + source_projection_sha256: canonicalAuthoritySha256(projection), + expected_shadow_provider_revision: committed.provider_revision, + }, + }); + assert.equal(fence.status, "applied"); + + const query = (include_archived: boolean, status: string | null = null) => readTodo({ + schema_version: TODO_READ_REQUEST, + kind: "query", + source: {runtime_root: root, goal_id: goal}, + observed_at: Date.parse(now) / 1000, + evaluated_at: now, + query: {goal_id: goal, role: "agent", status, todo_id: null, agent_id: null, limit: null, + read_scope: "active", include_archived, json_references: false, thin: false, + state_file: state, project: root}, + }); + + const normalResult = await query(false); + const mixedResult = await query(true); + const openOnlyResult = await query(true, "open"); + assert.ok(normalResult.payload, JSON.stringify(normalResult)); + assert.ok(mixedResult.payload, JSON.stringify(mixedResult)); + assert.ok(openOnlyResult.payload, JSON.stringify(openOnlyResult)); + const normal = normalResult.payload as JsonObject; + const mixed = mixedResult.payload as JsonObject; + const openOnly = openOnlyResult.payload as JsonObject; + assert.deepEqual((normal.todos as JsonObject[]).map(item => item.todo_id), ["todo_bound", "todo_unbound"]); + assert.deepEqual(new Set((mixed.todos as JsonObject[]).map(item => item.todo_id)), + new Set(["todo_bound", "todo_unbound", "todo_archived"])); + assert.deepEqual(new Set((openOnly.todos as JsonObject[]).map(item => item.todo_id)), + new Set(["todo_bound", "todo_unbound"])); + for (const payload of [normal, mixed, openOnly]) { + const summary = payload.agent_todos as JsonObject; + const contract = summary.goal_acceptance_contract as JsonObject; + assert.equal(contract.status, "held"); + assert.deepEqual(contract.held_todo_ids, ["todo_bound", "todo_unbound"]); + assert.deepEqual(summary.first_executable_items, []); + const active = (summary.items as JsonObject[]).filter(item => item.todo_id !== "todo_archived"); + assert.ok(active.every(item => (item.goal_acceptance_guard as JsonObject).allowed === false)); + } + }); +} + test("display limits retain full-source closure and reject altered evaluation evidence", async () => { const items = Array.from({length: 4096}, (_, i) => row(`todo_completed_${i}`, {index: i + 1, status: "done", done: true, no_followup: true}));