From 2dfb3fd8ad35c1286f1891f92bd85236c9a15e12 Mon Sep 17 00:00:00 2001
From: aihop <4408344+hhyykk@users.noreply.github.com>
Date: Mon, 19 Oct 2026 10:55:50 +0000
Subject: [PATCH 1/3] refactor(todos): compose full readback in one TypeScript
transaction
Move canonical source reads, full-source evaluations, summary construction,
query selection and CLI result presentation behind todo.read. Retire the old
Python ordinal coordinator and scalar completion RPCs while retaining explicit
legacy codecs, provider IO adapters and compatibility imports.
Keep normal installed transport, PostgreSQL and full-command performance
qualification as explicit review holds; source/stdio probes do not waive them.
Signed-off-by: aihop <4408344+hhyykk@users.noreply.github.com>
---
.../typescript-control-plane-migration-v0.md | 27 +-
...script-control-plane-migration-v0.zh-CN.md | 22 +-
docs/reference/todo-work-counts.md | 18 +-
.../periodic_report/post_writeback_hook.py | 26 +-
.../periodic_report/todo_source.py | 12 +-
loopx/cli_commands/todo_list.py | 2 +
.../coordination/local_authority.py | 115 ++--
loopx/control_plane/effect_runtime.py | 1 +
.../control_plane/effect_runtime_handlers.ts | 4 +-
loopx/control_plane/effect_runtime_server.ts | 1 +
.../control_plane/effect_runtime_snapshot.ts | 6 +
.../todos/active_state_todo_parser.py | 73 +--
.../control_plane/todos/active_state_todos.py | 20 +-
loopx/control_plane/todos/completion_state.py | 63 +--
loopx/control_plane/todos/completion_state.ts | 34 +-
.../todos/completion_value_codec.json | 17 +
.../control_plane/todos/context_projection.ts | 16 +
loopx/control_plane/todos/contract.py | 74 +--
.../control_plane/todos/frontier_revision.py | 35 +-
.../todos/goal_todo_projection.py | 170 ++----
loopx/control_plane/todos/list_projection.py | 498 ++----------------
loopx/control_plane/todos/list_readback.py | 237 ++-------
loopx/control_plane/todos/read_codec.ts | 254 +++++++++
.../todos/read_legacy_codec.json | 236 +++++++++
.../control_plane/todos/read_presentation.ts | 116 ++++
loopx/control_plane/todos/read_projection.py | 234 ++++++++
loopx/control_plane/todos/read_projection.ts | 327 ++++++++++++
loopx/control_plane/todos/read_transaction.ts | 166 ++++++
loopx/control_plane/todos/read_transport.ts | 65 +++
.../control_plane/todos/standing_decision.py | 37 +-
.../control_plane/todos/summary_projection.ts | 2 +-
loopx/control_plane/todos/todo_summary.py | 307 ++---------
.../work_items/refresh_recommendation.py | 11 +-
.../canonical_authority_fixture.py | 9 +
.../test_explore_result_writeback.py | 4 +-
.../test_frontier_revision_scope.py | 4 +-
.../test_handoff_context_projection.py | 2 +-
.../test_local_coordination_authority.py | 38 +-
.../test_monitor_followthrough_contract.py | 9 +-
.../test_native_todo_planning_update.py | 4 +-
...test_quota_authority_settlement_journey.py | 10 +-
.../test_quota_settlement_cli.py | 12 +-
.../test_retained_team_plan_receipt.py | 5 +-
.../test_settled_monitor_user_gate.py | 6 +-
.../test_todo_completion_state_runtime.py | 46 +-
.../control_plane/test_todo_consumer_scope.py | 43 +-
.../test_todo_conversation_context.py | 5 +-
.../test_todo_list_explicit_limit.py | 5 +-
.../test_todo_list_record_references.py | 14 +-
.../test_todo_summary_projection.py | 14 +-
.../todo_completion_state.test.ts | 42 +-
tests/control_plane_ts/todo_read.test.ts | 120 +++++
52 files changed, 2036 insertions(+), 1582 deletions(-)
create mode 100644 loopx/control_plane/todos/completion_value_codec.json
create mode 100644 loopx/control_plane/todos/read_codec.ts
create mode 100644 loopx/control_plane/todos/read_legacy_codec.json
create mode 100644 loopx/control_plane/todos/read_presentation.ts
create mode 100644 loopx/control_plane/todos/read_projection.py
create mode 100644 loopx/control_plane/todos/read_projection.ts
create mode 100644 loopx/control_plane/todos/read_transaction.ts
create mode 100644 loopx/control_plane/todos/read_transport.ts
create mode 100644 tests/control_plane_ts/todo_read.test.ts
diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md
index e3b01a16a5..231ac674fb 100644
--- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md
+++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md
@@ -4,7 +4,7 @@
- Supersedes / closes: none
- Proposed by: LoopX maintainers
- Date: 2026-08-15
-- Last revised: 2026-10-16
+- Last revised: 2026-10-19
- Scope: an incremental, replacement-first migration of the LoopX control-plane
core from Python to TypeScript without maintaining two semantic
implementations
@@ -46,6 +46,31 @@ Each page reuses the existing executable-revision request scope; operation
records remain live reads, and the next page resolves the current code again.
+### Todo read transaction source checkpoint
+
+`todo.read` now composes canonical provider reads, full-source resume and
+succession evidence, standing decisions, both role summaries, selection and the
+public result through `todos/read_projection.ts` and `todos/read_transaction.ts`.
+Ordinary canonical queries use one request. Large result continuations retain
+one observation and result digest; changed results refuse rather than combining
+pages. Dependency and closure decisions still use the complete source.
+Python retains registry, document/material IO, independent Vision-wait consumers
+and compatibility exports. Its replaced summary, filter and result coordination
+is retired. Persisted completion spellings use shared token data and compatibility codecs
+without scalar RPCs;
+`todo.summary.project`, `todo.completion_state.normalize` and
+`todo.completion_state.require_metadata` are retired after their production
+callers exit. Existing semantic owners execute in process inside the transaction.
+
+CLI query results carry a query-scoped `projection_envelope`. A read grants no
+execution, acceptance, lease or mutation authority. The shared wire and private
+snapshot budgets are unchanged. Oversized Python facts use an owned temporary
+source, not another durable store; complete results use digest-bound pieces.
+Remaining historical/source adapters still name their actual IO and crossings.
+Source/controlled-IPC evidence does not qualify normal installed CLI/Host use,
+full-command performance, R3 adoption or D2/D3/default delivery.
+
+
## Current delivery frontier (post-merge, 2026-10-16)
diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md
index eb238403b9..1fda15603d 100644
--- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md
+++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md
@@ -4,7 +4,7 @@
- 替代 / 关闭:无
- Proposed by:LoopX maintainers
- Date:2026-08-15
-- Last revised:2026-10-16
+- Last revised:2026-10-19
- Scope:LoopX 控制面核心从 Python 到 TypeScript 的增量、replacement-first
迁移;不长期维护两份语义实现
- Tracking issue:[#3225](https://github.com/loopx-project/loopx/issues/3225)
@@ -39,6 +39,26 @@ Python 保留有界地址扫描、请求者注册检查和实时 `Delegations.re
每页复用既有代码版本请求作用域;操作记录仍实时读取,下一页重新解析当前代码。
+### Todo 读取事务源码检查点
+
+`todo.read` 现由 `todos/read_projection.ts` 与 `todos/read_transaction.ts`
+组合 canonical provider 读取、完整源上的恢复与后继证据、standing decisions、
+两个角色的摘要、筛选与公共结果。普通 canonical 查询使用一次请求。
+大结果的连续页保留同一次观察和结果摘要;结果变化即拒绝,不拼接新旧页。
+依赖与闭合判断仍使用完整源。
+Python 保留 registry、文档/材料 IO、独立的 Vision-wait 消费者及兼容导出。
+被替代的摘要、筛选与结果协调已退役。持久化 completion 拼写使用共享 token 数据与兼容 codec,
+不再逐标量调用 RPC;实际生产调用者退出后,退役 `todo.summary.project`、
+`todo.completion_state.normalize` 与 `todo.completion_state.require_metadata`。
+事务在进程内执行既有语义 owner。
+
+CLI 查询结果携带查询范围内的 `projection_envelope`。读取不授予执行、验收、
+租约或修改权限。共享 wire 与私有快照预算不变。超大 Python 事实使用私有临时
+源,不增加持久化存储;完整结果通过摘要绑定的片段传输。仍保留的历史/源适配
+明确记录实际 IO 与跨运行时调用。源码/受控 IPC 证据不代表正常安装的 CLI/Host
+使用、完整命令性能、R3 采用或 D2/D3/默认交付资格。
+
+
## 当前交付边界(合入后,2026-10-16)
diff --git a/docs/reference/todo-work-counts.md b/docs/reference/todo-work-counts.md
index 31dfe11363..a11363b826 100644
--- a/docs/reference/todo-work-counts.md
+++ b/docs/reference/todo-work-counts.md
@@ -51,8 +51,9 @@ are lower bounds while `complete=false`. Current capability re-evaluation
replaces prior readiness for observed rows and cannot certify unseen rows.
The TypeScript `todos/summary_lanes.ts` owner returns indexes into the one input
-array instead of repeating full Todo bodies for each lane. The Python adapter
-normalizes legacy fields/timestamps and validates the returned ordinal bounds.
+array instead of repeating full Todo bodies for each lane. The native read owner
+normalizes canonical fields/timestamps and materializes the selected records.
+Python retains the legacy source codecs.
One observation time governs Monitor due and expiry classification within the
batch. Source order, completed/deferred conventions and claimant visibility
remain compatible; `done_count` still includes deferred rows as required by its
@@ -92,11 +93,14 @@ read and continue to show the whole Goal; no new configuration editor is needed.
Resume and succession are evaluated on the complete source before selection.
The typed batch filters rows without renumbering their original source indexes,
then builds lanes/counts, and only then applies display limits. Status/identity
-filters do not recompute dependencies from their smaller view. One internal `todo.summary.project` batch now composes selection, counts,
-visibility allocation and closure. Its transient request replaces the separate
-lane and closure RPC calls; it does not change persisted Todo or public summary
-schemas. Python decodes legacy input, validates source ordinals and materializes
-public fields, with no independent summary count, cap or claimant-allocation rule.
+filters do not recompute dependencies from their smaller view. The `todo.read` transaction composes source evaluation, selection, counts,
+frontier identity, handoff context and result construction through the existing
+TypeScript owners. Canonical provider IO runs in that transaction; Markdown
+source codecs and material IO remain explicit Python adapters. Filtering reuses
+validated full-source evidence, never dependencies inferred from a small view.
+The old `todo.summary.project` transport and Python ordinal materializer are
+retired. Large source/result transport preserves complete decisions and refuses
+changed continuations; display limits still disclose shown and available counts.
Route-continuation visibility also uses the existing typed quota planning batch.
It shares the normal claim/exclusion rule; eligibility, first-identity deduplication,
diff --git a/loopx/capabilities/periodic_report/post_writeback_hook.py b/loopx/capabilities/periodic_report/post_writeback_hook.py
index 6b03f7a1c9..3ef9c425c8 100644
--- a/loopx/capabilities/periodic_report/post_writeback_hook.py
+++ b/loopx/capabilities/periodic_report/post_writeback_hook.py
@@ -16,10 +16,6 @@
build_goal_frontier_projection_from_summaries,
)
from .todo_source import read_report_source_history, read_report_todo_source
-from ...control_plane.effect_runtime import (
- CANONICAL_AUTHORITY_READ_TIMEOUT_SECONDS,
- effect_runtime_result,
-)
from ...control_plane.todos.quota_summary import summarize_user_todos_for_quota
from ...control_plane.todos.todo_index import MAX_TODO_INDEX_ROLLOUT_EVENTS_PER_GOAL
from ...history import collect_history, load_index_snapshot, load_registry
@@ -202,18 +198,18 @@ def build_periodic_report_post_writeback_projection(
if available_capabilities is None and isinstance(payload.get("turn"), Mapping):
available_capabilities = payload["turn"].get("available_capabilities")
if source_todos is None and "todo_source" in payload:
- source_todos = effect_runtime_result(
- "coordination.local_authority.todo_source",
- {
- "schema_version": "loopx_local_coordination_todo_source_request_v0",
- "runtime_root": str(runtime_root), "goal_id": goal_id,
- "source": payload["todo_source"],
- },
- timeout=CANONICAL_AUTHORITY_READ_TIMEOUT_SECONDS,
- large_local_snapshot=True,
- )
+ from ...control_plane.todos.read_projection import project_todo_read, restore_todo_field_evaluations
+ from ...control_plane.runtime.time import now_utc
+
+ now = now_utc()
+ readback = project_todo_read({"kind": "historical_fields",
+ "source": {"runtime_root": str(runtime_root), "goal_id": goal_id, "original_source": payload["todo_source"]},
+ "rollout_events": [], "available_capabilities": available_capabilities,
+ "observed_at": now.timestamp(), "evaluated_at": now.isoformat()})
+ source_todos = readback["canonical"]
if source_todos.get("status") != "loaded":
raise ValueError("original canonical Todo source is unavailable")
+ source_todos["_todo_fields"] = restore_todo_field_evaluations(readback)
source_runs = read_report_source_history(
runtime_root=runtime_root, goal_id=goal_id, run=payload,
)
@@ -228,7 +224,7 @@ def build_periodic_report_post_writeback_projection(
state.get("path") if isinstance(state, Mapping) else None
) or payload.get("state_file")
if source_todos is not None:
- fields = canonical_todo_summary_fields(
+ fields = source_todos.get("_todo_fields") or canonical_todo_summary_fields(
source_todos["todos"],
rollout_events=events,
available_capabilities=available_capabilities,
diff --git a/loopx/capabilities/periodic_report/todo_source.py b/loopx/capabilities/periodic_report/todo_source.py
index b925285b29..b6ef0d6350 100644
--- a/loopx/capabilities/periodic_report/todo_source.py
+++ b/loopx/capabilities/periodic_report/todo_source.py
@@ -11,7 +11,6 @@
from typing import Any
from ...control_plane.coordination.local_authority import (
- canonical_todo_summary_fields,
read_canonical_todos_if_promoted,
)
from ...control_plane.todos.active_state_todo_parser import parse_active_state_todos
@@ -62,16 +61,11 @@ def read_report_todo_source(
registry, None, registry_path=registry_path
)
canonical = read_canonical_todos_if_promoted(
- runtime_root=runtime_root, goal_id=goal_id
+ runtime_root=runtime_root, goal_id=goal_id, include_summary=True,
+ rollout_events=rollout_events, available_capabilities=available_capabilities
)
if canonical is not None:
- fields = canonical_todo_summary_fields(
- canonical["todos"],
- rollout_events=rollout_events,
- available_capabilities=available_capabilities,
- goal_acceptance_contract=canonical.get("goal_acceptance_contract"),
- goal_acceptance_work_guards=canonical.get("goal_acceptance_work_guards"),
- )
+ fields = canonical["_todo_fields"]
return fields, [row for row in canonical["todos"] if row.get("role") == "user"]
state_path = state_path or resolve_state_file(
Path(str(goal.get("repo") or "")).expanduser(),
diff --git a/loopx/cli_commands/todo_list.py b/loopx/cli_commands/todo_list.py
index 335f80feb2..3f4a26b16d 100644
--- a/loopx/cli_commands/todo_list.py
+++ b/loopx/cli_commands/todo_list.py
@@ -42,6 +42,7 @@ def handle_todo_list_command(
project=Path(args.project).expanduser() if args.project else None,
state_file=Path(args.state_file).expanduser() if args.state_file else None,
runtime_root_arg=runtime_root_arg,
+ json_references=(format_name or str(getattr(args, "format", None) or "markdown")) == "json",
)
except Exception as exc:
from ..usage_ping import capture_failure
@@ -56,6 +57,7 @@ def handle_todo_list_command(
)
output_format = format_name or str(getattr(args, "format", None) or "markdown")
if (payload.get("ok") and not args.todo_id and not args.todo_thin
+ and "todo_list_record_references" not in payload
and output_format == "json"):
try:
payload = effect_runtime_result(
diff --git a/loopx/control_plane/coordination/local_authority.py b/loopx/control_plane/coordination/local_authority.py
index 513238df44..a48d5a3e1a 100644
--- a/loopx/control_plane/coordination/local_authority.py
+++ b/loopx/control_plane/coordination/local_authority.py
@@ -245,6 +245,7 @@ def claim_canonical_todo_if_promoted(
def read_canonical_todos_if_promoted(
*, runtime_root: Path, goal_id: str, include_leases: bool = False,
projection_readback: Mapping[str, Any] | None = None,
+ include_summary: bool = False, rollout_events=None, available_capabilities=None,
) -> dict[str, Any] | None:
"""Return canonical Todos after cutover, or ``None`` before cutover.
@@ -256,13 +257,28 @@ def read_canonical_todos_if_promoted(
if not local_authority_is_promoted(runtime_root=runtime_root, goal_id=goal_id):
return None
- result = read_canonical_snapshot(
- rpc=effect_runtime_result,
- runtime_root=str(runtime_root.expanduser().resolve(strict=False)),
- goal_id=goal_id, include_leases=include_leases,
- projection_readback=projection_readback,
- timeout=LOCAL_COORDINATION_TODO_LIST_TIMEOUT_SECONDS,
- )
+ projected = None
+ if include_summary:
+ from ..todos.read_projection import project_todo_read
+ from ..runtime.time import now_utc
+
+ now = now_utc()
+ projected = project_todo_read({"kind": "canonical_fields",
+ "source": {"runtime_root": str(runtime_root.expanduser().resolve(strict=False)), "goal_id": goal_id,
+ "include_leases": include_leases, "projection_readback": dict(projection_readback) if projection_readback is not None else None},
+ "rollout_events": list(rollout_events or []),
+ "rollout_event_source": getattr(rollout_events, "source", None),
+ "available_capabilities": sorted({str(value).strip().lower() for value in available_capabilities if str(value).strip()}) if available_capabilities is not None else None,
+ "observed_at": now.timestamp(), "evaluated_at": now.isoformat()})
+ result = projected["canonical"]
+ else:
+ result = read_canonical_snapshot(
+ rpc=effect_runtime_result,
+ runtime_root=str(runtime_root.expanduser().resolve(strict=False)),
+ goal_id=goal_id, include_leases=include_leases,
+ projection_readback=projection_readback,
+ timeout=LOCAL_COORDINATION_TODO_LIST_TIMEOUT_SECONDS,
+ )
if not isinstance(result, Mapping):
raise LocalCoordinationAuthorityUnavailable(
"local coordination authority returned an invalid Todo list",
@@ -316,6 +332,9 @@ def read_canonical_todos_if_promoted(
"canonical projection confirmation is missing or invalid",
code="local_authority_projection_confirmation_invalid", payload=payload,
)
+ if projected is not None and "fields" in projected:
+ from ..todos.read_projection import restore_todo_field_evaluations
+ payload["_todo_fields"] = restore_todo_field_evaluations(projected)
return payload
@@ -338,8 +357,10 @@ def read(self, *, runtime_root: Path, goal_id: str) -> dict[str, Any] | None:
key = (runtime_root.expanduser().resolve(strict=False), goal_id)
if key not in self._reads:
try:
+ from ..todos.resume_condition import TodoResumeRolloutEvents
self._reads[key] = read_canonical_todos_if_promoted(
- runtime_root=key[0], goal_id=goal_id,
+ runtime_root=key[0], goal_id=goal_id, include_summary=True,
+ rollout_events=TodoResumeRolloutEvents([], runtime_root=key[0], goal_id=goal_id),
)
except LocalCoordinationAuthorityUnavailable as error:
# A failed first read cannot become a successful attention view
@@ -367,13 +388,10 @@ def read_canonical_todo_fields_if_promoted(
read neither repairs Markdown nor grants mutation/promotion authority.
Callers pass the same fields to all decisions in one planning operation.
"""
- canonical = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=goal_id)
- return (
- canonical_todo_summary_fields(canonical["todos"], rollout_events=rollout_events,
- goal_acceptance_contract=canonical.get("goal_acceptance_contract"),
- goal_acceptance_work_guards=canonical.get("goal_acceptance_work_guards"))
- if canonical is not None else None
- )
+ canonical = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=goal_id,
+ include_summary=True, rollout_events=rollout_events)
+ return canonical["_todo_fields"] if canonical is not None else None
+
def canonical_todo_summary_fields(
@@ -384,69 +402,12 @@ def canonical_todo_summary_fields(
goal_acceptance_contract: dict[str, Any] | None = None,
goal_acceptance_work_guards: dict[str, Any] | None = None,
) -> dict[str, Any]:
- """Adapt canonical records into the existing Todo summary read model."""
-
- from ..todos.active_state_editing import TODO_SECTION_HEADINGS
- from ..todos.standing_decision import build_standing_decision_authority
- from ..todos.todo_summary import compact_todo_group, count_advancement_todos
+ from ..todos.read_projection import canonical_todo_fields
- # Read standing decisions before assigning presentation-only indexes, and
- # include retained history so archiving a revocation cannot revive approval.
- standing_authority = build_standing_decision_authority(
- [item for item in todos if item.get("role") == "user"], canonical_records=True,
- )
- archived_ids = {
- item["todo_id"]
- for item in todos
- if item.get("archive_state") == "archive"
- }
- todos = canonical_todo_items(todos)
- # These are native authority decisions, not persisted Todo fields. Keep the
- # records visible while every summary/selection uses the same work guard.
- if goal_acceptance_contract and goal_acceptance_contract.get("enabled") is True:
- guards = goal_acceptance_work_guards or {}
- todos = [{**item, "goal_acceptance_guard": guards[item["todo_id"]]}
- if item.get("todo_id") in guards else item for item in todos]
- fields: dict[str, Any] = {}
- for role in ("user", "agent"):
- items = [
- item
- for item in todos
- if ("user" if item.get("role") == "user" else "agent") == role
- and item.get("todo_id") not in archived_ids
- ]
- summary = compact_todo_group(
- items,
- source_section=TODO_SECTION_HEADINGS[role],
- role=role,
- include_empty_source=True,
- resume_source_items=todos,
- rollout_events=rollout_events,
- available_capabilities=available_capabilities,
- item_limit=None,
- )
- if summary:
- if role == "agent":
- if goal_acceptance_contract and goal_acceptance_contract.get("enabled") is True:
- summary["goal_acceptance_contract"] = goal_acceptance_contract
- archived_done = count_advancement_todos(
- [
- item
- for item in todos
- if item.get("todo_id") in archived_ids
- and item.get("done") is True
- ]
- )
- if archived_done:
- summary["archived_advancement_done_count"] = archived_done
- summary["advancement_done_count"] = (
- int(summary.get("advancement_done_count") or 0) + archived_done
- )
- fields[f"{role}_todos"] = summary
- if role == "user":
- if standing_authority:
- fields["standing_decision_authority"] = standing_authority
- return fields
+ return canonical_todo_fields(todos, rollout_events=rollout_events,
+ available_capabilities=available_capabilities,
+ goal_acceptance_contract=goal_acceptance_contract,
+ goal_acceptance_work_guards=goal_acceptance_work_guards)
def canonical_todo_items(todos: list[dict[str, Any]]) -> list[dict[str, Any]]:
diff --git a/loopx/control_plane/effect_runtime.py b/loopx/control_plane/effect_runtime.py
index 7e18939128..f275438d6c 100644
--- a/loopx/control_plane/effect_runtime.py
+++ b/loopx/control_plane/effect_runtime.py
@@ -42,6 +42,7 @@
MAX_REQUEST_BYTES = 2 * 1024 * 1024
MAX_LOCAL_SNAPSHOT_BYTES = 64 * 1024 * 1024
LOCAL_SNAPSHOT_METHODS = frozenset({
+ "todo.read",
"collaboration.delegation.inventory_page",
"benchmark.trajectory.codex-usage",
"coordination.local_authority.todo_source",
diff --git a/loopx/control_plane/effect_runtime_handlers.ts b/loopx/control_plane/effect_runtime_handlers.ts
index 809f5ee933..d83e604e36 100644
--- a/loopx/control_plane/effect_runtime_handlers.ts
+++ b/loopx/control_plane/effect_runtime_handlers.ts
@@ -268,13 +268,11 @@ export function createEffectRuntimeHandlers(
["turn_journal.find_settlement", lazyHandler(() => import("./turn_driver/turn_journal_query.ts"), ({findTurnJournalBySettlement}) => findTurnJournalBySettlement)],
["turn_journal.observed_capabilities", lazyHandler(() => import("./turn_driver/turn_journal_query.ts"), ({readTurnJournalCapabilities}) => readTurnJournalCapabilities)],
["todo.completion_fence.evaluate", lazyHandler(() => import("./todos/completion_fence.ts"), ({evaluateTodoCompletionFence}) => evaluateTodoCompletionFence)],
- ["todo.completion_state.normalize", lazyHandler(() => import("./todos/completion_state.ts"), ({normalizeTodoCompletionValue}) => normalizeTodoCompletionValue)],
- ["todo.completion_state.require_metadata", lazyHandler(() => import("./todos/completion_state.ts"), ({requireTodoCompletionMetadataValue}) => requireTodoCompletionMetadataValue)],
["todo.field_update.plan", lazyHandler(() => import("./todos/field_update.ts"), ({planTodoFieldUpdate}) => planTodoFieldUpdate)],
["todo.priority.plan", lazyHandler(() => import("./todos/priority.ts"), ({evaluateTodoPriority}) => evaluateTodoPriority)],
["todo.public_update.plan", lazyHandler(() => import("./todos/public_update.ts"), ({planPublicTodoUpdate}) => planPublicTodoUpdate)],
["todo.standing_decision.project", lazyHandler(() => import("./todos/standing_decision.ts"), ({evaluateStandingDecisionProjection}) => evaluateStandingDecisionProjection)],
- ["todo.summary.project", lazyHandler(() => import("./todos/summary_projection.ts"), ({projectTodoSummary}) => projectTodoSummary)],
+ ["todo.read", lazyHandler(() => import("./todos/read_transport.ts"), ({readTodoRuntime}) => readTodoRuntime)],
["capabilities.periodic_report.progress.select", lazyHandler(() => import("./capabilities/periodic_report_progress.ts"), ({selectPeriodicReportProgress}) => selectPeriodicReportProgress)],
["capabilities.periodic_report.approval_retry.select", lazyHandler(() => import("./capabilities/periodic_report_progress.ts"), ({selectPeriodicReportApprovalRetry}) => selectPeriodicReportApprovalRetry)],
["todo.succession.project", lazyHandler(() => import("./todos/succession.ts"), ({projectTodoSuccession}) => projectTodoSuccession)],
diff --git a/loopx/control_plane/effect_runtime_server.ts b/loopx/control_plane/effect_runtime_server.ts
index 57852c25d1..b85bf34bff 100644
--- a/loopx/control_plane/effect_runtime_server.ts
+++ b/loopx/control_plane/effect_runtime_server.ts
@@ -28,6 +28,7 @@ const MAX_REQUEST_BYTES = 2 * 1024 * 1024;
const MAX_INLINE_RESPONSE_BYTES = 2 * 1024 * 1024;
// Explicit opt-in: ordinary effects retain the 2 MiB request/response wire.
const LOCAL_SNAPSHOT_METHODS = new Set([
+ "todo.read",
"collaboration.delegation.inventory_page",
"benchmark.trajectory.codex-usage",
"coordination.local_authority.todo_source",
diff --git a/loopx/control_plane/effect_runtime_snapshot.ts b/loopx/control_plane/effect_runtime_snapshot.ts
index 94f493657b..b3bd40438a 100644
--- a/loopx/control_plane/effect_runtime_snapshot.ts
+++ b/loopx/control_plane/effect_runtime_snapshot.ts
@@ -60,6 +60,12 @@ export async function readPrivateJsonSnapshot(value: unknown): Promise
}
}
+/** A domain-owned source can exceed a wire frame; it still uses the same
+ * private-file admission. The domain verifies its full source identity. */
+export async function openPrivateSnapshotSource(path: unknown): Promise {
+ return privateFile(requireNonEmptyString(path, "source snapshot path"), constants.O_RDONLY);
+}
+
export async function openPrivateResponseSink(path: unknown): Promise {
try {
return await privateFile(requireNonEmptyString(path, "response sink"), constants.O_WRONLY);
diff --git a/loopx/control_plane/todos/active_state_todo_parser.py b/loopx/control_plane/todos/active_state_todo_parser.py
index ab43f928b0..f6a92b0d24 100644
--- a/loopx/control_plane/todos/active_state_todo_parser.py
+++ b/loopx/control_plane/todos/active_state_todo_parser.py
@@ -9,14 +9,10 @@
normalize_todo_id,
TODO_TASK_PATTERN,
)
-from .standing_decision import build_standing_decision_authority
from .machine_region import find_todo_source_regions, visible_markdown_lines
from .todo_block_codec import decode_todo_blocks
from .todo_summary import (
MAX_STATUS_TODOS_PER_ROLE,
- compact_todo_group,
- count_advancement_todos,
- normalize_todo_text,
)
from ..runtime.time import now_utc_iso
@@ -77,61 +73,20 @@ def parse_active_state_todos(
and int(orchestration.get("max_children") or 0) > 0
)
items, archive_items, source_sections = parse_todo_source(state_text, goal=goal, state_path=state_path)
- result: dict[str, Any] = {}
- archived_resume_source_items = [
- item for item in archive_items if normalize_todo_id(item.get("todo_id"))
- ]
- resume_source_items = [*items["user"], *items["agent"], *archived_resume_source_items]
- user = compact_todo_group(
- items["user"],
- source_section=source_sections["user"],
- role="user",
- include_empty_source=source_sections["user"] is not None,
- preferred_todo_ids=preferred_todo_ids,
- resume_source_items=resume_source_items,
- rollout_events=rollout_events,
- available_capabilities=available_capabilities,
- item_limit=item_limit,
- include_task_orchestration_authority=include_task_orchestration_authority,
- evaluated_at=resume_evaluated_at,
- )
- agent = compact_todo_group(
- items["agent"],
- source_section=source_sections["agent"],
- role="agent",
- include_empty_source=source_sections["agent"] is not None,
- preferred_todo_ids=preferred_todo_ids,
- resume_source_items=resume_source_items,
- rollout_events=rollout_events,
- available_capabilities=available_capabilities,
- item_limit=item_limit,
- include_task_orchestration_authority=include_task_orchestration_authority,
- vision_runs=(goal or {}).get("latest_runs"),
- evaluated_at=resume_evaluated_at,
- )
- archived_advancement_done_count = count_advancement_todos(
- # Lossless source text must not widen legacy attention classification.
- [
- {**item, "text": normalize_todo_text(str(item.get("text") or ""))}
- for item in archive_items if item.get("done") is True
- ]
- )
- if agent and archived_advancement_done_count:
- agent["archived_advancement_done_count"] = archived_advancement_done_count
- agent["advancement_done_count"] = (
- int(agent.get("advancement_done_count") or 0)
- + archived_advancement_done_count
- )
- if user:
- result["user_todos"] = user
- if agent:
- result["agent_todos"] = agent
+ from .read_projection import source_todo_fields
+ from .standing_decision import standing_decision_source_items
+
+ lineage = [*items["user"], *items["agent"],
+ *[item for item in archive_items if normalize_todo_id(item.get("todo_id"))]]
archived_decisions = [item for item in archive_items if item.get("role") == "user"]
- standing_authority = build_standing_decision_authority(
- [*items["user"], *archived_decisions],
- # Separate sections are not a single chronological append log.
+ result = source_todo_fields([
+ {"role": role, "source_section": source_sections[role], "items": items[role],
+ "include_empty_source": source_sections[role] is not None} for role in ("user", "agent")],
+ lineage=lineage, rollout_events=rollout_events, available_capabilities=available_capabilities,
+ preferred_todo_ids=preferred_todo_ids, item_limit=item_limit, evaluated_at=resume_evaluated_at,
+ archived_items=archive_items,
+ standing_items=standing_decision_source_items([*items["user"], *archived_decisions]),
legacy_source_order=not archived_decisions,
- )
- if standing_authority:
- result["standing_decision_authority"] = standing_authority
+ include_task_orchestration_authority=include_task_orchestration_authority,
+ vision_runs=(goal or {}).get("latest_runs"))
return result
diff --git a/loopx/control_plane/todos/active_state_todos.py b/loopx/control_plane/todos/active_state_todos.py
index a23b196c8d..8dedb8812d 100644
--- a/loopx/control_plane/todos/active_state_todos.py
+++ b/loopx/control_plane/todos/active_state_todos.py
@@ -6,7 +6,6 @@
from ..coordination.local_authority import (
CanonicalTodoSnapshot,
- canonical_todo_summary_fields,
read_canonical_todos_if_promoted,
)
from ..goals.active_state_metadata import parse_state_frontmatter
@@ -91,11 +90,11 @@ def active_state_todo_fields(
goal = {**goal, **source_goal}
# Inspect authority before the display file. A missing/stale projection is
# not an empty Todo collection, and an unavailable provider must fail closed.
- canonical_reader = todo_snapshot.read if todo_snapshot is not None else read_canonical_todos_if_promoted
- canonical = (
- canonical_reader(runtime_root=runtime_root, goal_id=goal_id)
- if runtime_root is not None and goal_id else None
- )
+ canonical = (None if runtime_root is None or not goal_id else
+ todo_snapshot.read(runtime_root=runtime_root, goal_id=goal_id) if todo_snapshot is not None else
+ read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=goal_id,
+ include_summary=True, rollout_events=TodoResumeRolloutEvents(
+ rollout_events or [], runtime_root=runtime_root, goal_id=goal_id)))
state_path = resolve_goal_local_path(goal.get("state_file"), goal, fallback_base=Path.cwd())
if canonical is None and state_path is not None:
from ..goals.legacy_event_source import require_no_legacy_todo_events
@@ -128,14 +127,7 @@ def active_state_todo_fields(
if runtime_root is not None and goal_id:
events = TodoResumeRolloutEvents(events, runtime_root=runtime_root, goal_id=goal_id)
if canonical is not None:
- fields = canonical_todo_summary_fields(
- canonical["todos"],
- rollout_events=events,
- goal_acceptance_contract=canonical.get("goal_acceptance_contract"),
- goal_acceptance_work_guards=canonical.get(
- "goal_acceptance_work_guards"
- ),
- )
+ fields = canonical["_todo_fields"]
# Canonical observation/successor transactions now support current
# lease proof. Scheduling exposes due work; mutation admission still
# validates the caller's proof and never falls back to the old writer.
diff --git a/loopx/control_plane/todos/completion_state.py b/loopx/control_plane/todos/completion_state.py
index 9337087b4d..57b65d918a 100644
--- a/loopx/control_plane/todos/completion_state.py
+++ b/loopx/control_plane/todos/completion_state.py
@@ -4,13 +4,15 @@
from enum import Enum
from functools import lru_cache
-from typing import Any, Mapping
+import json
+from pathlib import Path
+from typing import Any
-from ..effect_runtime import EffectRuntimeRejected, effect_runtime_result
TODO_COMPLETION_STATE_REQUEST_SCHEMA = "loopx_todo_completion_state_request_v0"
TODO_COMPLETION_STATE_RESULT_SCHEMA = "loopx_todo_completion_state_result_v0"
+_VALUE_CODEC = json.loads(Path(__file__).with_name("completion_value_codec.json").read_text(encoding="utf-8"))
class TodoCompletionContinuation(str, Enum):
@@ -36,37 +38,22 @@ def _no_followup_value(value: Any) -> bool | str:
return value if isinstance(value, bool) else _string_value(value)
-def _result(method: str, params: dict[str, Any]) -> Mapping[str, Any]:
- try:
- result = effect_runtime_result(method, params)
- except EffectRuntimeRejected as exc:
- raise ValueError(str(exc)) from None
- if not isinstance(result, Mapping):
- raise RuntimeError("TypeScript Todo completion state result must be an object")
- if result.get("schema_version") != TODO_COMPLETION_STATE_RESULT_SCHEMA:
- raise RuntimeError("TypeScript Todo completion state result shape mismatch")
- return result
@lru_cache(maxsize=64)
def _normalize_cached(kind: str, value: bool | str) -> bool | str | None:
- result = _result(
- "todo.completion_state.normalize",
- {
- "schema_version": TODO_COMPLETION_STATE_REQUEST_SCHEMA,
- "kind": kind,
- "value": value,
- },
- )
- normalized = result.get("value")
if kind == "no_followup":
- if normalized is not None and not isinstance(normalized, bool):
- raise RuntimeError("TypeScript no_followup normalization shape mismatch")
- elif normalized is not None and not isinstance(normalized, str):
- raise RuntimeError(
- "TypeScript completion metadata normalization shape mismatch"
- )
- return normalized
+ if isinstance(value, bool):
+ return value
+ candidate = value.strip(_VALUE_CODEC["ecmascript_trim"]).lower()
+ if candidate in _VALUE_CODEC["true_tokens"]:
+ return True
+ if candidate in _VALUE_CODEC["false_tokens"]:
+ return False
+ return None
+ candidate = str(value).strip(_VALUE_CODEC["ecmascript_trim"]).lower()
+ vocabulary = TodoCompletionContinuation if kind == "continuation" else TodoCompletionRecovery
+ return candidate if candidate in {item.value for item in vocabulary} else None
def normalize_todo_no_followup(value: Any) -> bool | None:
@@ -85,15 +72,13 @@ def normalize_todo_completion_recovery(value: Any) -> str | None:
def require_todo_completion_metadata(key: str, value: Any) -> str | None:
- result = _result(
- "todo.completion_state.require_metadata",
- {
- "schema_version": TODO_COMPLETION_STATE_REQUEST_SCHEMA,
- "key": key,
- "value": _string_value(value),
- },
- )
- normalized = result.get("value")
- if normalized is not None and not isinstance(normalized, str):
- raise RuntimeError("TypeScript completion metadata result shape mismatch")
+ raw = _string_value(value)
+ if key == "completion_continuation":
+ normalized = _normalize_cached("continuation", raw)
+ if normalized is None:
+ raise ValueError("completion_continuation must be one of: active_goal, successor, no_followup")
+ else:
+ normalized = _normalize_cached("recovery", raw)
+ if normalized is None and raw != "":
+ raise ValueError("completion_recovery must be same_turn_terminal_closeout or lifecycle_reentry_terminal_closeout")
return normalized
diff --git a/loopx/control_plane/todos/completion_state.ts b/loopx/control_plane/todos/completion_state.ts
index e2271ddc15..663902c0f1 100644
--- a/loopx/control_plane/todos/completion_state.ts
+++ b/loopx/control_plane/todos/completion_state.ts
@@ -1,4 +1,5 @@
import type { JsonObject } from "../effect_program.ts";
+import valueCodec from "./completion_value_codec.json" with {type: "json"};
import { EffectRuntimeRequestError } from "../effect_runtime_errors.ts";
import {
requireBoolean as requiredBoolean,
@@ -64,10 +65,10 @@ export function normalizeTodoNoFollowup(value: unknown): boolean | null {
throw new EffectRuntimeRequestError("no_followup must be a boolean, string, or null");
}
const candidate = value.trim().toLowerCase();
- if (["1", "true", "yes", "y", "no_followup", "no-followup"].includes(candidate)) {
+ if (valueCodec.true_tokens.includes(candidate)) {
return true;
}
- if (["0", "false", "no", "n"].includes(candidate)) return false;
+ if (valueCodec.false_tokens.includes(candidate)) return false;
return null;
}
@@ -136,35 +137,6 @@ export function completionContinuationForWrite(
return "active_goal";
}
-export function normalizeTodoCompletionValue(value: unknown): JsonObject {
- const request = requestObject(value, "normalize");
- const kind = request.kind;
- if (kind !== "no_followup" && kind !== "continuation" && kind !== "recovery") {
- throw new EffectRuntimeRequestError("completion normalization kind is unsupported");
- }
- const raw = request.value;
- const normalized = kind === "no_followup"
- ? normalizeTodoNoFollowup(raw)
- : kind === "continuation"
- ? normalizeTodoCompletionContinuation(raw)
- : normalizeTodoCompletionRecovery(raw);
- return {
- schema_version: TODO_COMPLETION_STATE_RESULT_SCHEMA,
- kind,
- value: normalized,
- };
-}
-
-export function requireTodoCompletionMetadataValue(value: unknown): JsonObject {
- const request = requestObject(value, "require_metadata");
- const key = optionalString(request.key, "key");
- if (key === null) throw new EffectRuntimeRequestError("key must be a string");
- return {
- schema_version: TODO_COMPLETION_STATE_RESULT_SCHEMA,
- value: requireTodoCompletionMetadata(key, request.value),
- };
-}
-
export function selectTodoCompletionState(value: unknown): TodoCompletionStateResult {
const request = requestObject(value, "evaluate");
const todo = requiredObject(request.todo, "todo");
diff --git a/loopx/control_plane/todos/completion_value_codec.json b/loopx/control_plane/todos/completion_value_codec.json
new file mode 100644
index 0000000000..efdf9eb246
--- /dev/null
+++ b/loopx/control_plane/todos/completion_value_codec.json
@@ -0,0 +1,17 @@
+{
+ "true_tokens": [
+ "1",
+ "true",
+ "yes",
+ "y",
+ "no_followup",
+ "no-followup"
+ ],
+ "false_tokens": [
+ "0",
+ "false",
+ "no",
+ "n"
+ ],
+ "ecmascript_trim": "\t\n\u000b\f\r \u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff"
+}
diff --git a/loopx/control_plane/todos/context_projection.ts b/loopx/control_plane/todos/context_projection.ts
index fa9f97cdff..22beacb5e0 100644
--- a/loopx/control_plane/todos/context_projection.ts
+++ b/loopx/control_plane/todos/context_projection.ts
@@ -3,6 +3,7 @@ import type { JsonObject } from "../effect_program.ts";
import { EffectRuntimeRequestError } from "../effect_runtime_errors.ts";
import { requireBoolean, requireInteger, requireJsonObject } from "../runtime_decode.ts";
import { canonicalAuthorityJson } from "../coordination/authority_store_codec.ts";
+import {compactThinTodoRead, compactTodoReadSummary, compactThinTodoReadSummary} from "./read_presentation.ts";
// A read lens over canonical records, shared by global and Goal conversations.
// These are existing Todo facts, not dependency evaluation or execution grants.
@@ -15,6 +16,21 @@ const CONTEXT_FIELDS = [
/** Select before redaction; never convert nested scopes or booleans to prose. */
export function projectTodoContextPage(input: JsonObject): JsonObject {
+ if (input.thin_summary_payload !== undefined) {
+ const details = requireJsonObject(input.details, "thin summary details");
+ return compactThinTodoReadSummary(requireJsonObject(input.thin_summary_payload, "thin Todo summary"), {
+ role: details.role, items_matched: requireInteger(details.items_matched, "items matched"),
+ items_returned: requireInteger(details.items_returned, "items returned"),
+ item_limit_per_role: requireInteger(details.item_limit_per_role, "item limit per role")});
+ }
+ if (input.thin_payload !== undefined) {
+ if (Object.keys(input).some(key => key !== "thin_payload")) throw new EffectRuntimeRequestError("thin Todo presentation cannot be combined with other views");
+ return compactThinTodoRead(requireJsonObject(input.thin_payload, "thin Todo payload"));
+ }
+ if (input.summary_payload !== undefined) {
+ if (Object.keys(input).some(key => !["summary_payload", "role", "limit"].includes(key))) throw new EffectRuntimeRequestError("Todo summary presentation cannot be combined with other views");
+ return compactTodoReadSummary(requireJsonObject(input.summary_payload, "Todo summary payload"), String(input.role), input.limit == null ? null : Number(input.limit));
+ }
if (input.handoff_sources !== undefined) {
if (Object.keys(input).some(key => key !== "handoff_sources" && key !== "handoff_followups")) {
throw new EffectRuntimeRequestError("handoff context cannot be combined with list/detail/page inputs");
diff --git a/loopx/control_plane/todos/contract.py b/loopx/control_plane/todos/contract.py
index 24e9fd269a..f51e7c6dbc 100644
--- a/loopx/control_plane/todos/contract.py
+++ b/loopx/control_plane/todos/contract.py
@@ -1,6 +1,8 @@
from __future__ import annotations
import hashlib
+import json
+from pathlib import Path
import re
from dataclasses import dataclass
from enum import Enum
@@ -28,6 +30,8 @@
)
+_READ_LEGACY_CODEC = json.loads(Path(__file__).with_name("read_legacy_codec.json").read_text(encoding="utf-8"))
+
TODO_TASK_PATTERN = re.compile(r"^\s*[-*]\s+\[([ xX-])\]\s+(.+?)\s*$")
TODO_METADATA_PATTERN = re.compile(r"^\s*\s*$")
TODO_METADATA_TOKEN_PATTERN = re.compile(
@@ -138,32 +142,8 @@ def resolve_next_user_task_class(
TODO_TERMINAL_STATUS_VALUES = {TODO_STATUS_DONE, TODO_STATUS_DEFERRED}
TODO_LEGACY_TERMINAL_STATUS_VALUES = {"completed", "closed", "archived"}
-TODO_ACTION_KIND_ADVANCEMENT_VALUES = {
- "advance",
- "analyze",
- "benchmark_run",
- "codex_run",
- "compact_blocker_writeback",
- "compare",
- "execute",
- "fix",
- "implement",
- "rebuild",
- "rebuild_score",
- "repair",
- "run",
- "run_eval",
- "test",
- "validate",
- "writeback",
-}
-TODO_ACTION_KIND_MONITOR_VALUES = {
- "external_evidence",
- "monitor",
- "observe",
- "poll",
- "watch",
-}
+TODO_ACTION_KIND_ADVANCEMENT_VALUES = set(_READ_LEGACY_CODEC["advancement_actions"])
+TODO_ACTION_KIND_MONITOR_VALUES = set(_READ_LEGACY_CODEC["monitor_actions"])
class TodoContinuationPolicy(str, Enum):
@@ -180,44 +160,12 @@ class TodoContinuationPolicy(str, Enum):
}
-TODO_HARD_MONITOR_PATTERNS = (
- re.compile(r"(?i)\bdo not\b.*\b(?:launch|run|execute|start)\b.*\buntil\b"),
- re.compile(r"(?i)\b(?:only|just)\b.*\b(?:after|when|once)\b.*\b(?:owner|user|credential|approval|prerequisite|evidence)\b"),
- re.compile(r"(?i)\b(?:credential|gcp|gcs|gcp_project|gcp_sa_key|gs://)\b.*\b(?:missing|required|provide|proof|prerequisite|gate|gated)\b"),
- re.compile(r"(?i)\b(?:readiness|proof)\b.*\bbefore any formal\b.*\brun\b"),
- re.compile(r"(?i)\bremaining formal\b.*\bpath\b"),
- re.compile(r"(?i)\b(?:route|input)\b.*\babsent\b"),
- re.compile(r"(?i)\b0\b.*\b(?:candidate|candidates)\b"),
-)
+TODO_HARD_MONITOR_PATTERNS = tuple(re.compile(pattern) for pattern in _READ_LEGACY_CODEC["TODO_HARD_MONITOR_PATTERNS"])
-TODO_ADVANCEMENT_OVERRIDE_PATTERNS = (
- re.compile(
- r"(?i)(?:^|[::]\s*)(?:implement|add|make|fix|build|wire|define|compare|run|"
- r"execute|test|validate|rebuild|repair|archive|publish|merge|write|attribute|"
- r"collect|aggregate|generate|produce|materialize|rerun|repeat|eval|evaluate|score)\b"
- ),
- re.compile(
- r"(?i)\b(?:implementation slice|validation-backed patch|smoke fixture|"
- r"regression suite|readiness scan|source preflight|setup-readiness scan)\b"
- ),
-)
-TODO_BLOCKED_MONITOR_PATTERNS = (
- *TODO_HARD_MONITOR_PATTERNS,
- re.compile(r"(?i)\b(?:blocked|gated|waiting)\b.*\b(?:owner|user|credential|substrate|proof|prerequisite|evidence)\b"),
-)
-TODO_MONITOR_PATTERNS = (
- re.compile(r"(?i)\bdependency monitor\b"),
- re.compile(r"(?i)\bobservation lane\b"),
- re.compile(r"(?i)(?:^|[::]\s*)observe\b"),
- re.compile(r"(?i)(?:^|[::]\s*)poll\b"),
- re.compile(r"(?i)(?:^|[::]\s*)watch\b"),
- re.compile(r"(?i)\bmonitor-only\b"),
- *TODO_BLOCKED_MONITOR_PATTERNS,
-)
-TODO_ADVANCEMENT_PATTERNS = (
- *TODO_ADVANCEMENT_OVERRIDE_PATTERNS,
- re.compile(r"(?i)\b(?:task|validation hypothesis|validation step|bounded step|learning run)\b"),
-)
+TODO_ADVANCEMENT_OVERRIDE_PATTERNS = tuple(re.compile(pattern) for pattern in _READ_LEGACY_CODEC["TODO_ADVANCEMENT_OVERRIDE_PATTERNS"])
+TODO_BLOCKED_MONITOR_PATTERNS = tuple(re.compile(pattern) for pattern in _READ_LEGACY_CODEC["TODO_BLOCKED_MONITOR_PATTERNS"])
+TODO_MONITOR_PATTERNS = tuple(re.compile(pattern) for pattern in _READ_LEGACY_CODEC["TODO_MONITOR_PATTERNS"])
+TODO_ADVANCEMENT_PATTERNS = tuple(re.compile(pattern) for pattern in _READ_LEGACY_CODEC["TODO_ADVANCEMENT_PATTERNS"])
NEXT_ACTION_HARD_MONITOR_PATTERNS = (
re.compile(r"(?i)\bdo not\b.*\b(?:launch|run|execute|start)\b.*\buntil\b"),
diff --git a/loopx/control_plane/todos/frontier_revision.py b/loopx/control_plane/todos/frontier_revision.py
index ac3b8f061f..2b29d51485 100644
--- a/loopx/control_plane/todos/frontier_revision.py
+++ b/loopx/control_plane/todos/frontier_revision.py
@@ -5,6 +5,7 @@
import base64
import json
import zlib
+from pathlib import Path
from typing import Any
# Refs #4447: the todo contract owns this vocabulary; import it instead of
@@ -20,39 +21,7 @@
TODO_FRONTIER_REVISION_SCHEMA_VERSION = "todo_frontier_revision_v0"
TODO_FRONTIER_REVISION_INDEX_SCHEMA_VERSION = "todo_frontier_revision_index_v0"
-FRONTIER_REVISION_FIELDS = (
- "todo_id",
- "status",
- "done",
- "title",
- "text",
- "task_class",
- "claimed_by",
- "bound_agent",
- "blocks_agent",
- "excluded_agents",
- "priority",
- "action_kind",
- "task_domain",
- "task_repository",
- "capability_binding_ref",
- "required_capabilities",
- "target_capabilities",
- "target_key",
- "continuation_policy",
- "removed_continuation_policy",
- "decision_scope",
- "required_decision_scopes",
- "decision_outcome",
- "replan_obligation_id",
- "unblocks_todo_id",
- "depends_on_todo_id",
- "depends_on_todo_ids",
- "resume_when",
- "no_followup",
- "successor_todo_ids",
- "completion_continuation",
-)
+FRONTIER_REVISION_FIELDS = tuple(json.loads(Path(__file__).with_name("read_legacy_codec.json").read_text(encoding="utf-8"))["frontier_fields"])
def frontier_source_facts(
diff --git a/loopx/control_plane/todos/goal_todo_projection.py b/loopx/control_plane/todos/goal_todo_projection.py
index 5e4fa34f8d..a2369ce037 100644
--- a/loopx/control_plane/todos/goal_todo_projection.py
+++ b/loopx/control_plane/todos/goal_todo_projection.py
@@ -14,15 +14,12 @@
from ..goals.legacy_event_source import require_no_legacy_todo_events
from .active_state_editing import TODO_SECTION_HEADINGS
-from .active_state_todo_parser import parse_active_state_todos, parse_todo_source
-from .list_projection import compact_explicit_limit_todo_summary
-from .succession_warning import public_todo_summary
from .contract import (
normalize_todo_claimed_by,
normalize_todo_id,
normalize_todo_status,
)
-from .todo_summary import compact_evaluated_todo_group, compact_todo_group
+from .todo_summary import compact_evaluated_todo_group
def empty_todo_summary(*, role: str) -> dict[str, Any]:
@@ -108,37 +105,20 @@ def goal_todo_summaries(
limit: int | None,
include_retained: bool = False,
) -> GoalTodoSummaries:
- """Project todo summaries from active-state text plus its event projection.
-
- The text is a parameter rather than a file read so a writer that still
- holds the state-file lock can project the exact bytes it is about to
- commit; ``list_goal_todos`` passes the on-disk text.
- """
+ from .read_projection import prepare_todo_source_text, project_todo_read
+ from ..runtime.time import now_utc
require_no_legacy_todo_events(goal or {}, state_path=state_path)
- if include_retained:
- active, archived, _sections = parse_todo_source(state_text, goal=goal, state_path=state_path)
- markdown_fields = retained_todo_summary_fields(
- [*active["user"], *active["agent"], *archived], rollout_events=rollout_events,
- )
- else:
- markdown_fields = parse_active_state_todos(
- state_text,
- goal=goal,
- state_path=state_path,
- item_limit=None,
- rollout_events=rollout_events,
- )
- return todo_summaries_from_fields(
- fields=markdown_fields,
- source="markdown_active_state",
- rollout_events=rollout_events,
- roles=roles,
- status=status,
- todo_id=todo_id,
- agent_id=agent_id,
- limit=limit,
- )
+ facts = prepare_todo_source_text(goal, state_text, state_path, todo_id=todo_id, retained=include_retained)
+ now = now_utc()
+ projected = project_todo_read({"kind": "source_fields", **facts,
+ "rollout_events": list(rollout_events), "rollout_event_source": getattr(rollout_events, "source", None),
+ "observed_at": now.timestamp(), "evaluated_at": now.isoformat(),
+ "projection_query": {"roles": roles, "status": normalize_todo_status(status),
+ "todo_id": normalize_todo_id(todo_id) if todo_id else None,
+ "agent_id": normalize_todo_claimed_by(agent_id) if agent_id else None,
+ "limit": limit, "source_name": "markdown_active_state"}})["projection"]
+ return GoalTodoSummaries(**projected)
def todo_summaries_from_fields(
@@ -152,41 +132,19 @@ def todo_summaries_from_fields(
agent_id: str | None,
limit: int | None,
) -> GoalTodoSummaries:
- """Apply the shared Todo consumer semantics to an authority read model."""
-
- summaries: dict[str, dict[str, Any]] = {}
- todos: list[dict[str, Any]] = []
- unfiltered_count = 0
- uncapped_todo_count = 0
- for item_role in roles:
- key = f"{item_role}_todos"
- raw_summary = fields.get(key) if isinstance(fields, dict) else None
- unfiltered_count += len((raw_summary or {}).get("items") or [])
- summary = filtered_todo_summary(
- raw_summary,
- role=item_role,
- status=status,
- todo_id=todo_id,
- agent_id=agent_id,
- item_limit=limit,
- )
- if limit is not None:
- summary = compact_explicit_limit_todo_summary(
- summary,
- role=item_role,
- item_limit=limit,
- )
- summary = public_todo_summary(summary)
- summaries[key] = summary
- todos.extend(summary.get("items") or [])
- uncapped_todo_count += int(summary.get("total_count") or 0)
- return GoalTodoSummaries(
- source=source,
- summaries=summaries,
- todos=todos,
- unfiltered_count=unfiltered_count,
- uncapped_todo_count=uncapped_todo_count,
- )
+ from .read_projection import project_todo_read
+ from ..runtime.time import now_utc
+
+ now = now_utc()
+ result = project_todo_read({"kind": "select", "fields": fields,
+ "field_evaluations": {f"{item_role}_todos": [getattr(item, "succession_evaluation", None)
+ for item in (fields.get(f"{item_role}_todos") or {}).get("items") or []] for item_role in roles},
+ "roles": roles, "status": normalize_todo_status(status),
+ "todo_id": normalize_todo_id(todo_id) if todo_id else None,
+ "agent_id": normalize_todo_claimed_by(agent_id) if agent_id else None,
+ "limit": limit, "source_name": source,
+ "observed_at": now.timestamp(), "evaluated_at": now.isoformat()})["projection"]
+ return GoalTodoSummaries(**result)
def retained_todo_summary_fields(
@@ -194,25 +152,13 @@ def retained_todo_summary_fields(
*,
rollout_events: list[dict[str, Any]],
) -> dict[str, Any]:
- """Feed retained source rows through the existing typed summary owner.
+ from .read_projection import source_todo_fields
- History is an explicit cold read; active scheduler/attention summaries
- continue to exclude archives. Preserve full text, evidence and source ids.
- """
- return {
- f"{role}_todos": compact_todo_group(
- [item for item in items if item.get("role") == role],
- source_section=TODO_SECTION_HEADINGS[role],
- role=role,
- include_empty_source=True,
- resume_source_items=items,
- rollout_events=rollout_events,
- item_limit=None,
- text_limit=None,
- include_content_revision=False,
- )
- for role in ("user", "agent")
- }
+ return source_todo_fields([
+ {"role": role, "source_section": TODO_SECTION_HEADINGS[role],
+ "items": [item for item in items if item.get("role") == role],
+ "include_empty_source": True, "text_limit": None, "include_content_revision": False}
+ for role in ("user", "agent")], lineage=items, rollout_events=rollout_events)
def exact_archived_todo_summaries(
@@ -226,43 +172,21 @@ def exact_archived_todo_summaries(
agent_id: str | None,
limit: int | None,
) -> GoalTodoSummaries | None:
- """Project one exact retained Todo without widening normal active lists."""
-
- item = next(
- (
- dict(candidate)
- for candidate in archived_items
- if normalize_todo_id(candidate.get("todo_id")) == todo_id
- and candidate.get("archive_state") == "archive"
- ),
- None,
- )
- if item is None:
- return None
- item_role = item.get("role")
- if item_role not in {"user", "agent"} or item_role not in roles:
- return None
- summary = compact_todo_group(
- [item],
- source_section=str(item.get("source_section") or "Completed Work Archive"),
- role=item_role,
- include_empty_source=True,
- resume_source_items=archived_items,
- rollout_events=rollout_events,
- item_limit=None,
- )
- if summary is None:
- return None
- return todo_summaries_from_fields(
- fields={f"{item_role}_todos": summary},
- source=source,
- rollout_events=rollout_events,
- roles=roles,
- status=status,
- todo_id=todo_id,
- agent_id=agent_id,
- limit=limit,
- )
+ from .read_projection import project_todo_read, prepare_todo_source_groups, legacy_lineage_facts
+ from ..runtime.time import now_utc
+
+ lineage, facts = legacy_lineage_facts(archived_items)
+ groups = [{"role": role, "source_section": "Completed Work Archive", "include_empty_source": True,
+ "items": [row for row in archived_items if row.get("role") == role]} for role in ("user", "agent")]
+ now = now_utc()
+ projected = project_todo_read({"kind": "source_fields", "groups": prepare_todo_source_groups(groups, lineage=archived_items),
+ "lineage": lineage, "lineage_facts": facts, "rollout_events": list(rollout_events),
+ "rollout_event_source": getattr(rollout_events, "source", None),
+ "observed_at": now.timestamp(), "evaluated_at": now.isoformat(),
+ "projection_query": {"exact_archive": True, "roles": roles, "status": normalize_todo_status(status),
+ "todo_id": normalize_todo_id(todo_id), "agent_id": normalize_todo_claimed_by(agent_id) if agent_id else None,
+ "limit": limit, "source_name": source}})["projection"]
+ return GoalTodoSummaries(**projected) if projected is not None else None
def project_goal_todo_items(
goal: dict[str, Any] | None,
diff --git a/loopx/control_plane/todos/list_projection.py b/loopx/control_plane/todos/list_projection.py
index 57df51e17e..fa6c9fe40b 100644
--- a/loopx/control_plane/todos/list_projection.py
+++ b/loopx/control_plane/todos/list_projection.py
@@ -1,493 +1,59 @@
+"""Compatibility exports for complete native Todo presentation lenses."""
from __future__ import annotations
+import json
+from pathlib import Path
from typing import Any, Literal
+from ..effect_runtime import effect_runtime_result
-from .todo_semantics import todo_blocker_reason
+_READ_CODEC = json.loads(Path(__file__).with_name("read_legacy_codec.json").read_text(encoding="utf-8"))
+_READ_LIST_LAYOUT = _READ_CODEC["list_constants"]
+AGENT_LANE_TODO_LIST_PROJECTION_SCHEMA_VERSION = _READ_LIST_LAYOUT["AGENT_LANE_TODO_LIST_PROJECTION_SCHEMA_VERSION"]
+AGENT_LANE_TODO_LIST_SUMMARY_SCHEMA_VERSION = _READ_LIST_LAYOUT["AGENT_LANE_TODO_LIST_SUMMARY_SCHEMA_VERSION"]
+EXPLICIT_LIMIT_TODO_LIST_SUMMARY_SCHEMA_VERSION = _READ_LIST_LAYOUT["EXPLICIT_LIMIT_TODO_LIST_SUMMARY_SCHEMA_VERSION"]
+THIN_TODO_LIST_PROJECTION_SCHEMA_VERSION = _READ_LIST_LAYOUT["THIN_TODO_LIST_PROJECTION_SCHEMA_VERSION"]
+THIN_TODO_LIST_SUMMARY_SCHEMA_VERSION = _READ_LIST_LAYOUT["THIN_TODO_LIST_SUMMARY_SCHEMA_VERSION"]
+AGENT_LANE_TODO_LIST_ITEM_LIMIT = _READ_LIST_LAYOUT["AGENT_LANE_TODO_LIST_ITEM_LIMIT"]
+AGENT_LANE_TODO_LIST_TEXT_LIMIT = _READ_LIST_LAYOUT["AGENT_LANE_TODO_LIST_TEXT_LIMIT"]
+THIN_TODO_LIST_ITEM_LIMIT_PER_ROLE = _READ_LIST_LAYOUT["THIN_TODO_LIST_ITEM_LIMIT_PER_ROLE"]
+THIN_TODO_LIST_NESTED_ITEM_LIMIT = _READ_LIST_LAYOUT["THIN_TODO_LIST_NESTED_ITEM_LIMIT"]
+THIN_TODO_LIST_NESTED_DICT_FIELD_LIMIT = _READ_LIST_LAYOUT["THIN_TODO_LIST_NESTED_DICT_FIELD_LIMIT"]
+THIN_TODO_LIST_ITEM_FIELDS = tuple(_READ_LIST_LAYOUT["THIN_TODO_LIST_ITEM_FIELDS"])
+AGENT_LANE_HOT_PATH_VIEW = "agent_lane_hot_path"
+EXPLICIT_LIMIT_COLD_PATH_VIEW = "explicit_limit_cold_path"
+THIN_EXPLICIT_VIEW = "thin_explicit_view"
+TodoListProjectionView = Literal["agent_lane_hot_path", "explicit_limit_cold_path"]
-AGENT_LANE_TODO_LIST_PROJECTION_SCHEMA_VERSION = "agent_lane_todo_list_projection_v0"
-AGENT_LANE_TODO_LIST_SUMMARY_SCHEMA_VERSION = (
- "agent_lane_todo_list_summary_compaction_v0"
-)
-EXPLICIT_LIMIT_TODO_LIST_SUMMARY_SCHEMA_VERSION = (
- "explicit_limit_todo_list_summary_compaction_v0"
-)
-THIN_TODO_LIST_PROJECTION_SCHEMA_VERSION = "todo_list_thin_projection_v0"
-THIN_TODO_LIST_SUMMARY_SCHEMA_VERSION = "todo_list_thin_summary_compaction_v0"
-AGENT_LANE_TODO_LIST_ITEM_LIMIT = 12
-AGENT_LANE_TODO_LIST_TEXT_LIMIT = 180
-# Two items per role keeps the maximally populated retained field shape inside
-# the registered 20k/600-line JSON budget; the regression owns that invariant.
-THIN_TODO_LIST_ITEM_LIMIT_PER_ROLE = 2
-THIN_TODO_LIST_NESTED_ITEM_LIMIT = 3
-THIN_TODO_LIST_NESTED_DICT_FIELD_LIMIT = 8
-AGENT_LANE_HOT_PATH_VIEW: Literal["agent_lane_hot_path"] = "agent_lane_hot_path"
-EXPLICIT_LIMIT_COLD_PATH_VIEW: Literal["explicit_limit_cold_path"] = (
- "explicit_limit_cold_path"
-)
-THIN_EXPLICIT_VIEW: Literal["thin_explicit_view"] = "thin_explicit_view"
-TodoListProjectionView = Literal[
- "agent_lane_hot_path",
- "explicit_limit_cold_path",
-]
-_RETAINED_LANE_LIMITS = {
- "items": AGENT_LANE_TODO_LIST_ITEM_LIMIT,
- "first_open_items": 3,
- "first_executable_items": 3,
- "deferred_items": 3,
- "monitor_due_items": 2,
- "monitor_schedule_gap_items": 2,
- "blocker_items": 2,
- "resume_blocked_items": 2,
- "active_next_action_items": 3,
- "active_next_action_executable_items": 3,
-}
-_RETAINED_DICTS = {
- "work_counts",
- "deferred_work_counts",
- "monitor_writeback",
- "source_proof",
- "terminal_closure_proof",
-}
-_ITEM_FIELDS = (
- "schema_version",
- "gate_state",
- "successor_count",
- "excluded_agents",
- "index",
- "todo_id",
- "role",
- "status",
- "priority",
- "text",
- "title",
- "note",
- "task_class",
- "action_kind",
- "task_domain",
- "capability_binding_ref",
- "task_repository",
- "continuation_policy",
- "required_capabilities",
- "target_capabilities",
- "claimed_by",
- "bound_agent",
- "goal_bound",
- "blocks_agent",
- "global_gate",
- "unblocks_todo_id",
- "successor_todo_ids",
- "completion_continuation",
- "completion_recovery",
- "resume_when",
- "resume_monitor_generation",
- "resume_ready",
- "target_key",
- "cadence",
- "next_due_at",
- "expires_at",
- "watch_only",
-)
+def compact_thin_todo_list_payload(payload):
+ return effect_runtime_result("todo.context.page", {"thin_payload": payload}, large_local_snapshot=True)
-THIN_TODO_LIST_ITEM_FIELDS = (
- "todo_id",
- "role",
- "status",
- "priority",
- "text",
- "task_class",
- "action_kind",
- "claimed_by",
- "bound_agent",
- "goal_bound",
- "blocks_agent",
- "global_gate",
- "unblocks_todo_id",
- "decision_scope",
- "required_decision_scopes",
- "resume_when",
- "resume_ready",
- "target_key",
- "cadence",
- "next_due_at",
- "expires_at",
- "watch_only",
-)
-_COMPACT_ITEM_TEXT_FIELDS = frozenset({"text", "title", "note"})
-_THIN_ITEM_TEXT_FIELDS = frozenset({"text"})
+def compact_agent_lane_todo_summary(summary, *, role):
+ return effect_runtime_result("todo.context.page", {"summary_payload": summary, "role": role, "limit": None}, large_local_snapshot=True)
-def _compact_text(value: Any) -> Any:
- if not isinstance(value, str):
- return value
- text = value.strip()
- if len(text) <= AGENT_LANE_TODO_LIST_TEXT_LIMIT:
- return text
- return text[: AGENT_LANE_TODO_LIST_TEXT_LIMIT - 3].rstrip() + "..."
+def compact_explicit_limit_todo_summary(summary, *, role, item_limit):
+ return effect_runtime_result("todo.context.page", {"summary_payload": summary, "role": role, "limit": item_limit}, large_local_snapshot=True)
-def _project_item_fields(
- value: Any,
- *,
- fields: tuple[str, ...],
- text_fields: frozenset[str],
-) -> Any:
- if not isinstance(value, dict):
- return value
- compact: dict[str, Any] = {}
- for key in fields:
- child = value.get(key)
- if child is None:
- continue
- compact[key] = (
- _compact_text(child) if key in text_fields else child
- )
- return compact
-
-
-def _compact_item(value: Any) -> Any:
- compact = _project_item_fields(
- value,
- fields=_ITEM_FIELDS,
- text_fields=_COMPACT_ITEM_TEXT_FIELDS,
- )
- if isinstance(value, dict) and isinstance(compact, dict):
- reason = todo_blocker_reason(value)
- if reason:
- compact["reason"] = reason
- return compact
-
-
-def _compact_thin_item(value: Any) -> Any:
- projected = _project_item_fields(
- value,
- fields=THIN_TODO_LIST_ITEM_FIELDS,
- text_fields=_THIN_ITEM_TEXT_FIELDS,
- )
- if not isinstance(projected, dict):
- return projected
- return {key: _compact_thin_value(child) for key, child in projected.items()}
-
-
-def _compact_thin_value(value: Any) -> Any:
- """Bound every retained thin field, including nested scope metadata."""
-
- if isinstance(value, str):
- return _compact_text(value)
- if isinstance(value, list):
- return [
- _compact_thin_value(child)
- for child in value[:THIN_TODO_LIST_NESTED_ITEM_LIMIT]
- ]
- if isinstance(value, dict):
- return {
- key: _compact_thin_value(child)
- for key, child in list(value.items())[
- :THIN_TODO_LIST_NESTED_DICT_FIELD_LIMIT
- ]
- }
- return value
-
-
-def _summary_source_view(value: Any) -> str:
- if not isinstance(value, dict):
- return "full_detail"
- view = value.get("view")
- if isinstance(view, str) and view:
- return view
- if value.get("schema_version") == AGENT_LANE_TODO_LIST_SUMMARY_SCHEMA_VERSION:
- return AGENT_LANE_HOT_PATH_VIEW
- return "full_detail"
-
-
-def compact_thin_todo_summary(
- summary: dict[str, Any],
- *,
- role: str,
- items_matched: int,
- items_returned: int,
- item_limit_per_role: int,
-) -> dict[str, Any]:
- """Project one role summary to a bounded field-only Todo list view."""
-
- compact: dict[str, Any] = {}
- omitted_nonempty_lane_count = 0
- omitted_nonempty_dict_count = 0
- source_view = "full_detail"
- for key, value in summary.items():
- if isinstance(value, list):
- if key != "items":
- omitted_nonempty_lane_count += bool(value)
- continue
- if isinstance(value, dict):
- if key in {"work_counts", "deferred_work_counts"}:
- compact[key] = value
- elif key == "payload_compaction":
- source_view = _summary_source_view(value)
- else:
- omitted_nonempty_dict_count += bool(value)
- continue
- compact[key] = value
- items_omitted = max(0, items_matched - items_returned)
- compacted_lanes = {}
- if items_omitted:
- compacted_lanes["items"] = {
- "shown": items_returned,
- "total": items_matched,
- }
- compact["payload_compaction"] = {
- "schema_version": THIN_TODO_LIST_SUMMARY_SCHEMA_VERSION,
- "view": THIN_EXPLICIT_VIEW,
- "source_view": source_view,
- "role": role,
- "items_projected_to": "todos",
- "item_limit_per_role": item_limit_per_role,
- "item_fields": list(THIN_TODO_LIST_ITEM_FIELDS),
- "item_text_limit": AGENT_LANE_TODO_LIST_TEXT_LIMIT,
- "nested_item_limit": THIN_TODO_LIST_NESTED_ITEM_LIMIT,
- "items_matched": items_matched,
- "items_returned": items_returned,
- "items_omitted": items_omitted,
- "compacted_lanes": compacted_lanes,
- "omitted_nonempty_lane_count": omitted_nonempty_lane_count,
- "omitted_nonempty_dict_count": omitted_nonempty_dict_count,
- "full_detail_cold_path": "todo list without --thin or active state",
- }
- return compact
-
-
-def thin_todo_list_field_projection_contract(
- *,
- matched_todo_count: int,
- returned_todo_count: int,
- item_limit_per_role: int,
- source_view: str,
-) -> dict[str, Any]:
- return {
- "schema_version": THIN_TODO_LIST_PROJECTION_SCHEMA_VERSION,
- "view": THIN_EXPLICIT_VIEW,
- "source_view": source_view,
- "item_container": "todos",
- "matched_todo_count": matched_todo_count,
- "returned_todo_count": returned_todo_count,
- "omitted_todo_count": max(
- 0,
- matched_todo_count - returned_todo_count,
- ),
- "item_limit_per_role": item_limit_per_role,
- "counts_cover_full_match": True,
- "item_fields": list(THIN_TODO_LIST_ITEM_FIELDS),
- "item_text_limit": AGENT_LANE_TODO_LIST_TEXT_LIMIT,
- "nested_item_limit": THIN_TODO_LIST_NESTED_ITEM_LIMIT,
- "nested_dict_field_limit": THIN_TODO_LIST_NESTED_DICT_FIELD_LIMIT,
- "full_detail_cold_paths": [
- "todo list without --thin",
- "todo list --todo-id without --thin",
- "active state",
- ],
- }
-
-
-def compact_thin_todo_list_payload(payload: dict[str, Any]) -> dict[str, Any]:
- """Return the opt-in Todo list field and cardinality projection."""
-
- compact = dict(payload)
- compact.pop("state_file", None)
- compact.pop("project", None)
- compact.pop("filter_semantics", None)
-
- explicit_limit = compact.get("explicit_limit")
- item_limit_per_role = THIN_TODO_LIST_ITEM_LIMIT_PER_ROLE
- if isinstance(explicit_limit, int):
- item_limit_per_role = min(item_limit_per_role, explicit_limit)
-
- source_projection = compact.get("todo_list_projection")
- source_projection = source_projection if isinstance(source_projection, dict) else {}
- source_view = str(source_projection.get("view") or "full_detail")
- matched_todo_count = int(
- source_projection.get("matched_todo_count") or compact.get("todo_count") or 0
- )
-
- projected_items: list[dict[str, Any]] = []
- for key, role in (("user_todos", "user"), ("agent_todos", "agent")):
- summary = compact.get(key)
- if not isinstance(summary, dict):
- continue
- source_items = [
- item for item in summary.get("items") or [] if isinstance(item, dict)
- ]
- retained_items = source_items[:item_limit_per_role]
- projected_role_items = [_compact_thin_item(item) for item in retained_items]
- projected_items.extend(projected_role_items)
- items_matched = int(summary.get("total_count") or len(source_items))
- projected = compact_thin_todo_summary(
- summary,
- role=role,
- items_matched=items_matched,
- items_returned=len(projected_role_items),
- item_limit_per_role=item_limit_per_role,
- )
- compact[key] = projected
- compact["todos"] = projected_items
- returned_todo_count = len(projected_items)
- omitted_todo_count = max(0, matched_todo_count - returned_todo_count)
- compact["matched_todo_count"] = matched_todo_count
- compact["returned_todo_count"] = returned_todo_count
- compact["omitted_todo_count"] = omitted_todo_count
- todo_id_filter = compact.get("todo_id_filter")
- if isinstance(todo_id_filter, str):
- matched = next(
- (
- item
- for item in projected_items
- if item.get("todo_id") == todo_id_filter
- ),
- None,
- )
- compact["todo"] = matched
- compact["relations"] = todo_item_relations(matched) if matched else {}
-
- compact["thin"] = True
- compact["todo_list_field_projection"] = thin_todo_list_field_projection_contract(
- matched_todo_count=matched_todo_count,
- returned_todo_count=returned_todo_count,
- item_limit_per_role=item_limit_per_role,
- source_view=source_view,
- )
- return compact
+def compact_thin_todo_summary(summary, *, role, items_matched, items_returned, item_limit_per_role):
+ return effect_runtime_result("todo.context.page", {"thin_summary_payload": summary, "details": {
+ "role": role, "items_matched": items_matched, "items_returned": items_returned,
+ "item_limit_per_role": item_limit_per_role}}, large_local_snapshot=True)
def todo_item_relations(item: dict[str, Any]) -> dict[str, Any]:
"""Project the stable relationship fields for one todo list item."""
relations: dict[str, Any] = {}
- for key in (
- "claimed_by",
- "bound_agent",
- "goal_bound",
- "blocks_agent",
- "excluded_agents",
- "global_gate",
- "unblocks_todo_id",
- "successor_todo_ids",
- "completion_continuation",
- "completion_recovery",
- "superseded_by",
- "resume_when",
- "resume_monitor_generation",
- "resume_condition",
- "resume_ready",
- "decision_scope",
- "required_decision_scopes",
- "required_write_scopes",
- "required_capabilities",
- "target_capabilities",
- "task_class",
- "action_kind",
- "continuation_policy",
- "target_key",
- "cadence",
- "next_due_at",
- "expires_at",
- ):
+ for key in _READ_CODEC["relation_fields"]:
value = item.get(key)
if value is not None and value != []:
relations[key] = value
return relations
-def compact_agent_lane_todo_summary(
- summary: dict[str, Any],
- *,
- role: str,
-) -> dict[str, Any]:
- """Bound one agent-lane list summary while retaining actionable identities."""
- compact: dict[str, Any] = {}
- compacted_lanes: dict[str, dict[str, int]] = {}
- omitted_nonempty_lane_count = 0
- omitted_nonempty_dict_count = 0
- for key, value in summary.items():
- if isinstance(value, list):
- limit = _RETAINED_LANE_LIMITS.get(key)
- if limit is None:
- omitted_nonempty_lane_count += bool(value)
- continue
- if key == "items":
- retained = [
- item
- for item in value
- if not isinstance(item, dict) or item.get("status") != "done"
- ]
- else:
- retained = value
- compact[key] = [_compact_item(item) for item in retained[:limit]]
- if len(retained) > limit:
- compacted_lanes[key] = {"shown": limit, "total": len(retained)}
- continue
- if isinstance(value, dict):
- if key in _RETAINED_DICTS:
- compact[key] = value
- else:
- omitted_nonempty_dict_count += bool(value)
- continue
- compact[key] = value
- compact["payload_compaction"] = {
- "schema_version": AGENT_LANE_TODO_LIST_SUMMARY_SCHEMA_VERSION,
- "role": role,
- "item_limit": AGENT_LANE_TODO_LIST_ITEM_LIMIT,
- "item_text_limit": AGENT_LANE_TODO_LIST_TEXT_LIMIT,
- "terminal_items_in_hot_path": False,
- "compacted_lanes": compacted_lanes,
- "omitted_nonempty_lane_count": omitted_nonempty_lane_count,
- "omitted_nonempty_dict_count": omitted_nonempty_dict_count,
- "full_detail_cold_path": (
- "todo list with --role, --status, or --todo-id; "
- "todo list without --agent-id; or active state"
- ),
- }
- return compact
-
-
-def compact_explicit_limit_todo_summary(
- summary: dict[str, Any],
- *,
- role: str,
- item_limit: int,
-) -> dict[str, Any]:
- """Bound every item-bearing lane for the explicit-limit cold path."""
- compact: dict[str, Any] = {}
- compacted_lanes: dict[str, dict[str, int]] = {}
- omitted_nonempty_dict_count = 0
- for key, value in summary.items():
- if isinstance(value, list):
- compact[key] = [_compact_item(item) for item in value[:item_limit]]
- if len(value) > item_limit:
- compacted_lanes[key] = {
- "shown": item_limit,
- "total": len(value),
- }
- continue
- if isinstance(value, dict):
- if key in _RETAINED_DICTS:
- compact[key] = value
- else:
- omitted_nonempty_dict_count += bool(value)
- continue
- compact[key] = value
- compact["payload_compaction"] = {
- "schema_version": EXPLICIT_LIMIT_TODO_LIST_SUMMARY_SCHEMA_VERSION,
- "view": EXPLICIT_LIMIT_COLD_PATH_VIEW,
- "role": role,
- "item_limit": item_limit,
- "item_text_limit": AGENT_LANE_TODO_LIST_TEXT_LIMIT,
- "compacted_lanes": compacted_lanes,
- "omitted_nonempty_dict_count": omitted_nonempty_dict_count,
- "full_detail_cold_path": "todo list without --limit or active state",
- }
- return compact
-
-
def todo_list_projection_contract(
*,
matched_todo_count: int,
diff --git a/loopx/control_plane/todos/list_readback.py b/loopx/control_plane/todos/list_readback.py
index 332588ea6d..36468af475 100644
--- a/loopx/control_plane/todos/list_readback.py
+++ b/loopx/control_plane/todos/list_readback.py
@@ -11,43 +11,12 @@
from ...history import load_registry
from ...paths import resolve_runtime_root
from ...rollout_event_log import load_rollout_events, rollout_event_log_path
-from ..effect_runtime import effect_runtime_result
from ..goals.state_resolution import resolve_goal_state
-from ..coordination.local_authority import (
- canonical_todo_items,
- canonical_todo_summary_fields,
- read_canonical_todos_if_promoted,
-)
-from .active_state_todo_parser import parse_todo_source
from .contract import normalize_todo_claimed_by, normalize_todo_id, normalize_todo_status
-from .goal_todo_projection import (
- exact_archived_todo_summaries,
- retained_todo_summary_fields,
- goal_todo_summaries,
- todo_summaries_from_fields,
-)
-from .list_projection import (
- compact_agent_lane_todo_summary,
- compact_thin_todo_list_payload,
- todo_item_relations,
- todo_list_projection_contract,
-)
from .todo_index import MAX_TODO_INDEX_ROLLOUT_EVENTS_PER_GOAL
from .resume_condition import TodoResumeRolloutEvents
-def _restore_full_source_body(
- detail: dict[str, Any], todo_id: str, source_items: list[dict[str, Any]],
-) -> None:
- """Restore source text only after the shared reader has scoped the record."""
- matches = [item for item in source_items
- if item.get("todo_id") == todo_id
- and item.get("role") == detail.get("role")
- and item.get("archive_state", "active") == detail.get("archive_state", "active")]
- if len(matches) == 1:
- detail["text"] = str(matches[0].get("text") or "")
- else:
- raise ValueError("Todo detail cannot recover an unambiguous full source body")
def list_goal_todos(
@@ -64,6 +33,7 @@ def list_goal_todos(
limit: int | None = None,
thin: bool = False,
read_scope: Literal["active", "completed_history"] = "active",
+ json_references: bool = False,
) -> dict[str, Any]:
if read_scope not in {"active", "completed_history"}:
raise ValueError("Todo read_scope must be active or completed_history")
@@ -101,173 +71,42 @@ def list_goal_todos(
rollout_events = TodoResumeRolloutEvents(rollout_events, runtime_root=runtime_root, goal_id=goal_id)
- roles = [role] if role else ["user", "agent"]
- canonical_read = read_canonical_todos_if_promoted(
- runtime_root=runtime_root,
- goal_id=goal_id,
- )
- if canonical_read is not None:
- projected = todo_summaries_from_fields(
- fields=(retained_todo_summary_fields(
- canonical_todo_items(canonical_read["todos"]), rollout_events=rollout_events,
- ) if read_scope == "completed_history" else canonical_todo_summary_fields(
- canonical_read["todos"],
- rollout_events=rollout_events,
- goal_acceptance_contract=canonical_read.get("goal_acceptance_contract"),
- goal_acceptance_work_guards=canonical_read.get("goal_acceptance_work_guards"),
- )),
- source="file_authority",
- rollout_events=rollout_events,
- roles=roles,
- status=status,
- todo_id=normalized_todo_id,
- agent_id=normalized_agent_id,
- limit=limit,
- )
- else:
- if not resolved_state_file.exists():
- raise ValueError(f"active state file does not exist: {resolved_state_file}")
- state_text = resolved_state_file.read_text(encoding="utf-8")
- projected = goal_todo_summaries(
- goal,
- state_text=state_text,
- state_path=resolved_state_file,
- rollout_events=rollout_events,
- roles=roles,
- status=status,
- todo_id=normalized_todo_id,
- agent_id=normalized_agent_id,
- limit=limit,
- include_retained=read_scope == "completed_history",
- )
- if read_scope == "active" and normalized_todo_id and not projected.todos:
- if canonical_read is not None:
- archived_items = [
- item
- for item in canonical_todo_items(canonical_read["todos"])
- if item.get("archive_state") == "archive"
- ]
- else:
- _active_items, archived_items, _source_sections = parse_todo_source(
- state_text,
- goal=goal,
- state_path=resolved_state_file,
- )
- archived_projection = exact_archived_todo_summaries(
- archived_items=archived_items,
- source=projected.source,
- rollout_events=rollout_events,
- roles=roles,
- status=status,
- todo_id=normalized_todo_id,
- agent_id=normalized_agent_id,
- limit=limit,
- )
- if archived_projection is not None:
- projected = archived_projection
- source = projected.source
- summaries = projected.summaries
- todos = projected.todos
- # Exact cold reads restore source bytes after the shared summary owner has
- # evaluated identity/status/guards. List and thin projections stay bounded;
- # a hot summary is never treated as the original request.
- if normalized_todo_id and not thin and len(todos) == 1:
- if canonical_read is not None:
- source_items = canonical_read["todos"]
- else:
- active, archived, _sections = parse_todo_source(
- state_text, goal=goal, state_path=resolved_state_file,
- )
- source_items = [*active["user"], *active["agent"], *archived]
- _restore_full_source_body(todos[0], normalized_todo_id, source_items)
- unfiltered_count = projected.unfiltered_count
- uncapped_todo_count = projected.uncapped_todo_count
+ from ..coordination.local_authority import local_authority_is_promoted, LocalCoordinationAuthorityUnavailable
+ from .read_projection import project_todo_read
+ from ..runtime.time import now_utc
- matched_todo_count = len(todos)
- agent_lane_hot_path = bool(
- read_scope == "active" and normalized_agent_id and limit is None
- and role is None
- and status is None
- and normalized_todo_id is None
- )
- if agent_lane_hot_path:
- summaries = {
- key: compact_agent_lane_todo_summary(
- summary,
- role=key.removesuffix("_todos"),
- )
- for key, summary in summaries.items()
- }
- todos = [
- item
- for key in ("user_todos", "agent_todos")
- for item in summaries.get(key, {}).get("items") or []
- if isinstance(item, dict)
- ]
+ now = now_utc()
+ params = {"kind": "query",
+ "source": {"runtime_root": str(runtime_root.expanduser().resolve(strict=False)), "goal_id": goal_id},
+ "rollout_events": list(rollout_events), "rollout_event_source": rollout_events.source,
+ "observed_at": now.timestamp(), "evaluated_at": now.isoformat(),
+ "query": {"goal_id": goal_id, "role": role, "status": normalize_todo_status(status),
+ "todo_id": normalized_todo_id, "agent_id": normalized_agent_id, "limit": limit,
+ "read_scope": read_scope, "json_references": json_references, "thin": thin,
+ "state_file": str(resolved_state_file), "project": str(resolved_project) if resolved_project else None}}
+ if not local_authority_is_promoted(runtime_root=runtime_root, goal_id=goal_id):
+ params["legacy_source"] = _legacy_todo_read_source(goal, resolved_state_file,
+ todo_id=normalized_todo_id, retained=read_scope == "completed_history")
+ result = project_todo_read(params)
+ if result.get("source_request") == "legacy_todo_source":
+ params["legacy_source"] = _legacy_todo_read_source(goal, resolved_state_file,
+ todo_id=normalized_todo_id, retained=read_scope == "completed_history")
+ result = project_todo_read(params)
+ if "payload" not in result:
+ failure = result["canonical"]
+ code = str(failure.get("reason_code") or "local_authority_todo_list_unavailable")
+ payload = {"source_authority": "unknown_fail_closed"} if code == "local_authority_mode_read_failed" else failure
+ raise LocalCoordinationAuthorityUnavailable(str(failure.get("reason") or "canonical Todo authority is unavailable"),
+ code=code, payload=payload)
+ return result["payload"]
+
+
+def _legacy_todo_read_source(goal, state_file, *, todo_id, retained):
+ from ..goals.legacy_event_source import require_no_legacy_todo_events
- matched_todo = todos[0] if len(todos) == 1 else None
- payload: dict[str, Any] = {
- "ok": True,
- "dry_run": True,
- "read_only": True,
- "command": "list",
- "goal_id": goal_id,
- "role": role or "all",
- "status_filter": normalize_todo_status(status) if status else None,
- "source": source,
- "todo_count": matched_todo_count,
- "todos": todos,
- "state_file": str(resolved_state_file),
- "project": str(resolved_project) if resolved_project else None,
- }
- if canonical_read is not None:
- payload["authority_read"] = {
- "source_authority": canonical_read["source_authority"],
- "provider_revision": canonical_read.get("provider_revision"),
- "cursor": canonical_read.get("cursor"),
- "todo_read_model": canonical_read.get("todo_read_model"),
- "decision_read_from_provider": True,
- "legacy_fallback_used": False,
- }
- if normalized_agent_id:
- payload["agent_id_filter"] = normalized_agent_id
- payload["unfiltered_todo_count"] = unfiltered_count
- payload["filter_semantics"] = (
- "agent todos include unclaimed items plus claimed_by=; "
- "User gates use global_gate, then blocks_agent, then legacy claimed_by scope; "
- "User actions use bound_agent, then legacy claimed_by scope; unscoped items remain visible"
- )
- if agent_lane_hot_path:
- payload["returned_todo_count"] = len(todos)
- payload["todo_list_projection"] = todo_list_projection_contract(
- matched_todo_count=matched_todo_count,
- returned_todo_count=len(todos),
- )
- if limit is not None:
- payload["explicit_limit"] = limit
- payload["unfiltered_todo_count"] = unfiltered_count
- payload["returned_todo_count"] = len(todos)
- payload["todo_list_projection"] = todo_list_projection_contract(
- matched_todo_count=uncapped_todo_count,
- returned_todo_count=len(todos),
- view="explicit_limit_cold_path",
- item_limit_per_role=limit,
- full_detail_cold_paths=(
- "todo list without --limit",
- "active state",
- ),
- )
- if normalized_todo_id:
- payload["todo_id_filter"] = normalized_todo_id
- payload["matched"] = bool(todos)
- payload["relations"] = todo_item_relations(matched_todo) if matched_todo else {}
- if len(todos) > 1:
- payload["ambiguous"] = True
- if not todos:
- payload["not_found"] = True
- if normalized_todo_id:
- return effect_runtime_result(
- "todo.context.page", {"detail_payload": payload}, large_local_snapshot=True,
- )
- payload.update(summaries)
- return compact_thin_todo_list_payload(payload) if thin else payload
+ if not state_file.exists():
+ raise ValueError(f"active state file does not exist: {state_file}")
+ state_text = state_file.read_text(encoding="utf-8")
+ require_no_legacy_todo_events(goal, state_path=state_file)
+ from .read_projection import prepare_todo_source_text
+ return prepare_todo_source_text(goal, state_text, state_file, todo_id=todo_id, retained=retained)
diff --git a/loopx/control_plane/todos/read_codec.ts b/loopx/control_plane/todos/read_codec.ts
new file mode 100644
index 0000000000..11da7fbe83
--- /dev/null
+++ b/loopx/control_plane/todos/read_codec.ts
@@ -0,0 +1,254 @@
+/** Compatibility codecs for canonical and historical Todo read records. */
+import {createHash} from "node:crypto";
+import type {JsonObject} from "../effect_program.ts";
+import {canonicalAuthorityJson, canonicalAuthoritySha256, authorityUnicodeCompare, AuthorityStoreProtocolError} from "../coordination/authority_store_codec.ts";
+import {COORDINATION_STATE_CONTRACT} from "../coordination/coordination_state_contract.generated.ts";
+import {compactPythonWhitespace, stripPythonWhitespace, normalizeTodoAgent} from "../coordination/todo_agents.ts";
+import {legacyTodoPriority} from "./priority.ts";
+import {normalizeTodoNoFollowup} from "./completion_state.ts";
+import {normalizeTodoResumeWhen, TODO_RESUME_NORMALIZE_REQUEST_SCHEMA_VERSION} from "./resume_condition.ts";
+import {normalizeTodoDecisionScope} from "./decision_metadata.ts";
+import {normalizeTodoCapabilities, normalizeTodoRepository} from "./work_requirements.ts";
+import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts";
+import codec from "./read_legacy_codec.json" with {type: "json"};
+
+export const recordFields = COORDINATION_STATE_CONTRACT.todo_read_record.fields;
+const classes = ["advancement_task", "continuous_monitor", "user_gate", "user_action", "blocker"];
+export const id = (value: unknown): string | null => {
+ const candidate = typeof value === "string" ? stripPythonWhitespace(value).toLowerCase() : "";
+ return /^todo_[a-z0-9_-]{3,64}$/.test(candidate) ? candidate : null;
+};
+export const object = (value: unknown): JsonObject => value !== null && typeof value === "object" && !Array.isArray(value) ? value as JsonObject : {};
+export const text = (value: unknown): string => value == null ? "" : String(value);
+export function ids(value: unknown): string[] {
+ const values = Array.isArray(value) ? value : [value];
+ const found = values.flatMap(raw => (typeof raw === "object" && raw !== null ? JSON.stringify(raw) : text(raw || ""))
+ .match(new RegExp(boundary + "todo_[A-Za-z0-9_-]+" + boundary, "gu")) ?? []);
+ return [...new Set(found.map(id).filter((v): v is string => v !== null))];
+}
+const hash = (value: string): string => createHash("sha256").update(value).digest("hex");
+export const withoutNull = (value: JsonObject): JsonObject => Object.fromEntries(Object.entries(value).filter(([, v]) => v != null));
+
+export function compactTodoReadText(value: string, limit: number | null = 500): string {
+ const full = compactPythonWhitespace(value), points = Array.from(full);
+ return limit === null || points.length <= limit ? full : stripPythonWhitespace(points.slice(0, limit - 1).join("")) + "…";
+}
+export function pythonJson(value: unknown): string {
+ return JSON.stringify(canonicalAuthorityJson(value)).replace(/[\u007f-\uffff]/g,
+ ch => "\\u" + ch.charCodeAt(0).toString(16).padStart(4, "0"));
+}
+const boundary = "(?:(?<=[\\p{L}\\p{N}_])(?![\\p{L}\\p{N}_])|(? values.map(pattern => new RegExp(pattern.replace("(?i)", "").replaceAll("\\b", boundary).replaceAll("\\s", " "), "iu"));
+const classification = [
+ [patterns(codec.TODO_HARD_MONITOR_PATTERNS), "continuous_monitor"],
+ [patterns(codec.TODO_ADVANCEMENT_OVERRIDE_PATTERNS), "advancement_task"],
+ [patterns(codec.TODO_BLOCKED_MONITOR_PATTERNS), "continuous_monitor"],
+ [patterns(codec.TODO_MONITOR_PATTERNS), "continuous_monitor"],
+] as const;
+
+export function taskClass(item: JsonObject): string {
+ const explicit = text(item.task_class).trim().toLowerCase();
+ if (classes.includes(explicit)) return explicit;
+ const action = text(item.action_kind).trim().toLowerCase();
+ if (codec.advancement_actions.includes(action)) return "advancement_task";
+ if (codec.monitor_actions.includes(action)) return "continuous_monitor";
+ const label = compactPythonWhitespace(text(item.text)).replace(/[\u0130\u0131]/gu, "i");
+ for (const [rules, kind] of classification) if (rules.some(rule => rule.test(label))) return kind;
+ return "advancement_task";
+}
+
+export function readAgent(value: unknown): string | null {
+ try {return normalizeTodoAgent(typeof value === "boolean" ? value ? "True" : "" : value, "agent");}
+ catch (error) {if (error instanceof AuthorityStoreProtocolError) return null; throw error;}
+}
+export function readExcludedAgents(value: unknown): string[] {
+ const values = typeof value === "string" ? value.split(",") : Array.isArray(value) ? value : [value];
+ return [...new Set(values.map(readAgent).filter((v): v is string => v !== null))].sort(authorityUnicodeCompare);
+}
+export const readNoFollowup = (value: unknown): boolean | null => normalizeTodoNoFollowup(typeof value === "boolean" ? value : text(value || ""));
+export function readResume(value: unknown): string | null {
+ const candidate = compactPythonWhitespace(text(value || ""));
+ const known = normalizeTodoResumeWhen({schema_version: TODO_RESUME_NORMALIZE_REQUEST_SCHEMA_VERSION, resume_when: candidate});
+ if (known) return known;
+ const lowered = candidate.toLowerCase();
+ return !lowered.startsWith("resume_at:") && /^[a-z][a-z0-9_-]{0,31}(?::[a-z0-9_.:@-]{1,96})?$/.test(lowered) ? lowered : null;
+}
+
+export function readScope(value: unknown): JsonObject | null {
+ const raw = typeof value === "string" ? value : object(value);
+ const selected = typeof raw === "string" ? raw : {kind: raw.kind, granularity: raw.granularity, scope_key: raw.scope_key,
+ ...(id(raw.decision_id) ? {decision_id: id(raw.decision_id)} : {})};
+ try {return normalizeTodoDecisionScope(selected);}
+ catch (error) {if (error instanceof EffectRuntimeRequestError) return null; throw error;}
+}
+function readScopes(value: unknown): JsonObject[] {
+ const values = Array.isArray(value) ? value : text(value || "").split(/[,;|]/u), result: JsonObject[] = [];
+ for (const raw of values) {
+ const scope = readScope(raw);
+ if (scope && !result.some(row => row.kind === scope.kind && row.granularity === scope.granularity && row.scope_key === scope.scope_key)) result.push(scope);
+ }
+ return result;
+}
+
+function listValues(value: unknown): unknown[] {
+ return Array.isArray(value) ? value : text(value || "").split(/[,;|]/u);
+}
+function readCapabilities(value: unknown): string[] {
+ const result: string[] = [];
+ for (const raw of listValues(value)) {
+ try {
+ for (const token of normalizeTodoCapabilities([scalarText(raw)], "capability")) if (!result.includes(token)) result.push(token);
+ } catch (error) {if (!(error instanceof EffectRuntimeRequestError)) throw error;}
+ }
+ return result;
+}
+function writeScopes(value: unknown): string[] {
+ const scopes = listValues(value).map(raw => compactPythonWhitespace(scalarText(raw))).filter(scope => scope &&
+ Array.from(scope).length <= 160 && !scope.startsWith("/") && !scope.startsWith("~") && !scope.split("/").includes("..") && !/[ <>]/u.test(scope));
+ return [...new Set(scopes)];
+}
+
+const truthy = (v: unknown): boolean => Array.isArray(v) ? v.length > 0 : v !== null && typeof v === "object" ? Object.keys(v).length > 0 : Boolean(v);
+function repr(value: unknown): string {
+ if (value == null) return "None";
+ if (typeof value === "boolean") return value ? "True" : "False";
+ if (Array.isArray(value)) return "[" + value.map(repr).join(", ") + "]";
+ if (typeof value === "object") return "{" + Object.entries(value).map(([k, v]) => `${repr(k)}: ${repr(v)}`).join(", ") + "}";
+ if (typeof value !== "string") return String(value);
+ const quote = value.includes("'") && !value.includes('"') ? '"' : "'";
+ return quote + Array.from(value).map(point => {
+ if (point === quote || point === "\\") return "\\" + point;
+ if (point === "\n") return "\\n";
+ if (point === "\r") return "\\r";
+ if (point === "\t") return "\\t";
+ if (point !== " " && /[\p{C}\p{Z}]/u.test(point)) {
+ const code = point.codePointAt(0)!;
+ return code <= 255 ? "\\x" + code.toString(16).padStart(2, "0") : code <= 65535 ?
+ "\\u" + code.toString(16).padStart(4, "0") : "\\U" + code.toString(16).padStart(8, "0");
+ }
+ return point;
+ }).join("") + quote;
+}
+const scalarText = (value: unknown): string => !truthy(value) ? "" : typeof value === "string" ? value : repr(value);
+
+export function handoffSource(item: JsonObject): JsonObject {
+ const refs = (value: unknown): JsonObject => ({...(truthy(value) ? {truthy: true} : {}),
+ ...(Array.isArray(value) && value.length ? {values: value.map(scalarText)} : {})});
+ const nestedMetadata: JsonObject = {}, nested: JsonObject = {};
+ for (const key of ["handoff", "handoff_note"]) {
+ const value = item[key], row = object(value);
+ if (truthy(value)) nested[key] = {truthy: true, fields: value !== null && typeof value === "object" && !Array.isArray(value)
+ ? Object.fromEntries(Object.entries(row).map(([k, v]) => [k, scalarText(v)])) : null};
+ nestedMetadata[key] = {from_agent: readAgent(truthy(row.from_agent) ? row.from_agent : item.agent_id),
+ to_agent: readAgent(row.to_agent), evidence_refs: refs(row.evidence_refs)};
+ }
+ const fields = ["continuation_hint", "suggested_next_action", "note", "reason", "summary", "intent", "blocked_on", "evidence", "title", "text", "task_class", "goal_id", "source", "latest_event_kind"];
+ const action = compactPythonWhitespace(scalarText(item.action_kind)).toLowerCase();
+ const legacy = "(" + [item.goal_id, item.role, item.index, truthy(item.text) ? item.text : item.title].map(repr).join(", ") + ")";
+ return {...nested, nested_metadata: nestedMetadata,
+ texts: Object.fromEntries(fields.filter(key => truthy(item[key])).map(key => [key, scalarText(item[key])])),
+ metadata: {todo_id: id(item.todo_id), claimed_by: readAgent(item.claimed_by), excluded_agents: readExcludedAgents(item.excluded_agents),
+ successor_todo_ids: ids(item.successor_todo_ids), unblocks_todo_id: id(item.unblocks_todo_id), superseded_by: id(item.superseded_by),
+ action_kind: /^[a-z][a-z0-9_-]{0,63}$/.test(action) ? action : null, resume_when: readResume(item.resume_when),
+ required_decision_scopes: readScopes(item.required_decision_scopes), decision_scope: readScope(item.decision_scope)},
+ evidence_refs: refs(item.evidence_refs), has_evidence: truthy(item.evidence), has_note: truthy(item.note),
+ goal_id: "", source: "", legacy_id: id(item.todo_id) ? null : "handoff_" + createHash("sha1").update(legacy).digest("hex").slice(0, 12)};
+}
+
+/** Provider records are already decoded. This changes presentation, never authority. */
+export function canonicalTodoReadItems(values: JsonObject[]): JsonObject[] {
+ return values.map((value, offset) => {
+ const archived = value.archive_state === "archive", native = value.schema_version === "todo_domain_record_v0";
+ const row = {...value, ...(archived ? {source_section: "Completed Work Archive", index: offset + 1} : {}),
+ ...(native ? {schema_version: "todo_item_v0", source_section: archived ? "Completed Work Archive" :
+ value.role === "agent" ? "Agent Todo" : "User Todo / Owner Review Reading Queue", index: offset + 1} : {})};
+ return row;
+ });
+}
+
+export function structureCanonicalTodo(item: JsonObject, role: unknown, section: unknown,
+ limit: number | null = 500, revision = true): JsonObject {
+ const full = compactPythonWhitespace(text(item.text)), body = compactTodoReadText(full, limit);
+ const normalizedStatus = stripPythonWhitespace(text(item.status)).toLowerCase();
+ const status = ["open", "blocked", "done", "deferred"].includes(normalizedStatus) ? normalizedStatus : item.done ? "done" : "open";
+ const parsed = legacyTodoPriority(body);
+ return {...readMetadata(item), schema_version: "todo_item_v0", role, source_section: section,
+ archive_state: item.archive_state ?? "active", status, done: status === "done" || status === "deferred",
+ text: body, task_class: taskClass({...item, text: body}),
+ ...(revision && full ? {content_revision: "sha256:" + hash(full)} : {}),
+ ...(parsed.priority ? {priority: parsed.priority, title: compactTodoReadText(parsed.title, limit)} : {})};
+}
+
+function readMetadata(item: JsonObject): JsonObject {
+ const result = {...item};
+ for (const key of ["created_by", "claimed_by", "bound_agent", "blocks_agent"]) {
+ const normalized = readAgent(item[key]);
+ if (normalized !== null) result[key] = normalized;
+ }
+ for (const key of ["global_gate", "goal_bound"]) {
+ const value = item[key];
+ if (typeof value === "boolean") continue;
+ const token = compactPythonWhitespace(text(value || "")).toLowerCase();
+ if (["1", "true", "yes", "y", "global_gate", "global-gate"].includes(token)) result[key] = true;
+ else if (["0", "false", "no", "n"].includes(token)) result[key] = false;
+ }
+ const excluded = readExcludedAgents(item.excluded_agents);
+ if (excluded.length) result.excluded_agents = excluded;
+ const resume = readResume(item.resume_when);
+ if (resume !== null) result.resume_when = resume;
+ for (const key of ["required_capabilities", "target_capabilities"]) {
+ const values = readCapabilities(item[key]);
+ if (values.length) result[key] = values;
+ }
+ const scopes = writeScopes(item.required_write_scopes);
+ if (scopes.length) result.required_write_scopes = scopes;
+ const decision = readScope(item.decision_scope), required = readScopes(item.required_decision_scopes);
+ if (decision) result.decision_scope = decision;
+ if (required.length) result.required_decision_scopes = required;
+ const outcome = compactPythonWhitespace(scalarText(item.decision_outcome)).toLowerCase();
+ if (["approve", "reject", "cancel"].includes(outcome)) result.decision_outcome = outcome;
+ const binding = stripPythonWhitespace(scalarText(item.capability_binding_ref)).toLowerCase();
+ if (/^[a-z][a-z0-9_.-]{0,31}:[a-z][a-z0-9_.:-]{2,95}$/u.test(binding)) result.capability_binding_ref = binding;
+ if (item.task_repository) {
+ try {const repository = normalizeTodoRepository(item.task_repository); if (repository) result.task_repository = repository;}
+ catch (error) {if (!(error instanceof EffectRuntimeRequestError)) throw error;}
+ }
+ for (const [key, values] of [["continuation_policy", ["independent_handoff", "same_agent_non_delivery"]],
+ ["removed_continuation_policy", ["primary_review", "review_handoff"]]] as const) {
+ const value = stripPythonWhitespace(scalarText(item[key])).toLowerCase();
+ if ((values as readonly string[]).includes(value)) result[key] = value;
+ }
+ for (const key of ["resume_monitor_generation", "material_change_generation"]) {
+ const value = item[key];
+ if (value != null && value !== "" && (typeof value === "number" || typeof value === "boolean" || typeof value === "string" && /^[+-]?\d+$/.test(value.trim()))) {
+ const number = Math.trunc(Number(value));
+ if (Number.isSafeInteger(number) && number >= 0) result[key] = number;
+ }
+ }
+ for (const key of ["action_kind", "task_domain"]) {
+ const value = compactPythonWhitespace(text(item[key])).toLowerCase();
+ if (new RegExp(key === "action_kind" ? "^[a-z][a-z0-9_-]{0,63}$" : "^[a-z][a-z0-9_.-]{0,63}$").test(value)) result[key] = value;
+ }
+ const noFollowup = readNoFollowup(item.no_followup);
+ if (noFollowup !== null) result.no_followup = noFollowup;
+ const declarationKeys = ["validation_command", "validation_command_argv", "validation_label", "validation_timeout_seconds"];
+ if (declarationKeys.some(key => item[key] != null && item[key] !== "")) {
+ const command = typeof item.validation_command === "string" ? stripPythonWhitespace(item.validation_command) : item.validation_command;
+ let argv = item.validation_command_argv ?? null;
+ if (typeof argv === "string") {
+ argv = stripPythonWhitespace(argv);
+ if (!argv) argv = null;
+ else {try {argv = JSON.parse(argv as string);} catch (error) {if (!(error instanceof SyntaxError)) throw error;}}
+ }
+ const timeout = typeof item.validation_timeout_seconds === "string" && /^\d+$/.test(item.validation_timeout_seconds.trim())
+ ? Number(item.validation_timeout_seconds.trim()) : item.validation_timeout_seconds === "" ? null : item.validation_timeout_seconds ?? null;
+ result.completion_validation_required = true;
+ result.completion_validation_sha256 = canonicalAuthoritySha256({validation_command: command === "" ? null : command ?? null,
+ validation_command_argv: argv, validation_label: item.validation_label === "" ? null : item.validation_label ?? null,
+ validation_timeout_seconds: timeout});
+ if (!Object.hasOwn(result, "completion_validation_revision")) result.completion_validation_revision = 0;
+ if (!Object.hasOwn(result, "completion_validation_revision_history")) result.completion_validation_revision_history = [];
+ }
+ for (const key of declarationKeys) delete result[key];
+ return result;
+}
diff --git a/loopx/control_plane/todos/read_legacy_codec.json b/loopx/control_plane/todos/read_legacy_codec.json
new file mode 100644
index 0000000000..3bea730d27
--- /dev/null
+++ b/loopx/control_plane/todos/read_legacy_codec.json
@@ -0,0 +1,236 @@
+{
+ "TODO_HARD_MONITOR_PATTERNS": [
+ "(?i)\\bdo not\\b.*\\b(?:launch|run|execute|start)\\b.*\\buntil\\b",
+ "(?i)\\b(?:only|just)\\b.*\\b(?:after|when|once)\\b.*\\b(?:owner|user|credential|approval|prerequisite|evidence)\\b",
+ "(?i)\\b(?:credential|gcp|gcs|gcp_project|gcp_sa_key|gs://)\\b.*\\b(?:missing|required|provide|proof|prerequisite|gate|gated)\\b",
+ "(?i)\\b(?:readiness|proof)\\b.*\\bbefore any formal\\b.*\\brun\\b",
+ "(?i)\\bremaining formal\\b.*\\bpath\\b",
+ "(?i)\\b(?:route|input)\\b.*\\babsent\\b",
+ "(?i)\\b0\\b.*\\b(?:candidate|candidates)\\b"
+ ],
+ "TODO_ADVANCEMENT_OVERRIDE_PATTERNS": [
+ "(?i)(?:^|[::]\\s*)(?:implement|add|make|fix|build|wire|define|compare|run|execute|test|validate|rebuild|repair|archive|publish|merge|write|attribute|collect|aggregate|generate|produce|materialize|rerun|repeat|eval|evaluate|score)\\b",
+ "(?i)\\b(?:implementation slice|validation-backed patch|smoke fixture|regression suite|readiness scan|source preflight|setup-readiness scan)\\b"
+ ],
+ "TODO_BLOCKED_MONITOR_PATTERNS": [
+ "(?i)\\bdo not\\b.*\\b(?:launch|run|execute|start)\\b.*\\buntil\\b",
+ "(?i)\\b(?:only|just)\\b.*\\b(?:after|when|once)\\b.*\\b(?:owner|user|credential|approval|prerequisite|evidence)\\b",
+ "(?i)\\b(?:credential|gcp|gcs|gcp_project|gcp_sa_key|gs://)\\b.*\\b(?:missing|required|provide|proof|prerequisite|gate|gated)\\b",
+ "(?i)\\b(?:readiness|proof)\\b.*\\bbefore any formal\\b.*\\brun\\b",
+ "(?i)\\bremaining formal\\b.*\\bpath\\b",
+ "(?i)\\b(?:route|input)\\b.*\\babsent\\b",
+ "(?i)\\b0\\b.*\\b(?:candidate|candidates)\\b",
+ "(?i)\\b(?:blocked|gated|waiting)\\b.*\\b(?:owner|user|credential|substrate|proof|prerequisite|evidence)\\b"
+ ],
+ "TODO_MONITOR_PATTERNS": [
+ "(?i)\\bdependency monitor\\b",
+ "(?i)\\bobservation lane\\b",
+ "(?i)(?:^|[::]\\s*)observe\\b",
+ "(?i)(?:^|[::]\\s*)poll\\b",
+ "(?i)(?:^|[::]\\s*)watch\\b",
+ "(?i)\\bmonitor-only\\b",
+ "(?i)\\bdo not\\b.*\\b(?:launch|run|execute|start)\\b.*\\buntil\\b",
+ "(?i)\\b(?:only|just)\\b.*\\b(?:after|when|once)\\b.*\\b(?:owner|user|credential|approval|prerequisite|evidence)\\b",
+ "(?i)\\b(?:credential|gcp|gcs|gcp_project|gcp_sa_key|gs://)\\b.*\\b(?:missing|required|provide|proof|prerequisite|gate|gated)\\b",
+ "(?i)\\b(?:readiness|proof)\\b.*\\bbefore any formal\\b.*\\brun\\b",
+ "(?i)\\bremaining formal\\b.*\\bpath\\b",
+ "(?i)\\b(?:route|input)\\b.*\\babsent\\b",
+ "(?i)\\b0\\b.*\\b(?:candidate|candidates)\\b",
+ "(?i)\\b(?:blocked|gated|waiting)\\b.*\\b(?:owner|user|credential|substrate|proof|prerequisite|evidence)\\b"
+ ],
+ "TODO_ADVANCEMENT_PATTERNS": [
+ "(?i)(?:^|[::]\\s*)(?:implement|add|make|fix|build|wire|define|compare|run|execute|test|validate|rebuild|repair|archive|publish|merge|write|attribute|collect|aggregate|generate|produce|materialize|rerun|repeat|eval|evaluate|score)\\b",
+ "(?i)\\b(?:implementation slice|validation-backed patch|smoke fixture|regression suite|readiness scan|source preflight|setup-readiness scan)\\b",
+ "(?i)\\b(?:task|validation hypothesis|validation step|bounded step|learning run)\\b"
+ ],
+ "advancement_actions": [
+ "advance",
+ "analyze",
+ "benchmark_run",
+ "codex_run",
+ "compact_blocker_writeback",
+ "compare",
+ "execute",
+ "fix",
+ "implement",
+ "rebuild",
+ "rebuild_score",
+ "repair",
+ "run",
+ "run_eval",
+ "test",
+ "validate",
+ "writeback"
+ ],
+ "monitor_actions": [
+ "external_evidence",
+ "monitor",
+ "observe",
+ "poll",
+ "watch"
+ ],
+ "frontier_fields": [
+ "todo_id",
+ "status",
+ "done",
+ "title",
+ "text",
+ "task_class",
+ "claimed_by",
+ "bound_agent",
+ "blocks_agent",
+ "excluded_agents",
+ "priority",
+ "action_kind",
+ "task_domain",
+ "task_repository",
+ "capability_binding_ref",
+ "required_capabilities",
+ "target_capabilities",
+ "target_key",
+ "continuation_policy",
+ "removed_continuation_policy",
+ "decision_scope",
+ "required_decision_scopes",
+ "decision_outcome",
+ "replan_obligation_id",
+ "unblocks_todo_id",
+ "depends_on_todo_id",
+ "depends_on_todo_ids",
+ "resume_when",
+ "no_followup",
+ "successor_todo_ids",
+ "completion_continuation"
+ ],
+ "list_constants": {
+ "_ITEM_FIELDS": [
+ "schema_version",
+ "gate_state",
+ "successor_count",
+ "excluded_agents",
+ "index",
+ "todo_id",
+ "role",
+ "status",
+ "priority",
+ "text",
+ "title",
+ "note",
+ "task_class",
+ "action_kind",
+ "task_domain",
+ "capability_binding_ref",
+ "task_repository",
+ "continuation_policy",
+ "required_capabilities",
+ "target_capabilities",
+ "claimed_by",
+ "bound_agent",
+ "goal_bound",
+ "blocks_agent",
+ "global_gate",
+ "unblocks_todo_id",
+ "successor_todo_ids",
+ "completion_continuation",
+ "completion_recovery",
+ "resume_when",
+ "resume_monitor_generation",
+ "resume_ready",
+ "target_key",
+ "cadence",
+ "next_due_at",
+ "expires_at",
+ "watch_only"
+ ],
+ "_RETAINED_LANE_LIMITS": {
+ "items": 12,
+ "first_open_items": 3,
+ "first_executable_items": 3,
+ "deferred_items": 3,
+ "monitor_due_items": 2,
+ "monitor_schedule_gap_items": 2,
+ "blocker_items": 2,
+ "resume_blocked_items": 2,
+ "active_next_action_items": 3,
+ "active_next_action_executable_items": 3
+ },
+ "_RETAINED_DICTS": [
+ "deferred_work_counts",
+ "monitor_writeback",
+ "source_proof",
+ "terminal_closure_proof",
+ "work_counts"
+ ],
+ "THIN_TODO_LIST_ITEM_FIELDS": [
+ "todo_id",
+ "role",
+ "status",
+ "priority",
+ "text",
+ "task_class",
+ "action_kind",
+ "claimed_by",
+ "bound_agent",
+ "goal_bound",
+ "blocks_agent",
+ "global_gate",
+ "unblocks_todo_id",
+ "decision_scope",
+ "required_decision_scopes",
+ "resume_when",
+ "resume_ready",
+ "target_key",
+ "cadence",
+ "next_due_at",
+ "expires_at",
+ "watch_only"
+ ],
+ "_COMPACT_ITEM_TEXT_FIELDS": [
+ "note",
+ "text",
+ "title"
+ ],
+ "_THIN_ITEM_TEXT_FIELDS": [
+ "text"
+ ],
+ "AGENT_LANE_TODO_LIST_ITEM_LIMIT": 12,
+ "AGENT_LANE_TODO_LIST_TEXT_LIMIT": 180,
+ "THIN_TODO_LIST_ITEM_LIMIT_PER_ROLE": 2,
+ "THIN_TODO_LIST_NESTED_ITEM_LIMIT": 3,
+ "THIN_TODO_LIST_NESTED_DICT_FIELD_LIMIT": 8,
+ "AGENT_LANE_TODO_LIST_PROJECTION_SCHEMA_VERSION": "agent_lane_todo_list_projection_v0",
+ "AGENT_LANE_TODO_LIST_SUMMARY_SCHEMA_VERSION": "agent_lane_todo_list_summary_compaction_v0",
+ "EXPLICIT_LIMIT_TODO_LIST_SUMMARY_SCHEMA_VERSION": "explicit_limit_todo_list_summary_compaction_v0",
+ "THIN_TODO_LIST_PROJECTION_SCHEMA_VERSION": "todo_list_thin_projection_v0",
+ "THIN_TODO_LIST_SUMMARY_SCHEMA_VERSION": "todo_list_thin_summary_compaction_v0"
+ },
+ "relation_fields": [
+ "claimed_by",
+ "bound_agent",
+ "goal_bound",
+ "blocks_agent",
+ "excluded_agents",
+ "global_gate",
+ "unblocks_todo_id",
+ "successor_todo_ids",
+ "completion_continuation",
+ "completion_recovery",
+ "superseded_by",
+ "resume_when",
+ "resume_monitor_generation",
+ "resume_condition",
+ "resume_ready",
+ "decision_scope",
+ "required_decision_scopes",
+ "required_write_scopes",
+ "required_capabilities",
+ "target_capabilities",
+ "task_class",
+ "action_kind",
+ "continuation_policy",
+ "target_key",
+ "cadence",
+ "next_due_at",
+ "expires_at"
+ ]
+}
diff --git a/loopx/control_plane/todos/read_presentation.ts b/loopx/control_plane/todos/read_presentation.ts
new file mode 100644
index 0000000000..489e72756d
--- /dev/null
+++ b/loopx/control_plane/todos/read_presentation.ts
@@ -0,0 +1,116 @@
+/** Existing Todo list response codecs, after full-source decisions. */
+import type {JsonObject} from "../effect_program.ts";
+import {stripPythonWhitespace} from "../coordination/todo_agents.ts";
+import {INTERNAL_STATE_SHAPE_PATTERNS} from "../goals/vision_checkpoint.ts";
+import {compactTodoReadText} from "./read_codec.ts";
+import codec from "./read_legacy_codec.json" with {type: "json"};
+
+const layout = codec.list_constants;
+const dict = (v: unknown): v is JsonObject => v !== null && typeof v === "object" && !Array.isArray(v);
+const nonempty = (v: unknown): boolean => Array.isArray(v) ? v.length > 0 : dict(v) ? Object.keys(v).length > 0 : Boolean(v);
+function compactText(v: unknown): unknown {
+ if (typeof v !== "string") return v;
+ const full = stripPythonWhitespace(v), points = Array.from(full);
+ return points.length <= 180 ? full : stripPythonWhitespace(points.slice(0, 177).join("")) + "...";
+}
+function compactItem(value: unknown, thin = false): unknown {
+ if (!dict(value)) return value;
+ const fields = thin ? layout.THIN_TODO_LIST_ITEM_FIELDS : layout._ITEM_FIELDS;
+ const result: JsonObject = {};
+ for (const key of fields) if (value[key] != null) result[key] = ["text", ...(thin ? [] : ["title", "note"])].includes(key) ? compactText(value[key]) : value[key];
+ if (!thin && (value.task_class === "blocker" || value.status === "blocked")) {
+ const reason = compactTodoReadText(String(value.reason || ""), 220);
+ if (reason && !INTERNAL_STATE_SHAPE_PATTERNS.some(rule => rule.test(reason))) result.reason = reason;
+ }
+ return thin ? Object.fromEntries(Object.entries(result).map(([key, v]) => [key, compactThinValue(v)])) : result;
+}
+function compactThinValue(value: unknown): unknown {
+ if (typeof value === "string") return compactText(value);
+ if (Array.isArray(value)) return value.slice(0, 3).map(compactThinValue);
+ if (dict(value)) return Object.fromEntries(Object.entries(value).slice(0, 8).map(([k, v]) => [k, compactThinValue(v)]));
+ return value;
+}
+
+export function compactTodoReadSummary(summary: JsonObject, role: string, limit: number | null): JsonObject {
+ const result: JsonObject = {}, lanes: JsonObject = {};
+ let omittedLanes = 0, omittedDicts = 0;
+ for (const [key, value] of Object.entries(summary)) {
+ if (Array.isArray(value)) {
+ const cap = limit ?? (layout._RETAINED_LANE_LIMITS as Record)[key];
+ if (cap === undefined) {omittedLanes += Number(nonempty(value)); continue;}
+ const retained = limit === null && key === "items" ? value.filter(row => !dict(row) || row.status !== "done") : value;
+ result[key] = retained.slice(0, cap).map(row => compactItem(row));
+ if (retained.length > cap) lanes[key] = {shown: cap, total: retained.length};
+ } else if (dict(value)) {
+ if (layout._RETAINED_DICTS.includes(key)) result[key] = value;
+ else omittedDicts += Number(nonempty(value));
+ } else result[key] = value;
+ }
+ result.payload_compaction = limit === null ? {
+ schema_version: layout.AGENT_LANE_TODO_LIST_SUMMARY_SCHEMA_VERSION, role, item_limit: 12, item_text_limit: 180,
+ terminal_items_in_hot_path: false, compacted_lanes: lanes, omitted_nonempty_lane_count: omittedLanes,
+ omitted_nonempty_dict_count: omittedDicts,
+ full_detail_cold_path: "todo list with --role, --status, or --todo-id; todo list without --agent-id; or active state",
+ } : {
+ schema_version: layout.EXPLICIT_LIMIT_TODO_LIST_SUMMARY_SCHEMA_VERSION, view: "explicit_limit_cold_path", role,
+ item_limit: limit, item_text_limit: 180, compacted_lanes: lanes, omitted_nonempty_dict_count: omittedDicts,
+ full_detail_cold_path: "todo list without --limit or active state",
+ };
+ return result;
+}
+
+export function todoReadProjectionContract(matched: number, returned: number, limit: number | null): JsonObject {
+ return {schema_version: layout.AGENT_LANE_TODO_LIST_PROJECTION_SCHEMA_VERSION,
+ view: limit === null ? "agent_lane_hot_path" : "explicit_limit_cold_path", matched_todo_count: matched,
+ returned_todo_count: returned, item_limit_per_role: limit ?? 12, counts_cover_full_match: true,
+ full_detail_cold_paths: limit === null ? ["todo list with --role, --status, or --todo-id", "todo list without --agent-id", "active state"] :
+ ["todo list without --limit", "active state"]};
+}
+
+export function compactThinTodoRead(payload: JsonObject): JsonObject {
+ const result = {...payload};
+ for (const key of ["state_file", "project", "filter_semantics"]) delete result[key];
+ const limit = Math.min(2, typeof result.explicit_limit === "number" ? result.explicit_limit : 2);
+ const projection = dict(result.todo_list_projection) ? result.todo_list_projection : {};
+ const sourceView = String(projection.view || "full_detail"), matched = Number(projection.matched_todo_count || result.todo_count || 0);
+ const todos: unknown[] = [];
+ for (const role of ["user", "agent"]) {
+ const key = `${role}_todos`, summary = result[key];
+ if (!dict(summary)) continue;
+ const items = Array.isArray(summary.items) ? summary.items.filter(dict) : [];
+ const chosen = items.slice(0, limit).map(row => compactItem(row, true));
+ todos.push(...chosen);
+ result[key] = compactThinTodoReadSummary(summary, {role, items_matched: Number(summary.total_count || items.length),
+ items_returned: chosen.length, item_limit_per_role: limit});
+ }
+ return {...result, todos, matched_todo_count: matched, returned_todo_count: todos.length,
+ omitted_todo_count: Math.max(0, matched - todos.length), thin: true,
+ todo_list_field_projection: {schema_version: layout.THIN_TODO_LIST_PROJECTION_SCHEMA_VERSION, view: "thin_explicit_view",
+ source_view: sourceView, item_container: "todos", matched_todo_count: matched, returned_todo_count: todos.length,
+ omitted_todo_count: Math.max(0, matched - todos.length), item_limit_per_role: limit, counts_cover_full_match: true,
+ item_fields: layout.THIN_TODO_LIST_ITEM_FIELDS, item_text_limit: 180, nested_item_limit: 3, nested_dict_field_limit: 8,
+ full_detail_cold_paths: ["todo list without --thin", "todo list --todo-id without --thin", "active state"]}};
+}
+
+export function compactThinTodoReadSummary(summary: JsonObject, details: {
+ role: unknown; items_matched: number; items_returned: number; item_limit_per_role: number;
+}): JsonObject {
+ const fields: JsonObject = {};
+ let omittedLanes = 0, omittedDicts = 0, view = "full_detail";
+ for (const [name, value] of Object.entries(summary)) {
+ if (Array.isArray(value)) {if (name !== "items") omittedLanes += Number(nonempty(value));}
+ else if (dict(value)) {
+ if (["work_counts", "deferred_work_counts"].includes(name)) fields[name] = value;
+ else if (name === "payload_compaction") view = typeof value.view === "string" && value.view ? value.view :
+ value.schema_version === layout.AGENT_LANE_TODO_LIST_SUMMARY_SCHEMA_VERSION ? "agent_lane_hot_path" : "full_detail";
+ else omittedDicts += Number(nonempty(value));
+ } else fields[name] = value;
+ }
+ const total = details.items_matched, omitted = Math.max(0, total - details.items_returned);
+ fields.payload_compaction = {schema_version: layout.THIN_TODO_LIST_SUMMARY_SCHEMA_VERSION, view: "thin_explicit_view", source_view: view,
+ role: details.role, items_projected_to: "todos", item_limit_per_role: details.item_limit_per_role, item_fields: layout.THIN_TODO_LIST_ITEM_FIELDS, item_text_limit: 180,
+ nested_item_limit: 3, items_matched: total, items_returned: details.items_returned, items_omitted: omitted,
+ compacted_lanes: omitted ? {items: {shown: details.items_returned, total}} : {}, omitted_nonempty_lane_count: omittedLanes,
+ omitted_nonempty_dict_count: omittedDicts, full_detail_cold_path: "todo list without --thin or active state"};
+ return fields;
+}
diff --git a/loopx/control_plane/todos/read_projection.py b/loopx/control_plane/todos/read_projection.py
new file mode 100644
index 0000000000..f134b68f6c
--- /dev/null
+++ b/loopx/control_plane/todos/read_projection.py
@@ -0,0 +1,234 @@
+"""Source codecs and transport for the native full-source Todo read owner."""
+from __future__ import annotations
+
+from typing import Any
+
+
+def project_todo_read(params: dict[str, Any]) -> dict[str, Any]:
+ from ..effect_runtime import EffectRuntimeRejected, effect_runtime_result
+
+ import base64
+ import hashlib
+ import json
+ import os
+ import tempfile
+ from contextlib import ExitStack
+ from pathlib import Path
+ from ..effect_runtime import MAX_LOCAL_SNAPSHOT_BYTES
+
+ request = {"schema_version": "todo_read_request_v0", **params}
+ encoded = json.dumps(request, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8")
+ chunks = []
+ identity = None
+ try:
+ with ExitStack() as stack:
+ if len(encoded) > MAX_LOCAL_SNAPSHOT_BYTES - 4096:
+ directory = Path(stack.enter_context(tempfile.TemporaryDirectory(prefix="loopx-effect-todo-")))
+ source = directory / "facts.json"
+ fd = os.open(source, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
+ with os.fdopen(fd, "wb") as file:
+ file.write(encoded)
+ request = {"schema_version": "todo_read_request_v0", "facts_ref": {
+ "path": str(source), "byte_count": len(encoded), "sha256": hashlib.sha256(encoded).hexdigest()}}
+ consumed = 0
+ while True:
+ result = effect_runtime_result("todo.read", request, large_local_snapshot=True,
+ timeout=15.0 if params.get("kind") in {"query", "canonical_fields", "historical_fields"} else 10.0)
+ if not isinstance(result, dict) or result.get("schema_version") != "todo_read_result_v0":
+ raise ValueError("invalid typed Todo read result")
+ piece = result.get("result_piece")
+ if piece is None:
+ if chunks and not (result.get("canonical") or result.get("rejection")):
+ raise ValueError("incomplete Todo read result")
+ break
+ if not isinstance(piece, dict) or piece.get("offset") != consumed:
+ raise ValueError("invalid Todo read result page")
+ current = (piece.get("sha256"), piece.get("byte_count"))
+ if identity is None:
+ identity = current
+ if identity != current or type(current[1]) is not int or current[1] <= consumed:
+ raise ValueError("Todo read result identity changed")
+ block = base64.b64decode(piece["data"], validate=True)
+ if not block:
+ raise ValueError("Todo read result page did not advance")
+ chunks.append(block)
+ consumed += len(block)
+ if consumed > current[1]:
+ raise ValueError("Todo read result exceeds its declared population")
+ after = piece.get("next")
+ if after is None:
+ complete = b"".join(chunks)
+ if consumed != current[1] or hashlib.sha256(complete).hexdigest() != current[0]:
+ raise ValueError("Todo read result digest mismatch")
+ result = json.loads(complete)
+ break
+ if not isinstance(after, dict) or after.get("offset") != consumed or after.get("sha256") != current[0]:
+ raise ValueError("invalid Todo read result continuation")
+ request = {**request, "result_page": after}
+ except EffectRuntimeRejected as exc:
+ raise ValueError(str(exc)) from exc
+ if not isinstance(result, dict) or result.get("schema_version") != "todo_read_result_v0":
+ raise ValueError("invalid typed Todo read result")
+ if result.get("rejection"):
+ raise EffectRuntimeRejected(result["rejection"]["message"])
+ if params.get("kind") == "group":
+ summary = result.get("summary")
+ if not isinstance(summary, dict) or summary.get("schema_version") != "todo_summary_v0":
+ raise ValueError("invalid typed Todo summary fields")
+ if params.get("role") == "agent":
+ frontier = summary.get("advancement_frontier_revision_index")
+ if not isinstance(frontier, dict) or frontier.get("schema_version") != "todo_frontier_revision_index_v0":
+ raise ValueError("invalid typed Todo summary frontier index")
+ return result
+
+
+def restore_todo_field_evaluations(result):
+ from .succession_warning import _EvaluatedTodo
+
+ fields = result["fields"]
+ for role, decisions in result["field_evaluations"].items():
+ fields[role]["items"] = [_EvaluatedTodo(item, decision)
+ for item, decision in zip(fields[role]["items"], decisions, strict=True)]
+ return fields
+
+
+def canonical_todo_fields(todos, *, rollout_events=None, **facts):
+ from ..runtime.time import now_utc
+
+ now = now_utc()
+ if facts.get("available_capabilities") is not None:
+ facts["available_capabilities"] = sorted({str(value).strip().lower()
+ for value in facts["available_capabilities"] if str(value).strip()})
+ return restore_todo_field_evaluations(project_todo_read({
+ "kind": "fields", "todos": todos, **facts,
+ "rollout_events": list(rollout_events or []),
+ "rollout_event_source": getattr(rollout_events, "source", None),
+ "observed_at": now.timestamp(), "evaluated_at": now.isoformat(),
+ }))
+
+
+def source_todo_fields(groups, *, lineage, rollout_events=None, available_capabilities=None,
+ preferred_todo_ids=None, item_limit=None, evaluated_at=None,
+ archived_items=None, standing_items=None, legacy_source_order=False,
+ vision_runs=None, include_task_orchestration_authority=False):
+ from ..runtime.time import now_utc
+
+ now = now_utc()
+ prepared = prepare_todo_source_groups(groups, lineage=lineage)
+ lineage_rows, lineage_facts = legacy_lineage_facts(lineage)
+ result = project_todo_read({"kind": "source_fields", "groups": prepared,
+ "lineage": lineage_rows, "lineage_facts": lineage_facts, "rollout_events": list(rollout_events or []),
+ "rollout_event_source": getattr(rollout_events, "source", None),
+ "available_capabilities": available_capabilities,
+ "preferred_todo_ids": sorted(preferred_todo_ids or []), "item_limit": item_limit,
+ "archived_items": archived_items, "standing_items": standing_items,
+ "legacy_source_order": legacy_source_order,
+ "include_full_source": bool(vision_runs),
+ "include_task_orchestration_authority": include_task_orchestration_authority,
+ "observed_at": now.timestamp(), "evaluated_at": evaluated_at or now.isoformat()})
+ fields = restore_todo_field_evaluations(result)
+ for role in ("user", "agent"):
+ summary = fields.get(f"{role}_todos")
+ if not summary:
+ continue
+ if include_task_orchestration_authority:
+ from .todo_summary import _task_orchestration_authority
+ summary["task_orchestration_authority"] = _task_orchestration_authority(
+ result["source_orchestration"][role], role=role)
+ if vision_runs:
+ from .succession_warning import _EvaluatedTodo
+ from ..goals.goal_vision_wait_projection import attach_active_vision_waits
+ source = [_EvaluatedTodo(item, decision) for item, decision in zip(
+ result["source_items"][role], result["source_evaluations"][role], strict=True)]
+ attach_active_vision_waits(summary, vision_runs, role=role, items=source,
+ lineage_items=lineage)
+ return fields
+
+
+def project_todo_read_group(items, *, source_section, role, preferred_todo_ids=None,
+ lineage=None, rollout_events=None, available_capabilities=None,
+ item_limit=None, evaluated_at=None, evaluated=False,
+ full_selection=True, selection=None, resume_lineage=None,
+ include_full_source=False, include_task_orchestration_authority=False):
+ from .succession_warning import _EvaluatedTodo
+ from ..runtime.time import now_utc
+
+ now = now_utc()
+ lineage_rows, lineage_facts = legacy_lineage_facts(lineage or [])
+ result = project_todo_read({
+ "kind": "group", "items": items, "lineage": lineage_rows, "lineage_facts": lineage_facts,
+ "resume_lineage": resume_lineage,
+ "evaluated": evaluated,
+ "evaluations": [item.succession_evaluation if isinstance(item, _EvaluatedTodo) else None
+ for item in items] if evaluated else None,
+ "source_section": source_section, "role": role,
+ "preferred_todo_ids": sorted(preferred_todo_ids or []),
+ "rollout_events": list(rollout_events or []),
+ "rollout_event_source": getattr(rollout_events, "source", None),
+ "available_capabilities": sorted(available_capabilities) if available_capabilities is not None else None,
+ "item_limit": item_limit, "evaluated_at": evaluated_at or now.isoformat(),
+ "observed_at": now.timestamp(), "full_selection": full_selection,
+ "selection": selection,
+ "include_full_source": include_full_source,
+ "include_task_orchestration_authority": include_task_orchestration_authority,
+ })
+ # The native owner validates this ephemeral evidence again on filtered reads.
+ if "items" in result:
+ result["items"] = [_EvaluatedTodo(item, decision)
+ for item, decision in zip(result["items"], result["evaluations"], strict=True)]
+ result["summary"]["items"] = [
+ _EvaluatedTodo(item, decision) for item, decision in zip(
+ result["summary"].get("items", []), result["visible_evaluations"], strict=True)
+ ]
+ return result
+
+
+def prepare_todo_source_groups(groups, *, lineage):
+ from .todo_summary import _structured_todo_group_items, _structured_resume_source_items
+ from .resume_condition import build_todo_resume_evaluation_request
+
+ prepared = []
+ for group in groups:
+ items = _structured_todo_group_items(group["items"], source_section=group["source_section"],
+ role=group["role"], text_limit=group.get("text_limit", 500),
+ include_content_revision=group.get("include_content_revision", True))
+ prepared.append({**group, "items": items,
+ "resume_lineage": build_todo_resume_evaluation_request([], source_items=_structured_resume_source_items(
+ lineage, source_section=group["source_section"]))["source_items"]
+ if any(item.get("resume_when") for item in items) else None})
+ return prepared
+
+
+def legacy_lineage_facts(items):
+ from .succession_warning import succession_facts
+
+ return ([{"todo_id": item.get("todo_id"), "role": item.get("role"), "source_section": item.get("source_section"),
+ "archive_state": item.get("archive_state") or "active"} for item in items],
+ [succession_facts(item) for item in items])
+
+
+def prepare_todo_source_text(goal, state_text, state_path, *, todo_id=None, retained=False):
+ from .active_state_todo_parser import parse_todo_source
+ from .todo_summary import _structured_resume_source_items
+ from .standing_decision import standing_decision_source_items
+ from .active_state_editing import TODO_SECTION_HEADINGS
+ from .contract import normalize_todo_id
+
+ active, archived, sections = parse_todo_source(state_text, goal=goal, state_path=state_path)
+ records = [*active["user"], *active["agent"], *archived]
+ groups = [{"role": role, "source_section": TODO_SECTION_HEADINGS[role] if retained else sections[role],
+ "items": [item for item in records if item.get("role") == role] if retained else active[role],
+ "include_empty_source": retained or sections[role] is not None,
+ "text_limit": None if retained else 500, "include_content_revision": not retained}
+ for role in ("user", "agent")]
+ archived_decisions = [item for item in archived if item.get("role") == "user"]
+ archived_query = _structured_resume_source_items(archived, source_section="Completed Work Archive") if todo_id else []
+ lineage = records if retained else [*active["user"], *active["agent"], *[item for item in archived if normalize_todo_id(item.get("todo_id"))]]
+ lineage_rows, lineage_facts = legacy_lineage_facts(lineage)
+ return {"groups": prepare_todo_source_groups(groups, lineage=records), "lineage": lineage_rows, "lineage_facts": lineage_facts,
+ "archived_items": None if retained else archived,
+ "standing_items": None if retained else standing_decision_source_items([*active["user"], *archived_decisions]),
+ "legacy_source_order": not archived_decisions,
+ "archived_items_for_query": archived_query,
+ "full_bodies": [item for item in records if item.get("todo_id") == todo_id] if todo_id else [],
+ "source_count": len(records)}
diff --git a/loopx/control_plane/todos/read_projection.ts b/loopx/control_plane/todos/read_projection.ts
new file mode 100644
index 0000000000..626010ca24
--- /dev/null
+++ b/loopx/control_plane/todos/read_projection.ts
@@ -0,0 +1,327 @@
+/** One full-source Todo read reduction; the existing owners decide every lane. */
+import {authorityUnicodeCompare} from "../coordination/authority_store_codec.ts";
+import {stripPythonWhitespace} from "../coordination/todo_agents.ts";
+import {compactTodoReadSummary} from "./read_presentation.ts";
+import type {JsonObject} from "../effect_program.ts";
+import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts";
+import {requireJsonObject, requireBoolean} from "../runtime_decode.ts";
+import {schedulerTimestampMilliseconds} from "../scheduler/state_store.ts";
+import {evaluateTodoResumeConditionsFromSource} from "./resume_condition.ts";
+import {normalizeTodoNoFollowup} from "./completion_state.ts";
+import {readTodoPriority, todoPriorityRank} from "./priority.ts";
+import {evaluateTodoSuccession, validateTodoSuccession, SUCCESSION_FACT_COLUMNS,
+ SUCCESSION_EVALUATION_COLUMNS} from "./succession.ts";
+import {projectTodoSummary, TODO_SUMMARY_PROJECTION_COLUMNS} from "./summary_projection.ts";
+import {projectTodoHandoffContext} from "./handoff_projection.ts";
+import {projectStandingDecisions} from "./standing_decision.ts";
+import codec from "./read_legacy_codec.json" with {type: "json"};
+
+export const TODO_READ_REQUEST = "todo_read_request_v0";
+export const TODO_READ_RESULT = "todo_read_result_v0";
+import {recordFields, id, object, text, ids, withoutNull, compactTodoReadText, pythonJson,
+ taskClass, canonicalTodoReadItems, structureCanonicalTodo, readAgent, readExcludedAgents, readNoFollowup, readResume, handoffSource} from "./read_codec.ts";
+
+function successionFacts(item: JsonObject): JsonObject {
+ const resume = readResume(item.resume_when) || "", handoff = readExcludedAgents(item.excluded_agents).length > 0 && id(item.unblocks_todo_id) !== null;
+ const status = stripPythonWhitespace(text(item.status)).toLowerCase();
+ return {todo_id: id(item.todo_id), status: ["open", "done", "blocked", "deferred"].includes(status) ? status : item.done ? "done" : "open",
+ active: (item.archive_state || "active") === "active", advancement: taskClass(item) === "advancement_task",
+ no_followup: readNoFollowup(item.no_followup) === true, successors: ids(item.successor_todo_ids),
+ superseded_by: id(item.superseded_by), unblocks: id(item.unblocks_todo_id),
+ resumes: resume.startsWith("todo_done:") ? id(resume.slice(10)) : null, handoff, done: item.done === true,
+ route_flag: typeof item.route_continuation_replan_required === "boolean" ? item.route_continuation_replan_required : null,
+ legacy_route_label: handoff && typeof item.route_continuation_replan_required !== "boolean"
+ ? [item.action_kind, item.title, item.text].filter(v => text(v).trim()).map(text).join(" ") : "",
+ context_fields: Object.keys(withoutNull(item)).filter(key => key !== "succession_evaluation").sort(authorityUnicodeCompare)};
+}
+function compactRecord(item: JsonObject): JsonObject {
+ const result: JsonObject = {index: item.index ?? null, done: Boolean(item.done), text: item.text ?? null};
+ for (const key of recordFields) if (!Object.hasOwn(result, key) && item[key] != null) result[key] = item[key]!;
+ if (item.content_revision) result.content_revision = item.content_revision;
+ if (item.goal_acceptance_guard && typeof item.goal_acceptance_guard === "object") result.goal_acceptance_guard = item.goal_acceptance_guard;
+ return result;
+}
+
+function summaryRow(item: JsonObject, evaluation: JsonObject, preferred: Set): JsonObject {
+ const resume = readResume(item.resume_when) || "", status = item.status || (item.done ? "done" : "open");
+ const index = typeof item.index === "number" && Number.isInteger(item.index) && item.index >= 0 ? item.index : 999999;
+ return {status, done: Boolean(item.done), task_class: taskClass(item), has_resume: Boolean(resume), resume_ready: item.resume_ready ?? null,
+ resume_evaluated: resumeEvaluated(item),
+ acceptance_blocked: object(item.goal_acceptance_guard).allowed === false, claimed: Boolean(item.claimed_by),
+ preferred: preferred.has(item.todo_id), watch_only: taskClass(item) === "continuous_monitor" && (item.watch_only === true || stripPythonWhitespace(text(item.watch_only || "")).toLowerCase() === "true"),
+ due_at: readScheduleTime(item.next_due_at), expires_at: readScheduleTime(item.expires_at),
+ sort: [todoPriorityRank(readTodoPriority(item)), index,
+ index === 999999 ? text(item.completed_at || item.updated_at) : "", index === 999999 ? text(item.todo_id) : ""],
+ completed_at: item.completed_at || null, updated_at: item.updated_at || null, completion_index: Number(item.index || 0),
+ linked_user_action: id(item.unblocks_todo_id) !== null, no_followup: readNoFollowup(item.no_followup) === true,
+ successor_gap: evaluation.successor_gap!, handoff_state: evaluation.handoff_state!, replan: evaluation.route_continuation_replan_required!,
+ todo_id: id(item.todo_id), claim: readAgent(item.claimed_by), bound: readAgent(item.bound_agent), blocks: readAgent(item.blocks_agent),
+ global: Boolean(item.global_gate), excluded: readExcludedAgents(item.excluded_agents)};
+}
+
+function resumeEvaluated(item: JsonObject): boolean {
+ const resume = readResume(item.resume_when), condition = object(item.resume_condition);
+ return !resume || condition.schema_version === "todo_resume_condition_v0" && condition.resume_when === resume &&
+ typeof condition.satisfied === "boolean" && item.resume_ready === condition.satisfied;
+}
+
+function readScheduleTime(value: unknown): number | null {
+ if (typeof value === "string") {
+ const date = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/.exec(value.trim());
+ if (date) {
+ const year = Number(date[1]), month = Number(date[2]), day = Number(date[3]);
+ const check = new Date(0); check.setUTCFullYear(year, month - 1, day);
+ if (check.getUTCFullYear() !== year || check.getUTCMonth() !== month - 1 || check.getUTCDate() !== day) return null;
+ }
+ }
+ const milliseconds = schedulerTimestampMilliseconds(value);
+ return milliseconds === null ? null : milliseconds / 1000;
+}
+
+function frontierFacts(items: JsonObject[]): JsonObject[] {
+ return items.map(item => ({id: stripPythonWhitespace(text(item.todo_id)), claim: readAgent(item.claimed_by),
+ excluded: readExcludedAgents(item.excluded_agents), updated: stripPythonWhitespace(text(item.updated_at || item.completed_at)),
+ advancement: taskClass(item) === "advancement_task", actionable: item.done !== true && item.status === "open" && (!readResume(item.resume_when) || item.resume_ready === true) && object(item.goal_acceptance_guard).allowed !== false,
+ serialized: pythonJson(Object.fromEntries(codec.frontier_fields.filter(key => item[key] != null).map(key => [key, item[key]!])))}));
+}
+
+function handoffGates(items: JsonObject[], evaluations: JsonObject[]): JsonObject[] {
+ const fields = ["todo_id", "role", "task_class", "action_kind", "task_domain", "task_repository", "continuation_policy", "claimed_by", "unblocks_todo_id", "resume_when", "no_followup", "superseded_by", "route_continuation_replan_required", "route_continuation_reason", "route_id", "route_key", "next_due_at"];
+ return items.flatMap((item, i) => {
+ const decision = evaluations[i]!;
+ if (decision.handoff_state == null) return [];
+ const successors = decision.successor_todo_ids as string[];
+ return [withoutNull({schema_version: "todo_handoff_gate_v1", gate_state: decision.handoff_state,
+ index: item.index ?? null, done: item.done === true || item.status === "done" || item.status === "deferred", status: item.status!,
+ text: stripPythonWhitespace(text(item.text)), excluded_agents: item.excluded_agents ?? [], successor_count: successors.length,
+ ...Object.fromEntries(fields.filter(key => item[key] != null).map(key => [key, item[key]!])),
+ ...(decision.route_continuation_replan_required ? {route_continuation_replan_required: true,
+ route_continuation_reason: item.route_continuation_reason ?? "stale handoff closeout requires a bounded route continuation replan"} : {}),
+ ...(successors.length ? {successor_todo_ids: successors} : {})})];
+ }).sort((a, b) => Number(a.index || 999999) - Number(b.index || 999999) || authorityUnicodeCompare(text(a.todo_id), text(b.todo_id)));
+}
+
+export async function projectTodoReadGroup(input: JsonObject): Promise {
+ if (!Array.isArray(input.items) || !Array.isArray(input.lineage)) throw new EffectRuntimeRequestError("Todo read requires complete source arrays");
+ const items = input.items.map(value => ({...requireJsonObject(value, "Todo read item")}));
+ const lineage = input.lineage.map(value => requireJsonObject(value, "Todo read lineage"));
+ const lineageFacts = input.lineage_facts == null ? lineage.map(successionFacts) : input.lineage_facts;
+ if (!Array.isArray(lineageFacts) || lineageFacts.length !== lineage.length) throw new EffectRuntimeRequestError("Todo lineage fact cardinality mismatch");
+ for (const [index, value] of lineageFacts.entries()) {
+ const fact = requireJsonObject(value, "Todo lineage facts");
+ if (fact.todo_id !== id(lineage[index]!.todo_id) || fact.active !== ((lineage[index]!.archive_state || "active") === "active")) {
+ throw new EffectRuntimeRequestError("Todo lineage facts disagree with source identity");
+ }
+ }
+ const evaluated = requireBoolean(input.evaluated, "evaluated");
+ if (evaluated && items.some(item => !resumeEvaluated(item))) {
+ throw new EffectRuntimeRequestError("Todo display requires a matching full-source resume evaluation");
+ }
+ if (!evaluated) {
+ const waiting = items.filter(item => readResume(item.resume_when));
+ if (waiting.length) {
+ const result = await evaluateTodoResumeConditionsFromSource({schema_version: "todo_resume_evaluation_request_v0", items: waiting,
+ source_items: [...(Array.isArray(input.resume_lineage) ? input.resume_lineage : lineage), ...items], rollout_events: input.rollout_events ?? [], evaluated_at: input.evaluated_at!,
+ ...(input.rollout_event_source == null ? {} : {rollout_event_source: input.rollout_event_source}),
+ available_capabilities: input.available_capabilities ?? null});
+ const conditions = new Map((result.conditions as JsonObject[]).map(row => [row.todo_id, row.condition]));
+ for (const item of waiting) {
+ item.resume_condition = conditions.get(item.todo_id) ?? {schema_version: "todo_resume_condition_v0", resume_when: item.resume_when!, satisfied: false, unsupported: true};
+ item.resume_ready = object(item.resume_condition).satisfied === true;
+ }
+ }
+ }
+ const facts = items.map(successionFacts);
+ let decisions: JsonObject[];
+ if (items.length === 0) {
+ decisions = [];
+ } else if (evaluated) {
+ if (!Array.isArray(input.evaluations) || input.evaluations.length !== items.length || input.evaluations.some(value => value == null)) throw new EffectRuntimeRequestError("Todo display requires a matching full-source succession evaluation");
+ const evaluations = input.evaluations;
+ decisions = facts.map((row, i) => validateTodoSuccession(row, evaluations[i]));
+ } else {
+ const selected = new Map();
+ const key = (row: JsonObject) => JSON.stringify([id(row.todo_id), row.role ?? null, row.archive_state || "active"]);
+ for (const item of items) selected.set(key(item), (selected.get(key(item)) ?? 0) + 1);
+ const source: JsonObject[] = [];
+ lineage.forEach((row, index) => {
+ const count = selected.get(key(row)) ?? 0;
+ if (!count) source.push(requireJsonObject(lineageFacts[index], "Todo lineage facts"));
+ else selected.set(key(row), count - 1);
+ });
+ try {
+ decisions = evaluateTodoSuccession([...source, ...facts]).slice(source.length);
+ } catch (error) {
+ if (!(error instanceof EffectRuntimeRequestError)) throw error;
+ return {schema_version: TODO_READ_RESULT, rejection: {phase: "succession", message: error.message}};
+ }
+ }
+ const preferred = new Set(Array.isArray(input.preferred_todo_ids) ? input.preferred_todo_ids : []);
+ const rows = items.map((item, i) => summaryRow(item, decisions[i]!, preferred));
+ const contexts = facts.map(row => row.context_fields!);
+ const compacts = items.map(compactRecord);
+ const projected = projectTodoSummary({schema_version: "todo_summary_projection_request_v3", columns: [...TODO_SUMMARY_PROJECTION_COLUMNS],
+ rows: rows.map(row => TODO_SUMMARY_PROJECTION_COLUMNS.map(name => row[name]!)),
+ succession: {schema_version: "todo_succession_request_v1", row_columns: [...SUCCESSION_FACT_COLUMNS],
+ rows: facts.map((row, i) => SUCCESSION_FACT_COLUMNS.map(name => name === "context_fields" ? i : row[name]!)),
+ context_field_sets: contexts, evaluation_columns: [...SUCCESSION_EVALUATION_COLUMNS],
+ evaluations: decisions.map(row => SUCCESSION_EVALUATION_COLUMNS.map(name => row[name]!))},
+ frontier_rows: input.role === "agent" ? frontierFacts(items) : null,
+ observed_at: input.observed_at!, selection: input.selection ?? null, role: input.role ?? null,
+ source_section: input.source_section ?? null, item_limit: input.item_limit ?? null, full_selection: input.full_selection ?? true});
+ const visible = [...new Set([...Object.values(projected.lanes).flatMap(lane => lane.format === "raw" ? [] : lane.indices),
+ ...Object.values(projected.orchestration).flat()])].sort((a, b) => a - b);
+ const handoffs = projectTodoHandoffContext(visible.map(index => handoffSource(compacts[index]!)));
+ visible.forEach((index, i) => {if (handoffs[i]!.note) compacts[index]!.handoff_note = handoffs[i]!.note;});
+ const summary = projected.fields;
+ for (const [name, lane] of Object.entries(projected.lanes)) {
+ summary[name] = lane.indices.map(index => {
+ if (lane.format === "raw") return items[index]!;
+ const row = {...compacts[index]!};
+ const removed = lane.format === "active" ? ["note", "evidence", "reason", "completed_at", "updated_at", "superseded_by"] :
+ lane.format === "recent" || lane.format === "gap" ? ["note", "evidence", "reason"] : [];
+ for (const key of removed) delete row[key];
+ if (lane.format === "gap") Object.assign(row, {succession_tracked: true, recommended_action: object(summary.todo_succession_warning).recommended_action});
+ return row;
+ });
+ }
+ if (summary.todo_succession_warning) object(summary.todo_succession_warning).items = summary.completed_without_successor_items!;
+ const chosen = projected.source_indices.map(index => items[index]!);
+ const chosenDecisions = projected.source_indices.map(index => decisions[index]!);
+ const gates = handoffGates(chosen, chosenDecisions);
+ if (gates.length) summary.handoff_gates = gates;
+ return {schema_version: TODO_READ_RESULT, summary, items: chosen, evaluations: chosenDecisions,
+ visible_evaluations: projected.lanes.items!.indices.map(index => decisions[index]!),
+ orchestration: Object.fromEntries(Object.entries(projected.orchestration).map(([name, ordinals]) => [name, ordinals.map(i => compacts[i]!)]))};
+}
+
+export async function projectCanonicalTodoFields(input: JsonObject): Promise {
+ if (!Array.isArray(input.todos)) throw new EffectRuntimeRequestError("canonical Todo read source must be an array");
+ const original = input.todos.map(value => requireJsonObject(value, "canonical Todo source"));
+ const decisions = original.filter(row => row.role === "user");
+ const standing = decisions.length ? projectStandingDecisions(decisions, false) : null;
+ const all = input.presentation_ready === true ? original : canonicalTodoReadItems(original);
+ const archived = new Set(all.filter(row => row.archive_state === "archive").map(row => row.todo_id));
+ const acceptance = object(input.goal_acceptance_contract), guards = object(input.goal_acceptance_work_guards);
+ 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)))
+ .map(row => structureCanonicalTodo({...row,
+ ...(input.retained !== true && acceptance.enabled === true && guards[text(row.todo_id)] ? {goal_acceptance_guard: guards[text(row.todo_id)]} : {})}, role, section,
+ input.retained === true ? null : 500, input.retained !== true));
+ const lineage = Array.isArray(input.lineage) ? input.lineage.map(value => requireJsonObject(value, "Todo lineage")) : all;
+ const grouped = await projectTodoReadGroup({...input, items, lineage, evaluated: false, role, source_section: section,
+ item_limit: null, full_selection: true, resume_lineage: all.map(row => structureCanonicalTodo(row, row.role ?? null,
+ row.source_section ?? section))});
+ if (grouped.rejection) return grouped;
+ const summary = requireJsonObject(grouped.summary, "canonical Todo summary");
+ if (role === "agent" && input.retained !== true) {
+ if (acceptance.enabled === true) summary.goal_acceptance_contract = acceptance;
+ const done = all.filter(row => archived.has(row.todo_id) && row.done === true && taskClass(row) === "advancement_task").length;
+ if (done) {summary.archived_advancement_done_count = done; summary.advancement_done_count = Number(summary.advancement_done_count || 0) + done;}
+ }
+ fields[`${role}_todos`] = summary;
+ evaluations[`${role}_todos`] = grouped.visible_evaluations;
+ }
+ if (standing && input.retained !== true) fields.standing_decision_authority = standing;
+ return {schema_version: TODO_READ_RESULT, fields, field_evaluations: evaluations};
+}
+
+function emptySummary(role: string): JsonObject {
+ return {schema_version: "todo_summary_v0", role, source_section: role === "agent" ? "Agent Todo" : "User Todo / Owner Review Reading Queue",
+ total_count: 0, open_count: 0, done_count: 0, items: [], first_open_items: []};
+}
+
+export async function selectTodoReadFields(input: JsonObject): Promise {
+ const fields = requireJsonObject(input.fields, "Todo source fields"), evaluations = requireJsonObject(input.field_evaluations, "Todo source evaluations");
+ if (!Array.isArray(input.roles) || input.roles.some(role => role !== "user" && role !== "agent")) throw new EffectRuntimeRequestError("Todo read roles must be user or agent");
+ const summaries: JsonObject = {}, todos: unknown[] = [];
+ let unfiltered = 0, uncapped = 0;
+ for (const role of input.roles as string[]) {
+ const key = `${role}_todos`, raw = object(fields[key]), items = Array.isArray(raw.items) ? raw.items : [];
+ unfiltered += items.length;
+ let summary = emptySummary(role);
+ if (items.length) {
+ const projected = await projectTodoReadGroup({...input, items, lineage: [], lineage_facts: [], role,
+ source_section: raw.source_section || summary.source_section, evaluated: true,
+ evaluations: evaluations[key], item_limit: input.limit ?? null, full_selection: true,
+ selection: {role, status: input.status ?? null, todo_id: input.todo_id ?? null, agent_id: input.agent_id ?? null}});
+ if (projected.rejection) return projected;
+ if ((projected.items as unknown[]).length) summary = requireJsonObject(projected.summary, "selected Todo summary");
+ }
+ if (input.limit != null) summary = compactTodoReadSummary(summary, role, Number(input.limit));
+ summaries[key] = summary;
+ todos.push(...summary.items as unknown[]);
+ uncapped += Number(summary.total_count || 0);
+ }
+ return {schema_version: TODO_READ_RESULT, projection: {source: input.source_name!, summaries, todos,
+ unfiltered_count: unfiltered, uncapped_todo_count: uncapped}};
+}
+
+export async function projectSourceTodoFields(input: JsonObject): Promise {
+ if (!Array.isArray(input.groups)) throw new EffectRuntimeRequestError("Todo source requires role groups");
+ let groups = input.groups;
+ const query = input.projection_query == null ? null : requireJsonObject(input.projection_query, "Todo projection query");
+ if (query?.exact_archive === true) {
+ const target = (input.lineage as JsonObject[]).find(row => id(row.todo_id) === query.todo_id && row.archive_state === "archive");
+ if (!target || !(query.roles as unknown[]).includes(target.role)) return {schema_version: TODO_READ_RESULT, projection: null};
+ const group = groups.map(value => requireJsonObject(value, "Todo archive group")).find(row => row.role === target.role)!;
+ const item = (group.items as JsonObject[]).find(row => id(row.todo_id) === query.todo_id)!;
+ groups = [{...group, items: [{...item, source_section: String(target.source_section || "Completed Work Archive")}],
+ source_section: String(target.source_section || "Completed Work Archive"), include_empty_source: true}];
+ }
+ const fields: JsonObject = {}, evaluations: JsonObject = {}, sources: JsonObject = {}, sourceEvaluations: JsonObject = {}, orchestration: JsonObject = {};
+ for (const value of groups) {
+ const group = requireJsonObject(value, "Todo source role group");
+ const projected = await projectTodoReadGroup({...input, ...group, evaluated: false});
+ if (projected.rejection) return projected;
+ const items = projected.items as unknown[];
+ if (!items.length && group.include_empty_source !== true) continue;
+ fields[`${String(group.role)}_todos`] = projected.summary;
+ evaluations[`${String(group.role)}_todos`] = projected.visible_evaluations;
+ if (input.include_full_source === true) {
+ sources[String(group.role)] = projected.items;
+ sourceEvaluations[String(group.role)] = projected.evaluations;
+ }
+ if (input.include_task_orchestration_authority === true) orchestration[String(group.role)] = projected.orchestration;
+ }
+ const agent = object(fields.agent_todos);
+ if (Object.keys(agent).length && Array.isArray(input.archived_items)) {
+ const count = input.archived_items.filter(value => {
+ const row = requireJsonObject(value, "archived Todo source");
+ return row.done === true && taskClass({...row, text: compactTodoReadText(text(row.text))}) === "advancement_task";
+ }).length;
+ if (count) {agent.archived_advancement_done_count = count; agent.advancement_done_count = Number(agent.advancement_done_count || 0) + count;}
+ }
+ if (Array.isArray(input.standing_items) && input.standing_items.length) {
+ const standing = projectStandingDecisions(input.standing_items.map(value => requireJsonObject(value, "standing decision source")), input.legacy_source_order === true);
+ if (standing) fields.standing_decision_authority = standing;
+ }
+ const result = {schema_version: TODO_READ_RESULT, fields, field_evaluations: evaluations,
+ ...(input.include_full_source === true ? {source_items: sources, source_evaluations: sourceEvaluations} : {}),
+ ...(input.include_task_orchestration_authority === true ? {source_orchestration: orchestration} : {})};
+ return query === null ? result : selectTodoReadFields({...input, ...query, fields, field_evaluations: evaluations});
+}
+
+export async function readTodo(value: unknown, observedAt = new Date().toISOString()): Promise {
+ const input = requireJsonObject(value, "Todo read request");
+ if (input.schema_version !== TODO_READ_REQUEST || !["group", "fields", "canonical_fields", "select", "source_fields", "query", "historical_fields"].includes(text(input.kind))) {
+ throw new EffectRuntimeRequestError("Todo read request schema mismatch");
+ }
+ if (input.kind === "canonical_fields") {
+ const {readCanonicalTodoProjection} = await import("./read_transaction.ts");
+ return readCanonicalTodoProjection({...input, read_observed_at: observedAt});
+ }
+ if (input.kind === "query") {
+ const {readTodoQuery} = await import("./read_transaction.ts");
+ return readTodoQuery({...input, read_observed_at: observedAt});
+ }
+ if (input.kind === "historical_fields") {
+ const {readHistoricalTodoProjection} = await import("./read_transaction.ts");
+ return readHistoricalTodoProjection(input);
+ }
+ if (input.kind === "select") return selectTodoReadFields(input);
+ if (input.kind === "source_fields") return projectSourceTodoFields(input);
+ return input.kind === "fields" ? projectCanonicalTodoFields(input) : projectTodoReadGroup(input);
+}
diff --git a/loopx/control_plane/todos/read_transaction.ts b/loopx/control_plane/todos/read_transaction.ts
new file mode 100644
index 0000000000..87c1656ecc
--- /dev/null
+++ b/loopx/control_plane/todos/read_transaction.ts
@@ -0,0 +1,166 @@
+/** Canonical provider IO and full-source summaries share one read transaction. */
+import type {JsonObject} from "../effect_program.ts";
+import {requireJsonObject} from "../runtime_decode.ts";
+import {stat} from "node:fs/promises";
+import {legacyCoordinationWriterFencePath} from "../coordination/legacy_writer_fence.ts";
+import {requireLocalAuthorityRuntimeRoot} from "../coordination/local_authority_provider.ts";
+import {requireAuthorityStoreId} from "../coordination/authority_store_codec.ts";
+import {readCanonicalSnapshot} from "../coordination/canonical_snapshot_page.ts";
+import {readLocalCoordinationTodoSource} from "../coordination/local_authority_read.ts";
+import {LOCAL_COORDINATION_TODO_SNAPSHOT_PAGE_REQUEST_SCHEMA,
+ LOCAL_COORDINATION_TODO_LIST_RESULT_SCHEMA} from "../coordination/coordination_state_contract.generated.ts";
+import {projectCanonicalTodoFields, selectTodoReadFields, projectSourceTodoFields, projectTodoReadGroup, TODO_READ_RESULT} from "./read_projection.ts";
+import {canonicalTodoReadItems, structureCanonicalTodo} from "./read_codec.ts";
+import {compactTodoReadSummary, compactThinTodoRead, todoReadProjectionContract} from "./read_presentation.ts";
+import {projectTodoContextPage} from "./context_projection.ts";
+import {sealProjectionEnvelope, PROJECTION_ENVELOPE_SEAL_REQUEST} from "../projection_envelope.ts";
+
+export async function readCanonicalTodoProjection(input: JsonObject): Promise {
+ const source = requireJsonObject(input.source, "canonical Todo read source");
+ const todos: unknown[] = [], leases: unknown[] = [], guards: JsonObject = {};
+ let after: unknown = null, first: JsonObject | null = null;
+ do {
+ const page = await readCanonicalSnapshot({schema_version: LOCAL_COORDINATION_TODO_SNAPSHOT_PAGE_REQUEST_SCHEMA,
+ runtime_root: source.runtime_root, goal_id: source.goal_id, include_leases: source.include_leases === true,
+ projection_readback: source.projection_readback ?? null, after});
+ if (page.status !== "page") return {schema_version: TODO_READ_RESULT, canonical: {
+ ...page, schema_version: LOCAL_COORDINATION_TODO_LIST_RESULT_SCHEMA}};
+ if (first === null) first = page;
+ todos.push(...page.todos as unknown[]);
+ if (Array.isArray(page.leases)) leases.push(...page.leases);
+ Object.assign(guards, page.goal_acceptance_work_guards ?? {});
+ after = page.next;
+ } while (after !== null);
+ const snapshot = requireJsonObject(first!.snapshot, "canonical snapshot identity");
+ const canonical: JsonObject = {schema_version: LOCAL_COORDINATION_TODO_LIST_RESULT_SCHEMA,
+ status: "loaded", ...requireJsonObject(first!.metadata, "canonical Todo metadata"),
+ todos, todo_ids: todos.map(value => requireJsonObject(value, "canonical Todo").todo_id),
+ store_identity: snapshot.store_identity,
+ ...(source.include_leases === true ? {leases} : {}),
+ source_authority: first!.source_authority, provider_revision: snapshot.provider_revision,
+ cursor: snapshot.cursor, decision_read_from_provider: true, legacy_fallback_used: false,
+ ...(Object.hasOwn(requireJsonObject(first!.metadata, "canonical metadata"), "goal_acceptance_contract") ? {goal_acceptance_work_guards: guards} : {})};
+ const projected = await projectCanonicalTodoFields({...input, todos, retained: requireJsonObject(input.query ?? {}, "Todo query").read_scope === "completed_history",
+ goal_acceptance_contract: canonical.goal_acceptance_contract ?? null,
+ goal_acceptance_work_guards: guards});
+ if (input.query == null || projected.rejection) return {...projected, canonical};
+ return todoListPayload(input, originalRecords(canonical.todos), projected, canonical);
+}
+
+const originalRecords = (value: unknown): JsonObject[] => Array.isArray(value) ? value.map(row => requireJsonObject(row, "Todo source row")) : [];
+
+export async function readHistoricalTodoProjection(input: JsonObject): Promise {
+ const source = requireJsonObject(input.source, "historical Todo read source");
+ const canonical = await readLocalCoordinationTodoSource({schema_version: "loopx_local_coordination_todo_source_request_v0",
+ runtime_root: source.runtime_root, goal_id: source.goal_id, source: source.original_source});
+ if (canonical.status !== "loaded") return {schema_version: TODO_READ_RESULT, canonical};
+ return {...await projectCanonicalTodoFields({...input, todos: canonical.todos,
+ goal_acceptance_contract: canonical.goal_acceptance_contract ?? null,
+ goal_acceptance_work_guards: canonical.goal_acceptance_work_guards ?? null}), canonical};
+}
+
+export async function readTodoQuery(input: JsonObject): Promise {
+ const source = requireJsonObject(input.source, "Todo read source");
+ const root = requireLocalAuthorityRuntimeRoot(source.runtime_root), goal = requireAuthorityStoreId(source.goal_id, "goal id");
+ let promoted = false;
+ try {
+ await stat(legacyCoordinationWriterFencePath(root, goal));
+ promoted = true;
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") return {schema_version: TODO_READ_RESULT, canonical: {
+ status: "failed", reason_code: "local_authority_mode_read_failed", reason: "local coordination authority mode cannot be inspected",
+ source_authority: "unknown_fail_closed"}};
+ }
+ if (promoted) return readCanonicalTodoProjection(input);
+ if (input.legacy_source == null) return {schema_version: TODO_READ_RESULT, source_request: "legacy_todo_source"};
+ const legacy = requireJsonObject(input.legacy_source, "legacy Todo source");
+ const projected = await projectSourceTodoFields({...input, ...legacy});
+ if (projected.rejection) return projected;
+ return todoListPayload(input, originalRecords(legacy.full_bodies), projected, null);
+}
+
+async function todoListPayload(input: JsonObject, original: JsonObject[], fields: JsonObject, canonical: JsonObject | null): Promise {
+ const query = requireJsonObject(input.query, "Todo list query");
+ const sourceName = canonical === null ? "markdown_active_state" : "file_authority";
+ const roles: unknown[] = query.role == null ? ["user", "agent"] : [query.role];
+ const select = async (source: JsonObject) => selectTodoReadFields({...input, fields: source.fields,
+ field_evaluations: source.field_evaluations, roles, status: query.status ?? null,
+ todo_id: query.todo_id ?? null, agent_id: query.agent_id ?? null, limit: query.limit ?? null, source_name: sourceName});
+ let selected = await select(fields);
+ if (selected.rejection) return selected;
+ let projection = requireJsonObject(selected.projection, "Todo read projection");
+ if (query.read_scope !== "completed_history" && query.todo_id && !(projection.todos as unknown[]).length) {
+ const legacy = requireJsonObject(input.legacy_source ?? {}, "legacy Todo source");
+ const archived = canonical === null ? originalRecords(legacy.archived_items_for_query) : canonicalTodoReadItems(original).filter(row => row.archive_state === "archive");
+ const target = archived.find(row => row.todo_id === query.todo_id);
+ if (target && roles.includes(target.role)) {
+ const section = target.source_section || "Completed Work Archive";
+ const item = canonical === null ? target : structureCanonicalTodo(target, target.role, section);
+ const grouped = await projectTodoReadGroup({...input, items: [item], role: item.role, source_section: section,
+ lineage: archived, resume_lineage: archived.map(row => canonical === null ? row : structureCanonicalTodo(row, row.role ?? null, row.source_section ?? section)),
+ evaluated: false, item_limit: null, full_selection: true,
+ ...(canonical === null && Array.isArray(legacy.archived_handoffs) ? {handoff_sources: [legacy.archived_handoffs[archived.indexOf(target)]]} : {})});
+ if (grouped.rejection) return grouped;
+ const key = `${String(target.role)}_todos`, exact = {fields: {[key]: grouped.summary}, field_evaluations: {[key]: grouped.visible_evaluations}};
+ selected = await select(exact);
+ if (selected.rejection) return selected;
+ projection = requireJsonObject(selected.projection, "archived Todo projection");
+ }
+ }
+ let todos = projection.todos as JsonObject[];
+ if (query.todo_id && todos.length === 1) {
+ const item = todos[0]!;
+ const matching = original.filter(row => row.todo_id === query.todo_id && row.role === item.role &&
+ (row.archive_state ?? "active") === (item.archive_state ?? "active"));
+ if (matching.length !== 1) throw new Error("Todo detail cannot recover an unambiguous full source body");
+ item.text = String(matching[0]!.text ?? "");
+ }
+ const matchedCount = todos.length;
+ const hot = query.read_scope !== "completed_history" && query.agent_id && query.limit == null &&
+ query.role == null && query.status == null && query.todo_id == null;
+ if (hot) {
+ const summaries = requireJsonObject(projection.summaries, "Todo summaries");
+ for (const [key, summary] of Object.entries(summaries)) summaries[key] = compactTodoReadSummary(
+ requireJsonObject(summary, "Todo summary"), key.replace(/_todos$/, ""), null);
+ todos = Object.values(summaries).flatMap(value => requireJsonObject(value, "Todo summary").items as JsonObject[]);
+ }
+ const payload: JsonObject = {ok: true, dry_run: true, read_only: true, command: "list", goal_id: query.goal_id,
+ role: query.role ?? "all", status_filter: query.status ?? null, source: sourceName, todo_count: matchedCount,
+ todos, state_file: query.state_file, project: query.project ?? null,
+ ...(canonical === null ? {} : {authority_read: {source_authority: canonical.source_authority, provider_revision: canonical.provider_revision,
+ cursor: canonical.cursor, todo_read_model: canonical.todo_read_model,
+ decision_read_from_provider: true, legacy_fallback_used: false}})};
+ if (query.agent_id) Object.assign(payload, {agent_id_filter: query.agent_id, unfiltered_todo_count: projection.unfiltered_count,
+ filter_semantics: "agent todos include unclaimed items plus claimed_by=; " +
+ "User gates use global_gate, then blocks_agent, then legacy claimed_by scope; " +
+ "User actions use bound_agent, then legacy claimed_by scope; unscoped items remain visible"});
+ if (hot) Object.assign(payload, {returned_todo_count: todos.length,
+ todo_list_projection: todoReadProjectionContract(matchedCount, todos.length, null)});
+ if (query.limit != null) Object.assign(payload, {explicit_limit: query.limit,
+ unfiltered_todo_count: projection.unfiltered_count, returned_todo_count: todos.length,
+ todo_list_projection: todoReadProjectionContract(Number(projection.uncapped_todo_count), todos.length, Number(query.limit))});
+ if (query.todo_id) {
+ const item = todos[0], relations: JsonObject = {};
+ const relationFields = ["claimed_by", "bound_agent", "goal_bound", "blocks_agent", "excluded_agents", "global_gate", "unblocks_todo_id",
+ "successor_todo_ids", "completion_continuation", "completion_recovery", "superseded_by", "resume_when", "resume_monitor_generation",
+ "resume_condition", "resume_ready", "decision_scope", "required_decision_scopes", "required_write_scopes", "required_capabilities",
+ "target_capabilities", "task_class", "action_kind", "continuation_policy", "target_key", "cadence", "next_due_at", "expires_at"];
+ if (item) for (const key of relationFields) if (item[key] != null && (!Array.isArray(item[key]) || (item[key] as unknown[]).length)) relations[key] = item[key];
+ Object.assign(payload, {todo_id_filter: query.todo_id, matched: todos.length > 0, relations,
+ ...(todos.length > 1 ? {ambiguous: true} : {}), ...(todos.length === 0 ? {not_found: true} : {})});
+ }
+ Object.assign(payload, projection.summaries);
+ // The existing formatter is composed here, so a public read never calls back
+ // across runtimes for fields of the result it just received.
+ const result = query.todo_id ? projectTodoContextPage({detail_payload: payload}) : query.thin ? compactThinTodoRead(payload) :
+ query.json_references ? projectTodoContextPage({list_payload: payload}) : payload;
+ const observed = String(input.read_observed_at);
+ result.projection_envelope = sealProjectionEnvelope({schema_version: PROJECTION_ENVELOPE_SEAL_REQUEST,
+ projection: "goal_todo_read", observed_at: observed,
+ sources: [{source_id: sourceName, read_status: "read", last_read_at: observed,
+ item_count: canonical === null ? Number(requireJsonObject(input.legacy_source, "legacy source").source_count) : original.length}],
+ coverage: {scope: "goal_todo_query", expected_count: projection.uncapped_todo_count,
+ included_count: todos.length, shown_count: todos.length, available_count: projection.uncapped_todo_count,
+ omitted: Number(projection.uncapped_todo_count) > todos.length ? [{reason: "query_limit", count: Number(projection.uncapped_todo_count) - todos.length}] : []}});
+ return {schema_version: TODO_READ_RESULT, payload: result};
+}
diff --git a/loopx/control_plane/todos/read_transport.ts b/loopx/control_plane/todos/read_transport.ts
new file mode 100644
index 0000000000..1c8eff700c
--- /dev/null
+++ b/loopx/control_plane/todos/read_transport.ts
@@ -0,0 +1,65 @@
+/** Python transport only; native callers consume readTodo in process. */
+import {createHash} from "node:crypto";
+import {readTodo, TODO_READ_REQUEST, TODO_READ_RESULT} from "./read_projection.ts";
+import type {JsonObject} from "../effect_program.ts";
+import {MAX_LOCAL_SNAPSHOT_BYTES, openPrivateSnapshotSource} from "../effect_runtime_snapshot.ts";
+import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts";
+import {requireJsonObject, requireNonEmptyString} from "../runtime_decode.ts";
+import {BARE_SHA256_PATTERN} from "../content_digest.ts";
+
+const completeBytes = MAX_LOCAL_SNAPSHOT_BYTES - 2 * 1024 * 1024;
+const pieceBytes = Math.floor(completeBytes / 4) * 3;
+
+async function sourceFacts(value: unknown): Promise {
+ const ref = requireJsonObject(value, "Todo fact source reference");
+ const digest = requireNonEmptyString(ref.sha256, "Todo fact source digest"), size = ref.byte_count;
+ if (!BARE_SHA256_PATTERN.test(digest) || typeof size !== "number" || !Number.isSafeInteger(size) || size <= 0) {
+ throw new EffectRuntimeRequestError("invalid complete Todo fact source");
+ }
+ const file = await openPrivateSnapshotSource(ref.path);
+ try {
+ const before = await file.stat();
+ if (before.size !== size) throw new Error("Todo source size changed");
+ const bytes = await file.readFile(), after = await file.stat();
+ if (bytes.length !== size || after.size !== size || before.ino !== after.ino || before.dev !== after.dev ||
+ createHash("sha256").update(bytes).digest("hex") !== digest) throw new Error("Todo source identity changed");
+ return requireJsonObject(JSON.parse(new TextDecoder("utf-8", {fatal: true, ignoreBOM: true}).decode(bytes)), "Todo source facts");
+ } finally {await file.close();}
+}
+
+export async function readTodoRuntime(value: unknown): Promise {
+ const request = requireJsonObject(value, "Todo read request");
+ if (request.schema_version !== TODO_READ_REQUEST) throw new EffectRuntimeRequestError("Todo read request schema mismatch");
+ let input = request;
+ if (request.facts_ref !== undefined) {
+ try {input = await sourceFacts(request.facts_ref);}
+ catch {throw new EffectRuntimeRequestError("complete Todo fact source unavailable or invalid; repeat the original read");}
+ if (input.facts_ref !== undefined || input.schema_version !== TODO_READ_REQUEST) throw new EffectRuntimeRequestError("invalid Todo fact source envelope");
+ }
+ const page = request.result_page == null ? null : requireJsonObject(request.result_page, "Todo result page");
+ const observed = page === null ? new Date().toISOString() : requireNonEmptyString(page.observed_at, "original read observation");
+ const clock = Date.parse(observed);
+ if (!Number.isFinite(clock) || clock > Date.now()) throw new EffectRuntimeRequestError("invalid original Todo read observation");
+ let result = await readTodo(input, observed);
+ if (result.rejection || result.source_request || (result.canonical && !result.fields && !result.payload)) return result;
+ if (input.kind === "group") {
+ const {items, evaluations, orchestration, ...projection} = result;
+ result = {...projection, selected_count: (items as unknown[]).length,
+ ...(input.include_full_source === true ? {items, evaluations} : {}),
+ ...(input.include_task_orchestration_authority === true ? {orchestration} : {})};
+ }
+ const bytes = Buffer.from(JSON.stringify(result), "utf8");
+ if (page === null && bytes.length <= completeBytes) return result;
+ const digest = createHash("sha256").update(bytes).digest("hex");
+ const offset = page === null ? 0 : page.offset;
+ if (typeof offset !== "number" || !Number.isSafeInteger(offset) || offset < 0 || offset >= bytes.length || offset % pieceBytes !== 0 ||
+ (page !== null && page.sha256 !== digest)) {
+ return {schema_version: TODO_READ_RESULT, canonical: {status: "failed", reason_code: "canonical_snapshot_changed",
+ reason: "Todo read result changed; restart the complete read, never append a newer page",
+ source_authority: "unknown_fail_closed", decision_read_from_provider: false, legacy_fallback_used: false}};
+ }
+ const end = Math.min(bytes.length, offset + pieceBytes);
+ return {schema_version: TODO_READ_RESULT, result_piece: {offset, byte_count: bytes.length, sha256: digest,
+ data: bytes.subarray(offset, end).toString("base64"),
+ next: end === bytes.length ? null : {offset: end, sha256: digest, observed_at: observed}}};
+}
diff --git a/loopx/control_plane/todos/standing_decision.py b/loopx/control_plane/todos/standing_decision.py
index efae270f74..4d163fb1c0 100644
--- a/loopx/control_plane/todos/standing_decision.py
+++ b/loopx/control_plane/todos/standing_decision.py
@@ -26,6 +26,26 @@ def build_standing_decision_authority(
Canonical IDs obey their provider contract, not the Markdown token codec.
Both sources use the same typed eligibility and chronology owner.
"""
+ items = standing_decision_source_items(user_items, canonical_records=canonical_records)
+ if not items:
+ return None
+ result = effect_runtime_result(
+ "todo.standing_decision.project",
+ {
+ "schema_version": "standing_decision_projection_request_v0",
+ "items": items,
+ "legacy_source_order": legacy_source_order and not canonical_records,
+ },
+ )
+ if result is not None and (
+ not isinstance(result, dict)
+ or result.get("schema_version") != "standing_decision_authority_v0"
+ ):
+ raise ValueError("invalid typed standing decision projection")
+ return result
+
+
+def standing_decision_source_items(user_items, *, canonical_records=False):
items = []
for item in user_items or []:
if not isinstance(item, dict):
@@ -85,19 +105,4 @@ def build_standing_decision_authority(
"unblocks_todo_id": item.get("unblocks_todo_id"),
}
)
- if not items:
- return None
- result = effect_runtime_result(
- "todo.standing_decision.project",
- {
- "schema_version": "standing_decision_projection_request_v0",
- "items": items,
- "legacy_source_order": legacy_source_order and not canonical_records,
- },
- )
- if result is not None and (
- not isinstance(result, dict)
- or result.get("schema_version") != "standing_decision_authority_v0"
- ):
- raise ValueError("invalid typed standing decision projection")
- return result
+ return items
diff --git a/loopx/control_plane/todos/summary_projection.ts b/loopx/control_plane/todos/summary_projection.ts
index c853825f06..033cf0f034 100644
--- a/loopx/control_plane/todos/summary_projection.ts
+++ b/loopx/control_plane/todos/summary_projection.ts
@@ -1,5 +1,5 @@
import {projectTodoHandoffContext} from "./handoff_projection.ts";
-/** Whole-source summary decisions. Python materializes these ordinals as public
+/** Whole-source summary decisions. The native read owner materializes public
* display records; counts and closure never depend on a presentation budget. */
import type {JsonObject} from "../effect_program.ts";
import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts";
diff --git a/loopx/control_plane/todos/todo_summary.py b/loopx/control_plane/todos/todo_summary.py
index fe8acdd63b..2bdd47af17 100644
--- a/loopx/control_plane/todos/todo_summary.py
+++ b/loopx/control_plane/todos/todo_summary.py
@@ -2,7 +2,7 @@
from datetime import datetime
import re
-from typing import Any, Callable, Optional, TypeGuard
+from typing import Any, Callable, Optional
from ..goals.goal_vision_wait_projection import attach_active_vision_waits
from .contract import (
@@ -39,14 +39,11 @@
)
from .summary_item import todo_text_content_revision
from .completion_validation_projection import project_completion_validation_authority
-from .frontier_revision import frontier_source_facts, TODO_FRONTIER_REVISION_INDEX_SCHEMA_VERSION
-from .handoff_gate import build_todo_handoff_gate_states
-from .handoff_note import attach_todo_handoff_notes, handoff_context_source, validate_handoff_context
+from .handoff_note import attach_todo_handoff_notes
from .todo_semantics import (
todo_item_is_actionable_open as projection_todo_item_is_actionable_open,
todo_item_is_deferred as projection_todo_item_is_deferred,
todo_item_is_due_monitor as projection_todo_item_is_due_monitor,
- todo_item_is_watch_only_monitor as projection_todo_item_is_watch_only_monitor,
todo_item_missing_monitor_schedule as projection_todo_item_missing_monitor_schedule,
todo_item_task_class as projection_todo_item_task_class,
todo_item_next_due_at as projection_todo_item_next_due_at,
@@ -56,8 +53,6 @@
todo_presentation_sort_key as projection_todo_presentation_sort_key,
todo_projection_sort_key as projection_todo_projection_sort_key,
)
-from .resume_condition import evaluate_todo_resume_conditions
-from ..runtime.time import now_utc, now_utc_iso
from ..work_items.project_asset import build_project_asset_todo_summary
from .user_gate import open_user_gate_todo_items
from ..coordination.coordination_state_contract import (
@@ -81,18 +76,6 @@
TASK_ORCHESTRATION_AUTHORITY_SCHEMA_VERSION = "task_orchestration_authority_v0"
TODO_ARCHIVE_STATE_ACTIVE = "active"
-# One internal batch carries the whole source, so the adapter sends columnar
-# facts: repeating every key name per Todo pushed a long-history request past the
-# effect-runtime request budget. The typed owner decodes the declared columns
-# back into row objects before validating them, so no cell changes meaning.
-SUMMARY_PROJECTION_REQUEST_SCHEMA_VERSION = "todo_summary_projection_request_v3"
-SUMMARY_PROJECTION_COLUMNS = (
- "status", "done", "task_class", "has_resume", "resume_ready", "resume_evaluated",
- "acceptance_blocked", "claimed", "preferred", "watch_only", "due_at", "expires_at",
- "sort", "completed_at", "updated_at", "completion_index", "linked_user_action",
- "no_followup", "successor_gap", "handoff_state", "replan", "todo_id", "claim",
- "bound", "blocks", "global", "excluded",
-)
AttentionItemBuilder = Callable[..., dict[str, Any]]
GoalLifecycleFields = Callable[[dict[str, Any], Optional[dict[str, Any]]], dict[str, Any]]
PublicSafeText = Callable[..., Optional[str]]
@@ -736,52 +719,6 @@ def attach_dependency_blockers(
item["dependency_blockers"] = blockers
-def _apply_resume_conditions(
- items: list[dict[str, Any]],
- *,
- source_section: str | None,
- resume_source_items: list[dict[str, Any]] | None = None,
- rollout_events: list[dict[str, Any]] | None = None,
- available_capabilities: Any = None,
- evaluated_at: str | None = None,
-) -> None:
- resume_items = [
- item
- for item in items
- if normalize_todo_resume_when(item.get("resume_when"))
- ]
- if not resume_items:
- return
- # Only prepare history when at least one item needs the typed evaluator.
- # Succession still receives the complete original lineage independently.
- source_items = [
- *_structured_resume_source_items(
- resume_source_items, source_section=source_section,
- ),
- *items,
- ]
- conditions = evaluate_todo_resume_conditions(
- resume_items,
- source_items=source_items,
- rollout_events=rollout_events,
- available_capabilities=available_capabilities,
- evaluated_at=evaluated_at or now_utc_iso(),
- )
- for item in items:
- resume_when = normalize_todo_resume_when(item.get("resume_when"))
- if not resume_when:
- continue
- todo_id = normalize_todo_id(item.get("todo_id"))
- condition = conditions.get(todo_id or "")
- if condition is None:
- condition = {
- "schema_version": "todo_resume_condition_v0",
- "resume_when": resume_when,
- "satisfied": False,
- "unsupported": True,
- }
- item["resume_condition"] = condition
- item["resume_ready"] = bool(condition.get("satisfied"))
def active_next_action_todo_ids(value: Any) -> set[str]:
@@ -851,177 +788,10 @@ def _structured_resume_source_items(
]
-def _resume_condition_evaluated(item: dict[str, Any], resume: str | None) -> bool:
- """The source's own full-source resume evaluation for this condition."""
- condition = item.get("resume_condition")
- return (isinstance(condition, dict)
- and condition.get("schema_version") == "todo_resume_condition_v0"
- and condition.get("resume_when") == resume
- and isinstance(condition.get("satisfied"), bool)
- and item.get("resume_ready") is condition.get("satisfied"))
-
-def _materialize_summary_lanes(items: list[dict[str, Any]], compacts: list[dict[str, Any]],
- result: dict[str, Any], summary: dict[str, Any], lanes: dict[str, Any],
- orchestration: dict[str, list[int]], *, fused_context: bool,
-) -> None:
- """Materialize validated ordinals with the existing display field allowlist."""
- if fused_context:
- contexts = validate_handoff_context(result.get("handoff_context"), len(items))
- for compact, context in zip(compacts, contexts, strict=True):
- if context["note"]:
- compact["handoff_note"] = context["note"]
- else:
- # Counts/closure still use every source row in their existing batch.
- # The independent display lens only needs the ordinals that are used.
- visible = sorted({index for lane in lanes.values() if lane["format"] != "raw" for index in lane["indices"]}
- | {index for indices in orchestration.values() for index in indices})
- attach_todo_handoff_notes([compacts[index] for index in visible])
- warning = summary.get("todo_succession_warning")
- warning_action = warning.get("recommended_action") if isinstance(warning, dict) else None
- if summary.get("completed_without_successor_count") and not isinstance(warning_action, str):
- raise ValueError("invalid typed Todo succession warning")
- for name, lane in lanes.items():
- mode = lane.get("format")
- if mode not in {"raw", "active", "compact", "recent", "gap"}:
- raise ValueError("invalid Todo summary display format")
- formatted = []
- for index in lane["indices"]:
- item = items[index]
- if mode == "raw":
- compact = item
- elif mode == "active":
- compact = dict(compacts[index])
- for key in ("note", "evidence", "reason", "completed_at", "updated_at", "superseded_by"):
- compact.pop(key, None)
- elif mode in {"compact", "recent", "gap"}:
- compact = dict(compacts[index])
- if mode in {"recent", "gap"}:
- for key in ("note", "evidence", "reason"):
- compact.pop(key, None)
- if mode == "gap":
- compact.update(succession_tracked=True,
- recommended_action=warning_action)
- else:
- raise ValueError("invalid Todo summary display format")
- formatted.append(compact)
- summary[name] = formatted
- if isinstance(warning, dict):
- warning["items"] = summary["completed_without_successor_items"]
-
-
-def _project_summary(items: list[dict[str, Any]], preferred_todo_ids: set[str] | None,
- *, selection: dict[str, Any] | None, role: str | None, source_section: str | None,
- item_limit: int | None, full_selection: bool,
-) -> dict[str, Any]:
- """Adapt evaluated facts and materialize one typed summary decision."""
- import json
- from ..effect_runtime import MAX_REQUEST_BYTES, EffectRuntimeRejected, effect_runtime_result
- from .succession_warning import succession_evaluations, succession_request
- # Display may never invent the source's resume decision. Assert the
- # full-source precondition before any RPC that reuses the evaluation, so an
- # unevaluated source fails with its own diagnostic instead of a downstream
- # "succession evaluation must be an object" from a later owner.
- for item in items:
- resume = normalize_todo_resume_when(item.get("resume_when"))
- if resume and not _resume_condition_evaluated(item, resume):
- raise ValueError("Todo display requires a matching full-source resume evaluation")
- # Evidence is merely carried here. The shared TS summary owner validates
- # the same full-source hashes/semantics before using any summary fact,
- # avoiding a separate RPC for every role/filter (including empty roles).
- succession = succession_evaluations(items)
- rows = []
- for item, evaluation in zip(items, succession, strict=True):
- resume = normalize_todo_resume_when(item.get("resume_when"))
- evaluated = _resume_condition_evaluated(item, resume)
- due = projection_todo_item_next_due_at(item)
- expires = projection_todo_item_expires_at(item)
- guard = item.get("goal_acceptance_guard")
- rows.append({"status": item.get("status") or ("done" if item.get("done") else "open"),
- "done": bool(item.get("done")), "task_class": projection_todo_item_task_class(item),
- "has_resume": bool(resume), "resume_ready": item.get("resume_ready"),
- "resume_evaluated": evaluated, "acceptance_blocked": isinstance(guard, dict) and guard.get("allowed") is False,
- "claimed": bool(item.get("claimed_by")), "preferred": item.get("todo_id") in (preferred_todo_ids or set()),
- "watch_only": projection_todo_item_is_watch_only_monitor(item),
- "due_at": due.timestamp() if due else None, "expires_at": expires.timestamp() if expires else None,
- "sort": list(projection_todo_presentation_sort_key(item)),
- "completed_at": str(item.get("completed_at") or "") or None,
- "updated_at": str(item.get("updated_at") or "") or None,
- "completion_index": int(item.get("index") or 0),
- "linked_user_action": bool(normalize_todo_id(item.get("unblocks_todo_id"))),
- "no_followup": normalize_todo_no_followup(item.get("no_followup")) is True,
- "successor_gap": evaluation["successor_gap"], "handoff_state": evaluation["handoff_state"],
- "replan": evaluation["route_continuation_replan_required"],
- **{"todo_id": normalize_todo_id(item.get("todo_id")),
- "claim": normalize_todo_claimed_by(item.get("claimed_by")),
- "bound": normalize_todo_bound_agent(item.get("bound_agent")),
- "blocks": normalize_todo_blocks_agent(item.get("blocks_agent")),
- "global": bool(item.get("global_gate")),
- "excluded": normalize_todo_excluded_agents(item.get("excluded_agents"))}})
- compacts = [_compact_todo_item_record(item) for item in items]
- contexts = [handoff_context_source(item) for item in compacts]
- for context in contexts:
- for key in ("todo_id", "claimed_by", "excluded_agents"):
- context.get("metadata", {}).pop(key, None)
- context.get("texts", {}).pop("task_class", None)
- if not context.get("metadata"):
- context.pop("metadata", None)
- try:
- request = {
- "schema_version": SUMMARY_PROJECTION_REQUEST_SCHEMA_VERSION,
- "handoff_sources": contexts,
- "columns": list(SUMMARY_PROJECTION_COLUMNS),
- "rows": [[row[name] for name in SUMMARY_PROJECTION_COLUMNS] for row in rows],
- "succession": succession_request(items, reuse=True),
- "frontier_rows": frontier_source_facts(items) if role == "agent" else None,
- "observed_at": now_utc().timestamp(),
- "selection": selection, "role": role, "source_section": source_section,
- "item_limit": item_limit, "full_selection": full_selection,
- }
- fused_context = len(json.dumps(request, separators=(",", ":")).encode()) <= MAX_REQUEST_BYTES - 4096
- if not fused_context:
- request.pop("handoff_sources")
- result = effect_runtime_result("todo.summary.project", request)
- except EffectRuntimeRejected as error:
- raise ValueError(str(error)) from error
- if not isinstance(result, dict) or result.get("schema_version") != "todo_summary_projection_v0":
- raise ValueError("invalid typed Todo summary projection")
-
- def valid_ordinals(value: Any) -> TypeGuard[list[int]]:
- return (isinstance(value, list)
- and all(type(index) is int and 0 <= index < len(items) for index in value)
- and len(set(value)) == len(value))
-
- selected = result.get("source_indices")
- if not valid_ordinals(selected):
- raise ValueError("invalid typed Todo selection ordinals")
- if type(result.get("full_selection")) is not bool:
- raise ValueError("invalid typed Todo selection ordinals")
- selected_set = set(selected)
- lanes, orchestration = result.get("lanes"), result.get("orchestration")
- if not isinstance(lanes, dict) or not isinstance(orchestration, dict):
- raise ValueError("invalid typed Todo summary lanes")
- for indices in [*(lane.get("indices") if isinstance(lane, dict) else None for lane in lanes.values()),
- *orchestration.values()]:
- if not valid_ordinals(indices):
- raise ValueError("invalid Todo summary source ordinal")
- if not set(indices) <= selected_set:
- raise ValueError("Todo summary lane escaped the selected source")
- summary = result.get("fields")
- if not isinstance(summary, dict) or summary.get("schema_version") != "todo_summary_v0":
- raise ValueError("invalid typed Todo summary fields")
- if role == "agent":
- index = summary.get("advancement_frontier_revision_index")
- if not isinstance(index, dict) or index.get("schema_version") != TODO_FRONTIER_REVISION_INDEX_SCHEMA_VERSION:
- raise ValueError("invalid typed Todo summary frontier index")
- _materialize_summary_lanes(items, compacts, result, summary, lanes, orchestration,
- fused_context=fused_context)
- return {"summary": summary, "items": [items[index] for index in selected],
- "succession": [succession[index] for index in selected],
- "orchestration": {name: [compacts[index] for index in indices] for name, indices in orchestration.items()}}
def compact_todo_group(
@@ -1041,32 +811,30 @@ def compact_todo_group(
vision_runs: list[dict[str, Any]] | None = None,
evaluated_at: str | None = None,
) -> dict[str, Any] | None:
+ from .read_projection import project_todo_read_group
+
if not items and not include_empty_source:
return None
- items = _structured_todo_group_items(
- items,
- source_section=source_section,
- role=role,
- text_limit=text_limit,
- include_content_revision=include_content_revision,
- )
- _apply_resume_conditions(
- items,
- source_section=source_section,
- resume_source_items=resume_source_items,
- rollout_events=rollout_events,
- available_capabilities=available_capabilities,
- evaluated_at=evaluated_at,
- )
- from .succession_warning import evaluate_succession
-
- evaluate_succession(items, resume_source_items)
- return compact_evaluated_todo_group(
- items, source_section=source_section, role=role,
- include_empty_source=include_empty_source, preferred_todo_ids=preferred_todo_ids,
- item_limit=item_limit, include_task_orchestration_authority=include_task_orchestration_authority,
- vision_runs=vision_runs, lineage_items=resume_source_items,
- )
+ items = _structured_todo_group_items(items, source_section=source_section, role=role,
+ text_limit=text_limit, include_content_revision=include_content_revision)
+ resume_lineage = (_structured_resume_source_items(resume_source_items, source_section=source_section)
+ if any(normalize_todo_resume_when(item.get("resume_when")) for item in items) else None)
+ projected = project_todo_read_group(items, source_section=source_section, role=role,
+ preferred_todo_ids=preferred_todo_ids, lineage=resume_source_items,
+ resume_lineage=resume_lineage, item_limit=item_limit,
+ rollout_events=rollout_events, available_capabilities=available_capabilities,
+ evaluated_at=evaluated_at, include_full_source=bool(vision_runs),
+ include_task_orchestration_authority=include_task_orchestration_authority)
+ items = projected.get("items", projected["summary"]["items"])
+ if not projected["selected_count"] and not include_empty_source:
+ return None
+ summary = projected["summary"]
+ attach_active_vision_waits(summary, vision_runs, role=role, items=items,
+ lineage_items=resume_source_items)
+ if include_task_orchestration_authority:
+ summary["task_orchestration_authority"] = _task_orchestration_authority(
+ projected["orchestration"], role=role)
+ return summary
@@ -1084,27 +852,22 @@ def compact_evaluated_todo_group(
full_selection: bool = True,
selection: dict[str, Any] | None = None,
) -> dict[str, Any] | None:
- """Filter/display an already evaluated snapshot, never re-evaluate topology.
+ from .read_projection import project_todo_read_group
- Callers must come from the full-source parser or canonical summary, not
- persisted derived fields. Missing or mismatched evaluations fail closed.
- """
if not items and not include_empty_source:
return None
- projected = _project_summary(items, preferred_todo_ids, selection=selection,
- role=role, source_section=source_section, item_limit=item_limit, full_selection=full_selection)
- items = projected["items"]
- if not items and not include_empty_source:
+ lineage = lineage_items
+ projected = project_todo_read_group(items, source_section=source_section, role=role,
+ preferred_todo_ids=preferred_todo_ids, lineage=lineage, item_limit=item_limit,
+ evaluated=True, selection=selection, full_selection=full_selection,
+ include_full_source=bool(vision_runs), include_task_orchestration_authority=include_task_orchestration_authority)
+ items = projected.get("items", projected["summary"]["items"])
+ if not projected["selected_count"] and not include_empty_source:
return None
- summary: dict[str, Any] = projected["summary"]
- handoff_gates = build_todo_handoff_gate_states(items, evaluations=projected["succession"])
- attach_active_vision_waits(
- summary, vision_runs, role=role, items=items,
- lineage_items=lineage_items,
- )
+ summary = projected["summary"]
+ attach_active_vision_waits(summary, vision_runs, role=role, items=items,
+ lineage_items=lineage_items)
if include_task_orchestration_authority:
summary["task_orchestration_authority"] = _task_orchestration_authority(
projected["orchestration"], role=role)
- if handoff_gates:
- summary["handoff_gates"] = handoff_gates
return summary
diff --git a/loopx/control_plane/work_items/refresh_recommendation.py b/loopx/control_plane/work_items/refresh_recommendation.py
index 78c5c003f5..e39deb7d41 100644
--- a/loopx/control_plane/work_items/refresh_recommendation.py
+++ b/loopx/control_plane/work_items/refresh_recommendation.py
@@ -6,7 +6,7 @@
from typing import Any
from ..agents.agent_lane_recommendation import build_agent_lane_next_action
-from ..coordination.local_authority import read_canonical_todos_if_promoted, canonical_todo_summary_fields
+from ..coordination.local_authority import read_canonical_todos_if_promoted
from ..effect_runtime import EffectRuntimeRejected, effect_runtime_result
from ..todos.active_state_todo_parser import parse_active_state_todos
from ..todos.contract import normalize_todo_id
@@ -48,12 +48,9 @@ def load_refresh_planning_source(
that task snapshot. Provider failures propagate rather than using prose.
"""
events = load_rollout_events(rollout_event_log_path(runtime_root, goal_id))
- canonical = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=goal_id)
- fields = canonical_todo_summary_fields(
- canonical["todos"], rollout_events=events,
- goal_acceptance_contract=canonical.get("goal_acceptance_contract"),
- goal_acceptance_work_guards=canonical.get("goal_acceptance_work_guards"),
- ) if canonical is not None else None
+ canonical = read_canonical_todos_if_promoted(runtime_root=runtime_root, goal_id=goal_id,
+ include_summary=True, rollout_events=events)
+ fields = canonical["_todo_fields"] if canonical is not None else None
try:
text = state_path.read_text(encoding="utf-8")
except FileNotFoundError:
diff --git a/tests/control_plane/canonical_authority_fixture.py b/tests/control_plane/canonical_authority_fixture.py
index f2deeb13d8..9b3d3e396e 100644
--- a/tests/control_plane/canonical_authority_fixture.py
+++ b/tests/control_plane/canonical_authority_fixture.py
@@ -12,6 +12,15 @@
import tempfile
+def assert_todo_read_unchanged(current, previous):
+ """Compare durable/read content while checking the separate observation scope."""
+ assert current["projection_envelope"]["schema_version"] == "loopx_projection_envelope_v0"
+ if "projection_envelope" in previous:
+ assert current["projection_envelope"]["coverage"] == previous["projection_envelope"]["coverage"]
+ assert {key: value for key, value in current.items() if key != "projection_envelope"} == {
+ key: value for key, value in previous.items() if key != "projection_envelope"}
+
+
def initialize_canonical_authority(runtime_root: Path, goal_id: str, projection: dict, *, state_path: Path, provider: str = "file") -> dict:
repository = Path(__file__).resolve().parents[2]
module = repository / "loopx/control_plane/coordination/file_authority_store.ts"
diff --git a/tests/control_plane/test_explore_result_writeback.py b/tests/control_plane/test_explore_result_writeback.py
index 7aa6ea04a7..9d125ef3b9 100644
--- a/tests/control_plane/test_explore_result_writeback.py
+++ b/tests/control_plane/test_explore_result_writeback.py
@@ -1,3 +1,5 @@
+
+from canonical_authority_fixture import assert_todo_read_unchanged
from copy import deepcopy
import json
@@ -994,7 +996,7 @@ def test_corrupt_graph_cannot_report_success_on_replay_and_recovers_after_repair
assert not result["ok"], "A partial log must not become a successful delivery receipt"
assert result["primary_committed"] and result["retryable"]
assert log.read_bytes() == corrupt
- assert list_goal_todos(registry_path=args["registry_path"], goal_id=args["goal_id"]) == before
+ assert_todo_read_unchanged(list_goal_todos(registry_path=args["registry_path"], goal_id=args["goal_id"]), before)
log.write_bytes(valid)
assert deliver_result_attachment(payload=payload(), **args)["idempotent_replay"]
assert log.read_bytes() == valid
diff --git a/tests/control_plane/test_frontier_revision_scope.py b/tests/control_plane/test_frontier_revision_scope.py
index e2edf99b90..1531fdec9f 100644
--- a/tests/control_plane/test_frontier_revision_scope.py
+++ b/tests/control_plane/test_frontier_revision_scope.py
@@ -27,8 +27,8 @@ def test_summary_adapter_rejects_non_object_batched_frontier(monkeypatch, index)
def invalid_frontier(method, request, **kwargs):
result = original(method, request, **kwargs)
- if method == "todo.summary.project":
- result["fields"]["advancement_frontier_revision_index"] = index
+ if method == "todo.read":
+ result["summary"]["advancement_frontier_revision_index"] = index
return result
monkeypatch.setattr(effect_runtime, "effect_runtime_result", invalid_frontier)
diff --git a/tests/control_plane/test_handoff_context_projection.py b/tests/control_plane/test_handoff_context_projection.py
index 214073822a..eab7c2892c 100644
--- a/tests/control_plane/test_handoff_context_projection.py
+++ b/tests/control_plane/test_handoff_context_projection.py
@@ -227,7 +227,7 @@ def track(method, request, **kwargs):
assert result["done_count"] == 1000 and result["total_count"] == 1000
assert len(result["items"]) == 12
assert result["recent_completed_advancement_items"][0]["handoff_note"]["summary"] == "a" * 279 + "..."
- assert calls == ["todo.summary.project", "todo.context.page"]
+ assert calls == ["todo.read"]
def test_byte_bounded_context_batches_retain_every_record(monkeypatch):
diff --git a/tests/control_plane/test_local_coordination_authority.py b/tests/control_plane/test_local_coordination_authority.py
index e73e677587..3756bbee32 100644
--- a/tests/control_plane/test_local_coordination_authority.py
+++ b/tests/control_plane/test_local_coordination_authority.py
@@ -61,6 +61,12 @@
)
+def _assert_same_todo_content(current, expected):
+ assert current["projection_envelope"]["coverage"] == expected["projection_envelope"]["coverage"]
+ assert {key: value for key, value in current.items() if key != "projection_envelope"} == {
+ key: value for key, value in expected.items() if key != "projection_envelope"}
+
+
def _engage_fence(runtime_root: Path, goal_id: str = "goal-a") -> None:
path = legacy_coordination_writer_fence_path(
runtime_root=runtime_root,
@@ -1116,29 +1122,13 @@ def test_todo_list_uses_provider_after_cutover_even_when_markdown_disagrees(
),
encoding="utf-8",
)
- _engage_fence(runtime_root)
+ projection = build_todo_runtime_shadow_projection(goal_id="goal-a", todos=[{
+ "schema_version": "todo_item_v0", "todo_id": "todo_provider", "index": 1,
+ "role": "agent", "status": "open", "done": False, "text": "provider Todo",
+ "task_class": "advancement_task", "archive_state": "active", "source_section": "Agent Todo",
+ }])
+ initialize_canonical_authority(runtime_root, "goal-a", projection, state_path=state_file)
state_file.unlink()
- monkeypatch.setattr(
- "loopx.control_plane.coordination.local_authority.effect_runtime_result",
- lambda method, params, **_kwargs: single_snapshot_page({
- "status": "loaded",
- "todos": [
- {
- "todo_id": "todo_provider",
- "role": "agent",
- "status": "open",
- "text": "provider Todo",
- }
- ],
- "todo_read_model": _todo_read_model(1),
- "provider_revision": "file:2",
- "cursor": "2",
- "source_authority": "file_v0",
- "decision_read_from_provider": True,
- "legacy_fallback_used": False,
- }),
- )
-
result = list_goal_todos(registry_path=registry_path, goal_id="goal-a")
assert result["source"] == "file_authority"
@@ -1255,7 +1245,7 @@ def test_canonical_hard_lease_claim_cli_atomically_acquires_ownership(
assert preview["status"] == "planned"
assert preview["todo_changed"] is True
assert preview["lease_changed"] is True
- assert list_goal_todos(registry_path=registry_path, goal_id="goal-a") == before
+ _assert_same_todo_content(list_goal_todos(registry_path=registry_path, goal_id="goal-a"), before)
applied = json.loads(
subprocess.run(
@@ -1298,7 +1288,7 @@ def invoke_edit(argv):
preview_edit = invoke_edit([*edit, "--dry-run"])
assert preview_edit.returncode == 0, preview_edit.stdout + preview_edit.stderr
assert json.loads(preview_edit.stdout)["status"] == "planned"
- assert list_goal_todos(registry_path=registry_path, goal_id="goal-a") == after
+ _assert_same_todo_content(list_goal_todos(registry_path=registry_path, goal_id="goal-a"), after)
first_edit = invoke_edit(edit)
assert first_edit.returncode == 0, first_edit.stdout + first_edit.stderr
assert json.loads(first_edit.stdout)["status"] == "applied"
diff --git a/tests/control_plane/test_monitor_followthrough_contract.py b/tests/control_plane/test_monitor_followthrough_contract.py
index 80ea23edd7..9f02e0ee47 100644
--- a/tests/control_plane/test_monitor_followthrough_contract.py
+++ b/tests/control_plane/test_monitor_followthrough_contract.py
@@ -1,5 +1,7 @@
from __future__ import annotations
+from canonical_authority_fixture import assert_todo_read_unchanged
+
import json
from pathlib import Path
@@ -1103,13 +1105,10 @@ def test_material_poll_user_followup_requires_explicit_class_without_writeback(
)
assert "todo_writeback" not in result
assert state.read_text(encoding="utf-8") == before_state
- assert (
- list_goal_todos(
+ assert_todo_read_unchanged(list_goal_todos(
registry_path=registry,
goal_id=GOAL_ID,
- )
- == before_todos
- )
+ ), before_todos)
def test_material_poll_explicit_user_gate_blocks_bound_agent(
diff --git a/tests/control_plane/test_native_todo_planning_update.py b/tests/control_plane/test_native_todo_planning_update.py
index 26f4c4ceb4..6e897c1f31 100644
--- a/tests/control_plane/test_native_todo_planning_update.py
+++ b/tests/control_plane/test_native_todo_planning_update.py
@@ -1,6 +1,8 @@
"""Real CLI and canonical FileAuthorityStore; no production registry or promotion."""
from __future__ import annotations
+from canonical_authority_fixture import assert_todo_read_unchanged
+
import json
import shlex
import subprocess
@@ -797,7 +799,7 @@ def test_public_cli_external_wait_uses_current_successor_source(
display = state.read_bytes() if state.exists() else None
rejected = update(registry, *args, ok=False)
assert "external-wait successor must be runnable, not resume-gated" in json.dumps(rejected)
- assert list_goal_todos(registry_path=registry, goal_id="goal-a") == source
+ assert_todo_read_unchanged(list_goal_todos(registry_path=registry, goal_id="goal-a"), source)
assert (state.read_bytes() if state.exists() else None) == display
return
update(registry, *args, "--dry-run")
diff --git a/tests/control_plane/test_quota_authority_settlement_journey.py b/tests/control_plane/test_quota_authority_settlement_journey.py
index 9e720c0161..e87a908641 100644
--- a/tests/control_plane/test_quota_authority_settlement_journey.py
+++ b/tests/control_plane/test_quota_authority_settlement_journey.py
@@ -1,6 +1,8 @@
"""Select current work and execute its returned settlement, using real stores/CLI."""
from __future__ import annotations
+from canonical_authority_fixture import assert_todo_read_unchanged
+
import json
import shlex
from datetime import datetime, timedelta, timezone
@@ -98,8 +100,8 @@ def test_canonical_todos_cannot_replace_missing_goal_intent(tmp_path, provider):
path.unlink()
# A read-only canonical Todo lookup still works; it cannot authorize a
# delivery whose Goal objective and constraints are unavailable.
- assert list_goal_todos(registry_path=registry, goal_id=cli.GOAL_ID,
- runtime_root_arg=str(runtime), todo_id=cli.TODO_ID) == before
+ assert_todo_read_unchanged(list_goal_todos(registry_path=registry, goal_id=cli.GOAL_ID,
+ runtime_root_arg=str(runtime), todo_id=cli.TODO_ID), before)
code, guard = _guard(project, runtime, registry)
assert code == 1
assert guard["should_run"] is False
@@ -107,8 +109,8 @@ def test_canonical_todos_cannot_replace_missing_goal_intent(tmp_path, provider):
assert cli._heartbeat_receipt_count(runtime, cli.TURN_ID) == 0
assert cli._spend_run_count(runtime) == 0
assert not path.exists()
- assert list_goal_todos(registry_path=registry, goal_id=cli.GOAL_ID,
- runtime_root_arg=str(runtime), todo_id=cli.TODO_ID) == before
+ assert_todo_read_unchanged(list_goal_todos(registry_path=registry, goal_id=cli.GOAL_ID,
+ runtime_root_arg=str(runtime), todo_id=cli.TODO_ID), before)
@pytest.mark.parametrize("provider", ["file", "sqlite"])
diff --git a/tests/control_plane/test_quota_settlement_cli.py b/tests/control_plane/test_quota_settlement_cli.py
index af472b4aaf..ebe379a977 100644
--- a/tests/control_plane/test_quota_settlement_cli.py
+++ b/tests/control_plane/test_quota_settlement_cli.py
@@ -1983,15 +1983,15 @@ def test_blocked_wait_transports_complete_lifecycle_facts_without_prose(
cadence="5m", expires_at="2026-01-02T00:00:00Z", watch_only=True)
source = {"agent_todos": {"items": [waiting, target]}, "user_todos": {"items": []}}
before = json.dumps(source, sort_keys=True)
- invoke = effect_runtime._request_with_info
+ invoke = effect_runtime.effect_runtime_request
requests = []
- def observe_request(info, **kwargs):
- if kwargs["method"] == "quota.settlement.read":
- requests.append(kwargs["params"])
- return invoke(info, **kwargs)
+ def observe_request(method, params, **kwargs):
+ if method == "quota.settlement.read":
+ requests.append(params)
+ return invoke(method, params, **kwargs)
- monkeypatch.setattr(effect_runtime, "_request_with_info", observe_request)
+ monkeypatch.setattr(effect_runtime, "effect_runtime_request", observe_request)
result = require_blocked_retry_wait(source, todo_id=TODO_ID, observed_at=observed,
allow_turn_settlement_retry=kind == "implicit")
assert len(requests) == 1
diff --git a/tests/control_plane/test_retained_team_plan_receipt.py b/tests/control_plane/test_retained_team_plan_receipt.py
index fa41ed520b..acc0789856 100644
--- a/tests/control_plane/test_retained_team_plan_receipt.py
+++ b/tests/control_plane/test_retained_team_plan_receipt.py
@@ -1,4 +1,7 @@
+
"""Recover a fixed 1.x receipt through the current public adapter."""
+
+from canonical_authority_fixture import assert_todo_read_unchanged
import json
from pathlib import Path
@@ -41,5 +44,5 @@ def replay(**overrides):
apply_team_plan(registry_path=registry, goal_id="goal-a", agent_id=None,
proposal={**history["plan"], "objective": "Changed original intent"},
expected_state_fingerprint=history["basis"])
- assert list_goal_todos(registry_path=registry, goal_id="goal-a") == before
+ assert_todo_read_unchanged(list_goal_todos(registry_path=registry, goal_id="goal-a"), before)
assert state.read_bytes() == display
diff --git a/tests/control_plane/test_settled_monitor_user_gate.py b/tests/control_plane/test_settled_monitor_user_gate.py
index f301ffedf9..64482db94f 100644
--- a/tests/control_plane/test_settled_monitor_user_gate.py
+++ b/tests/control_plane/test_settled_monitor_user_gate.py
@@ -1,6 +1,8 @@
"""Exact monitor closeout wins over an unrelated scoped user gate."""
from __future__ import annotations
+from canonical_authority_fixture import assert_todo_read_unchanged
+
import json
import pytest
@@ -162,7 +164,7 @@ def call(command):
before = list_goal_todos(registry_path=source, goal_id=GOAL_ID)
rc, denied = call("monitor-poll")
assert rc == 1 and denied["error_code"] == "monitor_poll_admission_rejected", denied
- assert list_goal_todos(registry_path=source, goal_id=GOAL_ID) == before
+ assert_todo_read_unchanged(list_goal_todos(registry_path=source, goal_id=GOAL_ID), before)
index = runtime / "goals" / GOAL_ID / "runs" / "index.jsonl"
assert not index.exists()
if gate_kind != "linked":
@@ -176,7 +178,7 @@ def call(command):
committed = list_goal_todos(registry_path=source, goal_id=GOAL_ID)
rc, replay = call("monitor-poll")
assert rc == 0 and replay["replayed"] is True and replay["appended"] is False
- assert list_goal_todos(registry_path=source, goal_id=GOAL_ID) == committed
+ assert_todo_read_unchanged(list_goal_todos(registry_path=source, goal_id=GOAL_ID), committed)
rows = [json.loads(line) for line in index.read_text().splitlines()]
assert [row["classification"] for row in rows] == ["quota_monitor_poll"]
diff --git a/tests/control_plane/test_todo_completion_state_runtime.py b/tests/control_plane/test_todo_completion_state_runtime.py
index 58fc60d0c9..a86d75510d 100644
--- a/tests/control_plane/test_todo_completion_state_runtime.py
+++ b/tests/control_plane/test_todo_completion_state_runtime.py
@@ -1,3 +1,4 @@
+"""Persisted completion spellings decode without a field-level runtime call."""
from __future__ import annotations
import pytest
@@ -6,37 +7,20 @@
from loopx.control_plane.todos import completion_state
-def _result(**values: object) -> dict[str, object]:
- return {
- "schema_version": completion_state.TODO_COMPLETION_STATE_RESULT_SCHEMA,
- **values,
- }
+@pytest.mark.parametrize(("raw", "expected"), [
+ ("YES", True), ("no-followup", True), ("FALSE", False), (False, False),
+ (None, None), ("unrecognized", None), ("\ufeffYES\ufeff", True), ("\x1cYES\x1c", None),
+])
+def test_metadata_spellings_do_not_start_the_runtime(monkeypatch, raw, expected):
+ def unexpected(*args, **kwargs):
+ raise AssertionError("metadata codecs must not call the runtime")
+ monkeypatch.setattr(effect_runtime, "effect_runtime_request", unexpected)
+ assert completion_state.normalize_todo_no_followup(raw) is expected
-def test_scalar_normalization_uses_bounded_process_cache(monkeypatch) -> None:
- calls = 0
- def call(_method: str, params: dict[str, object]) -> dict[str, object]:
- nonlocal calls
- calls += 1
- return _result(kind=params["kind"], value=True)
-
- completion_state._normalize_cached.cache_clear()
- monkeypatch.setattr(completion_state, "effect_runtime_result", call)
-
- assert completion_state.normalize_todo_no_followup("YES") is True
- assert completion_state.normalize_todo_no_followup("YES") is True
- assert calls == 1
- completion_state._normalize_cached.cache_clear()
-
-
-def test_python_facade_preserves_typed_rejection_as_value_error(monkeypatch) -> None:
- def reject(_method: str, _params: dict[str, object]) -> object:
- raise effect_runtime.EffectRuntimeRejected("typed state rejected")
-
- monkeypatch.setattr(completion_state, "effect_runtime_result", reject)
-
- with pytest.raises(ValueError, match="typed state rejected"):
- completion_state.require_todo_completion_metadata(
- "completion_continuation", "implicit",
- )
+def test_completion_metadata_rejects_unknown_continuation_without_inventing_recovery():
+ assert completion_state.require_todo_completion_metadata("completion_continuation", " successor ") == "successor"
+ assert completion_state.require_todo_completion_metadata("completion_recovery", "") is None
+ with pytest.raises(ValueError, match="completion_continuation must be one of"):
+ completion_state.require_todo_completion_metadata("completion_continuation", "implicit")
diff --git a/tests/control_plane/test_todo_consumer_scope.py b/tests/control_plane/test_todo_consumer_scope.py
index 5f3566af72..3730978a2f 100644
--- a/tests/control_plane/test_todo_consumer_scope.py
+++ b/tests/control_plane/test_todo_consumer_scope.py
@@ -89,51 +89,28 @@ def read(*args):
assert (state.read_bytes() if state.exists() else None) == before
-def test_filter_composes_with_existing_lane_call_and_rejects_downgraded_response(monkeypatch):
+def test_filter_uses_one_read_transaction_and_rejects_a_downgraded_response(monkeypatch):
from loopx.control_plane import effect_runtime
source = compact_todo_group([{"todo_id": "todo_one", "text": "Review result", "role": "user",
"status": "open", "task_class": "user_action", "bound_agent": "agent-a"}],
role="user", source_section="User Todo", item_limit=None)
original = effect_runtime.effect_runtime_result
requests = []
+
def track(method, request, **kwargs):
- if method == "todo.summary.project":
- requests.append(request)
+ requests.append((method, request))
return original(method, request, **kwargs)
+
monkeypatch.setattr(effect_runtime, "effect_runtime_result", track)
assert filtered_todo_summary(source, role="user", agent_id="agent-a")["total_count"] == 1
- assert len(requests) == 1
- assert requests[0]["schema_version"] == "todo_summary_projection_request_v3"
- # One whole-source batch stays columnar, so adding fields cannot silently
- # push a long-history request past the runtime request budget.
- assert requests[0]["columns"][:3] == ["status", "done", "task_class"]
- assert all(len(cells) == len(requests[0]["columns"]) for cells in requests[0]["rows"])
+ assert len(requests) == 1 and requests[0][0] == "todo.read"
+ assert requests[0][1]["schema_version"] == "todo_read_request_v0"
+
def downgrade(method, request, **kwargs):
result = original(method, request, **kwargs)
- if method == "todo.summary.project":
- result.pop("source_indices", None)
+ result.pop("schema_version", None)
return result
- monkeypatch.setattr(effect_runtime, "effect_runtime_result", downgrade)
- with pytest.raises(ValueError, match="selection ordinals"):
- filtered_todo_summary(source, role="user", agent_id="agent-a")
-
-@pytest.mark.parametrize("corruption", ["duplicate", "boolean", "outside_selection"])
-def test_typed_lane_response_cannot_alias_or_escape_selected_source(monkeypatch, corruption):
- from loopx.control_plane import effect_runtime
- source = compact_todo_group([
- {"todo_id": "todo_owned", "text": "Review result", "role": "user", "status": "open",
- "task_class": "user_action", "bound_agent": "agent-a"},
- {"todo_id": "todo_other", "text": "Review peer result", "role": "user", "status": "open",
- "task_class": "user_action", "bound_agent": "agent-b"}],
- role="user", source_section="User Todo", item_limit=None)
- outside = next(index for index, item in enumerate(source["items"]) if item["todo_id"] == "todo_other")
- original = effect_runtime.effect_runtime_result
- def corrupt(method, request, **kwargs):
- result = original(method, request, **kwargs)
- if method == "todo.summary.project":
- result["lanes"]["first_open_items"]["indices"] = {"duplicate": [0, 0], "boolean": [False], "outside_selection": [outside]}[corruption]
- return result
- monkeypatch.setattr(effect_runtime, "effect_runtime_result", corrupt)
- with pytest.raises(ValueError, match="source ordinal|escaped the selected source"):
+ monkeypatch.setattr(effect_runtime, "effect_runtime_result", downgrade)
+ with pytest.raises(ValueError, match="invalid typed Todo read result"):
filtered_todo_summary(source, role="user", agent_id="agent-a")
diff --git a/tests/control_plane/test_todo_conversation_context.py b/tests/control_plane/test_todo_conversation_context.py
index cbbc506dd5..ed6382f7cd 100644
--- a/tests/control_plane/test_todo_conversation_context.py
+++ b/tests/control_plane/test_todo_conversation_context.py
@@ -1,5 +1,8 @@
+
"""Conversation evidence reads use real canonical providers without granting work."""
+from canonical_authority_fixture import assert_todo_read_unchanged
+
import json
import subprocess
import sys
@@ -54,7 +57,7 @@ def test_common_context_recovers_constraints_from_canonical_authority(tmp_path,
allowed[0] = False
assert not tool.read(CONTEXT_TOOL_NAME, {"view": "todos", "goal_id": "goal-a"})["ok"]
assert len(observed) == 4
- assert list_goal_todos(registry_path=registry, goal_id="goal-a") == canonical
+ assert_todo_read_unchanged(list_goal_todos(registry_path=registry, goal_id="goal-a"), canonical)
assert not state.exists()
# The shipped CLI uses the same exact selector but an external audience.
diff --git a/tests/control_plane/test_todo_list_explicit_limit.py b/tests/control_plane/test_todo_list_explicit_limit.py
index 9989027c4f..f10a7d33f8 100644
--- a/tests/control_plane/test_todo_list_explicit_limit.py
+++ b/tests/control_plane/test_todo_list_explicit_limit.py
@@ -201,8 +201,7 @@ def test_default_payload_keeps_the_pre_limit_shape(tmp_path: Path) -> None:
payload = list_goal_todos(registry_path=registry_path, goal_id=GOAL_ID)
- # The no-limit cold path is byte-identical to the pre---limit shape:
- # no explicit-limit disclosure fields appear.
+ # The cold view retains its fields and adds the required read provenance.
assert set(payload) == {
"ok",
"dry_run",
@@ -218,7 +217,9 @@ def test_default_payload_keeps_the_pre_limit_shape(tmp_path: Path) -> None:
"project",
"user_todos",
"agent_todos",
+ "projection_envelope",
}
+ assert payload["projection_envelope"]["coverage"]["expected_count"] == 16
assert payload["todo_count"] == 16
assert len(payload["todos"]) == 16
for absent_key in (
diff --git a/tests/control_plane/test_todo_list_record_references.py b/tests/control_plane/test_todo_list_record_references.py
index 9a067cc0d3..7ad53db447 100644
--- a/tests/control_plane/test_todo_list_record_references.py
+++ b/tests/control_plane/test_todo_list_record_references.py
@@ -47,17 +47,17 @@ def cli(registry, runtime, flags, output_format="json"):
def test_projection_failure_returns_the_standard_cli_error_without_source_changes(tmp_path, monkeypatch, capsys):
from loopx.cli import main
- from loopx.cli_commands import todo_list as todo_command
+ from loopx.control_plane import effect_runtime
isolate_sqlite_runtime(tmp_path, monkeypatch)
registry, state = _write_fixture(tmp_path)
before = state.read_bytes()
def unavailable(operation, payload, **kwargs):
- assert operation == "todo.context.page" and payload["list_payload"]["ok"]
+ assert operation == "todo.read" and payload["kind"] == "query"
raise RuntimeError("Todo projection unavailable")
- monkeypatch.setattr(todo_command, "effect_runtime_result", unavailable)
+ monkeypatch.setattr(effect_runtime, "effect_runtime_result", unavailable)
result = main(["--registry", str(registry), "todo", "list", "--format", "json",
"--goal-id", GOAL_ID])
error = json.loads(capsys.readouterr().out)
@@ -91,6 +91,10 @@ def test_real_cli_references_reconstruct_all_scoped_lanes_and_counts(tmp_path, m
]:
native = list_goal_todos(registry_path=registry, goal_id=GOAL_ID, **options)
wire = cli(registry, runtime, flags)
+ native_envelope = native.pop("projection_envelope")
+ wire_envelope = wire.pop("projection_envelope")
+ assert native_envelope["schema_version"] == wire_envelope["schema_version"] == "loopx_projection_envelope_v0"
+ assert native_envelope["coverage"] == wire_envelope["coverage"]
disclosure = wire.pop("todo_list_record_references")
assert disclosure["replaced_view_records"] > 0
assert expand_references(wire, wire) == native
@@ -101,7 +105,9 @@ def test_real_cli_references_reconstruct_all_scoped_lanes_and_counts(tmp_path, m
assert cli(registry, runtime, flags, "markdown") == render_todo_markdown(native) + "\n"
thin = cli(registry, runtime, ["--thin"])
assert "todo_list_record_references" not in thin
- assert thin == list_goal_todos(registry_path=registry, goal_id=GOAL_ID, thin=True)
+ direct_thin = list_goal_todos(registry_path=registry, goal_id=GOAL_ID, thin=True)
+ assert thin.pop("projection_envelope")["coverage"] == direct_thin.pop("projection_envelope")["coverage"]
+ assert thin == direct_thin
exact = cli(registry, runtime, ["--todo-id", "todo_agent_current"])
assert "todo_list_record_references" not in exact
assert exact["todo"]["text"] == "Execute the current agent advancement."
diff --git a/tests/control_plane/test_todo_summary_projection.py b/tests/control_plane/test_todo_summary_projection.py
index bd62cf4394..d1bbe59500 100644
--- a/tests/control_plane/test_todo_summary_projection.py
+++ b/tests/control_plane/test_todo_summary_projection.py
@@ -84,12 +84,12 @@ def test_a_selection_cannot_restore_a_lost_full_source_proof():
def test_long_history_stays_inside_the_runtime_request_budget(monkeypatch):
"""A whole-source batch must not outgrow the co-deployed runtime's request."""
from loopx.control_plane import effect_runtime
- from loopx.control_plane.effect_runtime import MAX_REQUEST_BYTES
+ from loopx.control_plane.effect_runtime import MAX_LOCAL_SNAPSHOT_BYTES
requests = []
original = effect_runtime.effect_runtime_result
def track(method, request, **kwargs):
- if method == "todo.summary.project":
+ if method == "todo.read":
requests.append(request)
return original(method, request, **kwargs)
@@ -100,7 +100,7 @@ def track(method, request, **kwargs):
assert result["done_count"] == 4096
request = requests[0]
encoded = json.dumps(request, separators=(",", ":")).encode()
- assert len(encoded) < MAX_REQUEST_BYTES
+ assert len(encoded) < MAX_LOCAL_SNAPSHOT_BYTES
def test_no_wait_skips_resume_preparation_but_keeps_archived_successor(monkeypatch):
@@ -194,7 +194,7 @@ def track(method, request, **kwargs):
monkeypatch.setattr(succession_warning, "effect_runtime_result", track)
selected = filtered_todo_summary(source, role="agent", todo_id="todo_summary_1")
assert selected["done_count"] == 1 and not selected.get("completed_without_successor_count")
- assert calls.count("todo.summary.project") == 1
+ assert calls.count("todo.read") == 1
assert "todo.succession.project" not in calls
next(item for item in source["items"] if item["todo_id"] == "todo_summary_1")["no_followup"] = True
import pytest
@@ -237,7 +237,7 @@ def track(method, request, **kwargs):
monkeypatch.setattr(effect_runtime, "effect_runtime_result", track)
selected = filtered_todo_summary(source, role="agent", agent_id="agent-a", item_limit=1)
assert selected["advancement_frontier_revision_index"]["claimed_advancement_counts"] == {"agent-a": 24}
- assert calls == ["todo.summary.project"]
+ assert calls == ["todo.read"]
assert not hasattr(frontier_revision, "attach_advancement_frontier_revision_index")
assert not hasattr(frontier_revision, "build_advancement_frontier_revision_index")
@@ -251,9 +251,9 @@ def test_missing_batched_frontier_never_falls_back_to_another_owner_call(monkeyp
original = effect_runtime.effect_runtime_result
def drop_index(method, request, **kwargs):
- assert method == "todo.summary.project"
+ assert method == "todo.read"
result = original(method, request, **kwargs)
- result["fields"].pop("advancement_frontier_revision_index")
+ result["summary"].pop("advancement_frontier_revision_index")
return result
monkeypatch.setattr(effect_runtime, "effect_runtime_result", drop_index)
diff --git a/tests/control_plane_ts/todo_completion_state.test.ts b/tests/control_plane_ts/todo_completion_state.test.ts
index 03c7668f7e..0f722fb7ce 100644
--- a/tests/control_plane_ts/todo_completion_state.test.ts
+++ b/tests/control_plane_ts/todo_completion_state.test.ts
@@ -4,8 +4,10 @@ import test from "node:test";
import {
buildTodoCompletionMetadataUpdates,
- normalizeTodoCompletionValue,
- requireTodoCompletionMetadataValue,
+ normalizeTodoNoFollowup,
+ normalizeTodoCompletionContinuation,
+ normalizeTodoCompletionRecovery,
+ requireTodoCompletionMetadata,
selectTodoCompletionState,
TODO_COMPLETION_STATE_REQUEST_SCHEMA,
} from "../../loopx/control_plane/todos/completion_state.ts";
@@ -35,14 +37,22 @@ const fixture = JSON.parse(
}>;
};
+function normalizeValue(request: Record) {
+ const value = request.kind === "no_followup" ? normalizeTodoNoFollowup(request.value) :
+ request.kind === "continuation" ? normalizeTodoCompletionContinuation(request.value) : normalizeTodoCompletionRecovery(request.value);
+ return {schema_version: "loopx_todo_completion_state_result_v0", kind: request.kind, value};
+}
+function requireValue(request: Record) {
+ return {schema_version: "loopx_todo_completion_state_result_v0", value: requireTodoCompletionMetadata(String(request.key), request.value)};
+}
const methods = {
- normalize: normalizeTodoCompletionValue,
- require_metadata: requireTodoCompletionMetadataValue,
+ normalize: normalizeValue,
+ require_metadata: requireValue,
evaluate: selectTodoCompletionState,
metadata_updates: buildTodoCompletionMetadataUpdates,
} as const;
-test("pinned Python completion-state characterization remains exact", () => {
+test("persisted completion spellings and lifecycle compatibility remain exact", () => {
assert.equal(
fixture.schema_version,
"loopx_todo_completion_state_characterization_v0",
@@ -86,22 +96,12 @@ test("whole completion owner selects continuation and rejects contradictory inte
}), /cannot record both no_followup and a successor/);
});
-test("retired continuation-only RPC fails closed; live metadata decoding remains", async () => {
- const handlers = createEffectRuntimeHandlers({
- fingerprint: "completion-retirement-test", requestShutdown: () => undefined,
- });
- await assert.rejects(dispatchEffectRuntimeMethod(
- handlers, "todo.completion_state.continuation_for_write", {
- schema_version: TODO_COMPLETION_STATE_REQUEST_SCHEMA,
- no_followup: false, has_successor: false,
- },
- ), /unsupported Effect runtime method/);
- assert.deepEqual(await dispatchEffectRuntimeMethod(
- handlers, "todo.completion_state.require_metadata", {
- schema_version: TODO_COMPLETION_STATE_REQUEST_SCHEMA,
- key: "completion_continuation", value: "SUCCESSOR",
- },
- ), { schema_version: "loopx_todo_completion_state_result_v0", value: "successor" });
+test("retired scalar and summary RPCs fail closed", async () => {
+ const handlers = createEffectRuntimeHandlers({fingerprint: "completion-retirement-test", requestShutdown: () => undefined});
+ for (const method of ["todo.completion_state.continuation_for_write", "todo.completion_state.normalize",
+ "todo.completion_state.require_metadata", "todo.summary.project"]) {
+ await assert.rejects(dispatchEffectRuntimeMethod(handlers, method, {}), /unsupported Effect runtime method/);
+ }
});
test("runtime boundary rejects malformed authority input", () => {
diff --git a/tests/control_plane_ts/todo_read.test.ts b/tests/control_plane_ts/todo_read.test.ts
new file mode 100644
index 0000000000..3e6d4df450
--- /dev/null
+++ b/tests/control_plane_ts/todo_read.test.ts
@@ -0,0 +1,120 @@
+import assert from "node:assert/strict";
+import {mkdtemp, writeFile, rm} from "node:fs/promises";
+import {tmpdir} from "node:os";
+import {join} from "node:path";
+import test from "node:test";
+import {readTodo, TODO_READ_REQUEST} from "../../loopx/control_plane/todos/read_projection.ts";
+import type {JsonObject} from "../../loopx/control_plane/effect_program.ts";
+import {openLocalAuthorityStore, selectLocalSqliteAuthority} from "../../loopx/control_plane/coordination/local_authority_provider.ts";
+import {engageLegacyCoordinationWriterFence} from "../../loopx/control_plane/coordination/legacy_writer_fence.ts";
+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";
+
+const now = "2026-10-19T07:00:00Z";
+const row = (todo_id: string, extra: JsonObject = {}): JsonObject => ({schema_version: "todo_item_v0", todo_id,
+ index: 1, role: "agent", status: "open", done: false, text: "Review accepted evidence", task_class: "advancement_task",
+ archive_state: "active", source_section: "Agent Todo", ...extra});
+const group = (items: JsonObject[], extra: JsonObject = {}) => readTodo({schema_version: TODO_READ_REQUEST,
+ kind: "group", items, lineage: [], evaluated: false, role: "agent", source_section: "Agent Todo",
+ item_limit: 1, observed_at: Date.parse(now) / 1000, evaluated_at: now, ...extra});
+
+for (const provider of ["file", "sqlite"] as const) {
+ test(`${provider} full read keeps archived dependencies and fresh provider results without Markdown`, async t => {
+ const root = await mkdtemp(join(tmpdir(), "loopx-todo-read-"));
+ 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 body = "Verify the original acceptance requirement 😀. ".repeat(40);
+ const todos = [row("todo_waiting", {status: "deferred", done: true, text: body,
+ resume_when: "todo_done:todo_archived"}), row("todo_archived", {role: "user", status: "done", done: true,
+ task_class: "user_action", archive_state: "archive", source_section: "Completed Work Archive"})]
+ .sort((a, b) => String(a.todo_id).localeCompare(String(b.todo_id)));
+ const projection = {goal_id: goal, handoff_mode: "soft_claim", todos, leases: [],
+ todo_read_model: coordinationTodoReadModel(todos, TODO_CANONICAL_READ_RECORD_SCHEMA)};
+ const committed = await store.commitAuthority({expected_provider_revision: null, operation_id: "seed",
+ 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: "fixture", source_version: "fixture",
+ source_projection_sha256: canonicalAuthoritySha256(projection), expected_shadow_provider_revision: committed.provider_revision}});
+ assert.equal(fence.status, "applied");
+ await writeFile(state, new Uint8Array([255]));
+ const request = {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: null, status: null, todo_id: "todo_waiting", agent_id: null, limit: null,
+ read_scope: "active", json_references: true, thin: false, state_file: state, project: root}};
+ const result = await readTodo(request), payload = result.payload as JsonObject;
+ assert.ok(payload, JSON.stringify(result));
+ assert.equal(payload.read_only, true);
+ assert.equal((payload.todo as JsonObject).text, body);
+ assert.equal((payload.todo as JsonObject).resume_ready, true);
+ assert.equal((payload.authority_read as JsonObject).provider_revision, committed.provider_revision);
+ assert.equal((await store.loadAuthority()).status, "loaded");
+ const laterTodos = [row("todo_waiting", {status: "blocked", text: "Changed current requirement"}), todos.find(item => item.todo_id === "todo_archived")!]
+ .sort((a, b) => String(a.todo_id).localeCompare(String(b.todo_id)));
+ const changed = {...projection, todos: laterTodos,
+ todo_read_model: coordinationTodoReadModel(laterTodos, TODO_CANONICAL_READ_RECORD_SCHEMA)};
+ const later = await store.commitAuthority({expected_provider_revision: committed.provider_revision,
+ operation_id: "later", events: [], next_projection: changed, receipts: []});
+ assert.equal(later.status, "applied");
+ const reread = (await readTodo(request)).payload as JsonObject;
+ assert.equal((reread.todo as JsonObject).status, "blocked");
+ assert.equal((reread.todo as JsonObject).text, "Changed current requirement");
+ assert.notEqual((reread.authority_read as JsonObject).provider_revision, committed.provider_revision);
+ });
+}
+
+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}));
+ const result = await group(items), summary = result.summary as JsonObject;
+ assert.equal(summary.total_count, 4096);
+ assert.equal((summary.items as unknown[]).length, 1);
+ assert.equal((summary.terminal_closure_proof as JsonObject).item_count, 4096);
+ const first = await group([row("todo_owned", {status: "done", done: true, claimed_by: "agent-a"})]);
+ const changed = {...(first.items as JsonObject[])[0], no_followup: true};
+ await assert.rejects(() => group([changed], {evaluated: true, evaluations: first.evaluations}), /matching full-source/);
+});
+
+test("controller validation commands remain private while their declaration identity survives", async () => {
+ const result = await readTodo({schema_version: TODO_READ_REQUEST, kind: "fields",
+ todos: [row("todo_validation", {validation_command: " printf synthetic ", validation_label: "check"})],
+ observed_at: Date.parse(now) / 1000, evaluated_at: now});
+ const summary = (result.fields as JsonObject).agent_todos as JsonObject;
+ const item = (summary.items as JsonObject[])[0]!;
+ assert.equal(item.completion_validation_required, true);
+ // Pinned to the independent Python declaration codec, including null fields.
+ assert.equal(item.completion_validation_sha256, "31a837db75ed8cf0143228f0d3e94dae3886f0e79c496f7f4c335644f4c04020");
+ assert.equal(JSON.stringify(result).includes("printf synthetic"), false);
+});
+
+test("a receiver view cannot include a peer's addressed work in any selected lane", async () => {
+ const first = await group([row("todo_owned", {role: "user", task_class: "user_action", bound_agent: "agent-a"}),
+ row("todo_peer", {role: "user", task_class: "user_action", bound_agent: "agent-b", index: 2})],
+ {role: "user", source_section: "User Todo", item_limit: null});
+ const selected = await group(first.items as JsonObject[], {evaluated: true, evaluations: first.evaluations,
+ role: "user", source_section: "User Todo", item_limit: null,
+ selection: {role: "user", status: null, todo_id: null, agent_id: "agent-a"}});
+ assert.deepEqual((selected.items as JsonObject[]).map(item => item.todo_id), ["todo_owned"]);
+ for (const value of Object.values(selected.summary as JsonObject)) {
+ if (Array.isArray(value)) for (const item of value as JsonObject[]) {
+ if (item.todo_id) assert.equal(item.todo_id, "todo_owned");
+ }
+ }
+});
+
+test("source capture selects an evaluated view without reusing unrelated lineage carriers", async () => {
+ const item = row("todo_source_capture");
+ const result = await readTodo({schema_version: TODO_READ_REQUEST, kind: "source_fields",
+ groups: [{role: "agent", source_section: "Agent Todo", items: [item], include_empty_source: true}],
+ lineage: [item], evaluated_at: now, observed_at: Date.parse(now) / 1000,
+ projection_query: {roles: ["agent"], status: null, todo_id: null, agent_id: null, limit: null, source_name: "markdown_active_state"}});
+ const projected = result.projection as JsonObject;
+ assert.equal(projected.uncapped_todo_count, 1);
+ assert.equal((projected.todos as JsonObject[])[0]!.todo_id, item.todo_id);
+});
From 5e22ad93e2a09e158783d58a77a299eabf0d4390 Mon Sep 17 00:00:00 2001
From: aihop <4408344+hhyykk@users.noreply.github.com>
Date: Tue, 20 Oct 2026 14:01:14 +0000
Subject: [PATCH 2/3] fix(todos): remove retired acceptance import
Signed-off-by: aihop <4408344+hhyykk@users.noreply.github.com>
---
loopx/control_plane/coordination/local_authority.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/loopx/control_plane/coordination/local_authority.py b/loopx/control_plane/coordination/local_authority.py
index 1f4392d898..98f45aa9e5 100644
--- a/loopx/control_plane/coordination/local_authority.py
+++ b/loopx/control_plane/coordination/local_authority.py
@@ -7,8 +7,6 @@
from __future__ import annotations
-from ..goals.acceptance_readback import configured_checks
-
from collections.abc import Mapping
from copy import deepcopy
from pathlib import Path
From af00738263059cbab89278aa8b23fceff3653109 Mon Sep 17 00:00:00 2001
From: aihop <4408344+hhyykk@users.noreply.github.com>
Date: Wed, 21 Oct 2026 04:57:04 +0000
Subject: [PATCH 3/3] fix(collaboration): narrow nullable recovery reservations
Signed-off-by: aihop <4408344+hhyykk@users.noreply.github.com>
---
loopx/control_plane/collaboration/conversation_binding.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/loopx/control_plane/collaboration/conversation_binding.ts b/loopx/control_plane/collaboration/conversation_binding.ts
index 466fb6c52d..cf3068d553 100644
--- a/loopx/control_plane/collaboration/conversation_binding.ts
+++ b/loopx/control_plane/collaboration/conversation_binding.ts
@@ -496,7 +496,7 @@ function planContextRecovery(params: JsonObject, row: JsonObject, current: JsonO
|| !["context_window_exceeded", "host_gate", "upstream_transport_failure", "server_overloaded"].includes(String(failed.error_code))
|| typeof failed.upstream_turn_id !== "string" || !failed.upstream_turn_id) return no;
const previous = row.context_recovery == null ? null : requireJsonObject(row.context_recovery, "reserved context recovery");
- if (recovery.admitted_turn != null && previous?.failed_turn_id === failed.turn_id
+ if (recovery.admitted_turn != null && previous !== null && previous.failed_turn_id === failed.turn_id
&& Number.isSafeInteger(previous.routing_attempt) && Number(previous.routing_attempt) >= 1
&& Number(previous.routing_attempt) <= 3 && previous.version === version) {
// A crash after queue admission must adopt that exact canonical Turn,
@@ -560,7 +560,7 @@ function planRoutingRecovery(recovery: JsonObject, row: JsonObject, current: Jso
failed: JsonObject, previous: JsonObject | null, version: number): JsonObject | null {
if (!["host_gate", "upstream_transport_failure"].includes(String(failed.error_code))) return null;
// An unfinished older reservation must finish under its original identity.
- if (previous?.failed_turn_id === failed.turn_id && previous.routing_attempt === undefined) return null;
+ if (previous !== null && previous.failed_turn_id === failed.turn_id && previous.routing_attempt === undefined) return null;
const observed = recovery.native_observation == null ? null
: requireJsonObject(recovery.native_observation, "native failure readback");
if (observed && observed.pre_execution_routing_timeout !== true) return null;