skills(query): optimization flight — seven twice-stated rules merged into rules/, removed-key history collapsed to one table, search/flow-filter/compareTo pointed at their anchors, context and the calling convention taught (net −2,139 tokens) - #14443
Conversation
QRY-D-01 — delete the per-key "why it was removed" narration from rules/aggregation.md (`distinct: true`, `array_agg`/`string_agg`, `windowFunctions`) and rules/pagination.md (`cursor`, `distinct`, and the `cursor` Common-Mistakes pair). The keys are tombstoned: `tsc` types them `never` and a query carrying one fails to parse with the prescription, so the only decision-changing half is the replacement — now one table in SKILL.md. QRY-D-02 — delete the alias/push-down bug-history parenthesis at rules/aggregation.md; the behaviour it describes no longer exists. QRY-E-04 — the group-by example is shown in the real `engine.aggregate(obj, …)` shape with legal keys only; `fields` is not in ENGINE_AGGREGATE_OPTION_KEYS and is rejected by name, which the old "readability convention" note did not say. QRY-C-05 — `compareTo` is objectstack-ui's surface; the copy here becomes a pointer. aggregation.md 2226 -> 1846 tok, pagination.md 1381 -> 1158 tok. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
QRY-C-04 — the "a flow filter that loses a condition refuses to run" rule is a
flow-node authoring rule, and objectstack-automation already states it. Deleted
here; one pointer line stays. (Two things the automation anchor does NOT carry
are listed in the PR body as a follow-up for that package.)
QRY-F-03 — the string-operator table listed four operators and omitted three
that exist: `$icontains`, `$like`, `$ilike`. Added, together with the case rule
`packages/spec/src/data/filter.zod.ts` states outright ("`$contains` /
`$notContains` / `$startsWith` / `$endsWith` compare CASE-SENSITIVELY.
`$icontains` is the case-INSENSITIVE twin"), the ASCII-only folding domain, and
the faces that refuse `$like`/`$ilike` rather than approximating them.
Funded additions from the delivered objectstack-ui flight, each verified at
source before porting — this package is the anchor for the token vocabulary and
ui now points here: both `{token}` and `${token}` parse (DATE_MACRO_WRAPPED_RE,
shared by the context tokens); `{user_id}` and `{organization_id}` join the
near-miss list (CONTEXT_TOKEN_SUGGESTIONS); a token embedded in a larger string
is left untouched; `isDateMacroToken` / `isContextToken` from
`@objectstack/spec/data` are the author-time check; the build rule is named
(`filter-token-unknown`) along with why it exists.
Paid in-file by C-04 plus two schema-enforced Common-Mistakes blocks.
2144 -> 2100 tok.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
QRY-E-03 / QRY-B-04 — the package used two incompatible calling conventions and
named neither, and its most-copied example shape (`{ object: 'account', … }`
passed as an option bag) is not accepted by any engine method:
ENGINE_FIND_OPTION_KEYS and ENGINE_AGGREGATE_OPTION_KEYS are closed sets and an
unlisted key is refused by name. The file now opens with the convention —
object is the FIRST ARGUMENT, the legal key set per method, and where a bare
`{ object, … }` literal IS correct (findData's `query`, an `expand` value).
QRY-B-01 — the seven rules stated twice are merged into rules/*: field
references, keyset pagination, the OData alias, the aggregation functions
table, HAVING, filtered aggregation and window functions. SKILL.md keeps
one-line pointers.
QRY-D-01 — the four removal-history spans become one 6-row "removed key ->
live replacement" table.
QRY-C-01 — the search-mirror prescription is objectstack-data's (this file
already named data as the anchor); reduced to the rule plus that pointer.
QRY-C-03 / QRY-C-05 — the CRM Analytics Query Blueprint and `compareTo` are
objectstack-ui's dataset/widget surface; one routing line replaces them.
QRY-B-02 / QRY-B-03 / QRY-D-03 / QRY-D-04 — "When to Use This Skill", "Skill
Boundaries", the opening blurb, the callout legend and the positioning prose
restate the frontmatter or the catalog and change no decision.
QRY-E-02 (incidental falsehood 1) — the canonical `expand` example expanded
nothing: it projected `fields: ['title','status']` while expanding `assignee`
and `project`, dropping both foreign-key columns, and the engine skips a
relation whose FK is absent. The example keeps the FK columns and the
requirement is now the first Rules bullet.
QRY-F-01 — `context` had zero coverage; it is the RLS / system-read escape
hatch, and a read without it silently returns fewer rows. Added with the
`{ isSystem: true }` example and the query-bag vs trailing-argument rule.
QRY-F-02 / QRY-G-03 — the bare-string `search` plus sibling `searchFields` is
the canonical Tier-1 form; the three spellings of that one knob are named.
QRY-F-03 / QRY-A-01 / QRY-G-02 — the three missing string operators and the
case rule; a "which filter dialect" table; one spelling for the version fact.
QRY-A-01 also edits the frontmatter description: the list-view filter spec is
objectstack-ui's vocabulary, not this package's.
5443 -> 3784 tok.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
…tion edit Generator output only — `pnpm --filter @objectstack/spec gen:skill-docs`. `skills/README.md` is byte-unchanged: it renders only the description's FIRST sentence, which QRY-A-01 did not touch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
…ills-query-optimization
… $like faces
Two spans, both re-measured at source before touching them.
1. The calling-convention table listed nine keys for engine `find`/`findOne` and
called the set closed. `packages/objectql/src/engine.ts:396-400` spreads
`ENGINE_DRIVER_PASSTHROUGH_KEYS` (`:368-370`: `transaction`, `tenantId`,
`tenantIds`, `timezone`, `bypassTenantAudit`, `preserveAudit`) into it, so as
written the table said an explicit `tenantId` is refused, which is false. The
row now names all fifteen, and the prose carries the reason the source gives
at `:360-366`: on find/findOne (and update/delete) the option bag IS the base
of the driver options, while `count`/`aggregate` never forward the bag, so
those keys are deliberately illegal there. `count` and `aggregate` rows
unchanged — both were already exact.
2. Review asked me to verify the `$like`/`$ilike` face list at source or drop
it. Verified; all three refusals hold, and each is an ALLOWLIST MISS, which
is why grepping the drivers for `$like` does not find them:
- driver-mongodb — `mongodb-filter.ts:1071` throws `Unsupported filter
operator "$like" …`; its `unsupportedFilterError` (`:366-371`) sets
`INVALID_FILTER` / 400. Pinned at
`mongodb-operator-key-clobber.test.ts:158-161`.
- objectql `having` — `having-filter.ts:136-141` `CONDITION_OPERATORS` is
sixteen operators without `$like`/`$ilike`; anything else reaches
`unknownOperator` (`:174-205`) and `invalidFilterError`, `INVALID_FILTER`
/ 400 (the table at `:157-158` names the faces sharing that envelope).
- service-analytics — `strategies/filter-normalizer.ts:418-435`
`MONGO_TO_CUBE_OP` omits them; the miss branch at `:1032-1046` throws
`invalidFilterError` (`:403-406`, `INVALID_FILTER` / 400) with
`Unsupported filter operator "$like" …`.
The review's own two readings were the ones that did not survive measurement,
so the sentence is corrected rather than weakened: `driver-memory` ANSWERS
these operators (`memory-driver.ts:58,1522`, `memory-matcher.ts:458`), and
`filter-refusal.ts:720-726` refuses only a DANGLING ESCAPE in the pattern via
the spec's shared `hasDanglingLikeEscape`. The prose now states which faces
answer, which refuse, and the escape rule that binds all of them.
Paid in-file: `rules/filters.md` 2100 -> 2136 (ceiling 2149, and still under the
2144 it started at) by collapsing two Common-Mistakes blocks that restated the
Logical Operators and `$null` sections above them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
`check:role-word` reds on IMPROVEMENT as well as regression: round 2's deletion of two Common-Mistakes blocks in `skills/objectstack-query/rules/filters.md` removed one baselined occurrence, so the gate reported role-word count improved 9 -> 8 — ratchet DOWN: run `node scripts/check-role-word.mjs --update` and commit the baseline. This is that commit, and nothing else: `node scripts/check-role-word.mjs --update` moved exactly one row (9 -> 8) and no other, verified by `git diff` before committing. The gate is green after it. Why it reached CI rather than my machine: round 2 re-ran only the five card-named gates instead of re-deriving the union, and this family is pulled in by the prose paths, not by the card's list. Round 3 runs the whole re-derived list — which this baseline file itself widens. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
…ills-query-optimization
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33625162151 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Part of #14302 — the eval items (QRY-D-05 / H-02 / H-03) are deferred to #14296, so that card stays open.
Member of the skills catalog optimization program #14292. Governed surface (
skills/**) ⇒ this PR stays DRAFT;needs:contract-reviewis on this PR and on #14302.Audit record: posted verbatim as a comment on #14302 (read back byte-identical). It is the finding-by-finding spec this PR implements.
Head sha 49de20f (review round 1 + the role-word baseline ratchet, then merged with current
main).Files outside the declared surface
One file beyond the four authored skill files and the one generated doc:
scripts/role-word-baseline.jsoncheck:role-wordreds on improvement as well as regression. Round 2 deleted two Common-Mistakes blocks inrules/filters.md, removing one baselined occurrence, and the gate refused:role-word count improved 9 → 8 — ratchet DOWN: run node scripts/check-role-word.mjs --update and commit the baseline.--updatemoved exactly one row (skills/objectstack-query/rules/filters.md: 9 → 8) and no other, verified bygit diffbefore committing; it is its own commit,8208cf30. The data and api flights took the same path.No ceiling was changed, in this file or any other.
Review round 1 — both FAIL spans, measured and fixed
Round 1 passed everything except two spans. Both were re-measured at source before being touched; one was a real defect, one turned out to be correct as written and is now stated more precisely.
1.
ENGINE_FIND_OPTION_KEYSwas under-reported — REAL DEFECT, fixedThe calling-convention table listed nine keys for engine
find/findOneand the prose called the set closed.packages/objectql/src/engine.ts:396-400spreadsENGINE_DRIVER_PASSTHROUGH_KEYSinto it, and that constant (:368-370) istransaction,tenantId,tenantIds,timezone,bypassTenantAudit,preserveAudit. As written the table therefore told a reader that an explicittenantIdis refused by name, which is false.Fixed: the
find/findOnerow now names all fifteen keys, and the prose carries the reason the source itself gives at:360-366— onfind/findOne(andupdate/delete) the option bag IS the base of the driver options, which is how an explicittenantIdreaches the driver;countandaggregatenever forward the bag, so on those two the same keys are deliberately ILLEGAL. Thecount(context, where) andaggregate(six keys, no passthrough) rows are unchanged — both were already exact, and the source comment says the omission there is deliberate.2. The
$like/$ilikeface list — VERIFIED AT SOURCE, kept and sharpenedReview offered two routes: verify each named face with file:line and the exact message, or reduce the sentence. Taking the first route: all three refusals hold. Each is an allowlist miss, which is why grepping the drivers for the literal
$likedoes not find them.driver-mongodbdefault:arm)packages/drivers/driver-mongodb/src/mongodb-filter.ts:1071-1074unsupportedFilterErrorat:366-371setsINVALID_FILTER/ 400Unsupported filter operator "$like" on field … It is refused rather than passed through to MongoDBhavingpackages/objectql/src/having-filter.ts:136-141—CONDITION_OPERATORS, sixteen operators, no$like/$ilikeunknownOperatorat:174-205→invalidFilterErrorINVALID_FILTER/ 400 (the doc table at:157-158enumerates the faces sharing that envelope)Unsupported operator '$like' in having. … An unknown operator is refused rather than ignoredpackages/services/service-analytics/src/strategies/filter-normalizer.ts:418-435—MONGO_TO_CUBE_OP, no$like/$ilike:1032-1046invalidFilterErrorat:403-406setsINVALID_FILTER/ 400[analytics] Unsupported filter operator "$like" on … Dropping it would silently widen the querypackages/spec/src/data/filter.zod.ts:697states exactly this, so the spec.describe()is accurate and no spec-side twin card is owed.Two readings in the review did not survive measurement, so the sentence is corrected rather than weakened:
driver-memoryANSWERS$like/$ilike.packages/drivers/driver-memory/src/memory-driver.ts:58declares its vocabulary as[...FILTER_OPERATORS, '$like', '$ilike'], and the arms are atmemory-driver.ts:1522andmemory-matcher.ts:458.filter-refusal.tsis not an operator refusal here. ThehasDanglingLikeEscapeimport at:36gates a MALFORMED PATTERN — a lone unpaired trailing backslash — at:720-726, using the spec's shared test so every face refuses the same patterns. It does not refuse the operator.The prose now states which faces answer, which refuse, and the dangling-escape rule that binds all of them. Paid in-file: two Common-Mistakes blocks in
rules/filters.mdthat restated the Logical Operators and$nullsections directly above them — the deletion that then required the baseline ratchet above.Per-item ledger
SKILL.md:3-9frontmatterdescription[{ field, operator, value }]over the 20-operatorVIEW_FILTER_OPERATORSenum, which the package never names$/ view rule / dataset measure filterSKILL.md:214-231,274-295,303-310,316-338,361-378,380-399,548-566SKILL.md;rules/*are the anchor and keep one-line pointers. The rules files did NOT grow to absorb them — they already carried every oneSKILL.md:43-51descriptionthat already routed the loadSKILL.md:32-40descriptionandskills/README.md's cross-skill routingSKILL.md:20-52references/_index.md:14packages/spec/src/data/query.zod.tsSKILL.md:496-539objectstack-data/SKILL.md— including the heading, the "one prescription" sentence and the formula-field paragraph — while:535-537already named data as the anchorsearchableFields)". Anchor re-verified after the data flight landed on main:objectstack-data/SKILL.md:100rules/filters.md:221-258,:282-286rules/filters.md"SKILL.md:617-632rules/filters.md:260-280SKILL.md:624,rules/aggregation.md:199-207compareTodocumented twice here and a third time in full in objectstack-uicompareTois LIVE, not retired —packages/spec/src/ui/dashboard.zod.ts:438declarescompareTo: strictObject({…}); what was removed is the BARE-STRING form andcompareTo.offset(dashboard.zod.ts:272,:290). Live ⇒ delete + pointer. Target re-verified on the merged tree:objectstack-ui/SKILL.md:1410SKILL.md:274-279,327-337,440-445,550-558;rules/aggregation.md:16-27,138-144,156-166;rules/pagination.md:12-18,218-222,160-177retiredKey()typesneverSKILL.md; the per-key history deleted in all three filesrules/aggregation.md:88-96alias ?? field") staysSKILL.md:20-28description, plus a three-way callout legend whose warn arm is used exactly onceSKILL.md:87-88evals/README.mdevals/**untouched, 558 tok unchangedcheck-skill-examplesmarkersengine.*call shapes, which are not resolvable from@objectstack/specalone. See the gap ledgerSKILL.md:407-425+ Rules list:427-434fields: ['title','status']while expandingassigneeandproject— dropping both FK columns, so the query resolves NO relations and reports no errorpackages/spec/src/data/query.zod.ts:509("The projection must RETAIN the foreign-key column"); pinned atpackages/objectql/src/query-expression-conformance.test.ts:1575-1586; mechanism re-located at source —packages/objectql/src/engine.ts:8358-8359(const val = record[fieldName]; if (val == null) continue;) then:8372(if (uniqueIds.length === 0) continue;)SKILL.md:60-69,256-266,280-294,341-352,407-425,584-593vs:371-377,390-396,604-612{ object: 'account', … }as an option bag) is refused by every engine methodpackages/objectql/src/engine.ts:368-407—find/findOne= the nine query keys PLUS the six driver passthrough keys (review round 1);ENGINE_AGGREGATE_OPTION_KEYS=context, where, groupBy, aggregations, having, timezone;ENGINE_COUNT_OPTION_KEYS=context, where, both without passthrough and deliberately so — plus where a bare{ object, … }literal IS correct (findData'squery, anexpandvalue) and thetop/filteralias fold. Rejection pinned atpackages/objectql/src/engine-unknown-option.test.ts:124-135SKILL.md:341-352,rules/aggregation.md:43-54fields:(andorderBy:), neither of which is inENGINE_AGGREGATE_OPTION_KEYS;rules/aggregation.md:57-59calledfields"a readability convention" without saying it is rejectedengine.aggregate(obj, {…})with legal keys only, and the rejection statedcontext— 34 uses inpackages/objectql/src/*.test.tsquery literals, 40 inexamples/**+packages/apps/**— had zero coverage in any package{ isSystem: true }(the real spelling atexamples/app-showcase/src/security/bind-position-sets.ts:50and two siblings), and the query-bag vs trailing-argument rule with "the trailing one wins" (packages/objectql/src/engine.ts:1865-1877)SKILL.md:464-495,:479-504search: { query, fields }was taught — 5 real usages against 74 for the bare string, whichpackages/spec/src/data/data-engine.zod.ts:120-127calls "the canonical Tier-1 contract"; and three spellings of the narrowing knob appeared with none introducedsearchFieldsis the canonical example (the shapepackages/qa/dogfood/test/showcase-search.dogfood.test.ts:33,41pins); one line names all three spellings and which layer takes which; the object form demoted to one lineSKILL.md:136-146,rules/filters.md:18-21$icontains,$like,$ilike), and the case rule stated nowhere while the canonical examples were exactly the case-sensitive trapspackages/spec/src/data/filter.zod.ts:548-549, the ASCII-only folding domain, and — after review round 1 — the measured split of which faces answer$like/$ilikeand which refuse themSKILL.md:314-400,rules/aggregation.mdFunded additions from the delivered objectstack-ui flight
All five rows PR #14338 listed as absent here were verified at source before porting, and all five are TRUE. Paid inside
rules/filters.md, whose named payer is QRY-C-04's deletion.{token}and${token}spellings are acceptedpackages/spec/src/data/date-macros.zod.ts:203—DATE_MACRO_WRAPPED_REis/^\$?\{([a-zA-Z0-9_]+)\}$/;context-tokens.zod.ts:109—CONTEXT_TOKEN_WRAPPED_RE = DATE_MACRO_WRAPPED_RE{user_id},{organization_id}packages/spec/src/data/context-tokens.zod.ts—CONTEXT_TOKEN_SUGGESTIONSmapsuser_id → current_user_idandorganization_id → current_org_idpackages/core/src/utils/filter-tokens.ts:50— "Values that merely CONTAIN braces are left untouched"'user-{current_user_id}'specimenisDateMacroToken(tok)as the author-time checkpackages/spec/src/data/date-macros.zod.ts:166, re-exported bypackages/spec/src/data/index.ts:82isContextTokenand the "pass the token WITHOUT braces" caveatpackages/lint/src/validate-filter-tokens.ts:65—export const FILTER_TOKEN_UNKNOWN = 'filter-token-unknown'None withheld.
Per-file token delta
countTokensisceil(utf8 bytes / 4)(scripts/check-skills-token-ratchet.mjs:456). Figures measured at head 49de20f; round 3 changed no skill file, so they are round 2's.skills/objectstack-query/SKILL.mdskills/objectstack-query/rules/aggregation.mdskills/objectstack-query/rules/filters.mdskills/objectstack-query/rules/pagination.mdskills/objectstack-query/evals/README.mdskills/objectstack-query/references/_index.mdEvery edited skill file shrank against its pre-flight size. No ceiling raised, no file added, no file deleted,
scripts/check-skills-token-ratchet.mjsuntouched.Gap against the ≈ −3,530 target, itemised rather than smoothed
−4,088 − (−558), which still carries that −380$likeface split, net of what was paid in-filepremise_false
None of the audit's findings had a false premise; every span was re-verified at BASE (
git diff --stat a59f78d 96b627d1 -- skills/objectstack-queryis empty, so the audit's line numbers were exact). Corrections to stated premises, none of which changed a decision:premise_false: PM mechanism assumption 2 (half)— the QRY-A-01 description edit regeneratescontent/docs/ai/skills-reference.mdxONLY.skills/README.mdis byte-unchanged: it renders just the description's FIRST sentence, which A-01 did not touch. The generator writes both files andcheck:skill-docsverifies both; one of the two simply had no delta.premise_false: PM mechanism assumption 3— noskills/objectstack-queryheading is registered exhaustive. The Leg 2BINDINGStable holds no row in this package. Run before the first deletion and after the last: green every time. No deletion here could red Leg 2.premise_false: the audit's line cite for falsehood 1— the FK skip is atpackages/objectql/src/engine.ts:8358-8359, not:8345-8347(which is a comment block). The mechanism is exactly as described and is confirmed, with the second half at:8372.premise_false: review round 1's two readings of the $like faces—driver-memoryanswers$like/$ilikerather than refusing them, andfilter-refusal.ts'shasDanglingLikeEscapegate is about a malformed PATTERN, not the operator; the three refusing faces do carry anINVALID_FILTER/ 400 envelope. Evidence in the Review round 1 section above.Also measured while executing the C-05 ruling:
compareTois a LIVE surface, not a retired one, so the live branch applied (delete + pointer, not delete with no pointer).follow-up for objectstack-automation
QRY-C-04 deleted 21 lines here on the ruling that the flow-filter rule's anchor is
objectstack-automation/SKILL.md. That anchor carries the core rule (a dropped filter condition WIDENS the query, so routing it would let adelete_recordempty the object while the run reported success). Two things it does NOT carry, both deleted here:config.filter. The flow template engine runs first, but hands a recognised filter placeholder through untouched for the engine to expand; a flow variable named after a placeholder shadows it.objectstack validateFAILS on a{record.…}filter token naming an unknown field, or hopping through a relation the start node does notexpand; the same reference OUTSIDE a filter (a message body, anhttpurl, a write payload) stays a warning, because there it renders a blank rather than widening a query. An unresolved flow variable is not statically checkable at all.Neither is a query-DSL rule, so neither belongs here. Suggested for that package's own flight.
No follow-up for objectstack-data (the anchor is complete, re-verified after that flight landed) or objectstack-ui: the audit's incidental falsehood 2 (the ui claim that unknown placeholders "silently pass through as literal strings", contradicted by
packages/core/src/utils/filter-tokens.ts:378) is already gone frommain— the ui flight landed as 54a1125 and now points atrules/filters.md, which states it correctly.Gates
Head sha 49de20f, a merge of current
origin/main(a98b61b) into the branch, so the derivation is not from a stale tree. Exit codes captured before any pipe.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsre-derived over the now 6-path change set: 42 commands — six more than rounds 1-2, pulled in byscripts/role-word-baseline.json:check:bash32-floor,check:cli-command-ids,check:entry-guard,check:parse-guard,check:pnpm-filter-targets,check:watch-hint-literal. All 42 were run at 49de20f:node scripts/check-test-completeness.mjsexits 3 with "PREREQUISITE NOT MET — this gate grades a savedturbo run testlog, and no log was named … record this gate as NOT MEASURED. ⛔ It is not a red." Recorded as NOT MEASURED, not as green.The gate CI caught, now green, plus the six newly-pulled families:
The five card-named gates, at the same head:
(The ratchet's file count and Leg 2's binding count both dropped by one against round 2 because the data flight landed on
mainbetween the rounds and deletedskills/objectstack-data/rules/hooks.md; nothing in this PR moved either number.)check:skill-examplesran against a freshly rebuilt@objectstack/spec+@objectstack/client-reactclosure (pnpm --workspace-concurrency=2 --filter '@objectstack/spec...' --filter '@objectstack/client-react...' build, exit 0), so it refused nothing rather than false-greening on a stale dist. Heavy steps ran underscripts/pm/os-verify-lock.shwithOS_VERIFY_LOCK_SLOT=issue-14302.Why round 2 shipped a red: round 2 re-ran only the five card-named gates instead of re-deriving the union, and
check:role-wordis reached by the prose paths rather than by the card's list. Round 3 runs the whole re-derived list, which the new baseline file itself widens.skip-changeset
Nothing released by any package changes. The change set is 4 files under
skills/, one generated file undercontent/, and one gate baseline underscripts/;scripts/check-empty-changeset.mjs:357-360enumerates exactly that population:Route 2 taken: no changeset file, label applied.
Out of scope, filed
data/query.zod.ts— the whole QueryAST — "Sort Node", because its first JSDoc block isSortNode's #14441 — QRY-B-05, the spec-side twin:packages/spec/src/data/query.zod.tsopens withSortNode's JSDoc, so the generatedreferences/_index.mddescribes the whole QueryAST file as "Sort Node". Filed unassigned with thefindinglabel after a dedupe pass; the generated artifact is untouched here.No second out-of-scope card is owed from review round 1: the spec
.describe()atpackages/spec/src/data/filter.zod.ts:697was checked against all three faces and is accurate.🤖 Generated with Claude Code
https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1