Skip to content

test(architecture): pin the top-level module count before it grows again - #5191

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
karenchuu:codex/top-level-module-budget
Sep 27, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
karenchuu:codex/top-level-module-budget

Conversation

@karenchuu

@karenchuu karenchuu commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

Scope And Continuation

  • Completed scope and remaining work: complete within this scope for M0. The fixture and the guard are the two artefacts M0 names; the RFC index entry M0 also lists already exists on main. Remaining milestones stay open: M1 (loopx/chat_* → loopx/chat/ with shims) and M2 (*_goal_mode → loopx/hosts/) each move files and lower the pinned number, M3+ needs the unapproved decisions D1-D4 first.
  • One deliberate deviation, stated so it is not read as an oversight: the RFC example pins max_top_level_modules: 148, measured at its own base. The RFC's 2026-09-27 Appendix A entry says the count was re-measured "so M0 pins a count the tree actually has", and loopx/*.py is 147 at this base, so this PR pins 147 rather than copying 148 forward. Pinning 148 would let one more top-level module in on arrival.
  • Slice boundary / successor: this PR changes no product module and moves no file, so it cannot break a caller; the successor is M1, whose first step in the RFC's move sequence is "lower max_top_level_modules by the number of moved files" — which is exactly what the equality assertion here requires.

Validation

Public-safe summaries only.

  • Tested revision: d05dccedb (head of this branch); baseline comparison run on the unmodified 9eaacfbf2.
  • Run state: finished, including the full-suite lane recorded in the table below.
  • Input classes: synthetic (temporary directory trees) plus the repository tree itself.
Check kind Result Public-safe evidence / limitation
unit passed python3 -m pytest -q tests/architecture/test_top_level_module_budget.py → 6 passed. Two rows pin the real tree (count equals the budget; allowlisted entry points __init__.py, entrypoint.py, cli.py are still at the top level) and four rows pin the comparison helper against synthetic trees: over budget, exactly at budget, under budget, and a tree with a subpackage holding extra .py files.
integration passed python3 -m pytest -q tests/architecture with the repository's npm dev dependencies installed: head 2 failed, and the unmodified base at the same commit 2 failed, with the same two test ids on both sides (tests/architecture/test_goal_instance_binding_inventory.py::test_goal_instance_inventory_does_not_replace_the_registry_io_census, tests/architecture/test_project_registry_io_census.py::test_checked_in_project_registry_io_manifest_is_current), both pre-existing at this base. An earlier run of the same lane, taken before npm ci --ignore-scripts, reported 125 failed / 667 passed on head against 125 / 661 on base, with 90 of those sharing the one cause the repository prints itself (npm dev dependencies missing); the identical-failure comparison held there too, and that row is superseded rather than wrong.
static passed ruff check, ruff format --check on the new test file, git diff --check, and loopx check --scan-path tests/architecture (public-boundary scan clean, 25 files). mypy on the new file reports no issues; it is not in the pinned [tool.mypy] files allowlist and this PR does not widen that list.
regression_parity passed Baseline/head comparison above, plus eleven mutations of the guard. Nine fail: add a top-level module (1 failed), raise the budget to 148 without moving files (1 failed), lower it to 146 without moving files (1 failed), move loopx/cli.py into a subpackage (2 failed: allowlist and equality), rename the fixture schema (2 failed), shorten baseline_commit to an abbreviated sha (2 failed), repeat a name in allowlist (2 failed), delete the fixture file (2 failed), make the offender slice ignore the overflow width (1 failed). One is equivalent and disclosed as such: changing the early return from len(modules) <= maximum to len(modules) < maximum stays green, because at len == maximum the fall-through slice becomes [:0], which is also empty. Two of the nine are caught only by the synthetic rows, not by the real-tree rows, which is why those rows exist.
real_entrypoint not_applicable No shipped command, projection or host surface reads this fixture; the consumer is the test collector itself.
real_backend not_applicable No runtime state, database or provider is touched.
integration (full lane) passed python3 -m pytest -q tests -m "not stage2c_e2e" on head: 12324 passed / 15 failed / 49 skipped (47m31s); on the unmodified base at the same commit: 12318 passed / 15 failed / 49 skipped (1h03m). The passed delta is exactly the 6 tests this PR adds. Fourteen failures are shared; the remaining slot differs one way on each side, and both resolve as pre-existing or load-related under exclusive re-run of each test on each tree: tests/test_external_scheduler_worker.py::test_default_invocation_persists_backoff_state fails on both trees in isolation, and tests/extensions/test_extension_runtime.py::test_extension_run_terminates_provider_on_timeout passes on both trees in isolation. The two aggregate runs also overlapped in wall-clock with another suite, which is the likely reason a timing-sensitive case moved. No failure here is attributable to this diff.
  • Coverage and gaps: the changed paths are the fixture and the guard, and both are exercised through their real consumer (pytest collecting tests/architecture), so the covered surface is the whole diff. What is deliberately not enforced, and why:
    1. A PR can still raise max_top_level_modules in the same diff. Nothing in the tree holds a second copy of that number to contradict it, so keeping the budget honest stays a review judgement; the equality assertion at least makes such a change appear as a visible one-line budget edit instead of a silent acceptance of growth.
    2. The guard counts files, not public API, and says nothing about module size or quality. Section 9 of the RFC states this boundary for the same row.
    3. Modules below the top level are not bounded by this test at all — moving growth into loopx/<subpackage>/ is the behaviour the RFC asks for, not a bypass, so the guard must not (and does not) flag it.
    4. Environment, so the numbers are reproducible: the interpreter is a virtualenv built with uv venv plus uv pip install -e ".[test]" rather than uv sync; Node is pinned to the repository's qualified 22 line for the TypeScript-backed scan; and npm ci --ignore-scripts was run in both trees before the integration and full-lane numbers quoted above.

See validation disclosure guidance.

Frontend / Visual Evidence

  • UI impact: none
  • Before:
  • After:
  • States and viewports shown:
  • Source data: none
  • Attention review: N/A — no user-visible surface changes.

Type of Change

  • Test update
  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update

LoopX Area

  • Build, packaging, installer, or CI
  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Host or runtime integration

Technical Direction

Shared-authority RFC fixture impact

N/A — this PR claims no progress on the TypeScript control-plane migration or the shared Goal Authority RFC, changes no canonical record and adds no fixture dimension.

  • Production-scale fixture schema:
  • Semantic dimensions changed, or reviewed no-impact rationale:
  • Provider conformance arms run:
  • Read-only legacy/file/PostgreSQL three-arm rehearsal (required for promotion, runtime-routing, or compatibility-projection changes):

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • 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.
  • I completed the visual evidence section for UI changes, or marked UI impact none.

Milestone M0 of monorepo-distribution-split-v0 asked for a checked-in budget and
a guard that fails when loopx/ gains a top-level module. The import-boundary
tests only protect inward edges, so 66 new top-level modules landed in 45 days
with nothing to push back.

Signed-off-by: karenchuu <25980598+karenchuu@users.noreply.github.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.

Exact reviewed head: d05dccedb5d0a0ffe45f91ca85bb510efb2a690d; immutable baseline: 9eaacfbf2ff93d5386cee82ddf0847d9c739e78a.

动机

依据是 distribution-split RFC 的 M0 和 §9:给直接位于 loopx/ 的 Python 模块数量建立护栏,避免迁移前继续无声增长。独立读取基线 Git tree,实际为 147;RFC 的148是旧示例,不应照抄。这里完成的是 M0 的有界验收,不是关闭 #5072 的整个分发拆分计划。

改动思路

复用现有 architecture pytest 入口,用一个可审查 JSON fixture 保存基线、数量和三项入口 allowlist。直接 glob 只数顶层模块;数量超出时失败,迁移减少数量而未更新 pin 时也失败。既有 import-boundary 和 maintainability 测试管的是依赖边/单文件指标,没有等价的顶层数量护栏。保持现状会留下 M0 缺口;新增运行时服务或第二份隐藏基线都没有必要。

具体改动

完整 diff 是两个文件 +112/-0:top_level_module_budget.json 固定完整基线 SHA、147和 __init__.py / entrypoint.py / cli.py;test_top_level_module_budget.py 校验 fixture、真实树数量与入口,并用四个临时树测试超出、等于、低于预算及嵌套排除。生产代码、UI、权限和持久化均未改动。

独立跑 native collector:6 passed。额外使用基线真实147个文件名构造临时树,调用这份 PR 的真实测试函数:新增第148个顶层文件被拒绝;移走 CLI 入口被拒绝;146个文件仍固定147被拒绝;仅新增嵌套模块通过,迁移后降低 pin 通过。不是拿 helper 的当前输出反推预期。

对主干的风险

没有发现阻塞问题。Ruff、format、new-file mypy、diff check 和两条 changed-path public-boundary 检查通过。依赖准备一致后,完整 architecture lane:基线 812 passed / 2 failed,head 818 passed / 2 failed。我比较了失败用例身份和完整诊断,不只比较数量:registry-I/O census 与 goal-instance inventory 两处诊断相同,且不在这个 diff 的因果路径上;不能把它们算作本 PR 回归。按当前配置未查询或等待远端 CI。

非阻塞 P2:_count_offenders 的 “newest path first” 实际只是文件名字典序倒排,不知道哪个文件新加;真实失败信息也只报告数量,未像 PR 描述所说列出新增 offender。建议改准命名/注释和 PR 描述,或简化数量谓词。这个问题不影响当前 count/allowlist 的拒绝语义。

同一 PR 仍可修改预算并增加模块,这不是不可绕过的历史硬限;预算变化必须继续由 exact-diff review 审查。这里没有验证后续 M1/M2 的兼容 shim 或 M3 的分发决策,也不要求本 PR 先实现那些工作。

我的整体评价

APPROVE。这是有真实维护收益、已接入正常测试入口的完整小切片;同作者 bounded all-state 扫描未发现同形低价值批次。Future-facing pass 已检查最近的 import/size owner,认定不应强行合并不同演进原因的护栏;上述 lexical diagnostics 简化是非阻塞建议,不需要扩展新框架。批准这份测试 PR 不意味着批准后续分发迁移,也不是合并授权。

English verdict: APPROVE - head d05dcce. The accepted M0 module-count guard has distinct durable value: independently measured147, six native tests, real-consumer growth/move/entrypoint counterexamples and static checks pass. Full architecture base/head812/818 passed with the same two complete failure diagnostics. Clarify the non-blocking lexicographic "newest" wording; no remote CI queried and no runtime migration qualified.

@huangruiteng
huangruiteng merged commit 79d5a49 into loopx-project:main Sep 27, 2026
1 check passed
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.

2 participants