Repository navigation
fix(spec): label an any index signature as its own position, and state the direct-parent boundary - #17545
Conversation
`ts.isFunctionLike` is true for every SignatureDeclaration kind, so a bare `[k: string]: any` in a marked block fell into the `return type` arm and was reported at a position it does not occupy. Flagging it is right; the label was not. Adds an `IndexSignatureDeclaration` arm before the function-like fallback, and states the direct-parent boundary in the header so a function type's return `any` inside a type argument reads as the finding it already is. Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com>
Comment reflow only, no behaviour change. Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Seat at-tier review — PASS, zero must-fix. ⏳ Not enqueued: ④ still converging.Head
|
Fixes #14910
Clause-②: no
Re-derived from what this PR ships, not inherited from the claim. The diff is one file,
packages/spec/scripts/check-skill-examples.ts— CI tooling. Measured on this branch: the filecarries 0
^exportlines, so no new exported symbol; and no published payload gains a key— grepping
@objectstack/spec'sfiles[]roster (dist,json-schema,liveness,prompts,llms.txt,README.md,api-surface,spec-changes.json) for a symbol from the changed filereturns 0 files, with the positive control (
HookContext) lighting 34. The baseline thiscard worried about turns out not to exist as an artifact at all (see "Premise re-measured"), so
there is no governed accept set that moves either.
What was wrong
describeAnyPositionkeys a bareany's position on its DIRECT parent node kind. The last armwas
ts.isFunctionLike(parent) && parent.type === nodereturning'return type', andts.isFunctionLikeis true for every SignatureDeclaration kind —IndexSignatureDeclarationincluded. Measured on this tree's TypeScript (6.0.3):
isFunctionLikeparent.type === nodeinterface Bag { [k: string]: any }return type← wrong positionconst fns: Array<() => any> = []return type← correctfunction f(): anyreturn typeconst b: Record<string, any> = {}const r: any[] = []const t: [any, string] = [1, 'x']type D<T = any> = TSo an
anyindex signature was reported at a position it does not occupy. Flagging it is right— an
anyindex signature erases checking on every keyed access — so the fix is a label,not an exclusion: an
IndexSignatureDeclarationarm placed BEFORE the function-like fallback,returning
index signature.The judgement triage placed on this card, and how it was settled
Shape (2) — a function type's return
anyinside a type argument — is IN SCOPE. It staysflagged, labelled
return type, and the arm is unchanged. The header is what was wrong.Settled from the file's own declared rule, not by preference. The
describeAnyPositiondocblockdefines the boundary operationally in the same sentence that names the exclusions:
"Nested" is therefore a claim about the
anyitself — that it is a component of a compositetype, filling no annotation slot of its own — and not a claim about its ancestry. All three
named exclusions share exactly that property, and the table above confirms it: each has
parent.type === nodefalse. InArray<() => any>theanyfills the return slotof a FunctionTypeNode (
parent.type === nodetrue), which the top-of-file SCOPE list alreadynames in so many words: "a variable/property/return annotation". The header and the arm never
actually conflicted; the shorthand "nested
anyis deliberately not flagged" simply reads asancestry when quoted without its operational clause. So there was nothing to stop and escalate on
— the file does give a way to tell which reading is authoritative — and this PR writes that
reading into the header, the docblock and the gate's own failure text.
The four axes agree, and none was the deciding one on its own:
anyis the [docs] runtime-services 的 hook 示例在教ctx.services—— hook 上下文从来没有这个键,照抄的 hook 会拒掉每一次写入 #5720/[spec] HookContext.session 少声明了positions/preserveAudit—— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 defect class verbatim:every call of such a callback is unchecked. Narrowing would hand an author whose block just
went red a one-token evasion (wrap the function type in a type argument) and leave the gate
green over an unchanged defect. The file already refuses precisely that reasoning for
parameters: "a parameter-only rule is defeated by exactly the edit an author reaches for when
it goes red."
parent.type === nodeon the direct parent.The alternative reading requires a second, transitive notion of "nested" that the code cannot
express without an ancestry walk, and the header warns that widening the boundary is "a
different question with a different (much larger) baseline."
means the corpus cannot ship such a shape under an
os:checkmarker; narrowing would create adocumented-but-unenforced hole in exactly the surface AI authors read.
objectstack-ai/objectui#7653, which carriesBlocked-by:on thiscard): the decision is label shape (1), keep shape (2). Add the
IndexSignatureDeclarationarm before the function-like fallback with the label
index signature; do not narrow thefunction-like arm; state the direct-parent boundary in the header. The port should not re-derive
this.
Premise re-measured — one premise falsified
There is no baseline artifact for this gate. The card and the dispatch both plan around
"find the baseline, measure which rows move, regenerate it with the repo's own tooling". That
artifact does not exist. The file says so itself:
Measured rather than taken on the file's word, with a lit control:
packages/speccarriesnine ratchet/baseline artifacts —
authorable-surface.base.json,docs-import-surface.baseline.json,dual-source-exports.baseline.json,entry-nameability.baseline.json,react-declaration-parity.baseline.json,scripts/liveness/key-mention.baseline.json,scripts/liveness/undrilled-containers.baseline.json,test-typecheck-debt.json,unemitted-schemas.baseline.json— and 0 of them name this gate. The gate reads no acceptset at all: its only JSON reads are two
package.jsonreads. It callsfail()on the first hit.So the label is half a row key only in the prospective sense the card meant, and nothing to
regenerate exists today.
The corpus re-measure the card asked for (objectstack's own marked population, never measured
when the card was filed): zero sites, of either shape or any other.
⇒ no verdict changes, no message changes, no row moves. The grade stays
p3.Verification
Ablation — two mutations, both predicted before running, both proven landed on disk, both
restored by blob equality under an
EXIT/INT/TERMtrap. Nodistis involved: the gateruns from source via
tsx, so the mutation's path to the code under test is the file itself.IndexSignatureDeclarationarm1 insertion(+), 1 deletion(-), blob1b8f63a8…≠ HEADb7cf53d6…9 insertions(+), 1 deletion(-), blob0f328d26…≠ HEADAblation B exists because shape (2) was already flagged before this PR, so a leg asserting
only "line 8 is a finding" would pass on the broken code and pin nothing. B proves the leg can
fail, which is what makes it a pin on the boundary rather than a restatement of today's output.
Both legs assert the label string, for the same reason: the bug is the label, so "it is
flagged" is not an assertion about it.
Gates. Families derived mechanically, never hand-listed —
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, change setderived by the tool itself (1 path). Reconciled with
--ran, exit codes recorded per command:check:dual-build-cjs-loadsexits 3, its own "PREREQUISITE NOT MET — this is NOT a pass:nothing was measured" code: it reads built output and 33 packages have no
dist/in thisworktree. Declared to CI, which builds first; not reported as a failing check. All other 53
exited 0.
Tests and type-checking. Every heavy verdict read from
os-verify-lock's ownVERDICT command-exitline, never through a pipe:pnpm --filter @objectstack/spec run check:skill-examples— exit 0 (self-test plus thereal 258-block corpus; re-run green on the final commit).
pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2— exit 0,Test Files 500 passed (500),Tests 13781 passed (13781).pnpm --filter @objectstack/spec typecheck— exit 0, and proven to actually reach thechanged file rather than assumed.
tsconfig.jsonandtsconfig.test.jsonboth useinclude: ["src/**/*"]and list it 0 times; the package'stypecheckchainscheck:scripts-typecheck(tsc --noEmit -p tsconfig.scripts.json), whose--listFilesnamesscripts/check-skill-examples.ts1 time in a 909-file program. A directtsc --ignoreConfig --strictover the file reports 0 diagnostics, with a lit control (aninjected
const CONTROL: number = 'not a number') reddening it atTS2322and then restored,proven by blob equality against HEAD.
eslint . --no-inline-config --format json— exit 0, 6586 files, 0 errors, 0 warnings,the changed file present in the run, measured on the final commit
c23aa7a4. The repo-wide runfit in budget, so no narrowing is claimed and none is needed.
pnpm check:nul-bytesgreen, plus a directgrep -naPover the changedfile for the C0/DEL range — no hits.
No changeset —
skip-changesetNothing published moves, measured above (0 symbol hits across the
files[]roster, positivecontrol lit at 34).
packages/spec/scripts/**ships in no package. This is the case.github/workflows/lint.ymlnames in its own words: "'this PR edits a CI-internal script' isthe textbook
skip-changesetcase — such a PR releases nothing, so by the workflow's ownprescription it takes the label."
验收备注
skip-changeset. Flaggedrather than resolved silently. A changeset here would version-bump
@objectstack/specfor achange that ships in nothing.
MethodSignature,CallSignature,ConstructSignature,FunctionType,ConstructorTypeand the accessors. All of thosegenuinely have a return slot, so
return typeis correct for each — measured, not assumed. Nofurther arm is owed. 承接者: whoever ports this to objectui#7653.
packages/spec's tsconfigs suggestedscripts/**satoutside every tsc program. That reading was wrong and is corrected above —
tsconfig.scripts.jsoncovers it. Recorded because the wrong reading is one a future author islikely to reach independently. 承接者: none.
Reviewer's one-minute read
The behavioural delta is one label string, on one shape that does not occur in the corpus
today. Everything else in the diff is the header, the docblock, the gate's failure text and the
self-test legs — the parts that make the boundary say out loud what the code has always done, so
the next reader of "nested
anyis deliberately not flagged" does not narrow a good arm.⛔ Do not flip this out of draft, enqueue it, arm auto-merge or submit an approving review — the
at-tier verdict is the seat's.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Generated by Claude Code