Conversation
Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
…r vitest Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 6 package(s): 16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 3 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 144 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f38d916596beb104ab477b101d42404e709525dd && git checkout f38d916596beb104ab477b101d42404e709525dd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 500c1b56956537dd13dc6b2129dd2fb428da351c e9d4431a2d8f64c2cbc63caa299912dd154a6543 && git checkout -B drift-repro 500c1b56956537dd13dc6b2129dd2fb428da351c && git merge --no-ff e9d4431a2d8f64c2cbc63caa299912dd154a6543
node scripts/docs-audit/affected-docs.mjs --json 500c1b56956537dd13dc6b2129dd2fb428da351c
|
This PR is red on purpose, and it is blocked on one maintainer decision — not on an unhandled defectWhat is failing, read from Why. The card's ruling adopts The reachability question that decides whether those 21 matter was answered by a failing probe with a lit control, before the change: So Why nothing was done about them. The dispatch order for this card was explicit: if the 21 turned out to be real, stop and report — ⛔ do not rewrite twenty-one fixture ids on an implementer's own authority, and ⛔ do not weaken the regex. Both prohibitions still hold. Weakening is additionally refused by the ruling itself, which requires the pattern 「declared once and referenced from both sites so the two cannot drift again」; admitting underscores on one side re-opens exactly the drift the card exists to close. What is needed. One decision, and it is the maintainer's because the ruling's own population statement is what turned out to be wrong:
The decision is recorded on the card at #17534 and is with the maintainer now. This PR stays draft with no auto-merge and no enqueue until it is answered; the rest of the lane's queue keeps moving. Not blocked, and already fixed in this PR — a second premise that turned out to be real: every Generated by Claude Code |
…omain manifest ids `manifest.id` now enforces the reverse-domain rule, and 21 fixture declarations under `packages/qa` carried an underscore in their last segment. 20 of them are renamed here, underscore to hyphen, exactly the repair the refusal message itself suggests. The 21st, `com.dogfood.flow_fixture` in `packages/qa/dogfood/test/fixtures/flow-touch-fixture.ts`, is left alone: it is not a single-occurrence declaration. The same literal appears twice more in `packages/spec/src/kernel/manifest.test.ts`, as the sample input for the "repairs a dotted value in place" refusal-message assertions. The rename mandate covers single-occurrence declarations, so that one is reported rather than swept. `test/flow-node.dogfood.test.ts` therefore still refuses at boot. Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
`com.dogfood.flow_fixture` was held back in the previous commit because the literal also appears twice in `packages/spec/src/kernel/manifest.test.ts`, so it was not a single-occurrence declaration and the mechanical rename mandate did not obviously cover it. Those two hits are deliberate NEGATIVE TEST INPUT, not references to this fixture: line 643 asserts the refusal for that value suggests `com.dogfood.flow-fixture`, and line 653 feeds it alongside `blank`, `my_app` and `support_desk` as a batch of ids the schema must refuse. They are left exactly as they are — renaming them would delete the test of the rule this card adds. The new id is the one the refusal itself prescribes for this value, the same underscore-to-hyphen repair the other 20 took. `manifest.test.ts` stays green (66 passed) with the fixture renamed, which is what shows the two are independent. Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
Status refresh — the blocker changed, and my earlier comment on this PR is now the wrong description of itMy comment Measured at head The fact that decides how serious this is: of the 63 from the first pass, 62 are in a Current CI, head Why nothing further is being pushed. Renaming ~46 more fixtures across seven additional packages is a scope decision, not a repair — and an hour ago I wrongly told the implementer to fold two The full measurement and the question now with the maintainer are on the card: #17534 (comment) This PR stays draft, with no auto-merge and no enqueue, until that is answered. The 21 ruled renames stay as they are — they are correct and they are landed on this branch. Generated by Claude Code |
…pellings
The maintainer ruled that the fixtures outside `packages/qa` are renamed in
this PR too. Each new id is the one the schema's own refusal prescribes for
the old value, so the tree and the diagnostic agree.
Renamed (18 ids, 16 files): `packages/cli` 14, `packages/lint` 3,
`packages/metadata` 1, `packages/plugins/plugin-dev` 3 (see below), plus the
docblock noted last.
Three of these were NOT single-literal swaps, and are called out because a
naive rename would have silently deleted what the test pins:
* `dev-i18n-packages-reader.test.ts` builds a dependency CYCLE between two
packages, and the `dependencies` keys address the ids BY NAME. The ids and
both dependency keys are renamed together; renaming only the ids would
have dissolved the cycle and left the test asserting nothing. It still
reports `Circular dependency detected`.
* `metadata-type-schema-gate.test.ts` and `format-zod-union.test.ts` and
`authoring-rule-command-parity.test.ts` each carry a `namespace` that
repeats the old id, and the first also carries a route path
`/api/v1/apps/gate_probe/things`. Only `manifest.id` is renamed —
`namespace` has its own rule and the path pairs with it.
* `plugin-dev/src/index.ts` is a DOCBLOCK example, not a fixture: prose
teaching `manifest: { id: 'my-app' }`, an id its own schema now refuses.
It cannot red a test; it is corrected because it is published teaching.
Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>
…ges/qa
19 more ids, each the target the schema's own refusal prescribes for the old
value, rewritten at the exact offset of the `manifest.id` literal so that a
`namespace`, a `name` or a route path repeating the old spelling is left alone.
Scope of THIS commit is deliberately the single-occurrence ones. Every id here
occurs exactly once in its own file, so the rename cannot silently unhook an
assertion that addresses the old value.
Not included, and reported rather than guessed:
* 15 ids whose literal occurs MORE than once in its own file, so renaming
the declaration alone would leave assertions naming the old value —
`pkg-a` (7 occurrences), `test` (6), `a` (6), `my-app` (5), `demo` (3) and
ten more. These need a coordinated rename of the id and every reference,
which is reading work per file, not a mechanical swap.
* `com.test.14397` — the schema offers NO suggestion for it, because the
failing segment starts with a digit and no mechanical repair exists.
* `artifact-granted-permissions.test.ts` — not a rename at all: it pins the
ADR-0130 D4 door ORDER, that `''` passes the schema and is caught one door
later. The new rule moves the refusal to door 1, so no conforming id can
preserve what the test pins.
Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>
The last 16, each read before it was touched. Every replacement is anchored on
the `id:` key, so a `name`, a `namespace` or a field named the same is left
alone, and each file's occurrence count was asserted before and after.
Reading first is what made these safe, and two would have gone wrong without it:
* `artifact-collections.test.ts` has `'a'` six times, but only two are the
package id — the other four are a FIELD named `a`
(`{ a: { name: 'a', type: 'text' } }`). A whole-file swap would have
renamed a field the assertions address.
* `package-registry.test.ts` and `lint-protocol-range.test.ts` repeat the old
id as a `namespace`, and `serve-host-config.test.ts`,
`metadata-collection.test.ts` and others repeat it as a free-form `name`.
`namespace` has its own rule and `name` has none; neither is renamed.
Where the repeat WAS a real reference it moved with the id: all seven `pkg-a`
in `packages-capability-gate.test.ts` (package record ids and manifest ids in
the same mock), both in `packages-uninstall-envelope.test.ts`, both `test-app`
registrations in objectql, and both `locale-producer-app` sites in runtime.
`com.test.14397` becomes `com.test.card-14397`: the schema offers no suggestion
for it, because the last segment starts with a digit, so the name was ruled
rather than derived. `ManifestSchema` confirms the replacement parses.
Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
Co-authored-by: Claude <noreply@anthropic.com>
…nifest-id-reverse-domain-regex
…tree The os-regen merge sequence's collection commit. `api-surface/kernel.json` and `export-origins/kernel.json` were edited on BOTH sides, so the merge driver ran, exited 0 and silently kept one side; step 2 took main's side and this commit carries the regeneration from the merged tree, built first so api-surface reads a dist that matches src rather than reporting phantom removals. Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6 Co-authored-by: Claude <noreply@anthropic.com>
Part of #17534 — this lands the ruled change on the authoring contract, the refusal, both scaffolders and the ADR-0087 entry. The half it deliberately leaves open is named below under The one blocker, and needs the seat's word before it can be written; #17534 remains open until that half lands.
What the ruling asked for, and what is here
1. One declaration, referenced from both sites.
MANIFEST_ID_PATTERNis exported frompackages/spec/src/kernel/manifest.zod.ts;ManifestSchema.idandPackageSchema.manifestIdboth reference it.manifest.test.tsholds a per-field verdict table across the two, so a future edit to either has to break a test to drift. The.describe()moves to "must match reverse-domain notation" in the same commit as the regex — #17921's self-contained half, the wording constraint, is satisfied by construction.2. Refusal with a remedy (#4001). The message names the key, echoes the value, shows both documented examples, and prescribes a repair it has already checked against the pattern. Built on the existing
formatSuggestionfromshared/suggestions.zod.ts— no new message builder.The suggestion arm is conditional on purpose: a bare word gets
com.example.NAME; a value that already carries a dot gets its underscores hyphenated in place; a value nothing mechanical rescues gets no suggestion rather than a wrong one. Every candidate is tested against the pattern before it is offered — #17461's lesson applied to the remedy, not only to the examples.The two
@examples were verified first, as the ruling required.com.steedos.crmandorg.apache.supersetboth match. They are no longer a comment:MANIFEST_ID_EXAMPLESis the one list the TSDoc, the refusal and the pin all read, so an example that fails its own rule is now a red test rather than bad advice.3. Both scaffolders derive a conforming id. The bundled template ships
com.example.blank, andcreate-objectstackinterpolatesderiveManifestId(projectName).os initderives from the project name too. The second half prerequisite 4 named is real and is fixed here:os init my-appsanitizes the namespace tomy_appand every template interpolated that namespace into the id, producingcom.example.my_app— refused by this very schema. One project name, two identifiers, contradictory rules; neither can be read off the other. Pinned from both sides against the importedMANIFEST_ID_PATTERN, never a restated regex.4. Migration. ADR-0087 semantic entry
manifest-id-reverse-domain-required— a structured TODO, no automatic rename, because the value IS the identity: the registry addresses a package bymanifest_id, an installed row is keyed on it and a dependent declares it. The changeset carries the matching disposition marker and shipsminorunder the launch-window convention. Sub-question (1) is answered in the entry's ownreasonfrom the TSDoc's words: the rule is "unique across the entire ecosystem", so a package that is never published is still named for the ecosystem it may join.The one blocker — measured, not assumed
The ruling states "the one non-conforming id in the tree is the scaffold itself". That is contradicted by measurement.
First, the question nobody had measured — does
manifest.idreachManifestSchemaat authoring time, or only on the publish path? A failing probe, with a lit control on an existingManifestSchemaconstraint:The control proves the instrument:
manifestreally is parsed throughManifestSchemainsidedefineStack(). So the answer is yes, they parse — and with the regex in place, B and C refuse.Second, the population. Every
manifest: { id: ... }literal in the tree, run through the builtManifestSchema: 21 files underpackages/qaare refused, each for the same reason — an underscore in the last segment, whichmanifest.namespaceadmits and an id segment does not.Each fixture calls
defineStackat module top level, so the refusal is an import-time throw: seven of them were confirmed that way directly, the rest through the built schema per literal (the remaining fixtures import@objectstack/plugin-security, whosedistthis measurement did not build — that is NOT MEASURED at module level, and is why the per-literal instrument is the one quoted).They are not renamed here, and the regex is not weakened to admit underscores.
packages/qais a domain this card did not declare, and twenty fixture ids is a different PR from the one that was ruled. So this PR is red on the dogfood gate until the seat rules on the rename. Everything else is green; the gap is exactly these 21 files.What makes the rename cheap, if it is authorized: each id occurs exactly once in the tree — its own declaration.
git grep -Fover all twenty-one returns one hit each, so there is no installed row, nodependenciesentry and no snapshot pinning any of them. The transform is_to-in the last segment, and the refusal already prints the target value for each one.Two apparent counterexamples outside
packages/qaare not manifest ids, and nothing needs doing about them:packages/cli/src/commands/migrate/meta.stored-flow-resolution.integration.test.ts:62is a flow NODE id, andpackages/objectql/src/plugin.integration.test.ts:259is a plain object handed toctx.registerService, never parsed by this schema.In-package fixtures repaired here
The same defect existed inside the packages this card does own, and those are fixed — mechanical, single-literal, and each fixture keeps pinning exactly what it pinned before: three bare ids in
packages/spectests (p,acme), four inpackages/clitests (todo,demo,bad,a/b), and the five golden corpus entries inpackages/cli/src/lint/corpus.ts. That last one is not a fixture: it is the worked-example set the metadata generator imitates, and it was teaching bare-word ids.packages/create-objectstack/vitest.config.tsgains an anchored source alias for the one spec subpath its tests now import, becausepnpm check:test-source-aliascorrectly refuses a pin whose verdict is a function of build state.Not touched, deliberately
docs/adr/0087-metadata-protocol-upgrade-contract.md— the registration this gate wants is the changeset's disposition marker plus the entry file, both present. Editing the ADR would make this a governed-surface diff for no gain. Also untouched: every file in open PR #18198,packages/spec/src/shared/retired-key.ts,packages/spec/src/ui/chart.zod.ts.Verification
Run at
d0d859edbc, the final commit.node scripts/pm/dispatch-gates.mjs --ranpnpm --filter @objectstack/spec testpnpm --filter @objectstack/spec typecheckpnpm --filter @objectstack/cli exec vitest run --project unitpnpm --filter @objectstack/cli typecheckpnpm --filter create-objectstack testpnpm --filter create-objectstack typecheckpnpm lint(repo-wide,eslint . --no-inline-config)pnpm --filter @objectstack/spec check:generatedapi-surface,export-origins,content/docs/referencespackages/qadogfood tierpackages/cli's integration tier is declared to CI: the diff touches no spawn entry, nobin/, notest/helpers/serve-process.tsand no driver or kernel boot path.Generated by Claude Code