Repository navigation
Commit 96451ec
fix(spec): manifest.namespace refusal and TSDoc name the leading-letter rule (#19869)
Fixes #19831
Clause-②: no
`manifest.namespace` is enforced by `^[a-z][a-z0-9_]{1,19}$`, so its
first character must be a lowercase letter. The refusal sentence and the
TSDoc `Rules:` line only stated the length and the charset. `1leave` and
`_leave` satisfied every clause an author was shown, and the regex still
refused them. This PR makes both texts state what the regex enforces.
The regex is byte-identical, so the accept set does not move.
## What changes
| surface | before | after |
|:--|:--|:--|
| `ManifestSchema.namespace` refusal
(`packages/spec/src/kernel/manifest.zod.ts`) | `Namespace must be 2-20
chars, lowercase alphanumeric + underscore` | `Namespace must be 2-20
chars, start with a lowercase letter, and contain only lowercase
letters, digits and underscores` |
| same key, TSDoc `Rules:` line | `2-20 characters, lowercase letters,
digits, and underscores only.` | `2-20 characters, starting with a
lowercase letter; lowercase letters, digits, and underscores only
(1leave and _leave are refused).` |
| `PackageSchema.namespace` refusal
(`packages/spec/src/marketplace/package.zod.ts`). Through it,
`CreatePackageRequestSchema.namespace` and the scaffold-only
`TemplateManifestSchema.namespace` too | byte-identical copy of the old
sentence | the new sentence, byte-identical to the manifest's |
The `package.zod.ts` copy is the second author-facing restatement. The
step-1 sweep below found it in `packages/spec`, triage's grading named
it, and the dispatch authorised fixing it here. If only one copy
changed, the manifest and the publish payload would prescribe two
different rules for one vocabulary. Its in-code comment now says that
the sentence is kept identical as well as the pattern.
The new sentence has no trailing period on purpose. `duplicatePackage`
in `metadata-protocol` appends `. ` after the declaration's message
(`protocol.ts`, the explicit-`targetNamespace` and derived-namespace
refusals).
## Measured, before and after
Each value was parsed through `ManifestSchema.shape.namespace` from
source: `origin/main` `0e90a8d1c5` before, this branch `27704f4cc2`
after.
| value | before | after |
|:--|:--|:--|
| `1leave` | refused, old sentence | refused, new sentence |
| `_leave` | refused, old sentence | refused, new sentence |
| `a` (1 char) | refused, old sentence | refused, new sentence |
| 21-char name | refused, old sentence | refused, new sentence |
| `Leave` | refused, old sentence | refused, new sentence |
| `leave`, `ab`, 20-char name | accepted | accepted |
All refusals are one issue with code `invalid_format`. The verdicts are
identical on both sides. Only the text changed.
## Restatement census (step 1)
This is `git grep` over the whole tree at `0e90a8d1c5`, for the regex
literal, the sentence, `2-20 chars`, `2-20 characters` and `alphanumeric
+ underscore`. CHANGELOGs and release pages are excluded.
| site | states the rule completely? | action |
|:--|:--|:--|
| `packages/spec/src/kernel/manifest.zod.ts` refusal + TSDoc | no (the
leading letter is missing) | fixed here |
| `packages/spec/src/marketplace/package.zod.ts` refusal | no (same
sentence) | fixed here |
| `packages/spec/src/api/endpoint-publish-gate.ts` refusal (`apis:`
without a legal namespace) | yes, it quotes the regex | none |
| `packages/spec/src/kernel/namespace-prefix.ts` code comment `(2-20
chars)` | partial, but it is an internal comment directly above the
regex literal and not author-facing | none |
| `packages/cli/src/commands/package/publish.ts` refusal + docblock |
yes ("a lowercase letter followed by lowercase letters, digits or
underscores") | outside `packages/spec`, none |
| `packages/cli/src/commands/init.ts` docblock | yes, it quotes the
regex | outside `packages/spec`, none |
| `packages/create-objectstack/src/index.ts` docblock | yes, it quotes
the regex | outside `packages/spec`, none |
| `content/docs/api/declarative-endpoints.mdx` | yes, it quotes the
regex | outside `packages/spec`, none |
| `docs/adr/0048-…`, `docs/adr/0121-…` | yes, they quote the regex |
outside `packages/spec`, none |
| `.changeset/19577-duplicate-package-explicit-namespace.md`
(unreleased) | it quotes the OLD sentence verbatim as
`duplicatePackage`'s refusal text | outside `packages/spec`, not edited.
See Acceptance notes |
| `skills/**` | no restatement of the charset rule | none |
| `content/docs/references/**` (generated) | renders the `.describe()`
text only, which states no rule | none |
## Pins of the old sentence (step 4)
This is `git grep` over all of `packages/` (and `examples/`, `apps/`,
`scripts/`) for the sentence and its fragments.
- `packages/spec/src/marketplace/package-namespace.test.ts` pinned it
verbatim twice (`PackageSchema` and `TemplateManifestSchema` refusals).
Both now pin the new text.
-
`packages/metadata-protocol/src/protocol.duplicate-package-target-namespace.test.ts`
reads the sentence from `ManifestSchema.shape.namespace` at run time and
never retypes it. No edit was needed, and it passes against the rebuilt
spec.
- Zero other hits. The sibling `objectui` (`origin/main` `544ecba`) has
zero hits too, with the control word `namespace` hitting.
## Generated artifacts (step 5)
`pnpm --filter @objectstack/spec build` then `check:generated`: all 15
generated artifacts are up to date, and nothing was regenerated or
committed. The `.describe()` text is unchanged, so
`content/docs/references/kernel/manifest.mdx` does not move. The built
`dist/` carries the new sentence in 18 files and the old one in 0.
## Changeset (step 6)
`.changeset/manifest-namespace-leading-letter-refusal.md` is
`"@objectstack/spec": patch`. The reading:
- AGENTS.md Post-Task Checklist step 3 says a bug fix in a released
package takes a `patch`.
- The shipped text of `@objectstack/spec` changes: the built `dist/`
carries the new sentence, so `skip-changeset` does not apply.
- `Clause-②: no`: no authorable key is added, removed, widened or
narrowed, and the regex and accept set are byte-identical.
- `@objectstack/metadata-protocol` ships no changed bytes, because it
reads the message at run time. So it has no changeset entry.
## Tests
All of these ran at `27704f4cc2` unless another commit is noted.
- New pins in `packages/spec/src/kernel/manifest.test.ts`: `1leave` and
`_leave` are refused as exactly one `invalid_format` issue, and the
message matches `start with a lowercase letter`. The same sentence still
names `2-20 chars` and the charset. As a control, `leave`, `crm`,
`my_app_2`, `a1` and a 20-char name still parse, both on the field and
in a whole manifest.
- New pin in `package-namespace.test.ts` ("two gates, one vocabulary"):
all four namespace fields refuse `1leave` with the manifest's exact
sentence. The existing verdict table could not see the prose, and this
pin closes that class of drift.
- `pnpm --filter @objectstack/spec test`: 525 files passed, 15503 tests
passed, 1 todo.
- `pnpm --filter @objectstack/spec typecheck` (tsc + scripts +
`check:test-typecheck`): exit 0, and the debt ledger is unchanged.
- `@objectstack/metadata-protocol`: built its dependency closure, then
`pnpm --filter @objectstack/metadata-protocol test` ran 188 files passed
and 3 skipped, and 2673 tests passed and 19 skipped. The verbose run of
`protocol.duplicate-package-target-namespace.test.ts` passed 13/13.
- **Firing control**, at `48ea3d9960` (same `packages/spec` tree):
`scripts/ablation-replace.mjs` put today's sentence back through an
anchor that must hit, with a shell trap restoring from `HEAD`.
- Leg 1 changed `manifest.zod.ts` only. It went red, 4 failed / 85
passed: both leading-letter pins, the charset pin, and the four-field
sentence pin.
- Leg 2 changed `package.zod.ts` only (drift between the two
declarations). It went red, 3 failed / 86 passed: the four-field
sentence pin and both verbatim pins.
- Restored, it went back to green, 89/89. The restore was proven by blob
hash equal to `HEAD` and an empty `git diff HEAD` for both files.
Nothing was left in the tree.
- Lint was narrowed to the 4 touched `.ts` files: `eslint
--no-inline-config --format json`, 4 files reported, 0 errors, 0
warnings. The population comes from eslint's own `isPathIgnored` (false
for all 4). The config never enables type-aware linting (no
`parserOptions.project`), so this diff cannot change a verdict on an
untouched file.
## Gates
`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` at `27704f4cc2` derived 83 commands. All 83 ran, and `--ran`
reconciles them as 83 run, 0 NOT-MEASURED, 0 UNRUN. Three gates first
exited 3 (PREREQUISITE NOT MET, workspace `dist/` absent):
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:type-check-debt`. After `turbo run build --filter='./packages/*'
--filter='./packages/*/*'` all three exit 0. Six roster gates in
directories this diff touches also exit 0: `check-changeset-fixed`,
`check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`,
`check:error-code-casing` and `check:filter-alias-parity`. The spec
`repo` vitest project and the CI-only jobs are left to CI.
This branch is based on `0e90a8d1c5`. `origin/main` is 4 commits ahead
(`2bbb462335`). None of them touches a file in this diff, and their diff
has zero hits for the sentence.
## Acceptance notes
- **Sibling key, same class, not fixed here.** The dispatch rules out
every other `ManifestSchema` key. `manifest.id` /
`PackageSchema.manifestId` (`manifestIdRefusal`, same file) say
"lowercase dot-separated segments; hyphens allowed inside a segment,
underscores are not.", but the pattern also requires each segment to
open with a letter. Measured at `0e90a8d1c5`: `com.example.2app` and
`com.2example.app` are refused with exactly that sentence and no
suggestion. They satisfy every clause it states. This is reported for
the seat to file.
- **The unreleased changeset
`.changeset/19577-duplicate-package-explicit-namespace.md` (line 15)**
quotes the old sentence as the text `duplicatePackage` returns. After
this PR that quote describes text the runtime no longer produces. It is
outside `packages/spec`, so it is not edited here. This PR's own
changeset states the new sentence and names `duplicatePackage`, so the
same release carries the correction. The replacement for the quoted
fragment is `Namespace must be 2-20 chars, start with a lowercase
letter, and contain only lowercase letters, digits and underscores`.
- PR #19637 edits the `version` key and the imports of
`manifest.zod.ts`, and PR #19789 regenerates `manifest.mdx`. This PR
touches neither region and moves no generated file.
---
_Generated by [Claude
Code](https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent fd920ca commit 96451ec
5 files changed
Lines changed: 85 additions & 9 deletions
File tree
- .changeset
- packages/spec/src
- kernel
- marketplace
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
675 | 675 | | |
676 | 676 | | |
677 | 677 | | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
379 | 379 | | |
380 | 380 | | |
381 | 381 | | |
382 | | - | |
| 382 | + | |
| 383 | + | |
383 | 384 | | |
384 | 385 | | |
385 | 386 | | |
386 | 387 | | |
387 | 388 | | |
388 | 389 | | |
389 | 390 | | |
390 | | - | |
| 391 | + | |
391 | 392 | | |
392 | 393 | | |
393 | 394 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
100 | | - | |
| 100 | + | |
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
| |||
157 | 157 | | |
158 | 158 | | |
159 | 159 | | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
160 | 177 | | |
161 | 178 | | |
162 | 179 | | |
| |||
208 | 225 | | |
209 | 226 | | |
210 | 227 | | |
211 | | - | |
| 228 | + | |
212 | 229 | | |
213 | 230 | | |
214 | 231 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
195 | 195 | | |
196 | 196 | | |
197 | 197 | | |
198 | | - | |
199 | | - | |
200 | | - | |
201 | | - | |
202 | | - | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
203 | 204 | | |
204 | 205 | | |
205 | 206 | | |
| |||
0 commit comments