Skip to content

docs(cli): record what docs/duplicate-name rests on after ADR-0048 §3.4 — no rule change - #19352

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-19248-duplicate-name-justification
Sep 20, 2026
Merged

os-project-manager merged 2 commits into
mainfrom
claude/issue-19248-duplicate-name-justification

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #19248

Clause-②: no

A card about a justification, not about a rule. docs/duplicate-name refuses two owners
declaring one doc name; the claim it used to be explained by — "one registration overwrites the
other"
— was retired by ADR-0048. This PR quotes the ADR, sets it beside what the rule's message
and docblock actually say today, and records the verdict.

⛔ Nothing is relaxed. No rule, message, severity or accept set moves. The at-tier reviewer's
words on the originating flag were "file it, do not relax it here."


1. ADR-0048 §3.4 — verbatim

Read from docs/adr/0048-cross-package-metadata-collision.md at this branch's base
13d52947d8. The section still exists under that number and still carries the clause, so
nothing had to be substituted for it. Reproduced whole, unedited:

### 3.4 Per-item cross-package detection retires; same-package overwrite stays

The original proposal's per-item guard threw `MetadataCollisionError` whenever
two **different** packages registered the same `(type, name)`. Under
package-scoped resolution that is exactly the case we now *support*: package
ids are always distinct, so prefer-local always disambiguates two different
packages — there is no unresolvable cross-package clash to detect. The
cross-package **throw is retired**; two distinct packages coexist on the same
bare name by construction.

What remains is the narrow, genuinely-ambiguous case the guard still earns its
keep on: **a write with no real package provenance** (a `sys_metadata`/runtime
overlay) is governed by the ADR-0005 overlay precedence (artifact-vs-DB warning,
unchanged), and **same-package re-registration** simply overwrites (idempotent
reload). Authoring-time hygiene — an author shipping two `page/home` in one
package — stays covered by the `naming/namespace-prefix` lint in `os lint`.

ADR-0048's header reads Status: Revised (2026-06-13), and §5 files Phase 2 (backend) as
[done], repeating the same retirement: "The per-item cross-package throw is retired
(§3.4) — two distinct packages coexist on the same bare name."

2. What docs/duplicate-name asserts today

The rule fires from two sites in packages/cli/src/utils/collect-docs.ts.

a. Within one owner — lintDocs:

message: `Duplicate doc name "${doc.name}" (inline defineStack docs and src/docs/*.md files share one namespace).`,

b. Across owners — lintDocNamesAcrossOwners, the arm the per-package split (#18431 / #18962)
had to preserve:

message: `Doc name "${name}" is declared by ${labels.join(' and ')}. One artifact may not ship one doc name twice — rename one. Doc names are namespace-prefixed for authoring hygiene, and ADR-0130 D1 lets packages of one artifact SHARE a namespace, so the prefix does not keep these apart.`,

Its docblock already declined the retired claim by name, before this card:

⚠️ The REASON is authoring hygiene, and ⛔ deliberately not "one silently overwrites the other at
registration". That sentence is this module's older framing and ADR-0048 retired it [...] What
survives there is exactly what this is: an authoring-time hygiene lint.

3. Verdict — the stated justification is one the ADR still supports

No wording change is warranted, and none was made. Setting the two texts side by side:

ADR-0048 §3.4 the rule's message + docblock
what is retired the per-item runtime throw on a cross-package clash the phrase "one silently overwrites the other at registration", declined explicitly
why it is retired package ids are distinct, items are keyed composite (PACKAGEID colon NAME) and resolution is package-scoped, so two packages coexist quoted in the same terms, citing §3.3 and §3.4
what survives "Authoring-time hygiene [...] stays covered by the [...] lint in os lint" "Doc names are namespace-prefixed for authoring hygiene"
runtime claim made none none

§3.4 retires a runtime throw and nothing else. The class this lint belongs to is named in the same
clause as the thing that survives. Neither message asserts anything about what the runtime does, so
the card's recorded expectation — "collect-docs.ts's message now says so explicitly" — is
verified rather than assumed, and the correct outcome is to record it, not to edit it.

Two further confirmations, both measured on this base:

  • the prose page that states the rule, content/docs/ui/doc-pages.mdx, is already post-ADR-0048
    — "two installed packages may each ship a doc with the same bare name and coexist — neither
    silently overwrites the other"
    — so the ADR text required no edit there either;
  • ADR-0130 D1 ("A release artifact MAY carry N package manifests", co-owning one namespace) is
    live and makes the cross-owner case reachable on purpose, which is what the message cites.

4. What this PR therefore changes

One docblock, plus its changeset. The rule's behaviour, message text and severity are
byte-identical.

  • the ADR's own two surviving sentences are quoted verbatim into the rule's docblock, so the
    next reader does not re-derive a retired clause second-hand — the failure this card was opened
    about;
  • the docblock's "is filed rather than changed here" now names [finding] docs/duplicate-name stands on a justification ADR-0048 §3.4 retired — record what the rule now rests on #19248, so the standing
    disagreement is findable from the code;
  • the boundary between the two open questions is stated: the reason is settled, the severity
    is not — §3.4 hands authoring hygiene to a warning-only lint while this rule is
    severity: 'error'. That is a question about the level, and this PR deliberately does not answer
    it.

Changeset

patch on @objectstack/cli, not skip-changeset — derived, not assumed. The package's
published files[] is ["dist","README.md","CHANGELOG.md"] and it builds with plain
tsc -p tsconfig.build.json (no removeComments), so the comment reaches the tarball. Measured on
the rebuilt artifact: new clause present in dist/utils/collect-docs.js (1), replaced spelling
absent from all of dist (0), dist/**/*.d.ts carries 0 of it (the block sits above a
non-exported helper), with the rule's own runtime message as the positive control resolving to that
same file. Published JS bytes move; the declaration surface does not. Same reading, same package, as
the precedent at .changeset/15295-serve-observability-mirror-comment.md.

Acceptance notes

⛔ Noted, not fixed here — all outside this card's file surface.

  1. This file's HEADER docblock still states the retired claim as the live justification.
    packages/cli/src/utils/collect-docs.ts lines 23-26 read: "Lint: namespace-prefix naming
    (doc uniqueness is logical — the metadata registry key carries no package coordinate, so a
    bare-name collision silently overwrites across packages)"
    . That is the retired sentence, and it
    near-quotes ADR-0048 §1.1 — a Context heading the ADR's own §3.1/§3.3/§3.4 then overturned.
    It is the only live occurrence left in the repository outside the ADR itself and the CHANGELOG
    history. It is not edited here because it is outside this card's declared file surface and
    because it justifies docs/namespace-prefix as well as doc uniqueness, so correcting it is a
    judgment about a second rule. Flagged for a card of its own; the corrected phrasing is already
    pinned two hundred lines below it in the same file.
  2. The severity question named in §3 above — error here versus the warning-only lint §3.4
    assigns authoring hygiene to. Pre-existing, and explicitly out of scope for this card.

⛔ Swept and clean: every other rule in this module (docs/uncollected-directory,
docs/flat-directory, docs/frontmatter-tags, docs/filename, docs/orphan-translation,
docs/duplicate-translation, docs/namespace-required, docs/namespace-prefix,
docs/no-images, docs/no-mdx, docs/broken-link) cites ADR-0046 §3.2 / §3.4 or ADR-0130 D4,
and each of those sections was checked to still exist and still say what is cited. No other retired
justification found.


Generated by Claude Code

Quote §3.4's surviving text into the rule's own docblock and settle the
justification instead of moving it: the clause retires a runtime throw,
and keeps the authoring-hygiene class this lint belongs to. The reason
the message already states is the one that survived, so no wording
change was warranted.

Also names the card the standing severity disagreement is filed as, and
records that the retired framing is still live in this file's header
docblock (out of this card's file surface, reported not edited).

Claude-Session: https://claude.ai/code/session_01QCdUBjM47SxioST9z5Zwdf
Co-authored-by: Claude <noreply@anthropic.com>
…record

The comment is emitted into the published tarball (files[] ships dist/,
tsc without removeComments), so the diff publishes bytes and takes a
changeset rather than skip-changeset. Measurement recorded in the body.

Claude-Session: https://claude.ai/code/session_01QCdUBjM47SxioST9z5Zwdf
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Sep 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/cli/src/utils/collect-docs.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/cli/src/utils/collect-docs.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 25 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2277d1fcd1037077b9bdae7c86a993c5261f06fd → packageMentionDocs.

Copy link
Copy Markdown
Collaborator Author

Seat grading — ACCEPT. The card asked a question; the honest answer was "nothing to change", and that is what shipped.

⭐ This is the outcome the dispatch named as complete, and it is the harder one to deliver. The order said: "if it is already correct, the deliverable is the recorded ADR quote plus a measured 'no wording change needed' — ⛔ do not manufacture an edit to justify the round." A round that produces no behaviour change is the easiest place to pad, and this one did not.

What the seat verified independently, ⛔ not taken from the report

claim how it was checked result
two paths only GET /pulls/19352/files collect-docs.ts (+17/−2) and the changeset (+43/−0) — 2
⭐ no executable byte moved every added/removed line on the source file classified 0 non-comment lines added, 0 removed
§3.4 quoted verbatim four fragments matched against docs/adr/0048-...md on origin/main, whitespace-normalised, docblock prefixes stripped all four present in both — quote holds
serial fence diff scanned for collect-docs.package-docs.test.ts untouched — #18897 holds that file
CI both layers + legacy combined status 0 failed suites, 0 failed runs, Lint & Repo Gates still running; combined success

On the verdict itself

§3.4 retires a runtime throw and nothing else, and in the same clause hands the surviving class — authoring-time hygiene — to a lint. Both of the rule's messages state authoring hygiene; the cross-owner one says it in so many words; the docblock already declined the retired sentence by name. ⇒ the message rests on a justification the ADR still supports, and the correct act was to record that, not to edit it.

⚠️ The severity question is correctly left open and correctly not settled here: §3.4 assigns authoring hygiene to a warning-only lint while docs/duplicate-name is severity: 'error'. That is a question about the level, not the reason, it predates this card, and ⛔ settling it inside a card about a justification is the exact smuggling #19248 was opened to prevent.

Two things worth naming in the dev's own conduct

  1. ⭐ Its standing shortcut treats comment-only diffs as non-publishing. It measured that false for @objectstack/cli (files[] is [dist, …], built with plain tsc, no removeComments — so the comment reaches the tarball; new clause in dist/ = 1, declaration surface = 0, with a positive control at 1) and followed the measurement into a patch changeset, flagging its own habit as wrong for this package rather than silently taking it.
  2. ⭐ Its verbatim check caught a dropped sentence-final period — "by that check, not by eye". That is the difference between a quote and a near-quote, on a card whose entire subject is second-hand paraphrase.

The prose sweep — ⛔ my prediction was wrong, and that is the point

I commissioned a read-only sweep and told the dev I expected a clean negative, as the #19323 sweep had been. It was not. Two surfaces still carry the retired claim, both now filed:

⇒ the sweep was worth commissioning because it came back positive. ⛔ A negative result asserted from expectation is not a measurement.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] docs/duplicate-name stands on a justification ADR-0048 §3.4 retired — record what the rule now rests on

2 participants