ci(docs-drift): state the same-name-different-shape trap where the hand-grep remedy is prescribed - #19110
Merged
os-bill merged 1 commit intoSep 18, 2026
Conversation
…nd-grep remedy is prescribed The advisory's fold already prescribes a hand re-read for the rule-carrying blind spot. A hand re-read is a name-based search, and a name does not identify a key: the same spelling is authorable on one governed type and a `[REMOVED]` tombstone on another for `active`, `aria`, `joins`, `objects`, `template`, `tools` and `version`. Nothing in a search result tells the two apart, so a grep hit on a live example reads as evidence about the dead key — measured on #19059, where content/docs/ai/agents.mdx was reported as contradicting the `agent.tools` tombstone over an example inside a `defineSkill({` block. The remedy's own failure mode now renders beside the remedy, unconditionally, with the censused names as the payload and the discriminating parse as the fix. Pinned byte-exact on every case in check-drift-comment.mjs, the same shape the neighbouring unconditional line takes. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 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): |
os-bill
marked this pull request as ready for review
September 18, 2026 20:39
This was referenced Sep 18, 2026
os-bill
deleted the
claude/issue-19093-tools-name-collision-discriminator
branch
September 18, 2026 21:05
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #19093
Clause-②: no — the diff is workflow prose plus its pin. No schema, no accept set, no public surface.
What this lands
The Docs Drift Check's fold already carries one unconditional boundary line, and it ends by
prescribing a remedy: "a rule your change carries has to be re-read by hand in the pages
that restate it". That remedy is a name-based search, and this card is about the fact that a
name does not identify a key. So the remedy's own failure mode now renders beside the
remedy, on every run, with the censused names as its payload and the discriminating parse
as its fix — plus a byte-exact pin in
scripts/docs-audit/check-drift-comment.mjs, the sameshape the neighbouring unconditional line takes.
The census — first deliverable, and it chose the site
Measured on this branch by walking the liveness ledger's governed types through the same
registry and the same shape helpers
check-liveness.mtsuses, and reading each key's[REMOVED]marker. Names, never a count — on this card a count is structurally not areading, because the whole defect is that a count cannot tell two identically-named keys apart.
A key tombstoned on one governed type and authorable on another:
activeflowagent,skill,datasource,app,email_template,validation,sharing_ruleariadashboard,appaction,pagejoinsqueryanalytics_cubeobjectsapppermission,translation,manifesttemplateflowpagetoolsagentskillversionappflow,manifestSo
toolsis one of seven, not a singleton — which settles the card's "what is NOTestablished: how many other keys carry the same collision".
Radius of the instrument. Top-level keys of the governed types, resolved through
getMetadataTypeSchema()plus theSPEC_ONLY_SCHEMASoverrides, tombstone detected as a[REMOVED]description prefix. Two things follow from that radius and both are stated in thelanded text:
timeoutMsistombstoned at
flow.nodes.waitEventConfig.timeoutMswhileflow.nodes.timeoutMsisauthorable — a collision between siblings on one type); depth 3 and beyond adds only
query.expand.*self-recursion, so depth 2 is where the widening stops being new..strict()guidance-map route instead of
retiredKey()leaves no tombstone in the walked shape atall, so this instrument cannot see it in either direction.
tool.categoryis retired thatway (
TOOL_RETIRED_KEY_GUIDANCEinpackages/spec/src/ai/tool.zod.ts) whileAIToolDefinition.categoryinpackages/spec/src/contracts/ai-service.tsstays live and isdocumented as carrying
action.ai.category— the identical trap, invisible here. The sameroute hides a second instance of a name this census already flagged:
tool.active.The discriminating parse, re-derived first-hand
The card records that the dual-parse was measured by the reporting dev and not re-run by
the filing seat. It has now been re-run on this branch, against
SkillSchemaandAgentSchemaimported from source, with the object literal taken verbatim from
content/docs/ai/agents.mdx:155-162.as a
SkillSchema: PASS.as an
AgentSchema: FAIL, with an issue at["tools"]whose message opens, verbatim:(One edit to that quotation: the message spells its placeholder with angle brackets, which
this repo's body-sanitizer discipline keeps out of a PR body — it is written
action_ACTIONNAMEabove. Everything else is byte-for-byte what the parse emitted.)
One correction to the card, from that re-run. The card says the agent parse fails "with
exactly one issue". On the literal as written on the page it fails with two:
["tools"]and
["role"](invalid_type, required and absent —roleis required onAgentSchemaandthe page's skill block naturally has none). The "exactly one" reading holds only once every
AgentSchema-required key is supplied, which is presumably what the reporting dev did. Twocontrols settle it:
role, parsed as an agent: FAIL, exactly 1 issue, at["tools"].toolsdeleted, parsed as an agent: PASS, 0 issues.So
toolsis the sole cause, and the direction is the one the card claims. The substance isconfirmed; only the issue count was off.
Control C, on the card's claimed hard discriminator:
SkillSchemadeclarestriggerConditionsandAgentSchemadoes not. Also worth recording because it is themechanism of the whole trap:
AgentSchemadoes declaretools— the tombstone keeps thekey in the walked shape, which is exactly why a name-based search finds it there.
Page structure, re-read first-hand: the page's only
tools:authoring occurrence is:161, inside thedefineSkill({block opened at:155; the fourdefineAgent(blocks at:256,:292,:325,:361write zerotools.Why this site, and why not the other
The card offers two candidate sites, marked as input and not a prescription. This PR takes the
Docs Drift Check blind-spot text and deliberately does not write the liveness ledger's
_note.measured instance is a reviewer of PR fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059 who grepped
content/docsand read the hit.That reviewer meets the Docs Drift Check comment on the PR, unconditionally, at the moment
the hand-grep method is prescribed to them. They do not open a ledger file to decide whether
a grep hit is real.
agent.toolsopens
agent.json.agent.jsonis held by open PR fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059 and is fenced for this card, sothe only writable ledger site is
skill.json— the half that reader has no reason to open.A discriminator there is a warning posted on the door nobody walks through.
what sends a reader to grep by hand. Documenting that method's known failure anywhere else
splits a prescription from its caveat.
.github/workflows/**is not a PD-14 governed surface, and — measured — it publishesnothing.
packages/spec/livenessis in@objectstack/spec'sfiles[], so the ledgerroute would also have shipped this text into the npm tarball and taken a changeset.
Per the card's fence, exactly one site is written.
Class
Reviewer/tooling trap, not class (c). An author cannot reach the tombstoned key: the parse
door refuses it (the FAIL above is that door), and the ADR-0087
agent-tools-to-skillsconversion deletes it at rehydration. Nothing here is written up as a live authoring trap.
Verification
node scripts/docs-audit/check-drift-comment.mjs:: exit 0 —✓ check-drift-comment: 66 cases pass across 5 fixture diff(s). The new assertion pair runs on all 5 fixture cases,evidenced by the ablations below failing exactly 5 of the 66.
scripts/ablation-replace.mjs(anchor must hit; blob hashes compared against HEAD):joins→JOINS) — mutation landed(
anchor 1 -> 0, blob f050c7c23948 -> 79f4b52e58d0), pin RED on all 5 cases, restoreverified
blob == HEAD (f050c7c23948)andgit diff HEADempty.if (false) limits.push(...)) — mutation landed, pin RED onall 5 cases, restored byte-exact.
node scripts/pm/dispatch-gates.mjs --commands(no hand-written path list): 47 derived,47 run, reconciled with
--ran. 46 exit 0.pnpm check:pm-dispatch-gatesis thechecker's own repo-wide self-test suite and ran past two foreground budgets without
reaching a verdict — recorded as NOT MEASURED, left to CI, never read as green.
check:nul-bytes: no hits.549936fe47.Acceptance notes
Out of scope for this PR, noted and not filed:
no gate reading it is the "code
grepfinds and the runtime never runs" shape from Route &surface ownership §1, and wiring a new gate would be a new verification surface this card
does not authorize. The names are durable in the landed text and in the pin; the method is
written down here. Successor: none — no open PR or queued card walks this file.
packages/spec/liveness/agent.jsonandpackages/spec/liveness/README.mdwere notwritten, per the card's fence: both are held by open PR fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059 and both carry
merge=os-regen, where a conflict resolves silently. Independently re-derived here over all32 open PRs (371 file rows; lit control: docs(spec): state what the map renderer actually does with an undeclared key in
map#19102 holds exactly 3 files) — those two are theonly held paths among this card's candidates.
Generated by Claude Code