Skip to content

test(authority): add the shared-goal-authority E2E stage ladder - #3869

Merged
huangruiteng merged 6 commits into
mainfrom
test/shared-authority-e2e-ladder
Sep 4, 2026
Merged

huangruiteng merged 6 commits into
mainfrom
test/shared-authority-e2e-ladder

Conversation

@wchwawa

@wchwawa wchwawa commented Sep 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Add one incremental end-to-end "stage ladder" that exercises every completed stage claim of the Shared Goal Authority RFC through the real python -m loopx.cli (real_cli) or the retained store-level probes (store_direct): 12 rows for Stage 0, Stage 1, Stage 2A, Stage 2B, and the Stage 2C observation foundation; the 9 rows of the Stage 2C parity half are declared pending, never counted as pass.
  • Exit policy: exit 0 iff fail == 0 and (unverified == 0 or --allow-unverified) and (pending == 0 or --allow-pending). An environment-gated row without its stack reports unverified; a selected pending row is an unmet obligation and blocks a green exit; the pytest projection skips gated rows visibly as unverified: <reason>. A privacy scan rewrites any leaked temporary root, home directory, connection URL, configuration value, or NoKV authority input path into fail/privacy_violation.
  • Test-only. No production entry point constructs any store; the ladder adds no product path and reads candidate bytes only through the production TypeScript FileAuthorityStore via a read-only probe.

@huangruiteng This PR is stacked on #3818 (base branch codex/local-authority-shadow-product-path). The row s2c1.dual_runtime_root_consistency is the ladder form of the mismatched-root regression gate from #3818 round 3; retarget to main once #3818 merges.

Issue Or Task

  • Executable evidence for the completed stages of docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md (Stage 0, 1, 2B, 2C observation foundation), with the Stage 2A and Stage 2C parity rows declared pending.
  • Contributor task ID: stage-2c-e2e-stage-ladder

What is in the ladder

Row Stage Path Gate
s0.file_matrix_twelve_rows 0 store_direct deterministic
s0.nokv_live_matrix 0 store_direct env:nokv_legacy
s1.cli_document_decodes_through_ts_store 1 real_cli deterministic
s2a.nokv_live_qualification 2a store_direct env:nokv_authority
s2b.postgresql_conformance_live 2b store_direct env:postgresql
s2c1.configure_enable_disable_roundtrip 2c1 real_cli deterministic
s2c1.every_writer_family_captures 2c1 real_cli deterministic
s2c1.default_off_isolation 2c1 real_cli deterministic
s2c1.candidate_failure_preserves_primary 2c1 real_cli deterministic
s2c1.crash_gap_loses_observation 2c1 real_cli deterministic (POSIX)
s2c1.dual_runtime_root_consistency 2c1 real_cli deterministic
s2c1.migration_seeds_new_lineage 2c1 real_cli deterministic

Pending (declared, not claimed, and blocking a green exit unless --allow-pending): nine s2c2.* rows (outbox entries, idempotent drain, SIGKILL before and during drain, rollback with pending entries, parity equal and divergent, migration seed-and-drain, growth measurement). examples/shared-goal-authority-e2e/README.md lists the test seams the Stage 2C parity PRs must expose so those rows can be implemented.

Files: loopx/control_plane/testing/authority_e2e_ladder.py (row registry, Stage 0/1/2A/2B runners, loopx_shared_goal_authority_e2e_report_v0, bindings, privacy scan, exit policy), authority_e2e_row_support.py (row vocabulary and CLI helpers), authority_e2e_rows_stage2c.py (Stage 2C rows), loopx/control_plane/testing/authority_e2e_fixtures.py, tests/control_plane_ts/authority_store_readback_probe.ts, tests/control_plane/test_shared_goal_authority_e2e.py, examples/shared-goal-authority-e2e/, and a Stage-ladder evidence subsection in the RFC (en, zh-CN) plus section 8 of the evidence note.

Behavior disclosure: child CLI processes run with HOME (and USERPROFILE on Windows) pointed at a per-workspace directory so the ladder never touches the operator's real home.

Round 2 (exact head bdded14f4)

  • Stage 2A is no longer pending: s2a.nokv_live_qualification runs the merged examples/nokv-authority-store/live-qualification.ts --execute-live against an existing workbench with a fresh tenant/goal pair (gate env:nokv_authority: LOOPX_NOKV_AUTHORITY_LIVE=1, LOOPX_NOKV_AUTHORITY_CONFIG_JSON, LOOPX_NOKV_AUTHORITY_PYTHON, LOOPX_NOKV_AUTHORITY_WORKBENCH; each missing input has its own unverified reason). It requires ok=true, the single-node store-conformance scope, every check passed, SDK 0.11.0 / API 1, and no promotion or availability claim; evidence carries check ids, counts, and config and workbench digest prefixes only, and every configuration value and input path is a forbidden token.
  • Pending rows no longer exit green: exit_code_for treats pending != 0 like unverified != 0 unless --allow-pending; --row s2c2.parity_equal exits 1 with executed=0, --stage 2c2 exits 1, a mixed selection exits 1 even when its executable row passes, and --list prints the registry without an exit policy. summary.executed and exit_policy.allow_pending make "report generated" and "selection verified" distinguishable.
  • Live run on a local single-node NoKV stack (etcd routing + RustFS object store, SDK 0.11.0): --stage 0 --stage 2a: s0.file_matrix_twelve_rows pass, s0.nokv_live_matrix pass (13 rows, 12 parity rows, restored lineage fails closed), s2a.nokv_live_qualification pass (13 checks, final generation 3); the report contains no endpoint, credential, path, or workbench name.
  • The ladder module crossed the 1500-line maintainability ceiling, so the row vocabulary and the Stage 2C rows moved into two sibling modules (strict mypy, probe digests updated).

Round 3 (exact head 33889b0c1)

Fixes the blocker found on 579681fb9: a forbidden token that appeared only in bindings (for example a probe path) was redacted and marked bindings.privacy_violation=true, but exit_code_for() read row counts alone, so the run finalized as fail=0 / exit 0.

  • summary.privacy_violations counts leaking rows plus the bindings block; the exit policy consumes it ahead of every relaxation flag (exit 0 iff fail == 0 and privacy_violations == 0 and (unverified == 0 or allow_unverified) and (pending == 0 or allow_pending)), and the standalone runner prints the condition on stderr. A report-level count was chosen over a synthetic row so executed and the row registry keep meaning what they say.
  • Regression: a clean passing row plus a token that only the bindings carry now yields bindings.privacy_violation=true, summary.privacy_violations=1, fail=0, exit 1, and exit_code_for(..., allow_unverified=True, allow_pending=True) == 1.
  • The CI projection's skip list names each skipped row's product-CLI twin test and a guard fails when a twin is renamed or removed (your non-blocking note and @now-ing's point 2).
  • README, RFC (en and zh), and the evidence note state the rule.

Validation

Round 3 on 33889b0c1: pytest projection 12 passed / 8 skipped, LOOPX_LADDER_FULL=1 17 passed / 3 skipped; standalone ladder 9 pass / 3 unverified / 9 pending, privacy_violations=0, exit 0 with both relaxation flags; configured mypy (19 files), Ruff, git diff --check, docs governance and asset-integrity smokes, premerge against origin/main all clean.

  • pytest -n 2 tests/control_plane/test_shared_goal_authority_e2e.py tests/control_plane/test_local_authority_shadow_config.py tests/control_plane/test_local_authority_shadow_cli_e2e.py: 18 passed, 8 skipped (3 gated rows unverified: ..., 5 rows pinned by the CLI E2E suite); LOOPX_LADDER_FULL=1: 15 passed, 3 skipped; maintainability ratchet green after the module split
  • python examples/shared-goal-authority-e2e/ladder.py: pass 9, fail 0, unverified 3, pending 9, executed 12, exit 1; --allow-unverified alone still exits 1 (pending); --allow-unverified --allow-pending exits 0; --row s2c2.parity_equal exits 1 (executed 0) and 0 only with --allow-pending; --stage 2c2 exits 1; mixed --row s0.file_matrix_twelve_rows --row s2c2.parity_equal exits 1; the JSON report contains no temporary root, home, repository path, or configuration value
  • Live on a local NoKV stack: --stage 0 --stage 2a --allow-pending: 3 pass (s0.file_matrix_twelve_rows, s0.nokv_live_matrix 13 rows, s2a.nokv_live_qualification 13 checks, SDK 0.11.0 / API 1), exit 0, no leaked token
  • s2c1.every_writer_family_captures: 12 writer families captured, candidate cursor 12; s2c1.dual_runtime_root_consistency: 5 observations, 1 store identity, candidate cursor 5, registry root without lineage or lease state
  • npm run typecheck:control-plane (the read-only probe is in the include list)
  • Ruff on tests, loopx/control_plane, examples/shared-goal-authority-e2e; configured mypy with both new modules in the strict list
  • loopx canary premerge --from-git-diff --git-diff-base <#3818 head> on this head: standard tier, 4/4 direct checks, 18/18 catalog canaries with 0 failures and 0 warnings, full-public risk profile ok, premerge-public-boundary clean, 0 manual holds
  • s2b.postgresql_conformance_live was not run in this environment (no PostgreSQL); it reports unverified by design

Premerge comment fields: changed_surfaces = loopx/control_plane/testing/* (new, test-only), tests/control_plane_ts/authority_store_readback_probe.ts, tests/control_plane/test_shared_goal_authority_e2e.py, examples/shared-goal-authority-e2e/*, RFC and evidence docs, pyproject.toml mypy list, tsconfig.control-plane.json; direct_checks = 4/4; catalog_canaries = 18/18; risk_profile_smokes = full-public ok; public_private_boundary = clean; failures_or_skips = 3 env-gated rows unverified without their stacks (two NoKV rows verified live locally); manual_holds = none; merge_decision = ready for review after #3818.

Type of Change

  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Public docs or presentation surface (README, protocols, dashboard)

Technical Direction

Boundary Checklist

  • I did not commit .loopx/, .codex/goals/, live ACTIVE_GOAL_STATE.md, credentials, private benchmark traces, verifier output, raw agent sessions, internal document links, or local machine paths.
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

@wchwawa
wchwawa requested a review from huangruiteng September 3, 2026 01:30
@wchwawa

wchwawa commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

CI note on 7a6639390: the pytest job on this branch was cancelled twice by the workflow's 15-minute timeout-minutes (15m02s and 15m13s; the same job on current main runs 14m38s to 14m44s, and the identical suite passed on the stacked #3870 in 13m03s). Rather than wait on a workflow change, this head keeps the default CI projection to the rows that only the ladder exercises (file matrix, TypeScript read-back, every writer family, migration) and skips, with an explicit reason, the five s2c1.* rows whose assertions tests/control_plane/test_local_authority_shadow_cli_e2e.py already pins through the same product CLI path. LOOPX_LADDER_FULL=1 runs every row in pytest; examples/shared-goal-authority-e2e/ladder.py is unchanged and still runs all eleven rows (pass 9, unverified 2, pending 10 here). If you would rather raise the job timeout and keep the full projection in CI, that is a one-line revert.

@huangruiteng huangruiteng left a comment

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.

动机

这个 PR 想把 Shared Goal Authority RFC 已完成阶段的证据从分散的 unit/E2E、示例脚本和人工说明,收敛成一个可增量执行的 stage ladder。价值在于:每个阶段声明都有稳定 row id、明确 product path、环境 gate、机器可读结果和退出策略;尚未完成的 Stage 2A/2C2 项也应显式显示为 unverified 或 pending,而不是被绿色测试吞掉。旧状态下,file/NoKV/PostgreSQL、CLI observation、迁移、runtime-root 等证据分散,无法用一份报告回答“这个 exact checkout 实际证明了哪些阶段”。这个 PR 的方向与 RFC 晋升流程吻合,而且坚持 test-only:writer 由真实 python -m loopx.cli 驱动,candidate 通过生产 TypeScript FileAuthorityStore 只读回查,没有新增第二套生产写入口。

改动思路

authority_e2e_ladder.py 作为 registry/runner/report owner:LadderRow 定义 stage、path、gate 和执行函数,PendingRow 声明未来义务,run_row() 把 assertion、环境缺失和异常收敛为 pass/fail/unverified,build_report() 绑定 commit/probe digest/经过哈希的环境事实并做 privacy scan。authority_e2e_fixtures.py 创建隔离 registry、runtime、repo 和 HOME,所有行为写入都经 child CLI;authority_store_readback_probe.ts 只调用 load/scan/readReceipt,不调用会创建 identity 的 storeIdentity()。

正向路径是选择 stage/row → 检查环境 gate → 每行在独立临时 workspace 执行真实 CLI 或 store probe → 收集 public-safe evidence → 汇总 pending/bindings → privacy scan → 根据 fail/unverified 决定退出码。负向路径包括缺 NoKV/PostgreSQL 环境时显式 unverified、POSIX-only 在 Windows 上 unverified/skip、CLI/探针失败时 redacted fail、报告中出现 root/HOME/URL/config value 时改写为 privacy_violation。pytest projection 为 CI 提供逐行可见性,standalone example 保留完整 11-row 执行;五条已有相同 CLI E2E 覆盖的慢行默认 skip,并可用 LOOPX_LADDER_FULL=1 恢复。

具体改动

  • authority_e2e_fixtures.py:定义 GoalWorkspace、migration fixture、隔离 child environment、CLI spawn/kill、observation lock、candidate document 与 TAP/readback helpers。
  • authority_e2e_ladder.py:实现 Stage 0 file/NoKV matrix、Stage 1 CLI→TS store readback、Stage 2B live PostgreSQL,以及 Stage 2C1 configure、writer family、default-off、candidate failure、SIGKILL gap、dual runtime root、migration rows;并提供 row filtering、bindings、redaction、JSON report 和 CLI。
  • authority_store_readback_probe.ts:对真实 FileAuthorityStore 做 bounded load/scan/receipt projection,只输出 cursor、revision、ids 与计数等有限字段。
  • test_shared_goal_authority_e2e.py:把 row 投影为参数化 pytest,并钉住 vocabulary、pending registry、unverified exit policy、privacy violation、list/filter 和 report schema。
  • examples/shared-goal-authority-e2e/:提供可直接复跑的入口、row/gate/报告说明,以及后续 2C2 PR 必须暴露的测试 seams。
  • 英中 RFC 与 evidence note:登记 2026-09-03 的 9 pass / 2 unverified / 10 pending 边界;pyproject/tsconfig 纳入 strict mypy 与 probe typecheck。

关键代码讲解

  1. run_row() 是 typed failure boundary:先处理 OS/environment gate,再把 runner exception 变成经过 forbidden-token redaction 的 fail,不会让异常绕过报告。
  2. build_report()/assert_public_safe() 是公开证据边界:rows 中的泄漏降级为 privacy_violation,bindings 泄漏则整体清空并标记,之后重新计算 summary/exit code。
  3. _row_every_writer_family_captures() 和 _row_dual_runtime_root_consistency() 是最有产品价值的 Stage 2C1 行:它们从 CLI action 贯穿到 candidate cursor/head/store identity,并断言 provider 不参与 local decision/writeback。
  4. _row_crash_gap_loses_observation() 通过持有 observation lock、等待 primary Todo 可见、SIGKILL writer,再验证下一次 snapshot 恢复,准确描述 #3818 当前“无 durable outbox”的边界。
  5. exit_code_for() 是 harness 是否可用于 gate 的最终权威,目前只检查 fail/unverified,未处理 selected pending,这也是下述 blocker 之一。

对主干的风险

当前 exact head 有两个会让 stage report 产生错误绿色/错误完备性声明的 blocker。

  1. 已完成且已在本 head 中的 Stage 2A 仍被声明为等待合并。 PENDING_ROWS 把 s2a.nokv_live_qualification 写成 pending_until="PR #3819 merge",RFC/README 也说等待 #3819;但 #3819 已于 2026-09-02T15:59:33Z 合并,而 #3869 创建于次日,并且 merge commit ed00e671a… 已确认是 exact head 7a663939… 的 ancestor。代码树中也已经包含 examples/nokv-authority-store/live-qualification.ts 与相应 harness tests。于是本 PR “exercise every completed stage claim”的核心声明不成立。应把它升级为真正的 env-gated Stage 2A row(调用已合并的 live qualification,缺独立写入参数时报告具体 unverified),或收窄 PR/RFC 的完备性声明并给出新的、真实的 pending condition;不能继续以已经满足的 merge 条件标 pending。

  2. 选择 pending-only row 会零执行却 exit 0。 我在 exact head 运行 ladder.py --row s2c2.parity_equal,得到 pass=0, fail=0, unverified=0, pending=1 且进程退出 0。这与模块文档“green only when every selected row passed”冲突,也允许 CI 选择一个尚未实现的 obligation 并拿到成功。exit_code_for() 完全忽略 summary.pending。最小修复是:当用户显式通过 --row/--stage 选择到 pending 项且没有可执行通过证据时非零退出(或新增显式 --allow-pending,默认非零),并给 pending-only、mixed executable+pending、list-only 三条路径加测试。全量增量报告可以继续展示 pending,但必须把“报告生成成功”和“所选资格项验证成功”区分清楚。

独立验证:默认 pytest projection 8 passed, 7 skipped;standalone 完整 ladder 9 pass, 2 unverified, 10 pending,与作者结果一致;未传 --allow-unverified 的 exit policy 已由测试覆盖。TypeScript typecheck、两个新 Python module 的 configured mypy、Ruff、diff-check 和 GitHub required checks通过。NoKV/PostgreSQL live rows因本环境缺对应服务未执行;这部分正确显示为 unverified。默认 CI 跳过五条已有 CLI E2E 同形覆盖的慢行是公开且可恢复的,没有被我当作隐式通过。

我的整体评价

实现的 test-only 隔离、真实 CLI 路径、TS readback、public-safe report 和未验证状态表达都很扎实,9 条 deterministic 结果也确实可复现;约 2.7k 行主要是可维护的场景 fixture 和声明式证据,而非生产复杂度。不过这个 PR 的核心产品就是“证据是否诚实、完备、可作 gate”,因此 stale Stage 2A pending 与 pending-only 绿色退出不是文案小问题,而是 capability 语义错误。整 PR 结论为 REQUEST_CHANGES。修复两项并更新英中 RFC/README/测试后,可在新 exact head 重新评审。

English verdict: REQUEST_CHANGES at exact head 7a6639390cb5c00c068895acd43e3cff9b6fcc97. Stage 2A is still marked pending on “PR #3819 merge” even though #3819 is already merged and its merge commit is in this head, so the ladder does not cover every completed stage as claimed. Also, selecting a pending-only row executes zero tests but exits 0. The deterministic ladder otherwise reproduced at 9 pass / 2 unverified / 10 pending; pytest, typecheck, mypy, Ruff, diff-check, and GitHub checks pass.

@wchwawa

wchwawa commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@huangruiteng Both blockers are addressed on exact head bdded14f4; the "Round 2" section of the PR body has the details.

  1. Stage 2A is a real env-gated row now, not a stale pending declaration. s2a.nokv_live_qualification runs the merged examples/nokv-authority-store/live-qualification.ts --execute-live against an existing workbench with a fresh tenant/goal pair and requires ok=true, the single-node store-conformance scope, every check passed, SDK 0.11.0 / API 1, and no promotion or availability claim. Each missing input (LOOPX_NOKV_AUTHORITY_LIVE, LOOPX_NOKV_AUTHORITY_CONFIG_JSON, LOOPX_NOKV_AUTHORITY_PYTHON, LOOPX_NOKV_AUTHORITY_WORKBENCH) reports its own unverified reason; configuration values and input paths are forbidden tokens, and the evidence carries digest prefixes rather than the configuration or workbench name. I ran it live on a local single-node NoKV stack: 13 checks passed, final generation 3, together with s0.nokv_live_matrix (13 rows, 12 parity rows); the report leaked nothing.
  2. Pending rows block a green exit. exit 0 iff fail == 0 and (unverified == 0 or --allow-unverified) and (pending == 0 or --allow-pending); --row s2c2.parity_equal now exits 1 with executed=0, --stage 2c2 exits 1, a mixed selection exits 1 even when its executable row passes, --list stays a registry print without an exit policy. summary.executed and exit_policy.allow_pending separate "report generated" from "selection verified". Tests pin pending-only, pending-stage, mixed, and list-only paths.

The new row pushed the ladder module over the 1500-line ratchet ceiling, so the row vocabulary and the Stage 2C rows now live in two sibling modules (strict mypy, probe digests updated). README, RFC (en, zh-CN), and the evidence note no longer reference #3819 as pending. Premerge on this head: 4/4 direct checks, 18/18 catalog canaries, boundary clean, 0 holds.

@wchwawa
wchwawa requested a review from huangruiteng September 3, 2026 13:12
Base automatically changed from codex/local-authority-shadow-product-path to main September 3, 2026 15:02
@now-ing

now-ing commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

The honesty machinery here is the best part of the ladder. test_main_never_reports_green_while_unverified pins that env-gated rows force exit 1 without --allow-unverified, and the privacy scan is proven red-able by an injected leak. I reproduced the PR's numbers on 7a66393 (pre-Round-2 head; the Stage 2A row promotion below does not touch these points): default projection 8 passed / 7 skipped (~8s), LOOPX_LADDER_FULL=1 13 passed / 2 skipped (~13s), standalone runner pass 9, unverified 2, pending 10, exit 0 — and a simulated row regression produces fail/assertion_failed with exit 1. The evidence path is real rather than self-asserting: rows spawn python -m loopx.cli with an isolated HOME; the Stage 0 matrix races two executor handles over a threading.Barrier (same_todo_one_winner), turns an applied CAS into ambiguous through a LossyOnce wrapper and requires receipt recovery (lost_response_recovers_receipt), and stale_revision_conflicts checks the head/generation pair is unchanged. The s1 read-back pages at size 2 so scanCommitted must really paginate, and the probe never calls storeIdentity() so it cannot mint a lineage. The crash-gap row holds the observer's own flock, so an observer that ever locked before the primary commit goes red on visibility.

Three points (fine to defer any):

  1. The recoverable-execution claims are still folded into a Stage 0 row. Rows now carry a stage= field, but authority_e2e_ladder.py:543-544 still declares id="s0.file_matrix_twelve_rows", stage="0" while bundling the four verbs that live_e2e.py:239 itself labels "Stage 3: recoverable execution ownership" (renew, epoch reclaim, superseded-executor fence, atomic completion successor), and the stage vocabulary pinned by test_shared_goal_authority_e2e.py has no stage-3 entry. The owner-accepted "Stage 3 slice status" section of the RFC is exercised, but the report cannot express that mapping — the "every completed stage claim" audit rests on unwritten knowledge. A stage-3 group for those matrix verbs (or per-verb stage tags) makes it mechanical.

  2. The CI trim's equivalence is enforced only by prose. The five skipped rows are genuinely pinned near-verbatim (down to durable_source_outbox is False) in the four tests of test_local_authority_shadow_cli_e2e.py — but nothing fails if those tests are renamed or deleted later; the projection keeps skipping the ladder rows and the coverage silently leaves every default run. Measured here, the full projection costs ~5s more than the trimmed one, so the one-line timeout revert your CI note mentions buys the guarantee cheaply; if the trim stays, a registry guard asserting the pinned test node ids still exist closes the drift.

  3. Fail-closed against a corrupt existing candidate document is untested. _row_candidate_failure_preserves_primary (authority_e2e_ladder.py:577-581) only blocks directory construction. No row plants a truncated or foreign authority-store-*.json and asserts a typed failure, preserved primary, and healing on the next full-snapshot write. The crash-gap row proves a killed writer leaves no partial document, so corruption can only arrive externally — exactly the case worth pinning (the s2c1 observer's load path; distinct from the drain-side outbox parsing under review on feat(authority): add transaction-bound runtime shadow capture #3870).

@huangruiteng huangruiteng left a comment

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.

Findings

[P1] 默认测试投影仍然把必需的 pytest 检查推过 15 分钟上限

tests/control_plane/test_shared_goal_authority_e2e.py:77 会把未显式跳过的 ladder 行加入默认 pytest;虽然本轮已经排除了五个重复的 CLI E2E 行,但精确 head bdded14f477b11afd5d6fe85e128256e4e7a956c 的必需检查仍未完成:Python Tests / pytest 在 99% 时被 job 的 15 分钟上限取消(13:27:33 到达 99%,13:27:56 收到 The operation was canceled),因此没有 pytest 汇总、coverage 结论或绿色 required check。这不是 teardown 后才发生的取消;测试主体尚未完成。这个 PR 的目标是增加可复跑的 CI 证据,但当前默认投影使主验证通道无法给出结论。请继续削减默认 CI 的新增耗时,或调整/拆分 workflow 的预算,并在当前 head 上取得一次完整绿色的必需检查后再合并。

Open questions / assumptions

  • 我把 CI 上未完成的必需检查视为合并 blocker;本地窄测通过不能替代完整 required check。
  • 非阻塞建议:CLI_E2E_COVERED_ROW_IDS 依赖一组硬编码 row id 和“另一个测试文件已经覆盖”的约定,但没有守卫对应底层测试节点仍存在。后续可以把共享断言或节点映射做成可验证的单一来源,避免原测试被改名/删除后这里继续静默 skip。
  • 当前 candidate_failure_preserves_primary 覆盖的是候选目录创建失败;如果损坏的既有候选文档也属于本阶段承诺,建议补一条独立负例,避免把两种失败路径混为一谈。

动机

这组改动试图把 shared-goal authority RFC 中已经完成的 Stage 0/1/2A/2B/2C1 声明,收敛成一条逐行、可机读、默认 fail-closed 的 E2E ladder。它解决的核心问题是:RFC、既有测试和 live probe 分散,操作者难以从一次运行中判断哪些阶段真正执行、哪些只是环境缺失、哪些仍为 pending。

改动思路

整体设计方向是对的:用统一 row registry 描述 stage、gate、product path 与 runner;真实 CLI 路径负责写入,生产 TypeScript FileAuthorityStore 只读回放;报告把 pass、unverified、pending 分开,并把 commit、tree dirtiness 与 probe digest 绑定到证据。Stage 2C2 尚未实现的部分只声明为 pending,不冒充完成。

具体改动

  • 新增 Python ladder、fixture 和 Stage 2C1 row support,覆盖 file matrix、CLI/TS 回读、NoKV/PostgreSQL 环境门控、shadow writer family、default-off、candidate failure、crash gap、双 runtime root 与 migration。
  • 新增只读 TypeScript readback probe,并纳入 control-plane typecheck。
  • 新增 pytest 投影、standalone example、README,以及英中 RFC/证据台账更新。
  • 本轮已经修复旧 head 的两个关键问题:Stage 2A 现在是真实 env-gated 执行行;pending-only 或 mixed selection 在未传 --allow-pending 时会非零退出。

关键代码讲解

run_row 负责把 gate 与 runner 的结果归一成 typed row result;build_report 再统一计算计数、binding 和 privacy scan,最后由 exit_code_for 执行 fail-closed 策略。CLI fixture 通过真实 python -m loopx.cli 驱动主写,TS probe 仅调用生产 store 的 loadAuthority、scanCommitted、readReceipt 做候选回读。Stage 2C1 的 writer-family 行验证 observation 与 primary writeback 的关系,而 crash-gap 行明确只证明“主写成功但该次 observation 可丢”,没有越界宣称 outbox 或 parity。

对主干的风险

生产运行路径没有被修改,主要风险集中在 CI 预算、证据准确性和未来维护漂移。隐私扫描、显式 live gate、pending/unverified 的非绿色退出策略都降低了公共证据误报风险;但当前 required pytest timeout 是实际的主干合并风险,必须先消除。Stage 2C2 仍全部 pending,因此这次改动不能被解读为 provider 晋升或完整 parity 已交付。

我的整体评价

从产品和架构上看,这是一套比散落脚本更可复用的证据合同,尤其是 typed row、单一报告 schema、真实 CLI 驱动和 fail-closed 退出语义值得保留。当前实现已经解决前两轮的主要语义问题,本地窄测也支持实现正确性;但完整必需检查没有在 CI 预算内完成,所以现在仍应 hold / request changes。

验证:

  • pytest -q -n 2 tests/control_plane/test_shared_goal_authority_e2e.py tests/control_plane/test_local_authority_shadow_config.py tests/control_plane/test_local_authority_shadow_cli_e2e.py:18 passed,8 skipped。
  • standalone ladder 使用显式 --allow-unverified --allow-pending:9 pass,3 unverified,9 pending;pending-only 未放宽时退出 1,放宽后退出 0。
  • ruff check:通过。
  • mypy(四个新增 control-plane testing 模块):通过。
  • npm run typecheck:control-plane:通过。
  • git diff --check 与公共边界扫描:通过。
  • NoKV authority、NoKV legacy 与 PostgreSQL live 栈本地不可用,因此没有把 unverified 当作通过。

English verdict: Request changes — the exact head's required pytest job still times out before producing a complete test and coverage result; make the default CI projection fit the budget (or adjust/split that budget) and obtain a green required check.

Add one incremental end-to-end ladder that exercises every completed stage
of the shared-goal-authority RFC through the real `python -m loopx.cli`,
with an exit policy that never reports green while a selected row is
unverified.

- loopx/control_plane/testing/authority_e2e_ladder.py: row registry for
  Stage 0/1/2B/2C1, runners, the loopx_shared_goal_authority_e2e_report_v0
  JSON report, bindings, privacy scan, and the exit policy
  `exit 0 iff fail == 0 and (unverified == 0 or --allow-unverified)`;
  Stage 2A and Stage 2C parity rows are declared pending, not claimed.
- loopx/control_plane/testing/authority_e2e_fixtures.py: goal workspaces,
  CLI runners, observation-lock window, candidate read-back, TAP summary,
  and the lifted legacy migration source.
- tests/control_plane_ts/authority_store_readback_probe.ts: read-only
  FileAuthorityStore probe (loadAuthority, paged scanCommitted,
  readReceipt), included in tsconfig.control-plane.json.
- tests/control_plane/test_shared_goal_authority_e2e.py: one test per row
  (env-gated rows skip as unverified, POSIX-only rows skip on Windows)
  plus pins for the exit policy, privacy scan, listing, and registry.
- examples/shared-goal-authority-e2e: thin runner and README documenting
  rows, gates, environment variables, exit policy, and the test seams the
  Stage 2C parity PRs must provide.
- docs: Stage-ladder evidence subsection in RFC section 11 (en, zh-CN) and
  section 8 of the evidence note.

Test-only: no production entry point constructs any store.
Signed-off-by: wchwawa <wch19961116@gmail.com>
…erride

The Stage 2C observation PR now routes every writer hook through one effective
runtime root, so the dual-root scenario is no longer a pending parity row: it
is a deterministic Stage 2C1 row.

- fixtures: a `cli_override_divergent` binding registers a different
  `common_runtime_root` than the `--runtime-root` override.
- ladder: `s2c1.dual_runtime_root_consistency` drives todo add, task-lease
  acquire, todo update, capture-followups, and a leased completion through the
  real CLI and requires one store identity, a head with both todos and the
  released lease, lease state under the override root, and neither a candidate
  lineage nor lease state under the registry root.
- registry, pytest projection, README, RFC (en, zh-CN), and the evidence note
  drop the row from the pending list and count seven `s2c1.*` rows.

Signed-off-by: wchwawa <wch19961116@gmail.com>
…budget

The pytest job runs within seconds of its 15-minute timeout on current main
(14m38s to 14m44s), and the full ladder projection pushed this branch over it
twice. Five s2c1 rows repeat assertions that
tests/control_plane/test_local_authority_shadow_cli_e2e.py already pins through
the same product CLI path (configure round trip, default-off isolation,
candidate failure, crash gap, dual runtime root). The default pytest
projection now skips exactly those rows with an explicit reason; the rows that
only the ladder exercises (file matrix, TypeScript read-back, every writer
family, migration) stay in CI, LOOPX_LADDER_FULL=1 runs every row in pytest,
and the example runner is unchanged.

Signed-off-by: wchwawa <wch19961116@gmail.com>
…s from exiting green

Review findings on 7a66393:

1. Stage 2A was still declared pending on "PR #3819 merge" although that PR
   is merged and in this head. `s2a.nokv_live_qualification` is now an
   env-gated `store_direct` row: it runs the merged
   `examples/nokv-authority-store/live-qualification.ts --execute-live`
   against an existing workbench with a fresh tenant/goal pair and requires
   `ok=true`, the single-node store-conformance scope, every check `passed`,
   NoKV SDK 0.11.0 / API 1, and no promotion or availability claim. The gate
   `env:nokv_authority` needs `LOOPX_NOKV_AUTHORITY_LIVE=1` plus the ignored
   client configuration path, the Python executable that resolves the SDK,
   and the workbench name; each missing input reports its own unverified
   reason. Every string leaf of the configuration and every input path is a
   forbidden token, and the evidence carries only check ids, counts, and
   config and workbench digest prefixes. The bindings digest now prefers the
   authority configuration file.

2. Selecting only pending rows executed nothing but exited 0. Pending rows
   are unmet obligations: `exit 0 iff fail == 0 and (unverified == 0 or
   --allow-unverified) and (pending == 0 or --allow-pending)`. The report
   records `allow_pending` and `summary.executed`, the summary line names the
   pending rows, and tests pin pending-only, pending-stage, mixed, and
   list-only selections.

The ladder module crossed the 1500-line maintainability ceiling with the new
row, so the shared row vocabulary moves to `authority_e2e_row_support.py` and
the Stage 2C rows to `authority_e2e_rows_stage2c.py`; both join the strict
mypy list and the probe digests. README, RFC (en, zh-CN), and the evidence
note record the Stage 2A row, the new gate, the pending exit rule, and a live
run of both NoKV rows on a local single-node stack.

Signed-off-by: wchwawa <wch19961116@gmail.com>

@huangruiteng huangruiteng left a comment

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.

Findings

[P1] binding-only privacy violations still return a green exit

loopx/control_plane/testing/authority_e2e_ladder.py:931-959 handles a forbidden token in bindings by replacing every binding with null and setting bindings.privacy_violation=true, but it then calls _finalize_report() with the original rows. Because exit_code_for() only reads row counts, a report with one passing row and a leak confined to bindings.probe_sha256[].path is finalized as fail=0 and exit_code=0. I reproduced this on exact head 579681fb9ba394b60110dbc9cfb15efaf65d2d6a by building a clean one-row report with authority_e2e_ladder.py as the forbidden token: the binding block was redacted and marked privacy_violation, while the summary remained pass=1, fail=0 and the exit policy remained green.

This breaks the ladder's stated fail-closed contract: a privacy violation in report metadata is still a failed evidence run, even if the leaked value is successfully removed from the serialized output. Please make a binding leak participate in the failure/exit decision (for example, by adding a typed synthetic fail/privacy_violation result or by making the exit policy explicitly consume a report-level privacy failure), and add a regression test for a leak that appears only in bindings.

动机

这个 PR 想把 shared-goal authority RFC 中散落在 Stage 0、1、2A、2B 与 2C1 的证明收敛成一个可增量运行、可机读且默认 fail-closed 的 stage ladder。此前真实 CLI 写入、file/NoKV/PostgreSQL store 探针、local shadow 的异常路径以及 RFC 证据分别存在,操作者很难从一次运行里区分“已经通过”“环境不具备”“尚未实现”。这次精确 head 还针对前轮评审补齐了两个关键缺口:Stage 2A 不再是过期的 pending 声明,而是显式 opt-in 的 live qualification;pending-only 选择也不再因为零执行而退出 0。

改动思路

入口 examples/shared-goal-authority-e2e/ladder.py 只负责定位 checkout 并调用统一 main();authority_e2e_ladder.py 持有 row registry、环境 gate、Stage 0/1/2A/2B runner、typed report 与退出策略;fixture/support/Stage 2C 模块负责隔离 HOME、构造临时 goal、通过真实 python -m loopx.cli 写 primary state,再从候选字节或只读 TypeScript FileAuthorityStore 取证。正向路径是“选择 row -> gate -> runner -> typed RowResult -> bindings/privacy scan -> JSON 与 exit code”;负向路径把环境缺失记为 unverified、实现缺口记为 pending、断言/异常记为 fail,并要求显式 --allow-unverified/--allow-pending 才能放宽。架构边界总体清楚:candidate 不参与决策、不反写 local authority,Stage 2C2 也没有被冒充为已完成。

具体改动

  • 三份 RFC/证据文档新增 stage-ladder 证据、边界和未验证项;example README 描述 12 个可执行 rows、9 个 pending rows、环境变量、退出策略与后续 test seam。
  • authority_e2e_fixtures.py 提供隔离 registry/runtime/HOME、真实 CLI 子进程、SIGKILL 窗口、migration fixture、candidate document 和 TAP 解析;authority_e2e_row_support.py 集中 row outcome、写入 helper 与 observation 不变量。
  • authority_e2e_ladder.py 注册 file matrix、CLI-to-TS readback、NoKV qualification、PostgreSQL conformance 和报告生成;authority_e2e_rows_stage2c.py 覆盖配置往返、writer family、default-off、候选失败、crash gap、双 runtime root 与迁移新 lineage。
  • TypeScript probe 只调用 loadAuthority、分页 scanCommitted 和 readReceipt,不调用会创建 identity 的 storeIdentity();pytest 投影、strict mypy 列表与 control-plane tsconfig 同步纳入新表面。

关键代码讲解

run_row() 先应用显式 gate,再把 runner 的异常收敛为经过脱敏的 typed failure;_row_nokv_live_qualification() 要求独立 opt-in、绝对配置/解释器路径、全量 passed checks、固定 SDK/API 版本,并明确拒绝 promotion/HA 语义扩张;row_every_writer_family_captures() 与 row_default_off_isolation() 分别验证所有本地 writer 的单向 observation 不变量以及关闭时的响应/存储等价;TypeScript summarizeScan() 对生产 store 做分页只读回放。assert_public_safe() 是最终证据边界,但也正是在这里出现本轮 blocker:row leak 会变成 fail/privacy_violation,binding-only leak 却只清空 metadata,没有影响退出码。

对主干的风险

本 PR 没有新增生产调用者,rg 只发现 example/tests/config 引用这些模块;因此默认关闭路径和 authority source 都未改变。我独立运行了完整 standalone ladder:9 pass、3 unverified、9 pending、0 fail;default-off 无候选目录,候选失败保留 primary,crash gap 不声称 outbox,双 runtime root 只有一个 candidate identity。变更体量仍很大(13 files,+3275/-0),但已经按 fixture、support、Stage 2C rows 与主 registry 拆分,维护成本与其跨阶段证据目标基本相称。

主要回归风险是报告可信度而非生产写路径。当前 binding-only privacy case 能在明确记录 privacy_violation=true 的同时退出 0,会让自动化把不合格的证据包误判为绿色,必须先修。远端 required pytest 已经跑完整套件,但因一个未触及文件里的随机 scheduler blob 命中 credential-like heuristic 而红;该精确节点本地复跑通过,属于独立的 CI 波动,但合并前仍应取得一次绿色 required check。NoKV 与 PostgreSQL live 栈本轮不可用,因此我保留其 unverified 状态,没有从作者描述或历史 CI 推断为 verified。

验证:

  • focused Python suites:18 passed,8 skipped;单文件 ladder suite:10 passed,8 skipped。
  • standalone ladder:9 pass,3 unverified,9 pending,0 fail;显式放宽两类未满足义务后退出 0。
  • 新增四个 Python 模块 strict mypy 通过;Ruff 通过。
  • control-plane TypeScript typecheck 通过。
  • premerge canary:18/18(3 direct checks、1 Python compile、9 catalog、8 risk-profile、1 public-boundary),0 failures。
  • git diff --check 与 exact-head/readback 检查通过。

我的整体评价

这套 ladder 的整体方向值得保留:它用真实 CLI、生产只读 store、typed 状态和清晰的 Stage 2C2 pending 边界,显著提升了 RFC 证据的可复跑性;本轮也确实修复了 Stage 2A 与 pending exit 的前序问题。当前仍需 request changes,因为最终 privacy gate 存在一个可稳定复现的“已检测违规但仍返回绿色”分支。修复 binding-only leak 的失败传播、补上对应回归测试,并让 required pytest 在当前 head 上转绿后,这个 PR 才满足它自己声明的 fail-closed 验收标准。

English verdict: Request changes on exact head 579681fb9ba394b60110dbc9cfb15efaf65d2d6a — the ladder now correctly covers Stage 2A and pending-only selections, but a privacy violation confined to report bindings is redacted and marked while the run still exits 0; make that condition fail the evidence run and add a binding-only regression test. Focused suites, standalone ladder, strict mypy, Ruff, TypeScript typecheck, diff checks, and 18 premerge canaries passed; the required pytest check is currently red on an unrelated credential-heuristic flake and should be green before merge.

…port bindings

assert_public_safe() nulled the bindings and marked
bindings.privacy_violation when a forbidden token appeared only there, but
_finalize_report() derived the exit code from row counts alone, so a report
with one passing row and a leak confined to the bindings block finalized as
fail=0 and exit 0 while recording the violation.

The summary now carries privacy_violations (leaking rows plus the bindings
block), the exit policy consumes it ahead of every relaxation flag, and the
standalone runner prints the condition on stderr. A regression pins the
bindings-only case. The pytest projection's CI skip list now names each
row's product-CLI twin test and a guard fails when a twin is renamed or
removed, so the skip cannot outlive its coverage. README, RFC (en and zh),
and the evidence note state the rule.

Signed-off-by: wchwawa <wch19961116@gmail.com>
@wchwawa

wchwawa commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

@huangruiteng Round 3 is on exact head 33889b0c1.

The bindings-only leak reproduced exactly as you described (one passing row, authority_e2e_ladder.py as the forbidden token: bindings nulled and flagged, fail=0, exit 0). The fix makes the violation part of the exit decision at the report level: summary.privacy_violations counts leaking rows plus the bindings block, exit_code_for() consumes it before any relaxation flag, and the runner prints the condition on stderr. I chose the report-level count over a synthetic fail/privacy_violation row so executed and the row registry keep their meaning; the rule string, README, RFC (en/zh), and the evidence note now state it. The regression pins the bindings-only case including that --allow-unverified --allow-pending cannot relax it.

Your non-blocking note on CLI_E2E_COVERED_ROW_IDS (and @now-ing's point 2) is in: the skip list now maps each row to its product-CLI twin test and a guard fails when that test is renamed or removed, so the skip cannot outlive its coverage.

The red required check on 579681fb9 was tests/control_plane/test_actual_default_model_behavior_portfolio.py::test_planning_horizon_action_oracle_grades_semantic_stages[premature-spend], a random scheduler hint tripping the credential heuristic in a file this PR does not touch; the suite itself completed in 13m49s (5375 passed) under the 25-minute budget now on main, so the projection fits. This push re-runs it.

Deferred to keep this round to the blocker, both worth a follow-up: a negative row for a corrupt existing candidate document (your note and @now-ing's point 3) and a stage tag for the four recoverable-execution verbs inside the Stage 0 matrix row (@now-ing's point 1). If you want either in this PR instead, say so.

Validation on 33889b0c1: projection 12 passed / 8 skipped, full projection 17 / 3; standalone ladder 9 pass / 3 unverified / 9 pending with privacy_violations=0; configured mypy, Ruff, diff-check, docs smokes, premerge all clean.

@wchwawa
wchwawa requested a review from huangruiteng September 4, 2026 00:31

@huangruiteng huangruiteng left a comment

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.

评审 exact head:33889b0c11b868afe94e16427c044c8b47c308ee

动机

这个 PR 为 shared-goal authority RFC 建立从文件 provider、真实 CLI 写入与 TypeScript store 读回,到 Stage 2A/2B live gate 和 Stage 2C1 shadow observation 的一条可执行 E2E ladder。它的核心价值是让阶段声明对应到机器可判定证据,并确保未执行、待实现或环境缺失的行不会被误报为已验证。

改动思路

ladder 把每个已声明能力建模为 typed row,区分 deterministic、live env gate 与 pending;执行结果统一进入公开安全 report,再由 exit policy 决定整轮是否 green。pytest 提供 CI 投影,standalone runner 保留完整执行面;五条昂贵的 Stage 2C1 行只有在默认 pytest 投影中由已有 product-CLI E2E twin 覆盖,完整模式与 standalone 仍会真实执行。

具体改动

本轮新 head 修复了上一轮 blocker:summary.privacy_violations 同时统计 row 泄露和仅发生在 bindings 的泄露;bindings-only 情况会清空所有绑定、标记 bindings.privacy_violation=true,并且 --allow-unverified 与 --allow-pending 都不能放宽退出码。新增回归构造只在 probe manifest bindings 中出现的 token,证明 row 保持 pass、敏感 token 从最终 JSON 消失、summary 计数为 1、exit code 为 1。CI projection 也从裸 row 列表升级为 row→product-test 显式映射,并用 AST guard 防止 twin 被重命名或删除后继续静默跳过。

对主干的风险

没有发现新的 actionable blocker。隐私 fail-closed、pending-only 非绿色、Stage 2A 真实 gate、CI skip 映射和完整 Stage 2C1 行均有直接回归。完整 standalone 本地结果为 12 个执行行中 9 pass、3 个 live 环境行 unverified、9 个 Stage 2C2 声明 pending;在显式允许这两类未执行状态时退出 0,且 privacy violations 为 0。残余风险主要是 live NoKV/PostgreSQL 依赖外部环境,本地未执行,但远端 required checks 全绿,当前 head mergeable。

我的整体评价

结论为 APPROVE。上一轮仅 bindings 泄露仍可能退出 0 的问题已在 report schema、exit policy、回归测试与双语文档四层闭环;实现与 RFC 的公开证据边界一致。独立验证通过:默认 pytest 12 passed, 8 skipped,完整投影 17 passed, 3 skipped,standalone deterministic rows 全部通过,privacy/projection focused tests 通过,scoped Ruff、mypy、TypeScript typecheck、git diff --check 均通过;远端 required checks 全绿。

English verdict: APPROVE — exact head 33889b0c11b868afe94e16427c044c8b47c308ee. The bindings-only privacy leak now redacts every binding, increments summary.privacy_violations, and fails regardless of relaxation flags. Focused, default, full-mode, standalone, lint, type-check, and remote required checks pass; no blocker remains.

Signed-off-by: huangruiteng <huangrt01@163.com>
@huangruiteng
huangruiteng force-pushed the test/shared-authority-e2e-ladder branch from b8dd89d to 7d52029 Compare September 4, 2026 03:20
@huangruiteng
huangruiteng merged commit c514e02 into main Sep 4, 2026
11 checks passed
@huangruiteng
huangruiteng deleted the test/shared-authority-e2e-ladder branch September 4, 2026 05:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants