docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets - #14795

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing
Sep 3, 2026
Merged

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets#14795
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14794
Fixes#14566

Three edits to skills/README.md, and nothing else. Branch point re-measured at
origin/main89a156af88 — identical to the card's measurement, no drift.
Head sha for every gate reading below: 39da552e61.

Item 1 — :87, the OLD / NEW clause

Before (verbatim):

@objectstack/spec. Legacy OLD / NEW evaluate to null since M9.5.

After (verbatim):

@objectstack/spec. Legacy OLD / NEW are not CEL — use
previous. / record..

What settles it. The sentence was false in both halves.

  • M9.5 is anchored nowhere: git grep -rn 'M9\.5' -- packages skills docs returns
    exactly one hit, this README line itself. Nothing defines the milestone it cites.
  • OLD / NEW are not values that evaluate to null — they are undeclared
    identifiers
    . SCOPE_ROOTS (packages/formula/src/cel-engine.ts:95-119) is the
    published, closed set of roots a record-scoped CEL site may reference; it contains
    neither. firstUndeclaredReference (packages/formula/src/cel-engine.ts:186-205)
    therefore reports them as bare undeclared references — the class
    @objectstack/lint raises as an error, not a silent null
    (packages/lint/src/validate-expressions.ts:490, "Why it is an error and not a
    warning").

Measured directly against the built package rather than inferred:

SCOPE_ROOTS includes OLD: false | NEW: false | previous: true | record: true
"OLD.amount != NEW.amount" -> firstUndeclaredReference = "OLD"
"OLD" -> firstUndeclaredReference = "OLD"
"NEW.status" -> firstUndeclaredReference = "NEW"
"previous.amount != record.amount" -> firstUndeclaredReference = null

The replacement's prescription is objectstack-formula's own migration table, not a
new claim: skills/objectstack-formula/SKILL.md:345-346 maps OLD.x to
previous.x and NEW.x to record.x. The two published files now agree.

Item 2 — :105, "AI tool params" in the "Any CEL expression" bullet

Before (verbatim):

skill (data validations, automation guards, UI visibility, AI tool params).

After (verbatim):

skill (data validations, automation guards, UI visibility).

What settles it — and a correction to the card. The card's diagnosis is
confirmed and its prescribed replacement is false.

  • Confirmed: packages/spec/src/ai/tool.zod.ts matches cel|formula|expression
    zero times. Tool params carry no expression field of any kind.
  • Falsified: the card asked to substitute "a model-registry predicate, verified at
    packages/spec/src/ai/model-registry.zod.ts". There is no such field —
    predicate occurs 0 times in that file. Its only expression-typed keys are
    system / user at :121-122, and those are TemplateExpressionInputSchema,
    which packages/spec/src/shared/expression.zod.ts:117-124 defines as
    dialect: 'template'{{var}} interpolation, not CEL.
  • skills/objectstack-ai/SKILL.md:405-407 already says exactly this: the domain's
    expression site is "a model-registry promptTemplate.system / .user;
    ToolSchema carries no expression field of any kind."

So the AI domain has no CEL site at all, and there is no true item to swap in.
Taking the card's stated alternative, the item is dropped rather than replaced.
Adding an AI entry to a bullet headed "Any CEL expression" would have replaced one
false claim with another.

Item 3 — three routing bullets (:94-110)

Added verbatim, in the existing bullets' shape:

- **Labels vs. bundles** — a `label` you want translated is **objectstack-i18n**.
- **Upgrade vs. platform** — a protocol-major move is **objectstack-upgrade**.
- **Rendering this metadata** — the consuming UI is
[objectui `skills/objectui/`](https://github.com/objectstack-ai/objectui).
BulletCostVerified against
Labels vs. bundles (#14566, I18N-A-03)21 tok / 84 Bskills/objectstack-i18n/SKILL.md frontmatter description — "Author ObjectStack translation bundles — object/field labels, view text …"
Upgrade vs. platform (UPG-A-01)21 tok / 78 Bskills/objectstack-upgrade/SKILL.md frontmatter description — "Use when a project is on an older protocol major and must move to the current one, when @objectstack/spec was bumped across a major …"
Rendering this metadata (OUI-A-01)33 tok / 134 BPath confirmed present on the objectui checkout: git ls-tree origin/main skills/objectui/ at objectui origin/main39af82f returns SKILL.md, README.md, evals/, guides/, rules/
Total75 tok / 296 Bat the #14566 triage's ruling (comment 5514978050): one bullet, ~25 tokens

Each routing claim was checked against the target skill's own frontmatter
description, so the bullet sends the reader where the skill says it triggers.

The payment

Item 3's 75 tokens are the funded addition. Items 1 and 2 are funded from each
other
and net to exactly zero:

  • Item 2's drop of , AI tool params frees 16 B / 4 tok.
  • Item 1's truthful replacement costs 16 B / 4 tok.
  • Items 1 + 2 combined: 0 B, 0 tok — neutral, as the budget requires.

One further payment is inside item 3 itself, and it is what brought the block from
84 tokens to the ruling's 75. Bullet 2's body does not restate "not
objectstack-platform"; the surviving site is the bullet's own heading,
"Upgrade vs. platform", which already names both sides of the decision. That is
a strict restatement removed, not meaning shaved — the routing information (which
skill wins) is intact.

No further payment was available. The one candidate examined was the
process-skill paragraph at :64-66 against the frontmatter rows at :75
(compatibility — "for a process skill that binds to no schema") and :76
(metadata.domain — "a delivery-process skill that teaches no schema"). Rejected:
the paragraph also carries two facts that survive nowhere else — that such a skill
"carries SKILL.md alone" (no references/ directory) and the SKILL_MAP
mechanism behind it. Deleting it would have shaved meaning, so it was left alone.

File token table

ceil(utf8_bytes / 4), whole file:

BytesTokens
Before (89a156af88)79411986
After (39da552e61)82372060
Delta+296+74

The entire delta is item 3's funded block; items 1–2 contribute zero.
skills/README.md is outside the token-ratchet population
(scripts/check-skills-token-ratchet.mjs:133), and the ratchet is green regardless.

Edit-landed-on-disk proof, grep counts before → after:

TermBeforeAfter
M9.510
AI tool params10
objectstack-i18n12
objectstack-upgrade12
objectui12

Gates — all at head 39da552e61

Every exit code captured by redirect before any pipe; each row quotes the gate's
own verdict line.

GateExitVerdict
pnpm --filter @objectstack/spec check:skill-docs0✅ Skill docs in sync (✓ skills/README.md)
node scripts/check-skills-token-ratchet.mjs0✓ 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
node scripts/check-skills-token-ratchet.mjs --self-test0✓ check-skills-token-ratchet self-test: 64 cases pass.
pnpm check:role-word0check-role-word: OK, no new occurrences of the reserved word. — 224 files, ledger unchanged at 43 baselined files / 120 occurrences
pnpm check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 46 published file(s) …; Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
pnpm check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
pnpm check:published-readme-links0✓ 176 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 via redirect), 1 anchor(s) verified, 103/103 relative target(s) found in the tree. — the new objectui link resolves
node scripts/check-nul-bytes.mjs0OK (scanned 8064 text file(s) … no raw ASCII control bytes)

Re-derived union

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands,
re-derived after the last edit. Provenance line confirms it read the right tree:
"gate list derived from the tree of 'objectstack-ai/objectstack' at commit
39da552e61", change set "1 path(s) vs merge base 89a156af8" = skills/README.md.
All 16 commands run:

GateExit
node scripts/check-ci-filter-parity.mjs0
node scripts/check-cross-package-test-inputs.mjs0
node scripts/check-shard-attestation.mjs0
node scripts/check-test-completeness.mjs3 — NOT MEASURED
pnpm --filter @objectstack/lint run check:doc-formula-expressions0
pnpm --filter @objectstack/spec run check:skill-docs0
pnpm check:agent-test-spelling0
pnpm check:corpus-claim-drift0
pnpm check:cross-package-test-inputs0
pnpm check:doc-authoring0
pnpm check:merge-driver0
pnpm check:pm-governed-merges0
pnpm check:role-word0
pnpm check:skill-compatibility0
pnpm check:skill-frame-sync0
pnpm check:skill-identifier-liveness0

Two notes on that table:

  • check-test-completeness exits 3 = PREREQUISITE NOT MET, not red: it "grades a
    saved turbo run test log, and no log was named … the local reading for this gate
    is NOT MEASURED." CI tees the log and passes the path, so CI is unaffected.
  • check:doc-formula-expressionsalso exited 3 on the first pass (it imports the
    compiled @objectstack/formula and @objectstack/lint). Rather than report a
    non-reading, both packages were built and the gate re-run — it is the gate nearest
    this change, since item 1 introduces previous. / record. into published prose.
    It then measured green:
    ✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula.

Reverse-verification

check:skill-docs can fail, and it names this file. Guarded by
trap restore EXIT INT TERM with absolute paths throughout; the implementation was
committed first, so the restore leg points at a HEAD that already contains it.

  • Mutation landed on disk — not inferred from an editor's exit code, but from grep
    counts flipping in both directions inside the generated region
    (Construct ObjectQL queries 1 → 0, Construct ObjectQL queried 0 → 1) and the
    worktree blob moving 72afcf31b7…e60bea9ecc….
  • Mutated legexit 1, verdict
    ✗ skills/README.md is out of date — run pnpm --filter @objectstack/spec gen:skill-docs.
    The gate is live and it names skills/README.md.
  • Restore leggit checkout HEAD -- /home/user/…/skills/README.md (pinned to
    HEAD, never a bare checkout, so it cannot restore from a polluted index). Proved
    by blob hash equality against the HEAD blob (72afcf31b7…, non-empty and
    matching), an empty git diff HEAD, and the anchor counts returning to 1 / 0.

ESLint

Not a narrowing — the file is outside the linted population entirely, established
with all three pieces:

  1. Population, read from eslint's own config resolution (not from my reading of
    the globs): eslint --print-config skills/README.md emits an empty document
    (10 bytes). Every files: selector in eslint.config.mjs is
    {ts,tsx,mts,cts,js,jsx,mjs,cjs}; no selector matches markdown.
  2. Count from --format json for the file: 1 result entry, errorCount: 0,
    warningCount: 1, and the single message is eslint's own
    File ignored because no matching configuration was supplied. — zero rule
    findings, because zero rules apply.
  3. Invariance for untouched files: the repo runs one eslint.config.mjs which
    "never enables type-aware linting (no parserOptions.project, no typed
    @typescript-eslint rules) for ANY file, test or not"
    (eslint.config.mjs:326-328, carrying its own positive-control measurement).
    No untouched file's verdict can move as a function of this diff.

A full-repo pnpm lint is therefore provably vacuous with respect to this change.

Changeset

skip-changeset, applied. This PR releases nothing: the diff is one file under
skills/, and scripts/check-empty-changeset.mjs:359-362 enumerates exactly that
case — "It releases nothing (.github/, .claude/, skills/, docs/,
content/, examples/, tests-only, and the like) -> delete the changeset and apply
the 'skip-changeset' label (route 2)." The script pins this shape as GREEN 3, "a
skills/**-only PR carrying NO changeset (route 2)" (:559-565). No package
package.json, src/, or version is touched.

Scope

Untouched, as claimed: every SKILL.md; the README's generated region between the
BEGIN/END GENERATED markers (check:skill-docs green proves it byte-for-byte); the
anatomy tree at :50-62, so the hunks are disjoint from PR #14786's :55 — this
diff's two hunks start at :85 and :104. No other README sentence was edited.

Draft, and it stays draft — governed skills/**, human merge is the review record.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…in skills/README.md
Settle `skills/README.md`'s CEL conventions and cross-skill routing list
against source.
1. `:87` — "Legacy `OLD` / `NEW` evaluate to `null` since M9.5" was false in
both halves. `M9.5` is anchored nowhere in the repo, and `OLD` / `NEW` are
not values that evaluate to `null`: they are undeclared identifiers.
`SCOPE_ROOTS` (packages/formula/src/cel-engine.ts:95) is the published
closed set of record-scope roots and contains neither, so
`firstUndeclaredReference` (:186) reports them as bare undeclared
references — the class `@objectstack/lint` raises as an error, not a
silent null. Replaced with the true clause plus the migration objectstack-
formula's own table prescribes (`OLD.x` -> `previous.x`, `NEW.x` ->
`record.x`).
2. `:105` — "AI tool params" is not a CEL site. `packages/spec/src/ai/tool.zod.ts`
carries no expression field of any kind, and the AI domain's only expression
site is `model-registry.zod.ts:121-122`, which is `TemplateExpressionInputSchema`
— the `template` dialect (`{{var}}` interpolation), not CEL. Dropped the item;
no true replacement exists, because the AI domain has no CEL predicate.
3. `:94-110` — three routing bullets added for measured load-failure modes:
labels to objectstack-i18n, protocol-major moves to objectstack-upgrade,
and the consuming UI to the objectui skills catalog.
Items 1-2 net exactly zero bytes; item 3 costs 75 tokens, at the size ruling.
The generated region and the anatomy tree are untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

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

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

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

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets - #14795

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing
Sep 3, 2026
Merged

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets#14795
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14794
Fixes#14566

Three edits to skills/README.md, and nothing else. Branch point re-measured at
origin/main89a156af88 — identical to the card's measurement, no drift.
Head sha for every gate reading below: 39da552e61.

Item 1 — :87, the OLD / NEW clause

Before (verbatim):

@objectstack/spec. Legacy OLD / NEW evaluate to null since M9.5.

After (verbatim):

@objectstack/spec. Legacy OLD / NEW are not CEL — use
previous. / record..

What settles it. The sentence was false in both halves.

  • M9.5 is anchored nowhere: git grep -rn 'M9\.5' -- packages skills docs returns
    exactly one hit, this README line itself. Nothing defines the milestone it cites.
  • OLD / NEW are not values that evaluate to null — they are undeclared
    identifiers
    . SCOPE_ROOTS (packages/formula/src/cel-engine.ts:95-119) is the
    published, closed set of roots a record-scoped CEL site may reference; it contains
    neither. firstUndeclaredReference (packages/formula/src/cel-engine.ts:186-205)
    therefore reports them as bare undeclared references — the class
    @objectstack/lint raises as an error, not a silent null
    (packages/lint/src/validate-expressions.ts:490, "Why it is an error and not a
    warning").

Measured directly against the built package rather than inferred:

SCOPE_ROOTS includes OLD: false | NEW: false | previous: true | record: true
"OLD.amount != NEW.amount" -> firstUndeclaredReference = "OLD"
"OLD" -> firstUndeclaredReference = "OLD"
"NEW.status" -> firstUndeclaredReference = "NEW"
"previous.amount != record.amount" -> firstUndeclaredReference = null

The replacement's prescription is objectstack-formula's own migration table, not a
new claim: skills/objectstack-formula/SKILL.md:345-346 maps OLD.x to
previous.x and NEW.x to record.x. The two published files now agree.

Item 2 — :105, "AI tool params" in the "Any CEL expression" bullet

Before (verbatim):

skill (data validations, automation guards, UI visibility, AI tool params).

After (verbatim):

skill (data validations, automation guards, UI visibility).

What settles it — and a correction to the card. The card's diagnosis is
confirmed and its prescribed replacement is false.

  • Confirmed: packages/spec/src/ai/tool.zod.ts matches cel|formula|expression
    zero times. Tool params carry no expression field of any kind.
  • Falsified: the card asked to substitute "a model-registry predicate, verified at
    packages/spec/src/ai/model-registry.zod.ts". There is no such field —
    predicate occurs 0 times in that file. Its only expression-typed keys are
    system / user at :121-122, and those are TemplateExpressionInputSchema,
    which packages/spec/src/shared/expression.zod.ts:117-124 defines as
    dialect: 'template'{{var}} interpolation, not CEL.
  • skills/objectstack-ai/SKILL.md:405-407 already says exactly this: the domain's
    expression site is "a model-registry promptTemplate.system / .user;
    ToolSchema carries no expression field of any kind."

So the AI domain has no CEL site at all, and there is no true item to swap in.
Taking the card's stated alternative, the item is dropped rather than replaced.
Adding an AI entry to a bullet headed "Any CEL expression" would have replaced one
false claim with another.

Item 3 — three routing bullets (:94-110)

Added verbatim, in the existing bullets' shape:

- **Labels vs. bundles** — a `label` you want translated is **objectstack-i18n**.
- **Upgrade vs. platform** — a protocol-major move is **objectstack-upgrade**.
- **Rendering this metadata** — the consuming UI is
[objectui `skills/objectui/`](https://github.com/objectstack-ai/objectui).
BulletCostVerified against
Labels vs. bundles (#14566, I18N-A-03)21 tok / 84 Bskills/objectstack-i18n/SKILL.md frontmatter description — "Author ObjectStack translation bundles — object/field labels, view text …"
Upgrade vs. platform (UPG-A-01)21 tok / 78 Bskills/objectstack-upgrade/SKILL.md frontmatter description — "Use when a project is on an older protocol major and must move to the current one, when @objectstack/spec was bumped across a major …"
Rendering this metadata (OUI-A-01)33 tok / 134 BPath confirmed present on the objectui checkout: git ls-tree origin/main skills/objectui/ at objectui origin/main39af82f returns SKILL.md, README.md, evals/, guides/, rules/
Total75 tok / 296 Bat the #14566 triage's ruling (comment 5514978050): one bullet, ~25 tokens

Each routing claim was checked against the target skill's own frontmatter
description, so the bullet sends the reader where the skill says it triggers.

The payment

Item 3's 75 tokens are the funded addition. Items 1 and 2 are funded from each
other
and net to exactly zero:

  • Item 2's drop of , AI tool params frees 16 B / 4 tok.
  • Item 1's truthful replacement costs 16 B / 4 tok.
  • Items 1 + 2 combined: 0 B, 0 tok — neutral, as the budget requires.

One further payment is inside item 3 itself, and it is what brought the block from
84 tokens to the ruling's 75. Bullet 2's body does not restate "not
objectstack-platform"; the surviving site is the bullet's own heading,
"Upgrade vs. platform", which already names both sides of the decision. That is
a strict restatement removed, not meaning shaved — the routing information (which
skill wins) is intact.

No further payment was available. The one candidate examined was the
process-skill paragraph at :64-66 against the frontmatter rows at :75
(compatibility — "for a process skill that binds to no schema") and :76
(metadata.domain — "a delivery-process skill that teaches no schema"). Rejected:
the paragraph also carries two facts that survive nowhere else — that such a skill
"carries SKILL.md alone" (no references/ directory) and the SKILL_MAP
mechanism behind it. Deleting it would have shaved meaning, so it was left alone.

File token table

ceil(utf8_bytes / 4), whole file:

BytesTokens
Before (89a156af88)79411986
After (39da552e61)82372060
Delta+296+74

The entire delta is item 3's funded block; items 1–2 contribute zero.
skills/README.md is outside the token-ratchet population
(scripts/check-skills-token-ratchet.mjs:133), and the ratchet is green regardless.

Edit-landed-on-disk proof, grep counts before → after:

TermBeforeAfter
M9.510
AI tool params10
objectstack-i18n12
objectstack-upgrade12
objectui12

Gates — all at head 39da552e61

Every exit code captured by redirect before any pipe; each row quotes the gate's
own verdict line.

GateExitVerdict
pnpm --filter @objectstack/spec check:skill-docs0✅ Skill docs in sync (✓ skills/README.md)
node scripts/check-skills-token-ratchet.mjs0✓ 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
node scripts/check-skills-token-ratchet.mjs --self-test0✓ check-skills-token-ratchet self-test: 64 cases pass.
pnpm check:role-word0check-role-word: OK, no new occurrences of the reserved word. — 224 files, ledger unchanged at 43 baselined files / 120 occurrences
pnpm check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 46 published file(s) …; Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
pnpm check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
pnpm check:published-readme-links0✓ 176 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 via redirect), 1 anchor(s) verified, 103/103 relative target(s) found in the tree. — the new objectui link resolves
node scripts/check-nul-bytes.mjs0OK (scanned 8064 text file(s) … no raw ASCII control bytes)

Re-derived union

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands,
re-derived after the last edit. Provenance line confirms it read the right tree:
"gate list derived from the tree of 'objectstack-ai/objectstack' at commit
39da552e61", change set "1 path(s) vs merge base 89a156af8" = skills/README.md.
All 16 commands run:

GateExit
node scripts/check-ci-filter-parity.mjs0
node scripts/check-cross-package-test-inputs.mjs0
node scripts/check-shard-attestation.mjs0
node scripts/check-test-completeness.mjs3 — NOT MEASURED
pnpm --filter @objectstack/lint run check:doc-formula-expressions0
pnpm --filter @objectstack/spec run check:skill-docs0
pnpm check:agent-test-spelling0
pnpm check:corpus-claim-drift0
pnpm check:cross-package-test-inputs0
pnpm check:doc-authoring0
pnpm check:merge-driver0
pnpm check:pm-governed-merges0
pnpm check:role-word0
pnpm check:skill-compatibility0
pnpm check:skill-frame-sync0
pnpm check:skill-identifier-liveness0

Two notes on that table:

  • check-test-completeness exits 3 = PREREQUISITE NOT MET, not red: it "grades a
    saved turbo run test log, and no log was named … the local reading for this gate
    is NOT MEASURED." CI tees the log and passes the path, so CI is unaffected.
  • check:doc-formula-expressionsalso exited 3 on the first pass (it imports the
    compiled @objectstack/formula and @objectstack/lint). Rather than report a
    non-reading, both packages were built and the gate re-run — it is the gate nearest
    this change, since item 1 introduces previous. / record. into published prose.
    It then measured green:
    ✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula.

Reverse-verification

check:skill-docs can fail, and it names this file. Guarded by
trap restore EXIT INT TERM with absolute paths throughout; the implementation was
committed first, so the restore leg points at a HEAD that already contains it.

  • Mutation landed on disk — not inferred from an editor's exit code, but from grep
    counts flipping in both directions inside the generated region
    (Construct ObjectQL queries 1 → 0, Construct ObjectQL queried 0 → 1) and the
    worktree blob moving 72afcf31b7…e60bea9ecc….
  • Mutated legexit 1, verdict
    ✗ skills/README.md is out of date — run pnpm --filter @objectstack/spec gen:skill-docs.
    The gate is live and it names skills/README.md.
  • Restore leggit checkout HEAD -- /home/user/…/skills/README.md (pinned to
    HEAD, never a bare checkout, so it cannot restore from a polluted index). Proved
    by blob hash equality against the HEAD blob (72afcf31b7…, non-empty and
    matching), an empty git diff HEAD, and the anchor counts returning to 1 / 0.

ESLint

Not a narrowing — the file is outside the linted population entirely, established
with all three pieces:

  1. Population, read from eslint's own config resolution (not from my reading of
    the globs): eslint --print-config skills/README.md emits an empty document
    (10 bytes). Every files: selector in eslint.config.mjs is
    {ts,tsx,mts,cts,js,jsx,mjs,cjs}; no selector matches markdown.
  2. Count from --format json for the file: 1 result entry, errorCount: 0,
    warningCount: 1, and the single message is eslint's own
    File ignored because no matching configuration was supplied. — zero rule
    findings, because zero rules apply.
  3. Invariance for untouched files: the repo runs one eslint.config.mjs which
    "never enables type-aware linting (no parserOptions.project, no typed
    @typescript-eslint rules) for ANY file, test or not"
    (eslint.config.mjs:326-328, carrying its own positive-control measurement).
    No untouched file's verdict can move as a function of this diff.

A full-repo pnpm lint is therefore provably vacuous with respect to this change.

Changeset

skip-changeset, applied. This PR releases nothing: the diff is one file under
skills/, and scripts/check-empty-changeset.mjs:359-362 enumerates exactly that
case — "It releases nothing (.github/, .claude/, skills/, docs/,
content/, examples/, tests-only, and the like) -> delete the changeset and apply
the 'skip-changeset' label (route 2)." The script pins this shape as GREEN 3, "a
skills/**-only PR carrying NO changeset (route 2)" (:559-565). No package
package.json, src/, or version is touched.

Scope

Untouched, as claimed: every SKILL.md; the README's generated region between the
BEGIN/END GENERATED markers (check:skill-docs green proves it byte-for-byte); the
anatomy tree at :50-62, so the hunks are disjoint from PR #14786's :55 — this
diff's two hunks start at :85 and :104. No other README sentence was edited.

Draft, and it stays draft — governed skills/**, human merge is the review record.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…in skills/README.md
Settle `skills/README.md`'s CEL conventions and cross-skill routing list
against source.
1. `:87` — "Legacy `OLD` / `NEW` evaluate to `null` since M9.5" was false in
both halves. `M9.5` is anchored nowhere in the repo, and `OLD` / `NEW` are
not values that evaluate to `null`: they are undeclared identifiers.
`SCOPE_ROOTS` (packages/formula/src/cel-engine.ts:95) is the published
closed set of record-scope roots and contains neither, so
`firstUndeclaredReference` (:186) reports them as bare undeclared
references — the class `@objectstack/lint` raises as an error, not a
silent null. Replaced with the true clause plus the migration objectstack-
formula's own table prescribes (`OLD.x` -> `previous.x`, `NEW.x` ->
`record.x`).
2. `:105` — "AI tool params" is not a CEL site. `packages/spec/src/ai/tool.zod.ts`
carries no expression field of any kind, and the AI domain's only expression
site is `model-registry.zod.ts:121-122`, which is `TemplateExpressionInputSchema`
— the `template` dialect (`{{var}}` interpolation), not CEL. Dropped the item;
no true replacement exists, because the AI domain has no CEL predicate.
3. `:94-110` — three routing bullets added for measured load-failure modes:
labels to objectstack-i18n, protocol-major moves to objectstack-upgrade,
and the consuming UI to the objectui skills catalog.
Items 1-2 net exactly zero bytes; item 3 costs 75 tokens, at the size ruling.
The generated region and the anatomy tree are untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

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

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

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

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets - #14795

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing
Sep 3, 2026
Merged

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets#14795
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14794
Fixes#14566

Three edits to skills/README.md, and nothing else. Branch point re-measured at
origin/main89a156af88 — identical to the card's measurement, no drift.
Head sha for every gate reading below: 39da552e61.

Item 1 — :87, the OLD / NEW clause

Before (verbatim):

@objectstack/spec. Legacy OLD / NEW evaluate to null since M9.5.

After (verbatim):

@objectstack/spec. Legacy OLD / NEW are not CEL — use
previous. / record..

What settles it. The sentence was false in both halves.

  • M9.5 is anchored nowhere: git grep -rn 'M9\.5' -- packages skills docs returns
    exactly one hit, this README line itself. Nothing defines the milestone it cites.
  • OLD / NEW are not values that evaluate to null — they are undeclared
    identifiers
    . SCOPE_ROOTS (packages/formula/src/cel-engine.ts:95-119) is the
    published, closed set of roots a record-scoped CEL site may reference; it contains
    neither. firstUndeclaredReference (packages/formula/src/cel-engine.ts:186-205)
    therefore reports them as bare undeclared references — the class
    @objectstack/lint raises as an error, not a silent null
    (packages/lint/src/validate-expressions.ts:490, "Why it is an error and not a
    warning").

Measured directly against the built package rather than inferred:

SCOPE_ROOTS includes OLD: false | NEW: false | previous: true | record: true
"OLD.amount != NEW.amount" -> firstUndeclaredReference = "OLD"
"OLD" -> firstUndeclaredReference = "OLD"
"NEW.status" -> firstUndeclaredReference = "NEW"
"previous.amount != record.amount" -> firstUndeclaredReference = null

The replacement's prescription is objectstack-formula's own migration table, not a
new claim: skills/objectstack-formula/SKILL.md:345-346 maps OLD.x to
previous.x and NEW.x to record.x. The two published files now agree.

Item 2 — :105, "AI tool params" in the "Any CEL expression" bullet

Before (verbatim):

skill (data validations, automation guards, UI visibility, AI tool params).

After (verbatim):

skill (data validations, automation guards, UI visibility).

What settles it — and a correction to the card. The card's diagnosis is
confirmed and its prescribed replacement is false.

  • Confirmed: packages/spec/src/ai/tool.zod.ts matches cel|formula|expression
    zero times. Tool params carry no expression field of any kind.
  • Falsified: the card asked to substitute "a model-registry predicate, verified at
    packages/spec/src/ai/model-registry.zod.ts". There is no such field —
    predicate occurs 0 times in that file. Its only expression-typed keys are
    system / user at :121-122, and those are TemplateExpressionInputSchema,
    which packages/spec/src/shared/expression.zod.ts:117-124 defines as
    dialect: 'template'{{var}} interpolation, not CEL.
  • skills/objectstack-ai/SKILL.md:405-407 already says exactly this: the domain's
    expression site is "a model-registry promptTemplate.system / .user;
    ToolSchema carries no expression field of any kind."

So the AI domain has no CEL site at all, and there is no true item to swap in.
Taking the card's stated alternative, the item is dropped rather than replaced.
Adding an AI entry to a bullet headed "Any CEL expression" would have replaced one
false claim with another.

Item 3 — three routing bullets (:94-110)

Added verbatim, in the existing bullets' shape:

- **Labels vs. bundles** — a `label` you want translated is **objectstack-i18n**.
- **Upgrade vs. platform** — a protocol-major move is **objectstack-upgrade**.
- **Rendering this metadata** — the consuming UI is
[objectui `skills/objectui/`](https://github.com/objectstack-ai/objectui).
BulletCostVerified against
Labels vs. bundles (#14566, I18N-A-03)21 tok / 84 Bskills/objectstack-i18n/SKILL.md frontmatter description — "Author ObjectStack translation bundles — object/field labels, view text …"
Upgrade vs. platform (UPG-A-01)21 tok / 78 Bskills/objectstack-upgrade/SKILL.md frontmatter description — "Use when a project is on an older protocol major and must move to the current one, when @objectstack/spec was bumped across a major …"
Rendering this metadata (OUI-A-01)33 tok / 134 BPath confirmed present on the objectui checkout: git ls-tree origin/main skills/objectui/ at objectui origin/main39af82f returns SKILL.md, README.md, evals/, guides/, rules/
Total75 tok / 296 Bat the #14566 triage's ruling (comment 5514978050): one bullet, ~25 tokens

Each routing claim was checked against the target skill's own frontmatter
description, so the bullet sends the reader where the skill says it triggers.

The payment

Item 3's 75 tokens are the funded addition. Items 1 and 2 are funded from each
other
and net to exactly zero:

  • Item 2's drop of , AI tool params frees 16 B / 4 tok.
  • Item 1's truthful replacement costs 16 B / 4 tok.
  • Items 1 + 2 combined: 0 B, 0 tok — neutral, as the budget requires.

One further payment is inside item 3 itself, and it is what brought the block from
84 tokens to the ruling's 75. Bullet 2's body does not restate "not
objectstack-platform"; the surviving site is the bullet's own heading,
"Upgrade vs. platform", which already names both sides of the decision. That is
a strict restatement removed, not meaning shaved — the routing information (which
skill wins) is intact.

No further payment was available. The one candidate examined was the
process-skill paragraph at :64-66 against the frontmatter rows at :75
(compatibility — "for a process skill that binds to no schema") and :76
(metadata.domain — "a delivery-process skill that teaches no schema"). Rejected:
the paragraph also carries two facts that survive nowhere else — that such a skill
"carries SKILL.md alone" (no references/ directory) and the SKILL_MAP
mechanism behind it. Deleting it would have shaved meaning, so it was left alone.

File token table

ceil(utf8_bytes / 4), whole file:

BytesTokens
Before (89a156af88)79411986
After (39da552e61)82372060
Delta+296+74

The entire delta is item 3's funded block; items 1–2 contribute zero.
skills/README.md is outside the token-ratchet population
(scripts/check-skills-token-ratchet.mjs:133), and the ratchet is green regardless.

Edit-landed-on-disk proof, grep counts before → after:

TermBeforeAfter
M9.510
AI tool params10
objectstack-i18n12
objectstack-upgrade12
objectui12

Gates — all at head 39da552e61

Every exit code captured by redirect before any pipe; each row quotes the gate's
own verdict line.

GateExitVerdict
pnpm --filter @objectstack/spec check:skill-docs0✅ Skill docs in sync (✓ skills/README.md)
node scripts/check-skills-token-ratchet.mjs0✓ 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
node scripts/check-skills-token-ratchet.mjs --self-test0✓ check-skills-token-ratchet self-test: 64 cases pass.
pnpm check:role-word0check-role-word: OK, no new occurrences of the reserved word. — 224 files, ledger unchanged at 43 baselined files / 120 occurrences
pnpm check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 46 published file(s) …; Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
pnpm check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
pnpm check:published-readme-links0✓ 176 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 via redirect), 1 anchor(s) verified, 103/103 relative target(s) found in the tree. — the new objectui link resolves
node scripts/check-nul-bytes.mjs0OK (scanned 8064 text file(s) … no raw ASCII control bytes)

Re-derived union

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands,
re-derived after the last edit. Provenance line confirms it read the right tree:
"gate list derived from the tree of 'objectstack-ai/objectstack' at commit
39da552e61", change set "1 path(s) vs merge base 89a156af8" = skills/README.md.
All 16 commands run:

GateExit
node scripts/check-ci-filter-parity.mjs0
node scripts/check-cross-package-test-inputs.mjs0
node scripts/check-shard-attestation.mjs0
node scripts/check-test-completeness.mjs3 — NOT MEASURED
pnpm --filter @objectstack/lint run check:doc-formula-expressions0
pnpm --filter @objectstack/spec run check:skill-docs0
pnpm check:agent-test-spelling0
pnpm check:corpus-claim-drift0
pnpm check:cross-package-test-inputs0
pnpm check:doc-authoring0
pnpm check:merge-driver0
pnpm check:pm-governed-merges0
pnpm check:role-word0
pnpm check:skill-compatibility0
pnpm check:skill-frame-sync0
pnpm check:skill-identifier-liveness0

Two notes on that table:

  • check-test-completeness exits 3 = PREREQUISITE NOT MET, not red: it "grades a
    saved turbo run test log, and no log was named … the local reading for this gate
    is NOT MEASURED." CI tees the log and passes the path, so CI is unaffected.
  • check:doc-formula-expressionsalso exited 3 on the first pass (it imports the
    compiled @objectstack/formula and @objectstack/lint). Rather than report a
    non-reading, both packages were built and the gate re-run — it is the gate nearest
    this change, since item 1 introduces previous. / record. into published prose.
    It then measured green:
    ✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula.

Reverse-verification

check:skill-docs can fail, and it names this file. Guarded by
trap restore EXIT INT TERM with absolute paths throughout; the implementation was
committed first, so the restore leg points at a HEAD that already contains it.

  • Mutation landed on disk — not inferred from an editor's exit code, but from grep
    counts flipping in both directions inside the generated region
    (Construct ObjectQL queries 1 → 0, Construct ObjectQL queried 0 → 1) and the
    worktree blob moving 72afcf31b7…e60bea9ecc….
  • Mutated legexit 1, verdict
    ✗ skills/README.md is out of date — run pnpm --filter @objectstack/spec gen:skill-docs.
    The gate is live and it names skills/README.md.
  • Restore leggit checkout HEAD -- /home/user/…/skills/README.md (pinned to
    HEAD, never a bare checkout, so it cannot restore from a polluted index). Proved
    by blob hash equality against the HEAD blob (72afcf31b7…, non-empty and
    matching), an empty git diff HEAD, and the anchor counts returning to 1 / 0.

ESLint

Not a narrowing — the file is outside the linted population entirely, established
with all three pieces:

  1. Population, read from eslint's own config resolution (not from my reading of
    the globs): eslint --print-config skills/README.md emits an empty document
    (10 bytes). Every files: selector in eslint.config.mjs is
    {ts,tsx,mts,cts,js,jsx,mjs,cjs}; no selector matches markdown.
  2. Count from --format json for the file: 1 result entry, errorCount: 0,
    warningCount: 1, and the single message is eslint's own
    File ignored because no matching configuration was supplied. — zero rule
    findings, because zero rules apply.
  3. Invariance for untouched files: the repo runs one eslint.config.mjs which
    "never enables type-aware linting (no parserOptions.project, no typed
    @typescript-eslint rules) for ANY file, test or not"
    (eslint.config.mjs:326-328, carrying its own positive-control measurement).
    No untouched file's verdict can move as a function of this diff.

A full-repo pnpm lint is therefore provably vacuous with respect to this change.

Changeset

skip-changeset, applied. This PR releases nothing: the diff is one file under
skills/, and scripts/check-empty-changeset.mjs:359-362 enumerates exactly that
case — "It releases nothing (.github/, .claude/, skills/, docs/,
content/, examples/, tests-only, and the like) -> delete the changeset and apply
the 'skip-changeset' label (route 2)." The script pins this shape as GREEN 3, "a
skills/**-only PR carrying NO changeset (route 2)" (:559-565). No package
package.json, src/, or version is touched.

Scope

Untouched, as claimed: every SKILL.md; the README's generated region between the
BEGIN/END GENERATED markers (check:skill-docs green proves it byte-for-byte); the
anatomy tree at :50-62, so the hunks are disjoint from PR #14786's :55 — this
diff's two hunks start at :85 and :104. No other README sentence was edited.

Draft, and it stays draft — governed skills/**, human merge is the review record.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…in skills/README.md
Settle `skills/README.md`'s CEL conventions and cross-skill routing list
against source.
1. `:87` — "Legacy `OLD` / `NEW` evaluate to `null` since M9.5" was false in
both halves. `M9.5` is anchored nowhere in the repo, and `OLD` / `NEW` are
not values that evaluate to `null`: they are undeclared identifiers.
`SCOPE_ROOTS` (packages/formula/src/cel-engine.ts:95) is the published
closed set of record-scope roots and contains neither, so
`firstUndeclaredReference` (:186) reports them as bare undeclared
references — the class `@objectstack/lint` raises as an error, not a
silent null. Replaced with the true clause plus the migration objectstack-
formula's own table prescribes (`OLD.x` -> `previous.x`, `NEW.x` ->
`record.x`).
2. `:105` — "AI tool params" is not a CEL site. `packages/spec/src/ai/tool.zod.ts`
carries no expression field of any kind, and the AI domain's only expression
site is `model-registry.zod.ts:121-122`, which is `TemplateExpressionInputSchema`
— the `template` dialect (`{{var}}` interpolation), not CEL. Dropped the item;
no true replacement exists, because the AI domain has no CEL predicate.
3. `:94-110` — three routing bullets added for measured load-failure modes:
labels to objectstack-i18n, protocol-major moves to objectstack-upgrade,
and the consuming UI to the objectui skills catalog.
Items 1-2 net exactly zero bytes; item 3 costs 75 tokens, at the size ruling.
The generated region and the anatomy tree are untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

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

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

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

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets - #14795

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing
Sep 3, 2026
Merged

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets#14795
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14794
Fixes#14566

Three edits to skills/README.md, and nothing else. Branch point re-measured at
origin/main89a156af88 — identical to the card's measurement, no drift.
Head sha for every gate reading below: 39da552e61.

Item 1 — :87, the OLD / NEW clause

Before (verbatim):

@objectstack/spec. Legacy OLD / NEW evaluate to null since M9.5.

After (verbatim):

@objectstack/spec. Legacy OLD / NEW are not CEL — use
previous. / record..

What settles it. The sentence was false in both halves.

  • M9.5 is anchored nowhere: git grep -rn 'M9\.5' -- packages skills docs returns
    exactly one hit, this README line itself. Nothing defines the milestone it cites.
  • OLD / NEW are not values that evaluate to null — they are undeclared
    identifiers
    . SCOPE_ROOTS (packages/formula/src/cel-engine.ts:95-119) is the
    published, closed set of roots a record-scoped CEL site may reference; it contains
    neither. firstUndeclaredReference (packages/formula/src/cel-engine.ts:186-205)
    therefore reports them as bare undeclared references — the class
    @objectstack/lint raises as an error, not a silent null
    (packages/lint/src/validate-expressions.ts:490, "Why it is an error and not a
    warning").

Measured directly against the built package rather than inferred:

SCOPE_ROOTS includes OLD: false | NEW: false | previous: true | record: true
"OLD.amount != NEW.amount" -> firstUndeclaredReference = "OLD"
"OLD" -> firstUndeclaredReference = "OLD"
"NEW.status" -> firstUndeclaredReference = "NEW"
"previous.amount != record.amount" -> firstUndeclaredReference = null

The replacement's prescription is objectstack-formula's own migration table, not a
new claim: skills/objectstack-formula/SKILL.md:345-346 maps OLD.x to
previous.x and NEW.x to record.x. The two published files now agree.

Item 2 — :105, "AI tool params" in the "Any CEL expression" bullet

Before (verbatim):

skill (data validations, automation guards, UI visibility, AI tool params).

After (verbatim):

skill (data validations, automation guards, UI visibility).

What settles it — and a correction to the card. The card's diagnosis is
confirmed and its prescribed replacement is false.

  • Confirmed: packages/spec/src/ai/tool.zod.ts matches cel|formula|expression
    zero times. Tool params carry no expression field of any kind.
  • Falsified: the card asked to substitute "a model-registry predicate, verified at
    packages/spec/src/ai/model-registry.zod.ts". There is no such field —
    predicate occurs 0 times in that file. Its only expression-typed keys are
    system / user at :121-122, and those are TemplateExpressionInputSchema,
    which packages/spec/src/shared/expression.zod.ts:117-124 defines as
    dialect: 'template'{{var}} interpolation, not CEL.
  • skills/objectstack-ai/SKILL.md:405-407 already says exactly this: the domain's
    expression site is "a model-registry promptTemplate.system / .user;
    ToolSchema carries no expression field of any kind."

So the AI domain has no CEL site at all, and there is no true item to swap in.
Taking the card's stated alternative, the item is dropped rather than replaced.
Adding an AI entry to a bullet headed "Any CEL expression" would have replaced one
false claim with another.

Item 3 — three routing bullets (:94-110)

Added verbatim, in the existing bullets' shape:

- **Labels vs. bundles** — a `label` you want translated is **objectstack-i18n**.
- **Upgrade vs. platform** — a protocol-major move is **objectstack-upgrade**.
- **Rendering this metadata** — the consuming UI is
[objectui `skills/objectui/`](https://github.com/objectstack-ai/objectui).
BulletCostVerified against
Labels vs. bundles (#14566, I18N-A-03)21 tok / 84 Bskills/objectstack-i18n/SKILL.md frontmatter description — "Author ObjectStack translation bundles — object/field labels, view text …"
Upgrade vs. platform (UPG-A-01)21 tok / 78 Bskills/objectstack-upgrade/SKILL.md frontmatter description — "Use when a project is on an older protocol major and must move to the current one, when @objectstack/spec was bumped across a major …"
Rendering this metadata (OUI-A-01)33 tok / 134 BPath confirmed present on the objectui checkout: git ls-tree origin/main skills/objectui/ at objectui origin/main39af82f returns SKILL.md, README.md, evals/, guides/, rules/
Total75 tok / 296 Bat the #14566 triage's ruling (comment 5514978050): one bullet, ~25 tokens

Each routing claim was checked against the target skill's own frontmatter
description, so the bullet sends the reader where the skill says it triggers.

The payment

Item 3's 75 tokens are the funded addition. Items 1 and 2 are funded from each
other
and net to exactly zero:

  • Item 2's drop of , AI tool params frees 16 B / 4 tok.
  • Item 1's truthful replacement costs 16 B / 4 tok.
  • Items 1 + 2 combined: 0 B, 0 tok — neutral, as the budget requires.

One further payment is inside item 3 itself, and it is what brought the block from
84 tokens to the ruling's 75. Bullet 2's body does not restate "not
objectstack-platform"; the surviving site is the bullet's own heading,
"Upgrade vs. platform", which already names both sides of the decision. That is
a strict restatement removed, not meaning shaved — the routing information (which
skill wins) is intact.

No further payment was available. The one candidate examined was the
process-skill paragraph at :64-66 against the frontmatter rows at :75
(compatibility — "for a process skill that binds to no schema") and :76
(metadata.domain — "a delivery-process skill that teaches no schema"). Rejected:
the paragraph also carries two facts that survive nowhere else — that such a skill
"carries SKILL.md alone" (no references/ directory) and the SKILL_MAP
mechanism behind it. Deleting it would have shaved meaning, so it was left alone.

File token table

ceil(utf8_bytes / 4), whole file:

BytesTokens
Before (89a156af88)79411986
After (39da552e61)82372060
Delta+296+74

The entire delta is item 3's funded block; items 1–2 contribute zero.
skills/README.md is outside the token-ratchet population
(scripts/check-skills-token-ratchet.mjs:133), and the ratchet is green regardless.

Edit-landed-on-disk proof, grep counts before → after:

TermBeforeAfter
M9.510
AI tool params10
objectstack-i18n12
objectstack-upgrade12
objectui12

Gates — all at head 39da552e61

Every exit code captured by redirect before any pipe; each row quotes the gate's
own verdict line.

GateExitVerdict
pnpm --filter @objectstack/spec check:skill-docs0✅ Skill docs in sync (✓ skills/README.md)
node scripts/check-skills-token-ratchet.mjs0✓ 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
node scripts/check-skills-token-ratchet.mjs --self-test0✓ check-skills-token-ratchet self-test: 64 cases pass.
pnpm check:role-word0check-role-word: OK, no new occurrences of the reserved word. — 224 files, ledger unchanged at 43 baselined files / 120 occurrences
pnpm check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 46 published file(s) …; Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
pnpm check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
pnpm check:published-readme-links0✓ 176 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 via redirect), 1 anchor(s) verified, 103/103 relative target(s) found in the tree. — the new objectui link resolves
node scripts/check-nul-bytes.mjs0OK (scanned 8064 text file(s) … no raw ASCII control bytes)

Re-derived union

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands,
re-derived after the last edit. Provenance line confirms it read the right tree:
"gate list derived from the tree of 'objectstack-ai/objectstack' at commit
39da552e61", change set "1 path(s) vs merge base 89a156af8" = skills/README.md.
All 16 commands run:

GateExit
node scripts/check-ci-filter-parity.mjs0
node scripts/check-cross-package-test-inputs.mjs0
node scripts/check-shard-attestation.mjs0
node scripts/check-test-completeness.mjs3 — NOT MEASURED
pnpm --filter @objectstack/lint run check:doc-formula-expressions0
pnpm --filter @objectstack/spec run check:skill-docs0
pnpm check:agent-test-spelling0
pnpm check:corpus-claim-drift0
pnpm check:cross-package-test-inputs0
pnpm check:doc-authoring0
pnpm check:merge-driver0
pnpm check:pm-governed-merges0
pnpm check:role-word0
pnpm check:skill-compatibility0
pnpm check:skill-frame-sync0
pnpm check:skill-identifier-liveness0

Two notes on that table:

  • check-test-completeness exits 3 = PREREQUISITE NOT MET, not red: it "grades a
    saved turbo run test log, and no log was named … the local reading for this gate
    is NOT MEASURED." CI tees the log and passes the path, so CI is unaffected.
  • check:doc-formula-expressionsalso exited 3 on the first pass (it imports the
    compiled @objectstack/formula and @objectstack/lint). Rather than report a
    non-reading, both packages were built and the gate re-run — it is the gate nearest
    this change, since item 1 introduces previous. / record. into published prose.
    It then measured green:
    ✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula.

Reverse-verification

check:skill-docs can fail, and it names this file. Guarded by
trap restore EXIT INT TERM with absolute paths throughout; the implementation was
committed first, so the restore leg points at a HEAD that already contains it.

  • Mutation landed on disk — not inferred from an editor's exit code, but from grep
    counts flipping in both directions inside the generated region
    (Construct ObjectQL queries 1 → 0, Construct ObjectQL queried 0 → 1) and the
    worktree blob moving 72afcf31b7…e60bea9ecc….
  • Mutated legexit 1, verdict
    ✗ skills/README.md is out of date — run pnpm --filter @objectstack/spec gen:skill-docs.
    The gate is live and it names skills/README.md.
  • Restore leggit checkout HEAD -- /home/user/…/skills/README.md (pinned to
    HEAD, never a bare checkout, so it cannot restore from a polluted index). Proved
    by blob hash equality against the HEAD blob (72afcf31b7…, non-empty and
    matching), an empty git diff HEAD, and the anchor counts returning to 1 / 0.

ESLint

Not a narrowing — the file is outside the linted population entirely, established
with all three pieces:

  1. Population, read from eslint's own config resolution (not from my reading of
    the globs): eslint --print-config skills/README.md emits an empty document
    (10 bytes). Every files: selector in eslint.config.mjs is
    {ts,tsx,mts,cts,js,jsx,mjs,cjs}; no selector matches markdown.
  2. Count from --format json for the file: 1 result entry, errorCount: 0,
    warningCount: 1, and the single message is eslint's own
    File ignored because no matching configuration was supplied. — zero rule
    findings, because zero rules apply.
  3. Invariance for untouched files: the repo runs one eslint.config.mjs which
    "never enables type-aware linting (no parserOptions.project, no typed
    @typescript-eslint rules) for ANY file, test or not"
    (eslint.config.mjs:326-328, carrying its own positive-control measurement).
    No untouched file's verdict can move as a function of this diff.

A full-repo pnpm lint is therefore provably vacuous with respect to this change.

Changeset

skip-changeset, applied. This PR releases nothing: the diff is one file under
skills/, and scripts/check-empty-changeset.mjs:359-362 enumerates exactly that
case — "It releases nothing (.github/, .claude/, skills/, docs/,
content/, examples/, tests-only, and the like) -> delete the changeset and apply
the 'skip-changeset' label (route 2)." The script pins this shape as GREEN 3, "a
skills/**-only PR carrying NO changeset (route 2)" (:559-565). No package
package.json, src/, or version is touched.

Scope

Untouched, as claimed: every SKILL.md; the README's generated region between the
BEGIN/END GENERATED markers (check:skill-docs green proves it byte-for-byte); the
anatomy tree at :50-62, so the hunks are disjoint from PR #14786's :55 — this
diff's two hunks start at :85 and :104. No other README sentence was edited.

Draft, and it stays draft — governed skills/**, human merge is the review record.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…in skills/README.md
Settle `skills/README.md`'s CEL conventions and cross-skill routing list
against source.
1. `:87` — "Legacy `OLD` / `NEW` evaluate to `null` since M9.5" was false in
both halves. `M9.5` is anchored nowhere in the repo, and `OLD` / `NEW` are
not values that evaluate to `null`: they are undeclared identifiers.
`SCOPE_ROOTS` (packages/formula/src/cel-engine.ts:95) is the published
closed set of record-scope roots and contains neither, so
`firstUndeclaredReference` (:186) reports them as bare undeclared
references — the class `@objectstack/lint` raises as an error, not a
silent null. Replaced with the true clause plus the migration objectstack-
formula's own table prescribes (`OLD.x` -> `previous.x`, `NEW.x` ->
`record.x`).
2. `:105` — "AI tool params" is not a CEL site. `packages/spec/src/ai/tool.zod.ts`
carries no expression field of any kind, and the AI domain's only expression
site is `model-registry.zod.ts:121-122`, which is `TemplateExpressionInputSchema`
— the `template` dialect (`{{var}}` interpolation), not CEL. Dropped the item;
no true replacement exists, because the AI domain has no CEL predicate.
3. `:94-110` — three routing bullets added for measured load-failure modes:
labels to objectstack-i18n, protocol-major moves to objectstack-upgrade,
and the consuming UI to the objectui skills catalog.
Items 1-2 net exactly zero bytes; item 3 costs 75 tokens, at the size ruling.
The generated region and the anatomy tree are untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

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

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

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

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets - #14795

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing
Sep 3, 2026
Merged

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets#14795
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14794
Fixes#14566

Three edits to skills/README.md, and nothing else. Branch point re-measured at
origin/main89a156af88 — identical to the card's measurement, no drift.
Head sha for every gate reading below: 39da552e61.

Item 1 — :87, the OLD / NEW clause

Before (verbatim):

@objectstack/spec. Legacy OLD / NEW evaluate to null since M9.5.

After (verbatim):

@objectstack/spec. Legacy OLD / NEW are not CEL — use
previous. / record..

What settles it. The sentence was false in both halves.

  • M9.5 is anchored nowhere: git grep -rn 'M9\.5' -- packages skills docs returns
    exactly one hit, this README line itself. Nothing defines the milestone it cites.
  • OLD / NEW are not values that evaluate to null — they are undeclared
    identifiers
    . SCOPE_ROOTS (packages/formula/src/cel-engine.ts:95-119) is the
    published, closed set of roots a record-scoped CEL site may reference; it contains
    neither. firstUndeclaredReference (packages/formula/src/cel-engine.ts:186-205)
    therefore reports them as bare undeclared references — the class
    @objectstack/lint raises as an error, not a silent null
    (packages/lint/src/validate-expressions.ts:490, "Why it is an error and not a
    warning").

Measured directly against the built package rather than inferred:

SCOPE_ROOTS includes OLD: false | NEW: false | previous: true | record: true
"OLD.amount != NEW.amount" -> firstUndeclaredReference = "OLD"
"OLD" -> firstUndeclaredReference = "OLD"
"NEW.status" -> firstUndeclaredReference = "NEW"
"previous.amount != record.amount" -> firstUndeclaredReference = null

The replacement's prescription is objectstack-formula's own migration table, not a
new claim: skills/objectstack-formula/SKILL.md:345-346 maps OLD.x to
previous.x and NEW.x to record.x. The two published files now agree.

Item 2 — :105, "AI tool params" in the "Any CEL expression" bullet

Before (verbatim):

skill (data validations, automation guards, UI visibility, AI tool params).

After (verbatim):

skill (data validations, automation guards, UI visibility).

What settles it — and a correction to the card. The card's diagnosis is
confirmed and its prescribed replacement is false.

  • Confirmed: packages/spec/src/ai/tool.zod.ts matches cel|formula|expression
    zero times. Tool params carry no expression field of any kind.
  • Falsified: the card asked to substitute "a model-registry predicate, verified at
    packages/spec/src/ai/model-registry.zod.ts". There is no such field —
    predicate occurs 0 times in that file. Its only expression-typed keys are
    system / user at :121-122, and those are TemplateExpressionInputSchema,
    which packages/spec/src/shared/expression.zod.ts:117-124 defines as
    dialect: 'template'{{var}} interpolation, not CEL.
  • skills/objectstack-ai/SKILL.md:405-407 already says exactly this: the domain's
    expression site is "a model-registry promptTemplate.system / .user;
    ToolSchema carries no expression field of any kind."

So the AI domain has no CEL site at all, and there is no true item to swap in.
Taking the card's stated alternative, the item is dropped rather than replaced.
Adding an AI entry to a bullet headed "Any CEL expression" would have replaced one
false claim with another.

Item 3 — three routing bullets (:94-110)

Added verbatim, in the existing bullets' shape:

- **Labels vs. bundles** — a `label` you want translated is **objectstack-i18n**.
- **Upgrade vs. platform** — a protocol-major move is **objectstack-upgrade**.
- **Rendering this metadata** — the consuming UI is
[objectui `skills/objectui/`](https://github.com/objectstack-ai/objectui).
BulletCostVerified against
Labels vs. bundles (#14566, I18N-A-03)21 tok / 84 Bskills/objectstack-i18n/SKILL.md frontmatter description — "Author ObjectStack translation bundles — object/field labels, view text …"
Upgrade vs. platform (UPG-A-01)21 tok / 78 Bskills/objectstack-upgrade/SKILL.md frontmatter description — "Use when a project is on an older protocol major and must move to the current one, when @objectstack/spec was bumped across a major …"
Rendering this metadata (OUI-A-01)33 tok / 134 BPath confirmed present on the objectui checkout: git ls-tree origin/main skills/objectui/ at objectui origin/main39af82f returns SKILL.md, README.md, evals/, guides/, rules/
Total75 tok / 296 Bat the #14566 triage's ruling (comment 5514978050): one bullet, ~25 tokens

Each routing claim was checked against the target skill's own frontmatter
description, so the bullet sends the reader where the skill says it triggers.

The payment

Item 3's 75 tokens are the funded addition. Items 1 and 2 are funded from each
other
and net to exactly zero:

  • Item 2's drop of , AI tool params frees 16 B / 4 tok.
  • Item 1's truthful replacement costs 16 B / 4 tok.
  • Items 1 + 2 combined: 0 B, 0 tok — neutral, as the budget requires.

One further payment is inside item 3 itself, and it is what brought the block from
84 tokens to the ruling's 75. Bullet 2's body does not restate "not
objectstack-platform"; the surviving site is the bullet's own heading,
"Upgrade vs. platform", which already names both sides of the decision. That is
a strict restatement removed, not meaning shaved — the routing information (which
skill wins) is intact.

No further payment was available. The one candidate examined was the
process-skill paragraph at :64-66 against the frontmatter rows at :75
(compatibility — "for a process skill that binds to no schema") and :76
(metadata.domain — "a delivery-process skill that teaches no schema"). Rejected:
the paragraph also carries two facts that survive nowhere else — that such a skill
"carries SKILL.md alone" (no references/ directory) and the SKILL_MAP
mechanism behind it. Deleting it would have shaved meaning, so it was left alone.

File token table

ceil(utf8_bytes / 4), whole file:

BytesTokens
Before (89a156af88)79411986
After (39da552e61)82372060
Delta+296+74

The entire delta is item 3's funded block; items 1–2 contribute zero.
skills/README.md is outside the token-ratchet population
(scripts/check-skills-token-ratchet.mjs:133), and the ratchet is green regardless.

Edit-landed-on-disk proof, grep counts before → after:

TermBeforeAfter
M9.510
AI tool params10
objectstack-i18n12
objectstack-upgrade12
objectui12

Gates — all at head 39da552e61

Every exit code captured by redirect before any pipe; each row quotes the gate's
own verdict line.

GateExitVerdict
pnpm --filter @objectstack/spec check:skill-docs0✅ Skill docs in sync (✓ skills/README.md)
node scripts/check-skills-token-ratchet.mjs0✓ 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
node scripts/check-skills-token-ratchet.mjs --self-test0✓ check-skills-token-ratchet self-test: 64 cases pass.
pnpm check:role-word0check-role-word: OK, no new occurrences of the reserved word. — 224 files, ledger unchanged at 43 baselined files / 120 occurrences
pnpm check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 46 published file(s) …; Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
pnpm check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
pnpm check:published-readme-links0✓ 176 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 via redirect), 1 anchor(s) verified, 103/103 relative target(s) found in the tree. — the new objectui link resolves
node scripts/check-nul-bytes.mjs0OK (scanned 8064 text file(s) … no raw ASCII control bytes)

Re-derived union

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands,
re-derived after the last edit. Provenance line confirms it read the right tree:
"gate list derived from the tree of 'objectstack-ai/objectstack' at commit
39da552e61", change set "1 path(s) vs merge base 89a156af8" = skills/README.md.
All 16 commands run:

GateExit
node scripts/check-ci-filter-parity.mjs0
node scripts/check-cross-package-test-inputs.mjs0
node scripts/check-shard-attestation.mjs0
node scripts/check-test-completeness.mjs3 — NOT MEASURED
pnpm --filter @objectstack/lint run check:doc-formula-expressions0
pnpm --filter @objectstack/spec run check:skill-docs0
pnpm check:agent-test-spelling0
pnpm check:corpus-claim-drift0
pnpm check:cross-package-test-inputs0
pnpm check:doc-authoring0
pnpm check:merge-driver0
pnpm check:pm-governed-merges0
pnpm check:role-word0
pnpm check:skill-compatibility0
pnpm check:skill-frame-sync0
pnpm check:skill-identifier-liveness0

Two notes on that table:

  • check-test-completeness exits 3 = PREREQUISITE NOT MET, not red: it "grades a
    saved turbo run test log, and no log was named … the local reading for this gate
    is NOT MEASURED." CI tees the log and passes the path, so CI is unaffected.
  • check:doc-formula-expressionsalso exited 3 on the first pass (it imports the
    compiled @objectstack/formula and @objectstack/lint). Rather than report a
    non-reading, both packages were built and the gate re-run — it is the gate nearest
    this change, since item 1 introduces previous. / record. into published prose.
    It then measured green:
    ✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula.

Reverse-verification

check:skill-docs can fail, and it names this file. Guarded by
trap restore EXIT INT TERM with absolute paths throughout; the implementation was
committed first, so the restore leg points at a HEAD that already contains it.

  • Mutation landed on disk — not inferred from an editor's exit code, but from grep
    counts flipping in both directions inside the generated region
    (Construct ObjectQL queries 1 → 0, Construct ObjectQL queried 0 → 1) and the
    worktree blob moving 72afcf31b7…e60bea9ecc….
  • Mutated legexit 1, verdict
    ✗ skills/README.md is out of date — run pnpm --filter @objectstack/spec gen:skill-docs.
    The gate is live and it names skills/README.md.
  • Restore leggit checkout HEAD -- /home/user/…/skills/README.md (pinned to
    HEAD, never a bare checkout, so it cannot restore from a polluted index). Proved
    by blob hash equality against the HEAD blob (72afcf31b7…, non-empty and
    matching), an empty git diff HEAD, and the anchor counts returning to 1 / 0.

ESLint

Not a narrowing — the file is outside the linted population entirely, established
with all three pieces:

  1. Population, read from eslint's own config resolution (not from my reading of
    the globs): eslint --print-config skills/README.md emits an empty document
    (10 bytes). Every files: selector in eslint.config.mjs is
    {ts,tsx,mts,cts,js,jsx,mjs,cjs}; no selector matches markdown.
  2. Count from --format json for the file: 1 result entry, errorCount: 0,
    warningCount: 1, and the single message is eslint's own
    File ignored because no matching configuration was supplied. — zero rule
    findings, because zero rules apply.
  3. Invariance for untouched files: the repo runs one eslint.config.mjs which
    "never enables type-aware linting (no parserOptions.project, no typed
    @typescript-eslint rules) for ANY file, test or not"
    (eslint.config.mjs:326-328, carrying its own positive-control measurement).
    No untouched file's verdict can move as a function of this diff.

A full-repo pnpm lint is therefore provably vacuous with respect to this change.

Changeset

skip-changeset, applied. This PR releases nothing: the diff is one file under
skills/, and scripts/check-empty-changeset.mjs:359-362 enumerates exactly that
case — "It releases nothing (.github/, .claude/, skills/, docs/,
content/, examples/, tests-only, and the like) -> delete the changeset and apply
the 'skip-changeset' label (route 2)." The script pins this shape as GREEN 3, "a
skills/**-only PR carrying NO changeset (route 2)" (:559-565). No package
package.json, src/, or version is touched.

Scope

Untouched, as claimed: every SKILL.md; the README's generated region between the
BEGIN/END GENERATED markers (check:skill-docs green proves it byte-for-byte); the
anatomy tree at :50-62, so the hunks are disjoint from PR #14786's :55 — this
diff's two hunks start at :85 and :104. No other README sentence was edited.

Draft, and it stays draft — governed skills/**, human merge is the review record.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…in skills/README.md
Settle `skills/README.md`'s CEL conventions and cross-skill routing list
against source.
1. `:87` — "Legacy `OLD` / `NEW` evaluate to `null` since M9.5" was false in
both halves. `M9.5` is anchored nowhere in the repo, and `OLD` / `NEW` are
not values that evaluate to `null`: they are undeclared identifiers.
`SCOPE_ROOTS` (packages/formula/src/cel-engine.ts:95) is the published
closed set of record-scope roots and contains neither, so
`firstUndeclaredReference` (:186) reports them as bare undeclared
references — the class `@objectstack/lint` raises as an error, not a
silent null. Replaced with the true clause plus the migration objectstack-
formula's own table prescribes (`OLD.x` -> `previous.x`, `NEW.x` ->
`record.x`).
2. `:105` — "AI tool params" is not a CEL site. `packages/spec/src/ai/tool.zod.ts`
carries no expression field of any kind, and the AI domain's only expression
site is `model-registry.zod.ts:121-122`, which is `TemplateExpressionInputSchema`
— the `template` dialect (`{{var}}` interpolation), not CEL. Dropped the item;
no true replacement exists, because the AI domain has no CEL predicate.
3. `:94-110` — three routing bullets added for measured load-failure modes:
labels to objectstack-i18n, protocol-major moves to objectstack-upgrade,
and the consuming UI to the objectui skills catalog.
Items 1-2 net exactly zero bytes; item 3 costs 75 tokens, at the size ruling.
The generated region and the anatomy tree are untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

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

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

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

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets - #14795

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing
Sep 3, 2026
Merged

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets#14795
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14794
Fixes#14566

Three edits to skills/README.md, and nothing else. Branch point re-measured at
origin/main89a156af88 — identical to the card's measurement, no drift.
Head sha for every gate reading below: 39da552e61.

Item 1 — :87, the OLD / NEW clause

Before (verbatim):

@objectstack/spec. Legacy OLD / NEW evaluate to null since M9.5.

After (verbatim):

@objectstack/spec. Legacy OLD / NEW are not CEL — use
previous. / record..

What settles it. The sentence was false in both halves.

  • M9.5 is anchored nowhere: git grep -rn 'M9\.5' -- packages skills docs returns
    exactly one hit, this README line itself. Nothing defines the milestone it cites.
  • OLD / NEW are not values that evaluate to null — they are undeclared
    identifiers
    . SCOPE_ROOTS (packages/formula/src/cel-engine.ts:95-119) is the
    published, closed set of roots a record-scoped CEL site may reference; it contains
    neither. firstUndeclaredReference (packages/formula/src/cel-engine.ts:186-205)
    therefore reports them as bare undeclared references — the class
    @objectstack/lint raises as an error, not a silent null
    (packages/lint/src/validate-expressions.ts:490, "Why it is an error and not a
    warning").

Measured directly against the built package rather than inferred:

SCOPE_ROOTS includes OLD: false | NEW: false | previous: true | record: true
"OLD.amount != NEW.amount" -> firstUndeclaredReference = "OLD"
"OLD" -> firstUndeclaredReference = "OLD"
"NEW.status" -> firstUndeclaredReference = "NEW"
"previous.amount != record.amount" -> firstUndeclaredReference = null

The replacement's prescription is objectstack-formula's own migration table, not a
new claim: skills/objectstack-formula/SKILL.md:345-346 maps OLD.x to
previous.x and NEW.x to record.x. The two published files now agree.

Item 2 — :105, "AI tool params" in the "Any CEL expression" bullet

Before (verbatim):

skill (data validations, automation guards, UI visibility, AI tool params).

After (verbatim):

skill (data validations, automation guards, UI visibility).

What settles it — and a correction to the card. The card's diagnosis is
confirmed and its prescribed replacement is false.

  • Confirmed: packages/spec/src/ai/tool.zod.ts matches cel|formula|expression
    zero times. Tool params carry no expression field of any kind.
  • Falsified: the card asked to substitute "a model-registry predicate, verified at
    packages/spec/src/ai/model-registry.zod.ts". There is no such field —
    predicate occurs 0 times in that file. Its only expression-typed keys are
    system / user at :121-122, and those are TemplateExpressionInputSchema,
    which packages/spec/src/shared/expression.zod.ts:117-124 defines as
    dialect: 'template'{{var}} interpolation, not CEL.
  • skills/objectstack-ai/SKILL.md:405-407 already says exactly this: the domain's
    expression site is "a model-registry promptTemplate.system / .user;
    ToolSchema carries no expression field of any kind."

So the AI domain has no CEL site at all, and there is no true item to swap in.
Taking the card's stated alternative, the item is dropped rather than replaced.
Adding an AI entry to a bullet headed "Any CEL expression" would have replaced one
false claim with another.

Item 3 — three routing bullets (:94-110)

Added verbatim, in the existing bullets' shape:

- **Labels vs. bundles** — a `label` you want translated is **objectstack-i18n**.
- **Upgrade vs. platform** — a protocol-major move is **objectstack-upgrade**.
- **Rendering this metadata** — the consuming UI is
[objectui `skills/objectui/`](https://github.com/objectstack-ai/objectui).
BulletCostVerified against
Labels vs. bundles (#14566, I18N-A-03)21 tok / 84 Bskills/objectstack-i18n/SKILL.md frontmatter description — "Author ObjectStack translation bundles — object/field labels, view text …"
Upgrade vs. platform (UPG-A-01)21 tok / 78 Bskills/objectstack-upgrade/SKILL.md frontmatter description — "Use when a project is on an older protocol major and must move to the current one, when @objectstack/spec was bumped across a major …"
Rendering this metadata (OUI-A-01)33 tok / 134 BPath confirmed present on the objectui checkout: git ls-tree origin/main skills/objectui/ at objectui origin/main39af82f returns SKILL.md, README.md, evals/, guides/, rules/
Total75 tok / 296 Bat the #14566 triage's ruling (comment 5514978050): one bullet, ~25 tokens

Each routing claim was checked against the target skill's own frontmatter
description, so the bullet sends the reader where the skill says it triggers.

The payment

Item 3's 75 tokens are the funded addition. Items 1 and 2 are funded from each
other
and net to exactly zero:

  • Item 2's drop of , AI tool params frees 16 B / 4 tok.
  • Item 1's truthful replacement costs 16 B / 4 tok.
  • Items 1 + 2 combined: 0 B, 0 tok — neutral, as the budget requires.

One further payment is inside item 3 itself, and it is what brought the block from
84 tokens to the ruling's 75. Bullet 2's body does not restate "not
objectstack-platform"; the surviving site is the bullet's own heading,
"Upgrade vs. platform", which already names both sides of the decision. That is
a strict restatement removed, not meaning shaved — the routing information (which
skill wins) is intact.

No further payment was available. The one candidate examined was the
process-skill paragraph at :64-66 against the frontmatter rows at :75
(compatibility — "for a process skill that binds to no schema") and :76
(metadata.domain — "a delivery-process skill that teaches no schema"). Rejected:
the paragraph also carries two facts that survive nowhere else — that such a skill
"carries SKILL.md alone" (no references/ directory) and the SKILL_MAP
mechanism behind it. Deleting it would have shaved meaning, so it was left alone.

File token table

ceil(utf8_bytes / 4), whole file:

BytesTokens
Before (89a156af88)79411986
After (39da552e61)82372060
Delta+296+74

The entire delta is item 3's funded block; items 1–2 contribute zero.
skills/README.md is outside the token-ratchet population
(scripts/check-skills-token-ratchet.mjs:133), and the ratchet is green regardless.

Edit-landed-on-disk proof, grep counts before → after:

TermBeforeAfter
M9.510
AI tool params10
objectstack-i18n12
objectstack-upgrade12
objectui12

Gates — all at head 39da552e61

Every exit code captured by redirect before any pipe; each row quotes the gate's
own verdict line.

GateExitVerdict
pnpm --filter @objectstack/spec check:skill-docs0✅ Skill docs in sync (✓ skills/README.md)
node scripts/check-skills-token-ratchet.mjs0✓ 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
node scripts/check-skills-token-ratchet.mjs --self-test0✓ check-skills-token-ratchet self-test: 64 cases pass.
pnpm check:role-word0check-role-word: OK, no new occurrences of the reserved word. — 224 files, ledger unchanged at 43 baselined files / 120 occurrences
pnpm check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 46 published file(s) …; Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
pnpm check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
pnpm check:published-readme-links0✓ 176 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 via redirect), 1 anchor(s) verified, 103/103 relative target(s) found in the tree. — the new objectui link resolves
node scripts/check-nul-bytes.mjs0OK (scanned 8064 text file(s) … no raw ASCII control bytes)

Re-derived union

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands,
re-derived after the last edit. Provenance line confirms it read the right tree:
"gate list derived from the tree of 'objectstack-ai/objectstack' at commit
39da552e61", change set "1 path(s) vs merge base 89a156af8" = skills/README.md.
All 16 commands run:

GateExit
node scripts/check-ci-filter-parity.mjs0
node scripts/check-cross-package-test-inputs.mjs0
node scripts/check-shard-attestation.mjs0
node scripts/check-test-completeness.mjs3 — NOT MEASURED
pnpm --filter @objectstack/lint run check:doc-formula-expressions0
pnpm --filter @objectstack/spec run check:skill-docs0
pnpm check:agent-test-spelling0
pnpm check:corpus-claim-drift0
pnpm check:cross-package-test-inputs0
pnpm check:doc-authoring0
pnpm check:merge-driver0
pnpm check:pm-governed-merges0
pnpm check:role-word0
pnpm check:skill-compatibility0
pnpm check:skill-frame-sync0
pnpm check:skill-identifier-liveness0

Two notes on that table:

  • check-test-completeness exits 3 = PREREQUISITE NOT MET, not red: it "grades a
    saved turbo run test log, and no log was named … the local reading for this gate
    is NOT MEASURED." CI tees the log and passes the path, so CI is unaffected.
  • check:doc-formula-expressionsalso exited 3 on the first pass (it imports the
    compiled @objectstack/formula and @objectstack/lint). Rather than report a
    non-reading, both packages were built and the gate re-run — it is the gate nearest
    this change, since item 1 introduces previous. / record. into published prose.
    It then measured green:
    ✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula.

Reverse-verification

check:skill-docs can fail, and it names this file. Guarded by
trap restore EXIT INT TERM with absolute paths throughout; the implementation was
committed first, so the restore leg points at a HEAD that already contains it.

  • Mutation landed on disk — not inferred from an editor's exit code, but from grep
    counts flipping in both directions inside the generated region
    (Construct ObjectQL queries 1 → 0, Construct ObjectQL queried 0 → 1) and the
    worktree blob moving 72afcf31b7…e60bea9ecc….
  • Mutated legexit 1, verdict
    ✗ skills/README.md is out of date — run pnpm --filter @objectstack/spec gen:skill-docs.
    The gate is live and it names skills/README.md.
  • Restore leggit checkout HEAD -- /home/user/…/skills/README.md (pinned to
    HEAD, never a bare checkout, so it cannot restore from a polluted index). Proved
    by blob hash equality against the HEAD blob (72afcf31b7…, non-empty and
    matching), an empty git diff HEAD, and the anchor counts returning to 1 / 0.

ESLint

Not a narrowing — the file is outside the linted population entirely, established
with all three pieces:

  1. Population, read from eslint's own config resolution (not from my reading of
    the globs): eslint --print-config skills/README.md emits an empty document
    (10 bytes). Every files: selector in eslint.config.mjs is
    {ts,tsx,mts,cts,js,jsx,mjs,cjs}; no selector matches markdown.
  2. Count from --format json for the file: 1 result entry, errorCount: 0,
    warningCount: 1, and the single message is eslint's own
    File ignored because no matching configuration was supplied. — zero rule
    findings, because zero rules apply.
  3. Invariance for untouched files: the repo runs one eslint.config.mjs which
    "never enables type-aware linting (no parserOptions.project, no typed
    @typescript-eslint rules) for ANY file, test or not"
    (eslint.config.mjs:326-328, carrying its own positive-control measurement).
    No untouched file's verdict can move as a function of this diff.

A full-repo pnpm lint is therefore provably vacuous with respect to this change.

Changeset

skip-changeset, applied. This PR releases nothing: the diff is one file under
skills/, and scripts/check-empty-changeset.mjs:359-362 enumerates exactly that
case — "It releases nothing (.github/, .claude/, skills/, docs/,
content/, examples/, tests-only, and the like) -> delete the changeset and apply
the 'skip-changeset' label (route 2)." The script pins this shape as GREEN 3, "a
skills/**-only PR carrying NO changeset (route 2)" (:559-565). No package
package.json, src/, or version is touched.

Scope

Untouched, as claimed: every SKILL.md; the README's generated region between the
BEGIN/END GENERATED markers (check:skill-docs green proves it byte-for-byte); the
anatomy tree at :50-62, so the hunks are disjoint from PR #14786's :55 — this
diff's two hunks start at :85 and :104. No other README sentence was edited.

Draft, and it stays draft — governed skills/**, human merge is the review record.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…in skills/README.md
Settle `skills/README.md`'s CEL conventions and cross-skill routing list
against source.
1. `:87` — "Legacy `OLD` / `NEW` evaluate to `null` since M9.5" was false in
both halves. `M9.5` is anchored nowhere in the repo, and `OLD` / `NEW` are
not values that evaluate to `null`: they are undeclared identifiers.
`SCOPE_ROOTS` (packages/formula/src/cel-engine.ts:95) is the published
closed set of record-scope roots and contains neither, so
`firstUndeclaredReference` (:186) reports them as bare undeclared
references — the class `@objectstack/lint` raises as an error, not a
silent null. Replaced with the true clause plus the migration objectstack-
formula's own table prescribes (`OLD.x` -> `previous.x`, `NEW.x` ->
`record.x`).
2. `:105` — "AI tool params" is not a CEL site. `packages/spec/src/ai/tool.zod.ts`
carries no expression field of any kind, and the AI domain's only expression
site is `model-registry.zod.ts:121-122`, which is `TemplateExpressionInputSchema`
— the `template` dialect (`{{var}}` interpolation), not CEL. Dropped the item;
no true replacement exists, because the AI domain has no CEL predicate.
3. `:94-110` — three routing bullets added for measured load-failure modes:
labels to objectstack-i18n, protocol-major moves to objectstack-upgrade,
and the consuming UI to the objectui skills catalog.
Items 1-2 net exactly zero bytes; item 3 costs 75 tokens, at the size ruling.
The generated region and the anatomy tree are untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

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

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

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

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets - #14795

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing
Sep 3, 2026
Merged

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets#14795
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14794
Fixes#14566

Three edits to skills/README.md, and nothing else. Branch point re-measured at
origin/main89a156af88 — identical to the card's measurement, no drift.
Head sha for every gate reading below: 39da552e61.

Item 1 — :87, the OLD / NEW clause

Before (verbatim):

@objectstack/spec. Legacy OLD / NEW evaluate to null since M9.5.

After (verbatim):

@objectstack/spec. Legacy OLD / NEW are not CEL — use
previous. / record..

What settles it. The sentence was false in both halves.

  • M9.5 is anchored nowhere: git grep -rn 'M9\.5' -- packages skills docs returns
    exactly one hit, this README line itself. Nothing defines the milestone it cites.
  • OLD / NEW are not values that evaluate to null — they are undeclared
    identifiers
    . SCOPE_ROOTS (packages/formula/src/cel-engine.ts:95-119) is the
    published, closed set of roots a record-scoped CEL site may reference; it contains
    neither. firstUndeclaredReference (packages/formula/src/cel-engine.ts:186-205)
    therefore reports them as bare undeclared references — the class
    @objectstack/lint raises as an error, not a silent null
    (packages/lint/src/validate-expressions.ts:490, "Why it is an error and not a
    warning").

Measured directly against the built package rather than inferred:

SCOPE_ROOTS includes OLD: false | NEW: false | previous: true | record: true
"OLD.amount != NEW.amount" -> firstUndeclaredReference = "OLD"
"OLD" -> firstUndeclaredReference = "OLD"
"NEW.status" -> firstUndeclaredReference = "NEW"
"previous.amount != record.amount" -> firstUndeclaredReference = null

The replacement's prescription is objectstack-formula's own migration table, not a
new claim: skills/objectstack-formula/SKILL.md:345-346 maps OLD.x to
previous.x and NEW.x to record.x. The two published files now agree.

Item 2 — :105, "AI tool params" in the "Any CEL expression" bullet

Before (verbatim):

skill (data validations, automation guards, UI visibility, AI tool params).

After (verbatim):

skill (data validations, automation guards, UI visibility).

What settles it — and a correction to the card. The card's diagnosis is
confirmed and its prescribed replacement is false.

  • Confirmed: packages/spec/src/ai/tool.zod.ts matches cel|formula|expression
    zero times. Tool params carry no expression field of any kind.
  • Falsified: the card asked to substitute "a model-registry predicate, verified at
    packages/spec/src/ai/model-registry.zod.ts". There is no such field —
    predicate occurs 0 times in that file. Its only expression-typed keys are
    system / user at :121-122, and those are TemplateExpressionInputSchema,
    which packages/spec/src/shared/expression.zod.ts:117-124 defines as
    dialect: 'template'{{var}} interpolation, not CEL.
  • skills/objectstack-ai/SKILL.md:405-407 already says exactly this: the domain's
    expression site is "a model-registry promptTemplate.system / .user;
    ToolSchema carries no expression field of any kind."

So the AI domain has no CEL site at all, and there is no true item to swap in.
Taking the card's stated alternative, the item is dropped rather than replaced.
Adding an AI entry to a bullet headed "Any CEL expression" would have replaced one
false claim with another.

Item 3 — three routing bullets (:94-110)

Added verbatim, in the existing bullets' shape:

- **Labels vs. bundles** — a `label` you want translated is **objectstack-i18n**.
- **Upgrade vs. platform** — a protocol-major move is **objectstack-upgrade**.
- **Rendering this metadata** — the consuming UI is
[objectui `skills/objectui/`](https://github.com/objectstack-ai/objectui).
BulletCostVerified against
Labels vs. bundles (#14566, I18N-A-03)21 tok / 84 Bskills/objectstack-i18n/SKILL.md frontmatter description — "Author ObjectStack translation bundles — object/field labels, view text …"
Upgrade vs. platform (UPG-A-01)21 tok / 78 Bskills/objectstack-upgrade/SKILL.md frontmatter description — "Use when a project is on an older protocol major and must move to the current one, when @objectstack/spec was bumped across a major …"
Rendering this metadata (OUI-A-01)33 tok / 134 BPath confirmed present on the objectui checkout: git ls-tree origin/main skills/objectui/ at objectui origin/main39af82f returns SKILL.md, README.md, evals/, guides/, rules/
Total75 tok / 296 Bat the #14566 triage's ruling (comment 5514978050): one bullet, ~25 tokens

Each routing claim was checked against the target skill's own frontmatter
description, so the bullet sends the reader where the skill says it triggers.

The payment

Item 3's 75 tokens are the funded addition. Items 1 and 2 are funded from each
other
and net to exactly zero:

  • Item 2's drop of , AI tool params frees 16 B / 4 tok.
  • Item 1's truthful replacement costs 16 B / 4 tok.
  • Items 1 + 2 combined: 0 B, 0 tok — neutral, as the budget requires.

One further payment is inside item 3 itself, and it is what brought the block from
84 tokens to the ruling's 75. Bullet 2's body does not restate "not
objectstack-platform"; the surviving site is the bullet's own heading,
"Upgrade vs. platform", which already names both sides of the decision. That is
a strict restatement removed, not meaning shaved — the routing information (which
skill wins) is intact.

No further payment was available. The one candidate examined was the
process-skill paragraph at :64-66 against the frontmatter rows at :75
(compatibility — "for a process skill that binds to no schema") and :76
(metadata.domain — "a delivery-process skill that teaches no schema"). Rejected:
the paragraph also carries two facts that survive nowhere else — that such a skill
"carries SKILL.md alone" (no references/ directory) and the SKILL_MAP
mechanism behind it. Deleting it would have shaved meaning, so it was left alone.

File token table

ceil(utf8_bytes / 4), whole file:

BytesTokens
Before (89a156af88)79411986
After (39da552e61)82372060
Delta+296+74

The entire delta is item 3's funded block; items 1–2 contribute zero.
skills/README.md is outside the token-ratchet population
(scripts/check-skills-token-ratchet.mjs:133), and the ratchet is green regardless.

Edit-landed-on-disk proof, grep counts before → after:

TermBeforeAfter
M9.510
AI tool params10
objectstack-i18n12
objectstack-upgrade12
objectui12

Gates — all at head 39da552e61

Every exit code captured by redirect before any pipe; each row quotes the gate's
own verdict line.

GateExitVerdict
pnpm --filter @objectstack/spec check:skill-docs0✅ Skill docs in sync (✓ skills/README.md)
node scripts/check-skills-token-ratchet.mjs0✓ 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
node scripts/check-skills-token-ratchet.mjs --self-test0✓ check-skills-token-ratchet self-test: 64 cases pass.
pnpm check:role-word0check-role-word: OK, no new occurrences of the reserved word. — 224 files, ledger unchanged at 43 baselined files / 120 occurrences
pnpm check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 46 published file(s) …; Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
pnpm check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
pnpm check:published-readme-links0✓ 176 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 via redirect), 1 anchor(s) verified, 103/103 relative target(s) found in the tree. — the new objectui link resolves
node scripts/check-nul-bytes.mjs0OK (scanned 8064 text file(s) … no raw ASCII control bytes)

Re-derived union

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands,
re-derived after the last edit. Provenance line confirms it read the right tree:
"gate list derived from the tree of 'objectstack-ai/objectstack' at commit
39da552e61", change set "1 path(s) vs merge base 89a156af8" = skills/README.md.
All 16 commands run:

GateExit
node scripts/check-ci-filter-parity.mjs0
node scripts/check-cross-package-test-inputs.mjs0
node scripts/check-shard-attestation.mjs0
node scripts/check-test-completeness.mjs3 — NOT MEASURED
pnpm --filter @objectstack/lint run check:doc-formula-expressions0
pnpm --filter @objectstack/spec run check:skill-docs0
pnpm check:agent-test-spelling0
pnpm check:corpus-claim-drift0
pnpm check:cross-package-test-inputs0
pnpm check:doc-authoring0
pnpm check:merge-driver0
pnpm check:pm-governed-merges0
pnpm check:role-word0
pnpm check:skill-compatibility0
pnpm check:skill-frame-sync0
pnpm check:skill-identifier-liveness0

Two notes on that table:

  • check-test-completeness exits 3 = PREREQUISITE NOT MET, not red: it "grades a
    saved turbo run test log, and no log was named … the local reading for this gate
    is NOT MEASURED." CI tees the log and passes the path, so CI is unaffected.
  • check:doc-formula-expressionsalso exited 3 on the first pass (it imports the
    compiled @objectstack/formula and @objectstack/lint). Rather than report a
    non-reading, both packages were built and the gate re-run — it is the gate nearest
    this change, since item 1 introduces previous. / record. into published prose.
    It then measured green:
    ✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula.

Reverse-verification

check:skill-docs can fail, and it names this file. Guarded by
trap restore EXIT INT TERM with absolute paths throughout; the implementation was
committed first, so the restore leg points at a HEAD that already contains it.

  • Mutation landed on disk — not inferred from an editor's exit code, but from grep
    counts flipping in both directions inside the generated region
    (Construct ObjectQL queries 1 → 0, Construct ObjectQL queried 0 → 1) and the
    worktree blob moving 72afcf31b7…e60bea9ecc….
  • Mutated legexit 1, verdict
    ✗ skills/README.md is out of date — run pnpm --filter @objectstack/spec gen:skill-docs.
    The gate is live and it names skills/README.md.
  • Restore leggit checkout HEAD -- /home/user/…/skills/README.md (pinned to
    HEAD, never a bare checkout, so it cannot restore from a polluted index). Proved
    by blob hash equality against the HEAD blob (72afcf31b7…, non-empty and
    matching), an empty git diff HEAD, and the anchor counts returning to 1 / 0.

ESLint

Not a narrowing — the file is outside the linted population entirely, established
with all three pieces:

  1. Population, read from eslint's own config resolution (not from my reading of
    the globs): eslint --print-config skills/README.md emits an empty document
    (10 bytes). Every files: selector in eslint.config.mjs is
    {ts,tsx,mts,cts,js,jsx,mjs,cjs}; no selector matches markdown.
  2. Count from --format json for the file: 1 result entry, errorCount: 0,
    warningCount: 1, and the single message is eslint's own
    File ignored because no matching configuration was supplied. — zero rule
    findings, because zero rules apply.
  3. Invariance for untouched files: the repo runs one eslint.config.mjs which
    "never enables type-aware linting (no parserOptions.project, no typed
    @typescript-eslint rules) for ANY file, test or not"
    (eslint.config.mjs:326-328, carrying its own positive-control measurement).
    No untouched file's verdict can move as a function of this diff.

A full-repo pnpm lint is therefore provably vacuous with respect to this change.

Changeset

skip-changeset, applied. This PR releases nothing: the diff is one file under
skills/, and scripts/check-empty-changeset.mjs:359-362 enumerates exactly that
case — "It releases nothing (.github/, .claude/, skills/, docs/,
content/, examples/, tests-only, and the like) -> delete the changeset and apply
the 'skip-changeset' label (route 2)." The script pins this shape as GREEN 3, "a
skills/**-only PR carrying NO changeset (route 2)" (:559-565). No package
package.json, src/, or version is touched.

Scope

Untouched, as claimed: every SKILL.md; the README's generated region between the
BEGIN/END GENERATED markers (check:skill-docs green proves it byte-for-byte); the
anatomy tree at :50-62, so the hunks are disjoint from PR #14786's :55 — this
diff's two hunks start at :85 and :104. No other README sentence was edited.

Draft, and it stays draft — governed skills/**, human merge is the review record.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…in skills/README.md
Settle `skills/README.md`'s CEL conventions and cross-skill routing list
against source.
1. `:87` — "Legacy `OLD` / `NEW` evaluate to `null` since M9.5" was false in
both halves. `M9.5` is anchored nowhere in the repo, and `OLD` / `NEW` are
not values that evaluate to `null`: they are undeclared identifiers.
`SCOPE_ROOTS` (packages/formula/src/cel-engine.ts:95) is the published
closed set of record-scope roots and contains neither, so
`firstUndeclaredReference` (:186) reports them as bare undeclared
references — the class `@objectstack/lint` raises as an error, not a
silent null. Replaced with the true clause plus the migration objectstack-
formula's own table prescribes (`OLD.x` -> `previous.x`, `NEW.x` ->
`record.x`).
2. `:105` — "AI tool params" is not a CEL site. `packages/spec/src/ai/tool.zod.ts`
carries no expression field of any kind, and the AI domain's only expression
site is `model-registry.zod.ts:121-122`, which is `TemplateExpressionInputSchema`
— the `template` dialect (`{{var}}` interpolation), not CEL. Dropped the item;
no true replacement exists, because the AI domain has no CEL predicate.
3. `:94-110` — three routing bullets added for measured load-failure modes:
labels to objectstack-i18n, protocol-major moves to objectstack-upgrade,
and the consuming UI to the objectui skills catalog.
Items 1-2 net exactly zero bytes; item 3 costs 75 tokens, at the size ruling.
The generated region and the anatomy tree are untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

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

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

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

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets - #14795

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing
Sep 3, 2026
Merged

docs(skills): settle two false claims in skills/README.md and add three cross-skill routing bullets#14795
os-zhuang merged 1 commit into
mainfrom
claude/issue-14794-readme-truth-routing

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14794
Fixes#14566

Three edits to skills/README.md, and nothing else. Branch point re-measured at
origin/main89a156af88 — identical to the card's measurement, no drift.
Head sha for every gate reading below: 39da552e61.

Item 1 — :87, the OLD / NEW clause

Before (verbatim):

@objectstack/spec. Legacy OLD / NEW evaluate to null since M9.5.

After (verbatim):

@objectstack/spec. Legacy OLD / NEW are not CEL — use
previous. / record..

What settles it. The sentence was false in both halves.

  • M9.5 is anchored nowhere: git grep -rn 'M9\.5' -- packages skills docs returns
    exactly one hit, this README line itself. Nothing defines the milestone it cites.
  • OLD / NEW are not values that evaluate to null — they are undeclared
    identifiers
    . SCOPE_ROOTS (packages/formula/src/cel-engine.ts:95-119) is the
    published, closed set of roots a record-scoped CEL site may reference; it contains
    neither. firstUndeclaredReference (packages/formula/src/cel-engine.ts:186-205)
    therefore reports them as bare undeclared references — the class
    @objectstack/lint raises as an error, not a silent null
    (packages/lint/src/validate-expressions.ts:490, "Why it is an error and not a
    warning").

Measured directly against the built package rather than inferred:

SCOPE_ROOTS includes OLD: false | NEW: false | previous: true | record: true
"OLD.amount != NEW.amount" -> firstUndeclaredReference = "OLD"
"OLD" -> firstUndeclaredReference = "OLD"
"NEW.status" -> firstUndeclaredReference = "NEW"
"previous.amount != record.amount" -> firstUndeclaredReference = null

The replacement's prescription is objectstack-formula's own migration table, not a
new claim: skills/objectstack-formula/SKILL.md:345-346 maps OLD.x to
previous.x and NEW.x to record.x. The two published files now agree.

Item 2 — :105, "AI tool params" in the "Any CEL expression" bullet

Before (verbatim):

skill (data validations, automation guards, UI visibility, AI tool params).

After (verbatim):

skill (data validations, automation guards, UI visibility).

What settles it — and a correction to the card. The card's diagnosis is
confirmed and its prescribed replacement is false.

  • Confirmed: packages/spec/src/ai/tool.zod.ts matches cel|formula|expression
    zero times. Tool params carry no expression field of any kind.
  • Falsified: the card asked to substitute "a model-registry predicate, verified at
    packages/spec/src/ai/model-registry.zod.ts". There is no such field —
    predicate occurs 0 times in that file. Its only expression-typed keys are
    system / user at :121-122, and those are TemplateExpressionInputSchema,
    which packages/spec/src/shared/expression.zod.ts:117-124 defines as
    dialect: 'template'{{var}} interpolation, not CEL.
  • skills/objectstack-ai/SKILL.md:405-407 already says exactly this: the domain's
    expression site is "a model-registry promptTemplate.system / .user;
    ToolSchema carries no expression field of any kind."

So the AI domain has no CEL site at all, and there is no true item to swap in.
Taking the card's stated alternative, the item is dropped rather than replaced.
Adding an AI entry to a bullet headed "Any CEL expression" would have replaced one
false claim with another.

Item 3 — three routing bullets (:94-110)

Added verbatim, in the existing bullets' shape:

- **Labels vs. bundles** — a `label` you want translated is **objectstack-i18n**.
- **Upgrade vs. platform** — a protocol-major move is **objectstack-upgrade**.
- **Rendering this metadata** — the consuming UI is
[objectui `skills/objectui/`](https://github.com/objectstack-ai/objectui).
BulletCostVerified against
Labels vs. bundles (#14566, I18N-A-03)21 tok / 84 Bskills/objectstack-i18n/SKILL.md frontmatter description — "Author ObjectStack translation bundles — object/field labels, view text …"
Upgrade vs. platform (UPG-A-01)21 tok / 78 Bskills/objectstack-upgrade/SKILL.md frontmatter description — "Use when a project is on an older protocol major and must move to the current one, when @objectstack/spec was bumped across a major …"
Rendering this metadata (OUI-A-01)33 tok / 134 BPath confirmed present on the objectui checkout: git ls-tree origin/main skills/objectui/ at objectui origin/main39af82f returns SKILL.md, README.md, evals/, guides/, rules/
Total75 tok / 296 Bat the #14566 triage's ruling (comment 5514978050): one bullet, ~25 tokens

Each routing claim was checked against the target skill's own frontmatter
description, so the bullet sends the reader where the skill says it triggers.

The payment

Item 3's 75 tokens are the funded addition. Items 1 and 2 are funded from each
other
and net to exactly zero:

  • Item 2's drop of , AI tool params frees 16 B / 4 tok.
  • Item 1's truthful replacement costs 16 B / 4 tok.
  • Items 1 + 2 combined: 0 B, 0 tok — neutral, as the budget requires.

One further payment is inside item 3 itself, and it is what brought the block from
84 tokens to the ruling's 75. Bullet 2's body does not restate "not
objectstack-platform"; the surviving site is the bullet's own heading,
"Upgrade vs. platform", which already names both sides of the decision. That is
a strict restatement removed, not meaning shaved — the routing information (which
skill wins) is intact.

No further payment was available. The one candidate examined was the
process-skill paragraph at :64-66 against the frontmatter rows at :75
(compatibility — "for a process skill that binds to no schema") and :76
(metadata.domain — "a delivery-process skill that teaches no schema"). Rejected:
the paragraph also carries two facts that survive nowhere else — that such a skill
"carries SKILL.md alone" (no references/ directory) and the SKILL_MAP
mechanism behind it. Deleting it would have shaved meaning, so it was left alone.

File token table

ceil(utf8_bytes / 4), whole file:

BytesTokens
Before (89a156af88)79411986
After (39da552e61)82372060
Delta+296+74

The entire delta is item 3's funded block; items 1–2 contribute zero.
skills/README.md is outside the token-ratchet population
(scripts/check-skills-token-ratchet.mjs:133), and the ratchet is green regardless.

Edit-landed-on-disk proof, grep counts before → after:

TermBeforeAfter
M9.510
AI tool params10
objectstack-i18n12
objectstack-upgrade12
objectui12

Gates — all at head 39da552e61

Every exit code captured by redirect before any pipe; each row quotes the gate's
own verdict line.

GateExitVerdict
pnpm --filter @objectstack/spec check:skill-docs0✅ Skill docs in sync (✓ skills/README.md)
node scripts/check-skills-token-ratchet.mjs0✓ 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
node scripts/check-skills-token-ratchet.mjs --self-test0✓ check-skills-token-ratchet self-test: 64 cases pass.
pnpm check:role-word0check-role-word: OK, no new occurrences of the reserved word. — 224 files, ledger unchanged at 43 baselined files / 120 occurrences
pnpm check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 46 published file(s) …; Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
pnpm check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
pnpm check:published-readme-links0✓ 176 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 via redirect), 1 anchor(s) verified, 103/103 relative target(s) found in the tree. — the new objectui link resolves
node scripts/check-nul-bytes.mjs0OK (scanned 8064 text file(s) … no raw ASCII control bytes)

Re-derived union

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands,
re-derived after the last edit. Provenance line confirms it read the right tree:
"gate list derived from the tree of 'objectstack-ai/objectstack' at commit
39da552e61", change set "1 path(s) vs merge base 89a156af8" = skills/README.md.
All 16 commands run:

GateExit
node scripts/check-ci-filter-parity.mjs0
node scripts/check-cross-package-test-inputs.mjs0
node scripts/check-shard-attestation.mjs0
node scripts/check-test-completeness.mjs3 — NOT MEASURED
pnpm --filter @objectstack/lint run check:doc-formula-expressions0
pnpm --filter @objectstack/spec run check:skill-docs0
pnpm check:agent-test-spelling0
pnpm check:corpus-claim-drift0
pnpm check:cross-package-test-inputs0
pnpm check:doc-authoring0
pnpm check:merge-driver0
pnpm check:pm-governed-merges0
pnpm check:role-word0
pnpm check:skill-compatibility0
pnpm check:skill-frame-sync0
pnpm check:skill-identifier-liveness0

Two notes on that table:

  • check-test-completeness exits 3 = PREREQUISITE NOT MET, not red: it "grades a
    saved turbo run test log, and no log was named … the local reading for this gate
    is NOT MEASURED." CI tees the log and passes the path, so CI is unaffected.
  • check:doc-formula-expressionsalso exited 3 on the first pass (it imports the
    compiled @objectstack/formula and @objectstack/lint). Rather than report a
    non-reading, both packages were built and the gate re-run — it is the gate nearest
    this change, since item 1 introduces previous. / record. into published prose.
    It then measured green:
    ✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 426 files / 1365 TS blocks judged clean by @objectstack/formula.

Reverse-verification

check:skill-docs can fail, and it names this file. Guarded by
trap restore EXIT INT TERM with absolute paths throughout; the implementation was
committed first, so the restore leg points at a HEAD that already contains it.

  • Mutation landed on disk — not inferred from an editor's exit code, but from grep
    counts flipping in both directions inside the generated region
    (Construct ObjectQL queries 1 → 0, Construct ObjectQL queried 0 → 1) and the
    worktree blob moving 72afcf31b7…e60bea9ecc….
  • Mutated legexit 1, verdict
    ✗ skills/README.md is out of date — run pnpm --filter @objectstack/spec gen:skill-docs.
    The gate is live and it names skills/README.md.
  • Restore leggit checkout HEAD -- /home/user/…/skills/README.md (pinned to
    HEAD, never a bare checkout, so it cannot restore from a polluted index). Proved
    by blob hash equality against the HEAD blob (72afcf31b7…, non-empty and
    matching), an empty git diff HEAD, and the anchor counts returning to 1 / 0.

ESLint

Not a narrowing — the file is outside the linted population entirely, established
with all three pieces:

  1. Population, read from eslint's own config resolution (not from my reading of
    the globs): eslint --print-config skills/README.md emits an empty document
    (10 bytes). Every files: selector in eslint.config.mjs is
    {ts,tsx,mts,cts,js,jsx,mjs,cjs}; no selector matches markdown.
  2. Count from --format json for the file: 1 result entry, errorCount: 0,
    warningCount: 1, and the single message is eslint's own
    File ignored because no matching configuration was supplied. — zero rule
    findings, because zero rules apply.
  3. Invariance for untouched files: the repo runs one eslint.config.mjs which
    "never enables type-aware linting (no parserOptions.project, no typed
    @typescript-eslint rules) for ANY file, test or not"
    (eslint.config.mjs:326-328, carrying its own positive-control measurement).
    No untouched file's verdict can move as a function of this diff.

A full-repo pnpm lint is therefore provably vacuous with respect to this change.

Changeset

skip-changeset, applied. This PR releases nothing: the diff is one file under
skills/, and scripts/check-empty-changeset.mjs:359-362 enumerates exactly that
case — "It releases nothing (.github/, .claude/, skills/, docs/,
content/, examples/, tests-only, and the like) -> delete the changeset and apply
the 'skip-changeset' label (route 2)." The script pins this shape as GREEN 3, "a
skills/**-only PR carrying NO changeset (route 2)" (:559-565). No package
package.json, src/, or version is touched.

Scope

Untouched, as claimed: every SKILL.md; the README's generated region between the
BEGIN/END GENERATED markers (check:skill-docs green proves it byte-for-byte); the
anatomy tree at :50-62, so the hunks are disjoint from PR #14786's :55 — this
diff's two hunks start at :85 and :104. No other README sentence was edited.

Draft, and it stays draft — governed skills/**, human merge is the review record.


🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

…in skills/README.md
Settle `skills/README.md`'s CEL conventions and cross-skill routing list
against source.
1. `:87` — "Legacy `OLD` / `NEW` evaluate to `null` since M9.5" was false in
both halves. `M9.5` is anchored nowhere in the repo, and `OLD` / `NEW` are
not values that evaluate to `null`: they are undeclared identifiers.
`SCOPE_ROOTS` (packages/formula/src/cel-engine.ts:95) is the published
closed set of record-scope roots and contains neither, so
`firstUndeclaredReference` (:186) reports them as bare undeclared
references — the class `@objectstack/lint` raises as an error, not a
silent null. Replaced with the true clause plus the migration objectstack-
formula's own table prescribes (`OLD.x` -> `previous.x`, `NEW.x` ->
`record.x`).
2. `:105` — "AI tool params" is not a CEL site. `packages/spec/src/ai/tool.zod.ts`
carries no expression field of any kind, and the AI domain's only expression
site is `model-registry.zod.ts:121-122`, which is `TemplateExpressionInputSchema`
— the `template` dialect (`{{var}}` interpolation), not CEL. Dropped the item;
no true replacement exists, because the AI domain has no CEL predicate.
3. `:94-110` — three routing bullets added for measured load-failure modes:
labels to objectstack-i18n, protocol-major moves to objectstack-upgrade,
and the consuming UI to the objectui skills catalog.
Items 1-2 net exactly zero bytes; item 3 costs 75 tokens, at the size ruling.
The generated region and the anatomy tree are untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

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

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

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

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants

@os-litant@os-zhuang@claude