Skip to content

Commit 459b82f

Browse files
committed
Merge reviewed M1 main sync into generated Turn contracts
Signed-off-by: song <liusongstep@gmail.com>
2 parents e7231c5 + e8731c6 commit 459b82f

38 files changed

Lines changed: 2244 additions & 267 deletions

‎CONTRIBUTING.md‎

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -79,26 +79,20 @@ Before adding or consolidating a public smoke, use the bilingual
7979
[good smoke guide](docs/development/good-smokes.md) to define its durable
8080
invariant, independent oracle, cadence, and public-safe fixture boundary.
8181

82-
Install and verify the checkout:
82+
For source development, run commands from the repository or dedicated worktree
83+
root with `uv`. It manages a compatible Python and installs the current checkout
84+
in the project environment, keeping checks separate from a globally installed
85+
LoopX release. See the [local validation commands](docs/development/testing-and-quality.md#local-validation-environment--本地验证环境)
86+
for environment, lockfile, and CI boundaries.
8387

8488
```bash
85-
git clone https://github.com/huangruiteng/loopx ~/loopx
86-
~/loopx/scripts/install-local.sh
87-
export PATH="$HOME/.local/bin:$PATH"
88-
loopx doctor
89-
loopx demo
90-
```
91-
92-
Common focused checks:
93-
94-
```bash
95-
python -m pip install -e ".[test]"
96-
python -m ruff check tests loopx/canary loopx/control_plane loopx/domain_packs loopx/presentation
97-
python -m mypy
98-
python examples/control_plane/cli-output-budget-regression-smoke.py
99-
python -m pytest -q
100-
loopx canary premerge --from-git-diff
101-
loopx check --scan-path loopx/ --scan-path tests/ --scan-path examples/ --scan-path docs/
89+
uv sync --extra test
90+
uv run --extra test python -m ruff check tests loopx/canary loopx/control_plane loopx/domain_packs loopx/presentation
91+
uv run --extra test python -m mypy
92+
uv run --extra test python examples/control_plane/cli-output-budget-regression-smoke.py
93+
uv run --extra test python -m pytest -q
94+
uv run --extra test loopx canary premerge --from-git-diff
95+
uv run --extra test loopx check --scan-path loopx/ --scan-path tests/ --scan-path examples/ --scan-path docs/
10296
git diff --check
10397
```
10498

‎docs/architecture/rfcs/harness-selection-dsh-pi-v0.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,14 @@ could otherwise be read as offering it:
362362
* **no tool authority** — the segment is refused by the dsh sandbox itself when
363363
it reaches for a write, and the channel reports `trust_scope: read_only`.
364364

365+
Because the segment cannot read anything for itself, every source it is expected
366+
to speak about has to be supplied by LoopX in the same bounded prompt: the
367+
declared evidence window and the registered-source read are composed by the Turn
368+
owner, cached and budgeted so a wider reach cannot slow every turn. This is a
369+
transport consequence, not a new authority: the segment still cannot widen its
370+
own scope, and any source it did not receive is a named coverage gap rather than
371+
evidence of no progress.
372+
365373
The earlier typed reason `managed_host_chat_transport_unsupported` is retired
366374
with this change; it described a transport gap that no longer exists, and keeping
367375
it would have made a working host unreachable. The reasons the channel can still

‎docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,11 @@ dsh 片段**,把通道可见的有界历史与当前消息交给它,并返
292292
* **无工具权威**:片段伸手写文件时会被 dsh 沙箱本身拒绝,通道据实回报
293293
`trust_scope: read_only`。
294294

295+
正因为片段自己读不到任何东西,它**被期望谈论的每一个来源都必须由 LoopX 放进同一份有界
296+
提示**:声明的证据窗口与已注册来源的读取由 Turn 侧组装,并带缓存与预算,因此「看得更远」
297+
不会拖慢每一轮。这是传输形态的后果,而不是新增权威:片段仍不能扩大自己的范围,它没有收到
298+
的来源是一条具名的覆盖缺口,而不是「没有进展」的证据。
299+
295300
早先的 typed reason `managed_host_chat_transport_unsupported` 随本次变更退役:它描述的
296301
是当时并不存在的传输缺口,保留它会让一个可用的宿主无法被选择。通道仍可回报的原因就是
297302
托管宿主自己的可启动性事实(`dsh_runtime_unavailable`、

‎docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md‎

Lines changed: 107 additions & 28 deletions
Large diffs are not rendered by default.

‎docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md‎

Lines changed: 89 additions & 25 deletions
Large diffs are not rendered by default.

‎docs/development/testing-and-quality.md‎

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -331,22 +331,69 @@ negative 或 mutation-style 断言,并让各 provider 复用同一 envelope
331331
经 review 的兼容理由,不得削弱或删除已有维度。禁止复制生产文本、标识、路径、日志、
332332
凭据或私有快照。PR 验证证据需报告 fixture schema、语义维度、provider arms 与有意差异。
333333

334-
Install the test dependencies once:
334+
### Local Validation Environment / 本地验证环境
335+
336+
Run from the repository or dedicated worktree root with `uv`. The project's
337+
`requires-python` declares Python `>=3.11`; it does not require an executable
338+
named `python3.11`. `uv` selects a compatible interpreter, creates `.venv`, and
339+
installs the checkout with the selected extras. Interpreter downloads depend on
340+
uv's download settings and network access. A system `python3` may be too old,
341+
and a global `loopx` may resolve to a different installed source tree.
342+
343+
在仓库或独立 worktree 根目录使用 `uv`。`pyproject.toml` 要求 Python `>=3.11`,
344+
无需依赖名为 `python3.11` 的命令。uv 选择兼容解释器,在 `.venv` 中安装当前源码
345+
与测试依赖;能否自动下载 Python 取决于下载配置与网络。系统 `python3` 可能过旧,
346+
全局 `loopx` 也可能指向另一个已安装版本。
335347

336348
```bash
337-
python -m pip install -e ".[test]"
349+
uv sync --extra test
350+
uv run --extra test python -m ruff check tests loopx/canary loopx/control_plane loopx/domain_packs loopx/presentation
351+
uv run --extra test python -m mypy
352+
uv run --extra test python examples/control_plane/cli-output-budget-regression-smoke.py
353+
uv run --extra test python -m pytest -q
354+
uv run --extra test loopx canary premerge --from-git-diff
355+
# For a fork whose PR base is upstream/main, use this instead:
356+
uv run --extra test loopx canary premerge --from-git-diff --git-diff-base upstream/main
357+
# Run one semantic smoke or check its generated inventory:
358+
uv run --extra test loopx canary smoke-suite --script semantic-vocabulary-drift-smoke.py
359+
uv run python scripts/generate_semantic_inventory.py --check
360+
git diff --check
338361
```
339362

340-
Run the fast repository gate:
363+
Confirm the interpreter and imported checkout when diagnosing a mismatch:
341364

342365
```bash
343-
python -m ruff check tests loopx/canary loopx/control_plane loopx/domain_packs loopx/presentation
344-
python -m mypy
345-
python examples/control_plane/cli-output-budget-regression-smoke.py
346-
python -m pytest -q
347-
git diff --check
366+
uv run python -c "import sys, loopx; print(sys.executable); print(loopx.__file__)"
348367
```
349368

369+
Canary executes Python checks with the interpreter that launched LoopX
370+
(`sys.executable`). Its displayed `python3` command is not a second interpreter
371+
selection. Keep subprocesses on `sys.executable`; use `uv run` at the developer
372+
entrypoint. Avoid `uvx loopx` or `uv run --no-project` when validating this
373+
checkout, and change into the intended worktree before running Git-based checks.
374+
An activated compatible environment remains a supported alternative: install
375+
with `python -m pip install -e ".[test]"`, then use that environment's Python
376+
and LoopX commands directly.
377+
378+
Canary 使用启动 LoopX 的 `sys.executable` 执行 Python 检查,显示的 `python3`
379+
不是重新选择解释器。子进程继续复用 `sys.executable`,只在开发入口使用 `uv run`。
380+
检查当前源码时不要改用 `uvx loopx` 或 `uv run --no-project`;Git diff 检查前先进入
381+
目标 worktree。已有兼容虚拟环境也可用 `python -m pip install -e ".[test]"` 安装源码,
382+
随后直接使用该环境的命令。
383+
384+
The repository does not currently track `.python-version` or `uv.lock`. `uv`
385+
creates a local lockfile during resolution; keep that generated file out of
386+
unrelated PRs. Introducing a shared lock or interpreter pin is a separate
387+
repository policy change. Do not claim identical environments from
388+
`requires-python` alone, or use `--locked` before a reviewed lockfile exists.
389+
CI keeps its explicit Python versions and pinned/hash-checked installation
390+
paths. Historical validation receipts keep the commands that actually ran.
391+
392+
当前仓库未跟踪 `.python-version` 或 `uv.lock`。uv 解析依赖时生成的本地锁文件不要
393+
混入无关 PR;共享锁文件与解释器版本固定应单独评审。最低版本要求不等于环境完全
394+
可复现,没有已评审锁文件时也不使用 `--locked`。CI 保留显式 Python 版本与固定依赖/
395+
哈希校验的安装路径,历史验证记录保留实际执行过的命令。
396+
350397
`.github/workflows/python-tests.yml` runs this fast lane for relevant Python
351398
pull requests. It intentionally excludes provider-backed evaluation and the
352399
full smoke catalog, so ordinary iteration does not depend on credentials,
@@ -377,7 +424,7 @@ Sonar 只复用同一次 run 的 XML,不重复测试、不跨 run 取产物。
377424
Sonar,测试 job 不接收 Sonar secret。触发范围取原有两套 workflow 的并集;纯前端
378425
PR 使用前述豁免,Sonar 配置变更仍全量运行,包括没有 token 的 fork。
379426

380-
Reproduce one shard locally with `python -m pytest -q -n 2 --splits 4 --group 1
427+
Reproduce one shard locally with `uv run --extra test python -m pytest -q -n 2 --splits 4 --group 1
381428
--splitting-algorithm least_duration --cov=loopx`. Omit the split arguments to
382429
run the complete suite locally. 全量本地测试仍省略分片参数即可。
383430

‎docs/reference/protocols/manager-evidence-and-continuity-v0.md‎

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,49 @@ the window are outside coverage, not evidence of no progress, and the newest
238238
finding for a Goal is never dropped by the window bound.
239239

240240
The same block declares `sources`: the local registry source plus every
241-
configured SSH host alias with its read status and scope. Declaring a source
242-
performs no remote connection and grants no authority; reading remote rows still
243-
requires the explicit bounded remote read path and its before/after scope checks.
244-
A declared but unread source is a named coverage gap. The manager must state the
245-
window and the sources it actually read, and must not present a single-day read
246-
or an unread host as whole coverage.
241+
SSH host this machine **registered** for LoopX evidence (an `evidence_ssh_hosts`
242+
grant on any channel), with its read status and scope — not every configured SSH
243+
alias, since an operator's `git` host or personal jump host holds no Core state
244+
and only adds prompt noise. Declaring a source performs no remote connection and
245+
grants no authority. A declared but unread source is a named coverage gap. The
246+
manager must state the window and the sources it actually read, and must not
247+
present an unread host as whole coverage.
248+
249+
The window itself is a selected decision rather than a discovered fact:
250+
`days_source` reports `product_default`, `explicit_config`
251+
(`LOOPX_MANAGER_EVIDENCE_WINDOW_DAYS`, 1..30) or `explicit_argument`, beside
252+
`days_default`, `days_env_var`, `days_bounds` and `days_reason`. A missing,
253+
out-of-bounds or unreadable explicit value keeps the shipped default and reports
254+
its reason, so a misconfiguration can neither widen the prompt nor answer a
255+
narrower window than it declares.
256+
257+
## A prompt-only segment receives the declared source read
258+
259+
An interactive endpoint reads a declared remote source on demand and pays no
260+
source latency. A prompt-only steward segment has no read tool, so a declared
261+
source it never receives is a coverage gap it cannot close. In that case the Turn
262+
owner reads the registered sources before the segment starts and adds a
263+
`manager_remote_evidence_v0` block, with `remote_read` declaring `inline_in_prompt`
264+
rather than `on_demand_tool` in `evidence_window`.
265+
266+
The read is bounded so it cannot slow every Turn: one dial per Turn, at most two
267+
hosts, nine seconds per host inside a ten-second Turn budget, and eight portfolio
268+
rows per host. A read inside `ttl_seconds` (ten minutes) is served from cache
269+
instead of dialling again, and cache entries are keyed by host, window and the
270+
exact grant scope, so a changed grant or window re-reads rather than answering
271+
from a narrower cached read. The read reuses the same `read_remote` path and its
272+
before/after scope checks, so it never widens authority. The declaration and the
273+
read use the same SSH configuration, so one packet cannot call a host
274+
unconfigured and read it in the same Turn.
275+
276+
Every declared source carries a typed outcome in the same block: `read` or
277+
`cached` with `read_at` and `age_seconds`; `unavailable` with its reason, the last
278+
successful read and a `coverage_effect`; `not_configured` for alias drift; or
279+
`deferred_budget` with the last successful read. A failed read keeps its last
280+
successful rows only as `source_freshness: "stale"` with
281+
`remote_source_rows_are_stale` in `limitations`. A stale or unavailable source is
282+
a named coverage gap: it must never be presented as current progress, and it must
283+
never be read as no progress. A source read failure never fails the Turn.
247284

248285
## A bounded portfolio with explicit coverage
249286

‎docs/reference/sqlite-authority-store.md‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,14 +310,21 @@ The shipped version-1 database keeps one full projection per retained row, so it
310310
cannot be read by the version-2 provider. `sqlite_authority_migration.ts`
311311
migrates one Goal database in place: it reads the frozen version-1 rows, proves
312312
every stored commit digest, writes the checkpoint/delta log, proves that each
313-
written delta reconstructs its projection, requires the commit count to match,
314-
swaps tables and updates the schema version inside a single
313+
written delta reconstructs its projection, reads the not-yet-swapped tables back
314+
and replays them through the store's own delta decoder, requires the commit
315+
count to match, swaps tables and updates the schema version inside a single
315316
`BEGIN IMMEDIATE` transaction. Any failure rolls back and leaves version 1
316317
untouched; a second run reports `already_current`; a rewritten proof, a
317318
mismatched goal/incarnation or an existing swap target fails closed. Cursors,
318319
operation IDs, commit digests, provider revisions, receipts, events and scan
319320
pages are byte-identical after the migration.
320321

322+
Retained projections keep every JSON object key the version-1 provider accepted,
323+
including an empty key and a `__proto__` key: a database the previous provider
324+
could read must not become one the version-2 provider cannot. The replay proof
325+
is what keeps that promise honest, because identical identity and digest columns
326+
alone would not show that a migrated state log is unreadable.
327+
321328
A version-1 database that published only its schema and metadata — the state a
322329
goal leaves behind when it selected the provider and never committed — migrates
323330
to an equally empty version-2 database instead of failing, so the operator is
@@ -390,4 +397,8 @@ delta”:活跃头读取只用自己的行、对应提交和游标连续性自
390397
才写入,`--expected-identity` 可拒绝并非操作者所指的 incarnation,失败保持 v1
391398
原样)。只发布过 schema 与 metadata、从未提交的 v1 库会迁移成同样为空的 v2 库,
392399
不会让操作者落在两个 provider 都不接受的状态。迁移不改 cursor、operation id、
393-
commit digest、provider revision、receipt、event 或 scan 页面字节。
400+
commit digest、provider revision、receipt、event 或 scan 页面字节。迁移在提交前
401+
还会把刚写入的表读回来、用 store 自己的 delta 解码器重放一遍:只核对搬过去的
402+
标识与摘要无法证明新的状态日志可读。v1 能接受的 JSON key(包括空字符串和
403+
`__proto__`)在 v2 中保持同样的数据语义,迁移不会把原本可读的库变成读不出来的
404+
状态。

‎examples/loopx-steward-managed-chat-smoke.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,14 @@ def _run_turn(args: argparse.Namespace) -> int:
246246
with tempfile.TemporaryDirectory(prefix="loopx-steward-managed-chat-") as directory:
247247
root = Path(directory)
248248
store = ChatSessionStore(root / "store")
249+
# A real registry keeps this on the collected path the steward actually
250+
# runs, instead of the registry-unavailable envelope.
251+
registry = root / "registry.global.json"
252+
registry.write_text(json.dumps({"goals": []}), encoding="utf-8")
249253
runtime = ChatRuntimeController(
250254
store=store,
251255
codex_bin="fixture-codex",
256+
registry_path=registry,
252257
hard_timeout_sec=args.timeout_seconds,
253258
)
254259
try:
@@ -305,10 +310,20 @@ def _run_turn(args: argparse.Namespace) -> int:
305310
"manager_evidence_window_v0" in evidence_text
306311
and '"applies_to": "recent_delivery_history"' in evidence_text
307312
and '"receipt_detail_policy": "latest_full_per_goal"' in evidence_text
313+
and '"days_source": "product_default"' in evidence_text
314+
and '"days_bounds"' in evidence_text
315+
and '"remote_read": "inline_in_prompt"' in evidence_text
308316
and '"sources"' in evidence_text
309317
and '"declared_unread_sources"' in evidence_text,
310318
"the prompt-only segment must receive the bounded window and declared sources",
311319
)
320+
_assert(
321+
"manager_remote_evidence_v0" in evidence_text
322+
and '"read_status": "not_read"' in evidence_text
323+
and '"declared_source_count": 0' in evidence_text,
324+
"a prompt-only segment must receive the declared source read, and this "
325+
"fixture registers no host so the read stays empty instead of dialling",
326+
)
312327
payloads = [
313328
json.loads(item["body"])
314329
for item in CAPTURED_REQUESTS

‎examples/pr-review-command-smoke.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,7 @@ def fake_run_gh_json(args: list[str], *, cwd: Path | None = None) -> object:
910910
"behavior_change_disclosure",
911911
"guidance_vs_obligation",
912912
"durable_smoke_value",
913+
"semantic_alignment",
913914
}, requirements
914915
assert requirements["symbol_map"]["item_count"] == {"minimum": 2, "maximum": 5}
915916
assert "caller_evidence" in requirements["symbol_map"]["item_fields"]
@@ -966,6 +967,7 @@ def fake_run_gh_json(args: list[str], *, cwd: Path | None = None) -> object:
966967
"change_proportionality": ["disproportionate", "not_yet_proven"],
967968
"default_off_isolation": ["not_isolated", "not_yet_proven"],
968969
"authority_semantics": ["misleading", "not_yet_proven"],
970+
"semantic_alignment": ["not_yet_proven", "violated"],
969971
}
970972
assert execution["finding_contract"]["findings_first"] is True
971973
first_plan = first["review_plan"]

0 commit comments

Comments
 (0)