Repository navigation
Commit a1ae469
fix(scaffold): a scaffolded namespace fits the customization-pair cap, so a fresh project from a long name validates with no warning (#29103)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XWpxb51mia4Ts4VrhocktU6)_
Fixes #28992
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:`npx create-objectstack` 和 `os init` 由项目名生成命名空间时,上限从 20 个字符改为 13
个(即 `MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH`)。`qa-console-first-paint`
现在得到 `qa_console_fi`,脚手架里的对象名和文件名都跟着它走;截断后不留结尾的 `_`。13
个字符以内的项目名,生成结果与以前逐字节相同。
- 为什么改:超过 13 个字符的命名空间没有配对的定制命名空间(ADR-0146 D3),所以新人刚生成的项目第一次 `os
validate` 就弹出 `namespace-no-customization-pair` 警告,让他去改不是他写的对象名。
- 风险与回滚:只影响新生成的项目,已有项目不变。`create-objectstack` 运行时不依赖
spec,所以保留一份本地常量,由测试钉死等于 spec 的常量;`os init` 直接读 spec 常量。回滚即 revert。
- 先看:`packages/create-objectstack/src/rewrite-identity.ts` 的
`SCAFFOLD_NAMESPACE_MAX_LENGTH` 与 `sanitizeNamespace()`(从 index.ts
原样搬来,只改上限);`packages/cli/src/commands/init.ts` 的
`sanitizeNamespace()`;`packages/create-objectstack/src/scaffold-namespace-cap.test.ts`
的常量相等钉。
- 两分钟验证:`pnpm --filter create-objectstack exec vitest run
src/scaffold-namespace-cap.test.ts
src/scaffold-namespace-customization-pair.test.ts`,预期 `Tests 44 passed
(44)`。
- 需要你做的:无,席位落地。
## What changes
- **`packages/create-objectstack/src/rewrite-identity.ts`**:
`sanitizeNamespace()` moves here from `index.ts`, beside its sibling
`deriveManifestId()`, so it can be unit tested (`index.ts` calls
`program.parse()` on import). The only behaviour change is the cap:
`SCAFFOLD_NAMESPACE_MAX_LENGTH = 13` replaces the three `20`s. The
existing trailing-`_` strip after truncation is kept, so a namespace
whose 13th character is `_` ends one character earlier.
- **Why a local constant in create-objectstack (measured).** The order
asked to read `MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH` from one place
if the scaffolder can depend on it cheaply. `@objectstack/spec` is only
a devDependency of create-objectstack. A value import from `src/` would
be bundled into the published scaffolder, and
`check:undeclared-dep-imports` refuses exactly that (a devDependency
inlined into published source). Making the spec a runtime dependency
would add `zod` and `pg-connection-string` to a two-dependency `npx`
entry point. Bundle size was not the obstacle: an esbuild probe from
spec source gives 3,121 bytes and no zod. So the order's fallback
applies: a local constant, pinned equal to the spec constant by
`scaffold-namespace-cap.test.ts`, which resolves
`@objectstack/spec/kernel` to source through the package's existing
vitest alias.
- **`packages/create-objectstack/src/index.ts`**: imports
`sanitizeNamespace` from `./rewrite-identity.js`. The namespace is still
computed once (`const namespace = sanitizeNamespace(projectName)`) and
handed to the manifest, the config and the object-prefix rewrite, so
every derived name uses the truncated value.
- **`packages/cli/src/commands/init.ts`** (`os init`): the same function
had the same 20 cap. It now reads
`MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH` from
`@objectstack/spec/kernel`, a runtime dependency of the CLI already. The
seat widened the claim to cover it, so the two scaffolders keep
agreeing.
- **`.changeset/28992-scaffold-namespace-pair-cap.md`**:
`create-objectstack` patch and `@objectstack/cli` patch, with both
declaration lines. Nothing newly exported from `@objectstack/spec`. The
`create-objectstack` `index.ts` export of `sanitizeNamespace` is not in
its `exports` map (only `./created-summary` is), so no published surface
moved.
## Tests
- **Red first, unit (real scaffold).** New
`scaffold-namespace-customization-pair.test.ts` drives the real CLI
through `tsx` for `qa-console-first-paint`. On the unchanged code it
failed with `Expected: "qa_console_fi"`, `Received:
"qa_console_first_pai"`. Green, it asserts that the namespace is
`qa_console_fi`, that `customizationNamespace()` (the helper the lint
rule and the ensure door use) answers `{ ok: true, value:
'qa_console_fi_custom' }`, that the config literal matches, and that
every scaffolded object name passes `validateObjectNamespacePrefix`.
- **New `scaffold-namespace-cap.test.ts`** (43 cases):
- the constant equals `MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH`;
- for 27 names (empty, single character, reserved, scoped,
digit-leading, Unicode, exactly 13, 14, 26 characters), the result
passes `ManifestSchema.shape.namespace`, is not reserved, and has a
customization pair;
- truncation cases, including the 13th character being `_`
(`abcdefghijkl-mnop` → `abcdefghijkl`) and a digit-leading name
(`1234567890123456` → `a123456789012`);
- controls: names that already fit (`my-app`, `crm`, `abcdefghijklm`,
`thirteen-char`, `''`, `x`, `9-lives`, `sys`, `system`, `@acme/my-app`)
keep their previous value.
- **cli `test/init.test.ts`**: five truncation cases and one
customization-pair sweep added to its `sanitizeNamespace` block.
- **Real `os validate` (red and green), on scaffolds under
`packages/cli/node_modules/.probe-28992/`, run with the built
`packages/cli/bin/run.js`:**
- main's `index.ts` (from `0be247ce`): namespace `qa_console_first_pai`,
`os validate` exit 0 with 1 `namespace-no-customization-pair` warning
("namespace "qa_console_first_pai" is 20 characters …").
- this branch: namespace `qa_console_fi`, `os validate` exit 0, 0
occurrences of the rule and 0 `⚠` lines.
- `os init qa-console-first-paint --no-install` on this branch:
`Namespace: qa_console_fi`, object file `qa_console_fi_item.object.ts`,
`os validate` exit 0 with no `namespace-no-customization-pair`. (It
prints the pre-existing `⚠ No apps or plugins defined` for the `app`
template, which does not depend on the namespace.)
- **Ablation** (`scripts/ablation-replace.mjs`, anchor hit once each;
restore proved by blob == HEAD and an empty `git diff HEAD`):
- create-objectstack, `SCAFFOLD_NAMESPACE_MAX_LENGTH = 13` → `20` (blob
`8fb4bd56` → `3a388a6f`): `pnpm --filter create-objectstack test` reds
15 failed / 330 passed, in the 2 new files only, while the 19 existing
files stay green. The red cases are the constant pin, the 8 long names
in the pair sweep, the 5 truncation cases, and the real-scaffold case.
- cli, `const max = MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH` → `const
max = 20` (blob `ef45f599` → `a44b2c1f`): the five init suites red 5
failed / 118 passed, exactly the 4 new truncation cases and the pair
sweep. The exact-fit control stays green.
- No build was on either path: both suites import the changed file as
relative source.
- **Package suites** at `c422fbc2` (the follow-up `51ac52ae` changes one
comment line in a test): `pnpm --filter create-objectstack test` 21
files / 345 tests passed, and its `typecheck` (which includes
`src/**/*.test.ts`) passed. `pnpm --filter @objectstack/cli typecheck`
passed, and `vitest run --project unit` passed 368 files / 5,202 tests.
The cli `integration` layer is left to CI: no integration-layer file or
spawn entry is touched.
## Acceptance notes
- No "namespace was shortened" line was added. `create-objectstack` and
`os init` already print `Namespace: VALUE` before writing, and the value
is now the truncated one. A separate notice would need the untruncated
value threaded out of `sanitizeNamespace()`, which is more than the
order's "only if trivial".
- `docs/audits/2026-09-os-create-os-init-convergence-measurement.md`
mentions `sanitizeNamespace()` in a dated audit table. Its claim (one
call site, the config namespace) still holds, so it was left as is.
## Verification
Head `51ac52ae`.
- **Derived gate families** (`node scripts/pm/dispatch-gates.mjs
--commands`, change set vs merge base `0be247ce8`): 62 derived. 60 ran
green (exit 0). 2 NOT MEASURED, reason: prerequisite, because they read
every package's built output and only the CLI closure was built here:
`check:dual-build-cjs-loads` and `check:i18n-coverage` (exit 3,
`PREREQUISITE NOT MET`). CI runs both. Reconciliation: `dispatch-gates
--ran` → `62 derived famil(ies) accounted for — 60 run, 2 NOT-MEASURED`.
- One real red, fixed on this branch: `check:cross-package-test-inputs`
read a repo path spelled in a test comment (`packages/lint/src/…`) as an
undeclared input of the new real-scaffold test. The comment now names
the rule by its export (`51ac52ae`), and the gate passes: `OK: 31
package(s) read outside themselves, all declared`.
- `check-plugin-teardown-shape.mjs --self-test` first exited 3 because
the shallow clone could not reach its pinned fixture commit. After `git
fetch --depth=1` of that commit it passed (48 cases).
- `test:repo` was not run locally (no file of that project is touched).
CI's Test Core covers it.
<details><summary>Gate record (60 + 2)</summary>
```
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-test-child-env :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 3
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n :: exit 0
pnpm check:i18n-coverage :: exit 3
pnpm check:i18n-walk-parity :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:scaffold-emission-policy :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5oqaD6uoTrtmva9fde)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 104cd1a commit a1ae469
7 files changed
Lines changed: 225 additions & 24 deletions
File tree
- .changeset
- packages
- cli
- src/commands
- test
- create-objectstack/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
70 | | - | |
71 | | - | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
72 | 79 | | |
73 | 80 | | |
| 81 | + | |
74 | 82 | | |
75 | 83 | | |
76 | 84 | | |
77 | 85 | | |
78 | 86 | | |
79 | | - | |
80 | | - | |
81 | | - | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
82 | 90 | | |
83 | 91 | | |
84 | 92 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
404 | 404 | | |
405 | 405 | | |
406 | 406 | | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
407 | 427 | | |
408 | 428 | | |
409 | 429 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| 77 | + | |
77 | 78 | | |
78 | 79 | | |
79 | 80 | | |
| |||
107 | 108 | | |
108 | 109 | | |
109 | 110 | | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | 111 | | |
128 | 112 | | |
129 | 113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
119 | 156 | | |
120 | 157 | | |
121 | 158 | | |
| |||
| 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 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 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 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
0 commit comments