Skip to content

docs(cli): correct capability-loop comments — bare import, CLI copy wins (#10909) - #10956

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10909-capability-loop-comment-truth
Aug 22, 2026
Merged

docs(cli): correct capability-loop comments — bare import, CLI copy wins (#10909)#10956
os-elon merged 1 commit into
mainfrom
claude/issue-10909-capability-loop-comment-truth

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#10909

Measurement at head (re-derived, not inherited)

The card's line numbers (:37/:44 for the comments, :2987/:3052 for the load
sites) were measured on the #10769 branch and are stale after #10919 (moved
importFromHost to module scope) and #10948 (added ~104 lines). Re-derived by
content on 9cc6777d3e (branch base, current origin/main tip at dispatch time):

  • Comment sites: packages/cli/src/commands/serve.ts:36-37 (email) and
    :43-44 (sms) — both said "host copy first".
  • Load site (the capability loop): Serve.CAPABILITY_PROVIDERS iteration,
    now around :3058-3154const mod: any = await import(/* webpackIgnore: true */ spec.pkg); and, for extras, const exMod: any = await import(/* webpackIgnore: true */ ex.pkg);. Both are bareimport(), no
    importFromHost anywhere in this loop.

Node ESM resolves a bare import() against the importing module's own
realpath — i.e. this CLI's — so the CLI's bundled copy of
EmailServicePlugin/SmsServicePlugin always wins; the host app's copy is
never consulted. "Host copy first" described a behaviour the code does not
have, confirmed unchanged at head. (The class stays latent, not live: all 21
Serve.CAPABILITY_PROVIDERS packages are declared by packages/cli itself,
so bare resolution finds every one of them today — the same fact the card
recorded.)

Fix (option 1, as triage ruled)

Corrected both comments to state the measured behaviour, and made the file's
now-real split legible rather than just negating the old claim: after #10948
serve.ts contains both a host-anchored path (Serve.importConfigPlugin,
for the served app's own plugins: [...], where the app's declaration decides
which copy wins) and a bare-import path (the capability loop, where the CLI's
copy always wins). The corrected comments name importConfigPlugin as the
contrasting case so the next reader does not assume one rule governs the whole
file. Each comment also cites #10909 as its source, matching this file's own
convention of leaving a trail for corrections (#5307, #5448, #10943, …
all do the same nearby).

Option 2 (routing the loop through importFromHost) was not implemented,
per triage's ruling (comment 5373477292) and the harder constraint layered on
top of it: #10943 measured that createHostImporter's undeclared-fallback path
resolves from @objectstack/types, not the calling package — contradicting its
own docblock. Every CAPABILITY_PROVIDERS package is CLI-declared and none is
declared by the served app, so routing this loop through importFromHost today
would hand all 21 to that broken fallback and, on a pnpm-isolated install, turn
a working capability into a missing one. No code path changed in this PR — only
the two comments.

#10514 interaction — checked, not fixed (per brief)

#10514 records that two packages/cli scans read serve.tsraw (no
comment/code separator), so prose counts as code:

  • serve-email-config-parity.contract.test.tskeysReadFromConfigEmail()
    matches /cfgEmail\.[A-Za-z_$][\w$]*/g over the raw source.
  • serve-multi-node-cap-advisory.pin.test.ts — three assertions match
    checkMultiNodeAllowed(...) shapes over the raw source.

Neither corrected comment contains cfgEmail. or checkMultiNodeAllowed, so
neither token vocabulary is touched. Verified both directions, not just
reasoned about: ran both test files against the pre-edit tree (git checkout HEAD -- serve.ts, discarding the uncommitted comment edit) and again against
the corrected tree (re-applying it from a saved patch) — 10/10 pass in both
states, byte-identical result.
This edit trips neither scan and un-trips
neither scan.

Pinning the corrected claim

Considered whether the corrected statement could be pinned the way #10514's
scans pin other serve.ts claims. It could only be pinned by asserting, from
source, that the capability-loop's two import() calls carry no
importFromHost wrapper — a regex/AST assertion built solely to prove a
comment's prose true, with no behavioural difference for it to guard (the
"host wins" vs "CLI wins" distinction is not independently observable from
outside the process without faking two installed copies of a provider
package). That is disproportionate machinery for a comment fix, so none was
built here; if this class of claim recurs, the multi-node-cap-advisory pin is
the pattern to follow, keyed on the real production line
(await import(/* webpackIgnore: true */ spec.pkg)) rather than on the prose.

Verification (head 6780fab0e3)

  • Dependency closure: pnpm --filter '@objectstack/cli^...' build
    os-verify-lock: VERDICT command-exit 0 · held the lock 418s.
  • pnpm --filter @objectstack/cli typecheck (tsc --noEmit) —
    os-verify-lock: VERDICT command-exit 0.
  • pnpm --filter @objectstack/cli test -- --maxWorkers=2 — full suite:
    Test Files 150 passed (150) / Tests 1663 passed (1663),
    os-verify-lock: VERDICT command-exit 0.
  • Reverse verification does not apply (comment-only change, no behaviour to
    ablate); the Two packages/cli scans read serve.ts with no comment/code separator at all, so prose counts as code — the direction js-comment-mask's header calls the worse one #10514 scan re-run (pre-edit vs post-edit tree, both
    Test Files 2 passed (2) / Tests 10 passed (10)) is the closest
    behavioural check available here, per the brief.
  • Gate union re-derived at this exact commit: node scripts/pm/dispatch-gates.mjs
    (no paths — derives from the merge base 9cc6777d3 vs HEAD) named 14 local
    gates; all 14 run and green:
    check:changeset-gate-self-tests, check:cross-package-test-inputs,
    check:objectui-changeset, check:route-envelope, check:slot-lookup
    (✓ slot-lookup ratchet holds: 107 unswept site(s) … none new),
    check:test-source-alias, check:type-source-resolution,
    check-adr-0087-registration.mjs, check-changeset-no-major.mjs,
    check-ci-filter-parity.mjs, check-cross-package-test-inputs.mjs,
    check-empty-changeset.mjs, check-plugin-teardown-shape.mjs,
    docs-audit/check-affected-docs.mjs.
  • .changeset/capability-loop-host-copy-first.md added (@objectstack/cli
    patch) — comment-only fix, still user-visible in the published source, so a
    real changeset rather than skip-changeset.

⛔ Draft — PM seat reviews and enqueues. No behaviour changed; no CI wait
performed past this report per the standing lane rule.

Generated by Claude Code


Generated by Claude Code

Two `serve.ts` comment sites claimed the capability loop resolves
`EmailServicePlugin`/`SmsServicePlugin` "host copy first". Measured at head,
the loop does a bare `await import(spec.pkg)` / `await import(ex.pkg)` — no
`importFromHost` — which Node ESM resolves against this CLI's own realpath,
so the CLI's bundled copy always wins and the host's copy is never
consulted. Corrected both comments to state that, and named the contrast
with `Serve.importConfigPlugin` (the served app's own `plugins: [...]`,
which IS host-anchored) so the file's now-real split stays legible.
Comment-only: no runtime path, resolution order, or accepted specifier
changes.
Fixes#10909
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 1 documentable anchor(s).

16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 9cc6777d3e228952bc554c5f1c4f34b063fcf105.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see

Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 9cc6777d3e228952bc554c5f1c4f34b063fcf105packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7377273ec8bb9a2b06e471b9961405a18a737894 — the merge of head 6780fab0e3ea306d37d23bf3aa4d7b72054098d6 into base 9cc6777d3e228952bc554c5f1c4f34b063fcf105, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7377273ec8bb9a2b06e471b9961405a18a737894 && git checkout 7377273ec8bb9a2b06e471b9961405a18a737894
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9cc6777d3e228952bc554c5f1c4f34b063fcf105 6780fab0e3ea306d37d23bf3aa4d7b72054098d6 && git checkout -B drift-repro 9cc6777d3e228952bc554c5f1c4f34b063fcf105 && git merge --no-ff 6780fab0e3ea306d37d23bf3aa4d7b72054098d6
node scripts/docs-audit/affected-docs.mjs --json 9cc6777d3e228952bc554c5f1c4f34b063fcf105

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 9cc6777d3e228952bc554c5f1c4f34b063fcf105 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tooling labels Aug 21, 2026
@os-elon
os-elon marked this pull request as ready for review August 22, 2026 01:16
@os-elon
os-elon added this pull request to the merge queueAug 22, 2026
Merged via the queue into main with commit 5a90c56Aug 22, 2026
32 checks passed
@os-elon
os-elon deleted the claude/issue-10909-capability-loop-comment-truth branch August 22, 2026 01:28
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32542946606 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.96s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 54 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

os-elon pushed a commit that referenced this pull request Aug 22, 2026
… scans (#10514)
Two `packages/cli` scans read `serve.ts` raw, with no comment/code separator
at all, so prose counted as code -- the FABRICATES direction
`scripts/js-comment-mask.mjs`'s own header calls the worse one. Neither scan
was wrong today, but both were one ordinary comment away from being wrong:
- `serve-email-config-parity.contract.test.ts`'s `keysReadFromConfigEmail()`
matched `cfgEmail\.\w+` over raw source, so a comment naming an undeclared
key would have fabricated a false "undeclared key" red, and -- the worse
direction -- a comment naming a declared-but-unread key would have
silently restored the exact `DECLARED_BUT_UNREAD` exemption this file's
own docblock says was deleted for good after #5447/#5470.
- `serve-multi-node-cap-advisory.pin.test.ts`'s four shape assertions
matched regexes over raw source, so a reverted call could hide behind a
trailing comment quoting the shape it replaced.
Both now scan `maskComments(SERVE_SOURCE)` instead. `interfaceFields()`'s own
narrower, brace-matched strip is untouched -- out of scope per the issue.
Re-verified per the card's release condition: on origin/main at branch point
(047ac86), the raw / naive-stripped / masked key sets for subject 1 and
the raw / masked match verdicts for subject 2's four assertions are all
identical -- no verdict moved by #10453/#10919/#10948/#10956's changes to
serve.ts since the card was written. `CROSS_PACKAGE_TEST_INPUTS['@objectstack/cli']`
and the turbo.json `@objectstack/cli#test` input already carry
`scripts/js-comment-mask.mjs`/`.d.mts` (landed with #10453/PR #10513), so no
registration work was needed -- just the two-site routing.
Adds vacuity-proof tests demonstrating the FABRICATES shapes on synthetic
sources: a comment containing the exact pattern each scan keys on is shown
producing the wrong verdict when read raw, and the right one once masked.
Fresh answer to the open question #10909's dev never returned: yes, editing
comment prose in serve.ts could move both scans' results pre-fix (demonstrated
above); post-fix it cannot.
Side effect caught by `check:cross-package-test-inputs`: adding the
`maskComments` import gave `serve-email-config-parity.contract.test.ts` its
first real escaping read, which newly exposed a PRE-EXISTING prose mention of
`packages/spec/src/system/email-config.test.ts` (a doc cross-reference, never
actually read) to the gate's literal collector. Reworded to name it without a
single quoted repo-relative literal, following the precedent already set in
this directory's `serve-multi-node-cap-advisory.pin.test.ts` for the identical
gate.
Fixes#10514
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/stooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Two serve.ts comments claim the capability loop loads "host copy first", but it bare-imports — the CLI's copy always wins

2 participants

@os-elon@claude