Skip to content

refactor(chat): the capabilities route is decided by its server - #5388

Open
karenchuu wants to merge 3 commits into
loopx-project:mainfrom
karenchuu:codex/chat-capabilities-route-owner
Open

karenchuu wants to merge 3 commits into
loopx-project:mainfrom
karenchuu:codex/chat-capabilities-route-owner

Conversation

@karenchuu

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

  • Outcome basis / optional anchor: Refs [Task]: Semantic vocabulary convergence follow-ups after RFC PR #4433 / 语义词表收敛后续任务 #4447 Track A and the inventory_ratchets note in loopx/semantics/vocabulary_v0.json, which makes a repeated module-level constant duplicate knowledge regardless of whether the copies currently agree.
  • Goal/source and gap: /api/chat/capabilities is a wire identity with three owners. loopx/chat_server.py:100 binds it and dispatches on it at line 1309; loopx/dashboard_launcher.py:19 bound its own copy and probes with it at line 60 to decide whether an already-running server may be reused; and the shipped browser build fetches the same string in apps/presentation/dashboard/src/data/chat.ts:618. The two Python copies were also a counted fork in the generated inventory, so the drift smoke already treated them as one decision spelled twice.
  • Observable before -> after, with the validation row that proves it: the server is the only Python module that binds the name and the only one that spells the value; the launcher asks the server for the route at the moment it probes; the new guard fails on a private copy, on a new Python literal, and on an undeclared or removed browser-build literal. The regression_parity row shows the inventory now reports same_runtime_forks=10/10, same_runtime_fork_definitions=23/23 and same_runtime_forks_semantic=8/8.
  • Issue/task and intended base: Refs [Task]: Semantic vocabulary convergence follow-ups after RFC PR #4433 / 语义词表收敛后续任务 #4447 Track A. Base is main at f49b4a008.

Scope And Continuation

  • Completed scope and remaining work: dashboard_launcher.py lost its module-level copy and reads chat_server.CHAT_CAPABILITIES_PATH inside _probe_existing_chat, and three budgets were re-anchored to the values measured on this revision. The import sits in the probe, not at module level, for a measured reason: with a module-level import, importing loopx.dashboard_launcher cost 118-121ms across three samples against 10-13ms on base, because the launcher is imported by loopx/cli_commands/support_control.py and frequently runs against a port that holds no LoopX process at all; with the deferred import the same three samples are 12-13ms. The probe path itself is unchanged, and the guard pins it behaviourally.
  • Slice boundary / successor:
    • apps/presentation/dashboard/src/data/chat.ts:618 keeps its own literal. A browser module cannot import a Python constant, and that file is being changed by in-flight PR fix(chat): recover uncertain instructions after page reloads #5373. The copy is therefore declared by file and count in DECLARED_WEB_CLIENT_RESTATEMENTS, so a second hardcoded fetch, or removing the declared one without updating the census, fails the guard. A generated binding is the real fix and is out of scope here.
    • The capabilities schema version is deliberately not folded into this owner. chat_server.py:1315 advertises loopx_chat_capabilities_v1 as an inline literal while dashboard_launcher.py:21 and the browser build declare the accepted set (chat.ts:143 accepts both ..._v0 and ..._v1). Advertised-versus-accepted is a per-surface policy decision, so merging them would erase a compatibility window rather than a duplicate. The one visible defect - the advertised version having no name in its owner module - is left as a follow-up with its file:line.
    • Tests and examples keep their own expected route on purpose: if they read it from the owner, a wrong owner would have nothing to fail against.

Validation

  • Tested revision: afb94f9b8
  • Run state: finished
  • Input classes: synthetic fixtures, a real localhost HTTP probe, and the repository's own chat suites
Check kind Result Public-safe evidence / limitation
unit passed New tests/architecture/test_chat_capabilities_route_single_owner.py: 9 passed, covering the module-level binding scan, the Python literal census, the declared browser-build census, the owner's value, the launcher probe through a recording HTTPConnection, the absence of a private copy in the launcher, and the generated inventory.
integration passed tests/architecture plus tests/canary: 1098 passed, 0 failed. Chat/dashboard selection (test_dashboard_command, both test_support_control_*, test_chat_server_cors, test_chat_startup_isolation, test_manager_channel_binding and the new guard): 145 passed, 5 failed.
static passed python -m ruff check clean on all changed files; configured python -m mypy succeeded for 19 source files; git diff --check clean. python -m ruff format --diff flags the same pre-existing files on base and head, so no format finding is introduced.
real_entrypoint passed tests/test_chat_startup_isolation.py starts the real chat server and reads /healthz and /api/chat/capabilities over HTTP, so the owner's route is exercised as a live endpoint rather than a constant.
regression_parity passed The same selection on an untouched worktree at the base commit fails the identical 5 tests (tests/test_dashboard_command.py) and otherwise passes 136; the head adds 9 passing tests and no new failure. Those 5 fail for an environment reason this change cannot affect: the compiled Chat bundle is gitignored build output and is absent from the worktree, and examples/loopx-chat-server-smoke.py stops on the same missing bundle on base and head. The drift smoke was run on both trees; every line is byte-identical except the ratchet line, which moves same_runtime_forks=11/11 -> 10/10, same_runtime_fork_definitions=25/25 -> 23/23 and same_runtime_forks_semantic=9/9 -> 8/8, with schema_version_same_runtime_forks unchanged at 2/2.
premerge passed loopx canary premerge over the exact four changed files completed 5 checks with 0 failures and no manual hold; loopx check --scan-path reported the public boundary clean for the changed paths.
  • Mutation battery: nine mutants applied one at a time in a separate worktree and reverted between runs. Killed: launcher keeps a private module-level copy (4 red); the probe spells the route inline (1 red); a third module binds the name (3 red); a second hardcoded fetch is added to the browser build (1 red); the declared browser copy is removed while its declaration stays (1 red); the registry anchor raised without the smoke anchor (smoke fails: "the registry and the anchor move together in one diff"); both anchors lowered below measured (smoke fails: "grew to 23; budget is 22"); the owner's route value changed (5 red across the guard and the CORS suite, so the route is behaviourally load-bearing). Survived, and why: a consumer that rebuilds the route by concatenation, "/api/chat/" + "capabilities", passes the census, because the literal scan looks for the whole quoted value. That is a real gap in the guard, disclosed rather than papered over; the binding scan and the inventory assertion would still catch it if the consumer also bound a name.
  • Coverage and gaps: the full repository suite was not run. No persisted state, backend or wire payload changes; the probe sends the same request bytes as before.
  • Environment disclosure: validation reused an already-installed local interpreter (uv pip install -e ".[test]") instead of uv sync --extra test, with Node 22.23.2 on PATH and the repository's npm dev dependencies installed, both required by the drift smoke.

Frontend / Visual Evidence

  • UI impact: none
  • Before: N/A
  • After: N/A
  • States and viewports shown: N/A
  • Source data: none
  • Attention review: N/A

Type of Change

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

LoopX Area

  • 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)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Shared-authority RFC fixture impact

N/A. This PR does not claim progress against the shared Goal Authority or TypeScript migration RFCs.

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.
  • 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 task.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

Technical Direction

Notes For The Reviewer

  • A sibling PR in this account lowers the same two ratchet keys from the same base commit. If that one lands first, the anchors here need same_runtime_forks 10 -> 9 and same_runtime_fork_definitions 23 -> 21 re-measured on the integrated base; same_runtime_forks_semantic stays at the 8 measured here. The quoted values are measured against f49b4a008.

dashboard_launcher restated /api/chat/capabilities as its own module-level
constant while chat_server dispatches on it. The import is inside the probe:
measured on this machine, a module-level import of chat_server raises the
launcher import from ~12ms to ~120ms, and the launcher runs on a port that may
hold no LoopX process at all.

Signed-off-by: karenchuu <25980598+karenchuu@users.noreply.github.com>
The census pins three shapes: the module-level binding, every Python literal,
and the browser client's copy by file and count, so a new hardcoded fetch in
either runtime fails here.

Signed-off-by: karenchuu <25980598+karenchuu@users.noreply.github.com>
same_runtime_forks 11 -> 10, same_runtime_fork_definitions 25 -> 23 and
same_runtime_forks_semantic 9 -> 8, measured on this revision.

Signed-off-by: karenchuu <25980598+karenchuu@users.noreply.github.com>
@karenchuu

Copy link
Copy Markdown
Contributor Author

CI triage for exact head afb94f9b8bf2c332a9574a32a33ddd2622dbc692:

The failed checks do not point to any of this PRs four changed files:

  • test-shard (2) failed two source-fingerprint churn tests in tests/control_plane/test_turn_journal_runtime_readiness.py.
  • test-shard (3) failed another source-churn diagnostic plus test_source_first_usage_disclosure_keeps_json_pure_and_does_not_send.
  • typescript-core (1/3) failed the existing single-owner digest census on loopx/control_plane/work_items/task_lease_workspace.ts:26.

The PR changes only loopx/dashboard_launcher.py, its new route-owner architecture test, and the semantic budget files. None of the failing implementation/test paths are changed by this PR.

Current main is two commits ahead of this head. It already includes 647756d21 / #5367, which updates the runtime source-fingerprint implementation and the failing readiness tests. Please update this branch from current main and rerun CI. Any remaining digest or usage-disclosure failure should be repaired at its owning mainline boundary rather than folded into this route-owner refactor.

This explains the current red checks only; it is not an approval. The PR still needs exact-head review after the branch update.

@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.

动机

评审精确 head:afb94f9b8bf2c332a9574a32a33ddd2622dbc692;比较基线:f49b4a00870604d39fa4318da24d6dd35e72bb6e。#4447 Track A 的这个阶段解决真实 wire 身份重复:Chat server dispatch 与 dashboard launcher probe 各持有 /api/chat/capabilities,未来仅调整一边会把有效服务误判成 foreign,要求用户重复启动。浏览器仍是独立运行时,不能直接导入 Python 常量。

改动思路

launcher 在探测函数内导入 chat_server.CHAT_CAPABILITIES_PATH,保留服务端的既有 route owner,不改变 URL、capabilities schema、版本指纹校验或配置门。函数内导入避免扩大 launcher 模块加载图,但首次独立 probe 仍会加载 server 图;这不是零成本改动。当前公开 CLI 的 support_control.py 已先导入 chat_server,所以普通 dashboard/chat 入口无需新增操作或重复导入。架构 census 将浏览器的一个 literal 明确列出,同时降低现有 fork 预算。

具体改动

四文件 +196/-7,生产代码是删除 launcher 的私有定义、添加本地 import;新增 185 行 source guard,另有两处预算同步。

关键代码讲解

  • dashboard_launcher.py:36 的 _probe_existing_chat 在第 53 行取得 server-owned route;HTTP 请求仍有原超时、响应大小上限和 close。malformed/foreign、stale identity、配置不匹配、空闲端口的结果及处理顺序不变。
  • 未改动的 chat_server.py:1309 的 ChatRequestHandler.do_GET 仍在原 URL 返回 v1 capabilities;read-only、approval_policy=never、preview-locked Todo 写入等 authority 字段均保留。来源归一不赋予 launcher 停止 foreign listener 或绕过保护的权限。
  • launch_dashboard 的 packaged validation → probe → matching reuse / refusal / fresh serve 链未改;tests/architecture/test_chat_capabilities_route_single_owner.py:52 扫描 module-level binding,独立 route oracle 与 browser file/count 防止普通 literal 副本静默新增。该 census 的语法范围有限,不宣称识别动态拼接等任意代码。

独立验证:先用未构建 checkout 复现 base/head 相同五个 bundle-manifest 缺失失败,逐个核对测试身份及错误;没有按总数直接豁免。随后分别 npm ci --ignore-scripts、npm run build:chat,同一 dashboard/CORS/startup isolation 选择在 base/head 各 98 passed。新增架构测试和完整 semantic checks 在原 head 的相关选择中另有 213 passed;早期五失败已由上述构建后验证消除。

同一独立 harness 经真实 serve_chat、隔离 runtime/registry、实际打包 assets 和 HTTP,验证健康、capabilities、/chat/ 及 launcher reuse;七场景的完整输入/结果在 base/head 相同(只规范化随机监听端口)。故意改错 probe URL 后,独立正常复用 oracle 按预期失败。全树 semantic smoke、changed-diff advisory、改动文件 Ruff、whitespace 和公私边界检查也通过。未调用模型、外部 Lark 或生产服务。

对主干的风险

未发现阻塞问题。首次直接调用独立 launcher probe 的 server import 有可见冷启动成本:本次单次测量 base 约 2ms、head 约 1.06s,不能把作者约 110ms 的测量当成通用保证;普通 CLI 已预加载该图,重复探测不重复初始化。这个邻接边界适合在未来真正需要独立轻量 launcher 时抽取现有 wire contract,而不是现在另建通用框架。本次实际 packaged 服务复用/拒绝链已覆盖,未声称改善冷启动性能。

没有 UI 源码/视觉变化,新构建产品资源已真实服务;没有 persisted-state 或 PostgreSQL owner 变化,后端存储迁移资格不适用。浏览器 literal 和独立 schema 接受策略保留,未来跨运行时变更仍需共同资格。与同作者 work-lane PR 共享预算锚点,合并时应重新测量最新主干。未查询远端 CI;批准不代表可自合并。

我的整体评价

APPROVE。真实生产副本被删除,route 身份收敛到既有 server owner;包内用户路径、完整 capabilities 和失败诊断保持一致。已有环境缺产物的失败没有掩盖,最终在 base/head 构建后通过。相邻轻量契约/重复 census 重构已考虑并保留为实际下一次需要时的边界,不扩大本 PR。批准后会读回有效旧阻塞评审,只有已解决且授权的过期评审才撤销;不合并。

English verdict: APPROVE — server-owned route reuse preserves the packaged HTTP/launcher journey and negative classifications. The five initial missing-bundle failures were reproduced at base and resolved by building both checkouts; cold standalone import cost remains disclosed, not claimed improved.

This branch has not been deployed

No deployments
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