Repository navigation
Commit 3bd28e2
feat(spec): a package-id segment may open with a digit, and the refusal states the rule the pattern enforces (#20172)
Fixes #19870
Clause-②: yes
Implements ruling `5807946372` on this card (maintainer, verbatim:
「19870 去掉」; re-graded p2 in `5808347937`), items 1 to 5, in the
package-ID region of `manifest.zod.ts` / `manifest.test.ts` only. The
VERSION-grammar hunks that draft PR #19637 carries in the same two files
are untouched; a no-driver merge-tree probe of this head against
`origin/main` `369bcbe` is clean.
## What changes
- **`MANIFEST_ID_PATTERN`** (`packages/spec/src/kernel/manifest.zod.ts`)
becomes `/^[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*)+$/` (item 1).
Additive widening: every id legal before stays legal; `com.163.crm`,
`com.example.2app` and `local.2024-app` become legal. Still refused: a
hyphen-led segment, an underscore, upper case, a single segment, an
empty segment.
- **The refusal sentence** (`manifestIdRefusal`) now reads `... Expected
reverse-domain notation ('com.steedos.crm', 'org.apache.superset') —
lowercase dot-separated segments of letters, digits and inner hyphens; a
segment may not open with a hyphen; underscores are not admitted.` The
pattern docblock loses 「Each segment starts with a letter」 and says why
a digit may open a segment; the `id` TSDoc states the rule (item 2).
`MANIFEST_ID_EXAMPLES` unchanged. A digit-led bare word now gets the
prefixed repair (`2fa` gets `Did you mean 'com.example.2fa'?`), where it
used to get none.
- **Pins** (item 3), `manifest.test.ts`: one `ACCEPTED` / `REFUSED`
table. `com.example.2app` moved to accepted with `com.163.crm` beside
it; each accepted row asserts the id parses to itself.
`com.example.-app`, `com.example.my_app` and `Com.Example.App` stay
refused, each asserting `invalid_format` and the declaration's own
sentence. The `PackageSchema.manifestId` door is judged once on the same
table, the expected verdict read from the table and never from the
pattern. One more pin: the sentence names digits and the leading-hyphen
rule and states no leading-letter clause.
- **`manifest.namespace` is untouched** (item 4). Its leading-letter
rule stays for the SQL-identifier reason.
- **Changeset**: `@objectstack/spec` **minor** (item 5),
`.changeset/19870-manifest-id-digit-led-segment.md`.
## Repo-wide pin sweep (card-class clauses ① and ②)
Swept by RULE, not by literal: every string literal in `git ls-files`
(ts/mts/mjs/js/json/md/mdx/yml, release-owned trees excluded) that the
OLD pattern refuses and the NEW one accepts. That gave 586 raw hits,
triaged by hand to the package-id ones. Every same-semantic pin is
flipped in this PR, and each flipped pin asserts the new behaviour
rather than just dropping the old assertion:
| where | was | now |
|---|---|---|
| `packages/metadata-protocol/src/protocol.install-manifest-id.test.ts`
| `com.4example.crm` a REFUSED row | an ADMITTED lit control: installs,
and the registry receives the id as written. The refused reason is kept
by `com.-example.crm` (hyphen-led) at the install and duplicate doors |
|
`packages/cloud-connection/src/marketplace-install-local-id-gate.test.ts`
| `com.1acme.crm` a REFUSED row | a lit control: `200`, `manifestId`
echoed, registered and filed as `com.1acme.crm.json`. The refused reason
is kept by `com.-acme.crm` |
| `packages/cli/test/package-publish-manifest-id.test.ts` | `9foo.bar`
in the relaxation table; `2024 App` derived `local.2024-app` and was
refused | CLI and spec agree `9foo.bar`, `com.163.crm`, `local.2024-app`
are ids. `2024 App` publishes: exit 0, both POSTs, wire `manifest_id`
`local.2024-app`, not rewritten. Every slug the derivation can produce,
from the name or the filename, parses |
| same file, anti-transcription control | the old pattern literal,
described as "the schema's own pattern" | the widened literal |
Prose that stated the retired rule, made false by this change:
-
`packages/spec/src/migrations/entries/semantic/18.manifest-id-reverse-domain-required.ts`:
the unreleased v18 prescription said "each opening with a letter".
Reworded, and `registry.ts` regenerated with `gen:migration-registry`
(not hand-edited).
- `content/docs/deployment/cli.mdx` and the `publish.ts` docblocks: they
said `local.2024-app` is rejected, and listed `9foo.bar` as a shape the
control plane refuses. These are comment and docs changes only.
## ⚠ Three PENDING release notes corrected (confirmation requested)
`check-empty-changeset` is red on this PR **by design**. This is its
DELIBERATE CORRECTION class. Three pending changesets on `main` quote
the refusal sentence verbatim in a `text` block, and this PR changes
that sentence. Each block now quotes what the door will actually print.
Nothing else in those notes moved: their "If you are refused" prose was
already true under the widened rule.
- `.changeset/19417-install-door-parses-manifest-id.md`: the `pkg-a`
refusal quote.
- `.changeset/19417-protocol-install-primitive-parses-manifest-id.md`:
the `com.example.my_erp` refusal quote.
- `.changeset/19576-install-local-id-gate.md`: the `late-app` refusal
quote.
Restoring them from base would ship a sentence no door prints.
`skip-changeset` is deliberately **not** applied. **Please confirm these
three edits in writing on this PR.** `Check Changeset` is not a required
context.
## PM mechanism hypotheses — measured
- **H1, census.** None of the seven named files keeps its own copy of
the rule. Each imports `MANIFEST_ID_PATTERN`, `manifestIdRefusal` or
`ManifestSchema`, or names them in a comment. Pins that refused a
digit-led segment under another spelling: the three consumer rows above.
Two producers still assume a letter-led last segment and prefix a
digit-led name (`2fa` becomes `app-2fa`): `manifestIdSlug`
(`packages/cli/src/commands/init.ts`) and `deriveManifestId`
(`packages/create-objectstack/src/rewrite-identity.ts`). Their output
stays legal, so they are left as they are (Acceptance notes).
- **H2 holds, by reading the running code.**
`deriveNamespaceFromPackageId` from the built spec gives:
`com.example.2app` → `app`, `com.163.crm` → `crm`, `com.4example.crm` →
`crm`, `com.example.123` → `null`. `namespace-prefix.test.ts` is green,
and so is the metadata-protocol duplicate-door namespace test.
- **H3, objectui.** It has one local copy of the rule: `PACKAGE_ID_RE`
in `packages/app-shell/src/views/studio-design/packages-io.ts:244`, the
same at the pin `f8a9d0fb05` and at `origin/main` `c3a26cc`. That copy
requires a letter first, lets a later segment open with a digit, and
admits underscores. The one new drift this PR adds: a digit-led FIRST
segment (`9foo.bar`) is now legal in spec, and the Studio duplicate
form's client check still refuses it. objectui does not import the spec
constant, and nothing is removed or renamed, so the Console Pin Gate is
unaffected.
- **H4, cloud.** NOT MEASURED: the repository is not attached to this
container. The ruling's own reading (no DB-level pattern on
`manifest_id`, cloud `b524051`) is carried as a lead.
## Verification, at head `a97a38db0`
- `@objectstack/spec`: full suite `vitest run --project local`: **540
files, 15800 passed, 2 todo**. `typecheck` (tsc, scripts, test layer):
exit 0. Focused (`manifest`, `namespace-prefix`, `package-namespace`,
`template-manifest-id`, `package-api`): 5 files, 188 passed.
- Consumer packages, one reading each:
- metadata-protocol (`protocol.install-manifest-id`,
`protocol.duplicate-package-target-namespace`): 32 passed, `typecheck` 0
errors.
- runtime (`packages-install-manifest-id`,
`artifact-granted-permissions`, `domain-handler-registry`): 101 passed.
- cli (`init-manifest-id`, `nav-contribution-groups.package-id`,
`package-publish-manifest-id`): 43 passed, `typecheck` 0 errors.
- create-objectstack (`rewrite-identity`): 22 passed.
- cloud-connection (`marketplace-install-local-id-gate`): 23 passed. It
has no `typecheck` script.
- **Ablation**, a one-time proof run from the committed state through
`scripts/ablation-replace.mjs`. Both legs landed on disk, and each
restore was proven: blob == HEAD blob `f0913fa`, `git diff HEAD` empty.
- Leg A put the pattern back to the leading-letter rule:
`manifest.test.ts` went **5 red**. That is the two accepted digit rows,
the shared-table door test, the `2fa` repair, and the
every-suggestion-parses test.
- Leg B put the old sentence clause back: **1 red**, the sentence pin.
- Direction: turns red, as predicted.
- **Generated artifacts:** `pnpm --filter @objectstack/spec
check:generated --fix` reports all 15 up to date against a dist it
built. The only regenerated file is `registry.ts`.
- **Gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --ran` gives 115 derived, 110 run, 5 NOT
MEASURED, 0 unrun. Of the 110 run, 109 exit 0 and
`check-empty-changeset` exits 1 by design (above).
- NOT MEASURED, prerequisite refusal (exit 3), needing a whole-workspace
build: `check:dual-build-cjs-loads`, `check:i18n`,
`check:i18n-coverage`, `check:i18n-walk-parity`.
- NOT MEASURED, no verdict: `check:type-check-debt`. Its whole-workspace
re-measure was cut off by the batch timeout.
- CI owns all five.
## Acceptance notes
None of these is filed. Each is an observation with no carrier (承接者:无).
- The scaffolders' prefix for a digit-led project name (`2fa` becomes
`app-2fa` in `os init`; `123` becomes `com.example.app-123` in
create-objectstack) is no longer required by the rule. Their comment "A
segment must OPEN with a letter" now states a retired clause. Their
output pins stay true as pins of the output.
- `os package publish`: the refusal remedy arms for DERIVED sources
(`manifestIdRemedy`, `artifact-manifest-name` / `artifact-filename`) are
now unreachable. A slug is always one legal segment, so a derived id
always parses. The arm for a declared `manifest.id` stays live.
- objectui `PACKAGE_ID_RE` (H3): a second transcription of the rule. It
was already looser (it admits underscores, which the server refuses
loudly) and is now stricter on a digit-led first segment. Not exercised.
- `deriveNamespaceFromPackageId` drops a leading digit run, even when
that run is part of a word (`com.example.2fa-leave` gives `fa_leave`).
This is pre-existing, only a default, and the author can declare
`namespace`.
- The Studio plugin id (`studio/plugin.zod.ts`, and
`content/docs/plugins/development.mdx`) keeps its own leading-letter
pattern. It is a different key under a different rule, not a package id.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a243cfb commit 3bd28e2
13 files changed
Lines changed: 196 additions & 91 deletions
File tree
- .changeset
- content/docs/deployment
- packages
- cli
- src/commands/package
- test
- cloud-connection/src
- metadata-protocol/src
- spec/src
- kernel
- migrations
- entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
38 | | - | |
39 | | - | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
46 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
47 | 48 | | |
48 | 49 | | |
49 | 50 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
34 | | - | |
35 | | - | |
36 | | - | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| |||
| 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 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1900 | 1900 | | |
1901 | 1901 | | |
1902 | 1902 | | |
1903 | | - | |
| 1903 | + | |
1904 | 1904 | | |
1905 | 1905 | | |
1906 | 1906 | | |
| |||
1922 | 1922 | | |
1923 | 1923 | | |
1924 | 1924 | | |
1925 | | - | |
1926 | | - | |
1927 | | - | |
1928 | | - | |
1929 | | - | |
1930 | | - | |
1931 | | - | |
1932 | | - | |
1933 | | - | |
1934 | | - | |
1935 | | - | |
1936 | | - | |
1937 | | - | |
| 1925 | + | |
| 1926 | + | |
| 1927 | + | |
| 1928 | + | |
| 1929 | + | |
| 1930 | + | |
| 1931 | + | |
| 1932 | + | |
| 1933 | + | |
| 1934 | + | |
| 1935 | + | |
| 1936 | + | |
| 1937 | + | |
| 1938 | + | |
| 1939 | + | |
1938 | 1940 | | |
1939 | 1941 | | |
1940 | 1942 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
45 | | - | |
| 44 | + | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| |||
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
152 | 153 | | |
153 | 154 | | |
154 | 155 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
73 | | - | |
74 | | - | |
75 | | - | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
76 | 81 | | |
77 | 82 | | |
78 | 83 | | |
79 | 84 | | |
80 | 85 | | |
81 | | - | |
82 | 86 | | |
83 | 87 | | |
84 | 88 | | |
| |||
166 | 170 | | |
167 | 171 | | |
168 | 172 | | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
169 | 176 | | |
170 | 177 | | |
171 | 178 | | |
| |||
319 | 326 | | |
320 | 327 | | |
321 | 328 | | |
322 | | - | |
323 | | - | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
324 | 333 | | |
325 | 334 | | |
326 | | - | |
327 | | - | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
328 | 339 | | |
329 | 340 | | |
330 | | - | |
| 341 | + | |
331 | 342 | | |
332 | 343 | | |
333 | 344 | | |
334 | 345 | | |
335 | 346 | | |
336 | | - | |
337 | | - | |
338 | | - | |
339 | | - | |
340 | | - | |
341 | | - | |
342 | | - | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
343 | 352 | | |
344 | 353 | | |
345 | 354 | | |
346 | 355 | | |
347 | 356 | | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
348 | 367 | | |
349 | 368 | | |
350 | 369 | | |
| |||
420 | 439 | | |
421 | 440 | | |
422 | 441 | | |
423 | | - | |
| 442 | + | |
424 | 443 | | |
425 | 444 | | |
426 | 445 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
148 | 148 | | |
149 | 149 | | |
150 | 150 | | |
151 | | - | |
| 151 | + | |
152 | 152 | | |
153 | 153 | | |
154 | 154 | | |
| |||
222 | 222 | | |
223 | 223 | | |
224 | 224 | | |
225 | | - | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
226 | 228 | | |
227 | 229 | | |
228 | 230 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
| 46 | + | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
50 | | - | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
51 | 55 | | |
52 | 56 | | |
53 | 57 | | |
| |||
0 commit comments