Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Progress-review receipt qualification (2026-11-02)

## Owner boundary

This checkpoint moves the complete loaded-receipt qualification decision from
`capabilities/progress_review/context.py` to
`control_plane/work_items/progress_review_evidence.ts`, exposed through one
`progress_review.context.qualify` request. The existing built-in progress-review
capability retains its provider boundary; this adds no capability or provider.
The TS owner composes `inspectLocalGoalAcceptance` and
`progressReviewCriterionBasis` in process, caches each Todo inspection for the
batch, compares canonical bindings, withdraws obsolete signals, selects the
newest revision from the first qualified receipt, and applies the manual pin.
The Python loader's `progress_review_receipt_order_key` is the single ordering
owner: its stable newest-first output also preserves first-maximal tie behavior.
TS preserves that input order rather than sorting again.
These are successive reads, **not an atomic snapshot**. One unavailable Todo
cannot suppress other Todos. Exact Goal-instance refusal remains with the
acceptance owner. Operator studies and legacy receipts retain their semantics.

## Economics and retained Python

Instrumentation on both File and SQLite measures context-read crossings:

| Scenario | Before | After |
| --- | ---: | ---: |
| Off / no receipts / unavailable receipt store | 0 | 0 |
| Repeated canonical receipts for one Todo | 5 | 3 |
| Canonical receipts for two Todos | 6 | 3 |
| Removed criterion / newer receipt outside pin | 3 | 2 |
| Unavailable Todo plus current Todo | 5 | 3 |
| Wrong Goal instance | 2 | 2 |
| Operator study plus legacy receipt | 1 | 2 |

The general qualification boundary changes E + T + R to E + 1; E still
counts per-receipt evidence-scope decoding during receipt IO. Off and empty
paths do not call the batch. Manual/legacy-only batches and refused-instance
batches do not necessarily save crossings. No latency or long-horizon benefit
is claimed. The manual/legacy case rises from 1 to 2 because evidence-scope
IO still costs one call and every nonempty batch adds qualification. With no
`goal_acceptance` binding and no pin, no TS qualification call is semantically
needed; this slice reports that waste rather than duplicating the TS rule in
Python to bypass it. Product code changes by +111/−53 physical lines, net +58.
The Python context adapter adds 32 lines of transport/codec composition while
removing 50; handler registration adds one bridge line. The bounded net
increase buys one complete decision owner rather than another leaf facade.

Python deletes the Todo cache, acceptance inspection orchestration, criterion
comparison, stale rewriting, newest selection and pin filtering. It retains
receipt file IO and persisted-format/signal codecs, policy decoding, read-error
projection and summary composition for existing status, attention, refresh and
Chat callers. Selected source indices let the transport retain historical
numeric facts (integral floats and large integers) without repeating decisions.
The response omits the unused qualified array and computes selected source
indices directly. Sequence tokens and BigInt ordering are removed; arbitrary
integer ordering remains in the Python loader's single ordering owner. The acceptance
inspection facade still serves CLI, Chat and delegation; the criterion-basis
operation still serves the optional observer's canonical-basis reads. Neither
is an unused compatibility export.

## Qualification and exit

Before product changes, the test setup pins a synthetic relative acceptance
argv before contract hashing. File and SQLite pass the unchanged characterization
three consecutive times; the complete per-scenario context and status rendering
fixture is then frozen at 841 lines, with no output normalizer. It covers off,
empty, repeated/multiple Todos, wrong instance, removed criterion, unavailable
inspection/store, manual/legacy observations and pin drift. Separate numeric IO
and run-order tests protect the transport and selection boundaries.

The existing Python context adapter and this bridge operation exit when status,
attention, refresh-state and Chat consume the native receipt read/summary owner
without Python receipt IO or composition. Retire the temporary characterization
and crossing fixtures at that same verified caller cutover, preserving semantic
negative tests. This slice neither changes a public journey nor qualifies an

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 补齐当前事务的安装包深层资格(CE-INSTALL)。迁移前已接受的 RFC §6 对当前生产事务切换仍要求此资格,源码验证与较少 crossings 不能替代。在该 exact head 构建 wheel 和 sdist,分别安装到全新隔离环境,从 packaged files 执行真实资格判断、拒绝路径与 CLI 深层探针,记录来源和结果。

installed distribution, provider performance or long-horizon outcomes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 补齐默认 SQLite 全路径成本资格(CE-COST)。迁移前已接受的 RFC §6 对当前生产事务切换仍要求此资格,源码验证与较少 crossings 不能替代。按迁移前固定 base 与当前 head、相同 fixture 和环境,在默认 SQLite 测 cold/warm request、完整事务和完整 CLI 的 p50/p95,以及 idle/burst daemon RSS;按既有阈值判断,File 单独披露。


## Review repair validation

The fake loader now uses the real loader's ordering helper; both frozen JSON
fixtures remain byte-identical (SHA-256 checked). A TS test proves that newest
follows input order even when run metadata suggests another order. Existing
Python run-order and pin characterization remains green.

- Ruff: 2 changed Python files pass; pytest: 77 passed, 0 failed.
- Requested Node tests: 39 passed, 0 failed; control-plane typecheck passes.
- Semantic vocabulary smoke passes (29/29 vocabularies, 56/56 owner symbols).
- CLI base/head parity: File and SQLite, JSON and Markdown (4 comparisons).
Markdown is byte-identical; JSON differs only in source read clocks/ages,
which also vary on base-to-base reads. Progress-review payloads are unchanged.
- `git diff --check` passes. No generated `uv.lock` retained. All changes remain
unstaged; no commit, push or PR update performed.

Validation uses this checkout's installed `.venv` after verifying import
provenance; `uv run` could not acquire its default cache lock in the sandbox.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 进度评审 receipt 资格判定(2026-11-02)

## 所有者边界

本检查点将完整的已加载 receipt 资格判定,从
`capabilities/progress_review/context.py` 迁至
`control_plane/work_items/progress_review_evidence.ts`,通过一次
`progress_review.context.qualify` 请求暴露。现有内置 progress-review capability
保留 provider 边界;不新增 capability 或 provider。
TS 所有者在进程内组合 `inspectLocalGoalAcceptance` 和
`progressReviewCriterionBasis`,在批次内缓存每个 Todo 的检查,比较规范绑定,撤回
过期信号,以首个资格判定后的 receipt 选择最新 revision,并应用人工 pin。
Python 加载器的 `progress_review_receipt_order_key` 是唯一排序所有者:稳定的
最新优先输出同时保留并列时首个最大值的行为。TS 保持输入顺序,不再排序。
这些是先后发生的读取,**不是原子快照**。单个 Todo 不可用不会抑制其他 Todo。
精确 Goal 实例的拒绝仍由 acceptance 所有者负责。操作员研究与旧 receipt 的语义不变。

## 成本与保留的 Python

File 和 SQLite 上的插桩测得 context 读取跨运行时次数:

| 场景 | 之前 | 之后 |
| --- | ---: | ---: |
| 关闭 / 无 receipt / receipt 存储不可用 | 0 | 0 |
| 同一 Todo 的重复规范 receipt | 5 | 3 |
| 两个 Todo 的规范 receipt | 6 | 3 |
| criterion 被删除 / 较新 receipt 在 pin 之外 | 3 | 2 |
| 不可用 Todo 与当前 Todo 混合 | 5 | 3 |
| Goal 实例不符 | 2 | 2 |
| 操作员研究与旧 receipt 混合 | 1 | 2 |

通用资格判定边界由 E + T + R 变为 E + 1;E 仍包括 receipt IO 中逐条执行的
证据 scope 解码。关闭和空路径不调用批次。纯人工/旧 receipt 批次及实例被拒绝的
批次不一定减少跨运行时次数。不声称延迟或长程收益。人工/旧 receipt 场景从
1 增至 2,因为证据 scope IO 仍需一次调用,每个非空批次又增加一次资格判定。
没有 `goal_acceptance` 绑定且没有 pin 时,语义上不需要 TS 资格判定调用;本次
报告该浪费,不在 Python 中复制 TS 规则来绕过调用。产品代码物理行数
为 +111/−53,净增 58 行。Python context adapter 增加 32 行传输/codec 组合,
删除 50 行;handler 注册增加一行 bridge。此有界净增长换来一个完整判定所有者,
而不是再添一个叶子 facade。

Python 删除 Todo 缓存、acceptance 检查编排、criterion 比较、stale 改写、最新
revision 选择及 pin 过滤。保留 receipt 文件 IO、持久化格式/信号 codec、policy
解码、读取错误投影和摘要组合,服务现有 status、attention、refresh 与 Chat 调用者。
所选源索引使传输可保留历史数值事实(整数值浮点及大整数),而不重复判定规则。
响应移除未使用的 qualified 数组,并直接计算所选源索引。删除 sequence token
和 BigInt 排序;任意精度整数排序仍由 Python 加载器这一唯一排序所有者负责。acceptance 检查 facade 仍服务 CLI、Chat
和 delegation;criterion-basis 操作仍服务可选 observer 的规范 basis 读取。
二者均不是无调用者的兼容导出。

## 验证与退出条件

修改产品代码前,测试 setup 在合同哈希之前固定合成的相对 acceptance argv。
File 和 SQLite 在未改动产品代码上连续三轮通过 characterization,随后冻结完整的
逐场景 context 和 status 渲染 fixture,共 841 行,不使用输出 normalizer。
覆盖关闭、空集合、重复/多个 Todo、实例错误、criterion 删除、检查/存储不可用、
人工/旧观察与 pin 漂移。独立数值 IO 和运行排序测试保护传输与选择边界。

当 status、attention、refresh-state 和 Chat 不再依赖 Python receipt IO 或组合,
直接消费原生 receipt 读取/摘要所有者时,删除现有 Python context adapter 及此
bridge 操作。在同一次经过验证的调用者切换中删除临时 characterization 与跨运行时
次数 fixture,保留语义反例测试。本 slice 不改变公开用户旅程,也不证明已安装
发行版、provider 性能或长程成果。

## 评审修复验证

模拟加载器现复用真实加载器的排序 helper;两个冻结 JSON fixture 的 SHA-256
保持不变。TS 测试证明即使运行元数据暗示其他顺序,最新值仍遵循输入顺序。
既有 Python 运行排序及 pin characterization 继续通过。

- Ruff:2 个变更 Python 文件通过;pytest:77 通过,0 失败。
- 指定 Node 测试:39 通过,0 失败;control-plane typecheck 通过。
- 语义词汇 smoke 通过(29/29 vocabulary,56/56 owner symbol)。
- CLI base/head:File 与 SQLite 的 JSON、Markdown 共 4 项比较通过。
Markdown 字节一致;JSON 仅源读取时钟/年龄不同,base 重复读取也会变化。
progress-review payload 不变。
- `git diff --check` 通过;不保留生成的 `uv.lock`。全部修改保持 unstaged,
未 commit、push 或更新 PR。

验证使用已核对导入来源的本 checkout `.venv`;`uv run` 在 sandbox 中无法
取得默认 cache lock。
Original file line number Diff line number Diff line change
Expand Up @@ -2843,3 +2843,4 @@ with shared write/read status-phase checks. See the [2026-10-02 checkpoint](ledg
- Complete global summary and gate read transactions: [2026-11-02 checkpoint](ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.md).
- Complete global-risk read ownership: [2026-11-02](ledger/typescript-control-plane-migration-v0/2026-11-02-global-risks-read.md).
- Complete durable-completion evidence read: [2026-11-02 owner and crossings ledger](ledger/typescript-control-plane-migration-v0/2026-11-02-completion-evidence-read.md).
- Progress-review receipt qualification: [2026-11-02 checkpoint](ledger/typescript-control-plane-migration-v0/2026-11-02-progress-review-qualification.md).
Original file line number Diff line number Diff line change
Expand Up @@ -2108,3 +2108,4 @@ SQLite 中位数略升,收益不能外推为全面加速。这只资格化本
- 完整全局摘要与门禁读取事务:[2026-11-02 检查点](ledger/typescript-control-plane-migration-v0/2026-11-02-global-manager-reads.zh-CN.md)。
- 完整全局风险读取所有权:[2026-11-02](ledger/typescript-control-plane-migration-v0/2026-11-02-global-risks-read.zh-CN.md)。
- 完整 durable-completion 证据读取:[2026-11-02 owner 与调用边界记录](ledger/typescript-control-plane-migration-v0/2026-11-02-completion-evidence-read.zh-CN.md)。
- 进度评审 receipt 资格判定:[2026-11-02 检查点](ledger/typescript-control-plane-migration-v0/2026-11-02-progress-review-qualification.zh-CN.md)。
87 changes: 36 additions & 51 deletions loopx/capabilities/progress_review/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,77 +25,43 @@ def external_progress_review_context(
from .policy import progress_review_goal_policy
from .receipt import (
load_progress_review_receipts,
progress_review_receipt_order_key,
progress_review_receipt_summary,
)

policy = progress_review_goal_policy(goal)
goal_id = str(goal.get("id") or "").strip()
if policy["mode"] == "off" or runtime_root is None or not goal_id:
return None
from ...control_plane.effect_runtime import effect_runtime_result, EffectRuntimeRemoteError
from ...control_plane.goals.goal_ref_validation import exact_goal_ref
from ...control_plane.effect_runtime import (
EffectRuntimeRemoteError,
effect_runtime_projection_result,
)
read_error = None
try:
loaded, rejected = load_progress_review_receipts(Path(runtime_root), goal_id)
except (OSError, ValueError, EffectRuntimeRemoteError):
loaded, rejected = [], 0
read_error = "receipt_store_unavailable"
# Only newly scoped canonical observations need this current-owner check.
# Old receipts and manual studies retain their existing policy semantics.
current_by_todo = {}
qualified = []
for receipt in loaded:
binding = receipt.get("evidence_scope", {}).get("criterion_binding", {})
if binding.get("origin") == "goal_acceptance":
todo_id = binding["todo_id"]
if todo_id not in current_by_todo:
try:
goal_instance_id = goal.get("goal_instance_id")
observation = effect_runtime_result("goal.acceptance.inspect", {
"goal_id": goal_id, "runtime_root": str(runtime_root), "todo_id": todo_id,
**({"goal_ref": exact_goal_ref(goal_id, goal_instance_id)}
if goal_instance_id is not None else {}),
})
current_by_todo[todo_id] = observation.get("completion_requirements")
except (OSError, ValueError, EffectRuntimeRemoteError):
current_by_todo[todo_id] = None
try:
requirements = current_by_todo[todo_id]
selected = effect_runtime_result("progress_review.criterion_basis", {
"requirements": requirements, "criterion_ids": binding["criterion_ids"], "acceptance": [],
"goal_id": goal_id, "agent_id": receipt["run"]["agent_id"],
})["binding"] if requirements is not None else None
current = selected == binding
except (ValueError, EffectRuntimeRemoteError):
current = False
receipt = {**receipt, "criterion_current": current}
if not current:
receipt.update(status="stale", reason="criterion_basis_unavailable",
drift_signal={"noul": None, "choice": None})
qualified.append(receipt)
loaded = qualified
pinned = policy.get("contract_revision")
# Newest by run order, never by the observer's local sequence counter.
newest = max(loaded, key=progress_review_receipt_order_key, default=None)
newest_revision = str(newest["contract_revision"]) if newest else None
if pinned:
# Only receipts bound to the pinned revision are current evidence; the
# pin is manual, so a newer receipt under another revision means the
# observer basis moved and the Goal owner must re-pin (or not).
receipts = [item for item in loaded if item["contract_revision"] == pinned]
stale = len(loaded) - len(receipts) + sum(item.get("criterion_current") is False for item in receipts)
else:
receipts, stale = loaded, sum(item.get("criterion_current") is False for item in loaded)
# Empty IO results need no qualification; all nonempty decisions belong to TS.
qualification = effect_runtime_projection_result("progress_review.context.qualify", {
"goal_id": goal_id, "runtime_root": str(runtime_root),
"goal_instance_id": goal.get("goal_instance_id"),
"receipts": loaded,
"contract_revision": policy.get("contract_revision"),
}) if loaded else None
receipts = [
_receipt_number_codec(row, loaded[index])
for row, index in zip(qualification["selected"], qualification["selected_indices"], strict=True)
] if qualification else []
result = {
"policy": policy,
"receipts": receipts,
"summary": progress_review_receipt_summary(
receipts,
policy=policy,
rejected=rejected,
stale=stale,
newest_contract_revision=newest_revision,
stale=qualification["stale"] if qualification else 0,
newest_contract_revision=qualification["newest_contract_revision"] if qualification else None,
),
}
result["summary"]["read_state"] = "unavailable" if read_error else "observed" if loaded else "missing"
Expand All @@ -104,4 +70,23 @@ def external_progress_review_context(
return result


def _receipt_number_codec(value: Any, source: Any) -> Any:
"""Qualification never edits numeric facts; retain their Python IO spelling.

JSON numbers cannot carry integral floats or arbitrary integer precision
through JavaScript. Selected source indices preserve those historical facts
without repeating any qualification or selection decision here.
"""
if type(value) in (int, float) and type(source) in (int, float):
return source
if isinstance(value, dict) and isinstance(source, dict):
return {key: _receipt_number_codec(child, source.get(key)) for key, child in value.items()}
if isinstance(value, list) and isinstance(source, list):
return [
_receipt_number_codec(child, original)
for child, original in zip(value, source, strict=True)
]
return value


__all__ = ["external_progress_review_context"]
1 change: 1 addition & 0 deletions loopx/control_plane/effect_runtime_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ export function createEffectRuntimeHandlers(
["collaboration.delegation.result_use_limits", lazyHandler(() => import("./collaboration/delegation_result_use.ts"), ({delegationResultUseLimits}) => delegationResultUseLimits)],
["collaboration.delegation.result_use", lazyHandler(() => import("./collaboration/delegation_result_use.ts"), ({qualifyDelegationResultUse}) => qualifyDelegationResultUse)],
["progress_review.evidence_scope", lazyHandler(() => import("./work_items/progress_review_evidence.ts"), ({progressReviewEvidenceScope}) => progressReviewEvidenceScope)],
["progress_review.context.qualify", lazyHandler(() => import("./work_items/progress_review_evidence.ts"), ({qualifyProgressReviewContext}) => qualifyProgressReviewContext)],
["progress_review.criterion_basis", lazyHandler(() => import("./work_items/progress_review_evidence.ts"), ({progressReviewCriterionBasis}) => progressReviewCriterionBasis)],
["collaboration.delegation.adoption", lazyHandler(() => import("./collaboration/delegation.ts"), ({recordDelegationAdoption}) => recordDelegationAdoption)],
[
Expand Down
1 change: 1 addition & 0 deletions loopx/control_plane/effect_runtime_snapshot_methods.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"goal.checkpoint_read_context.commit",
"goal.checkpoint_read_context.inspect_replay",
"performance_diagnosis.inspect",
"progress_review.context.qualify",
"state.recovery.archive.verify",
"state.recovery.manifest.verify",
"runtime.checkpoint_artifacts"
Expand Down
Loading
Loading