Uh oh!
There was an error while loading. Please reload this page.
fix(cli): init scaffolds pass the author-time rules dev runs (OWD + scaffold rule set) - #9736
Conversation
`objectstack init my-app -t app --install` printed `Scaffold validated` and the next documented command, `npm run dev`, failed to compile: the shipped template declared no `sharingModel`, which the shipped `security-owd-unset` rule refuses (ADR-0090 D1 — absence is not a decision). The CLI's own template was rejected by the CLI's own rule set, and the developer on-ramp was dead. Two halves, both needed: 1. The `app` and `plugin` templates now author `sharingModel: 'private'` — the rule's own recommended default. A per-template sweep found `plugin` in the same state as the reported `app`; `empty` emits no objects and was clean. 2. `init`'s scaffold self-test now runs the author-time rule registry instead of only checking that the rendered config loads. It runs the `build` command's rule set — the same set `os dev` reaches by spawning `os compile` — so this is a shift-left, not a new bar: nothing that compiles today stops compiling, and a template that cannot compile fails at generation instead of at a user's first `dev`. The loader and the file emitter are now shared with the pin test (`validateScaffold`, `writeTemplateSrcFiles`), so the test drives the real command path rather than a copy that could drift from it. The pin sweeps `TEMPLATES` rather than asserting on `app`, so a template added later is covered the day it lands. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The TEST_DEBT ratchet measured +4 raw tsc errors from the new test file: two TS2835 (relative imports need explicit extensions under moduleResolution NodeNext) and two more — TS7006 and TS18046 — that were downstream of those imports resolving to `any`. Fixing the two extensions cleared all four, so the @objectstack/cli entry sits back at its recorded 146 rather than being raised. Co-Authored-By: Claude <noreply@anthropic.com>
…t-template-owd-and-scaffold-rules
📓 Docs Drift CheckThis PR changes 1 package(s): 19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 23 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 43526bd4b94f07ad5de8f31d80682710f860bdc8 && git checkout 43526bd4b94f07ad5de8f31d80682710f860bdc8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e196c6a9baa8684c14e5e72545c8009f98a9ed14 1b03083e21ea14c40795bfdd2b8aea958bd85328 && git checkout -B drift-repro e196c6a9baa8684c14e5e72545c8009f98a9ed14 && git merge --no-ff 1b03083e21ea14c40795bfdd2b8aea958bd85328
node scripts/docs-audit/affected-docs.mjs --json e196c6a9baa8684c14e5e72545c8009f98a9ed14
|
os-zhuang
commented
Aug 18, 2026
PM review — accepted, ready + auto-merge armed. ⛔ And it refuted a "finding" I sent it mid-run.
⛔ My false positive, first — I sent it and it was wrongMid-run I messaged this dev a "measured finding": that That is a prose Callout naming the scaffold's file path. It reproduces no object literal, and every claim in it stays true after the fix. And What went wrong in my method, precisely. I ran two greps over one file — ⇒ ⭐ A control that proves your query works does not validate the inference you draw from the hit. I have spent this whole shift demanding controls, and here I had one and was still wrong, because it validated the wrong proposition. The dev's own enumeration went further than mine — it checked ⭐ The Zone 2 item I flagged as most likely wrong was wrong — in the useful directionI wrote that the assumption I most expected to fail was "the Falsified: Clause-② proven by construction rather than assertedI ruled Clause-② ⭐ The ratchet failed and was FIXED, not ledgered
That is exactly the line: a ratchet going up is the gate reporting your defect, not an inconvenience to be re-baselined. ⛔ Raising it would have been gate-weakening. The surface addition, declared as asked
Also correct
#9737 filed for the template descriptions advertising views/actions/extensions no template emits — needs a product call, correctly not fixed here. ⛔ Not graded by this seat. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9666
npx @objectstack/cli@17.0.0 init my-app -t app --installsucceeded, printed✓ Scaffold validated, and the very next command in the documented on-ramp —npm run dev— failed to compile:The CLI's own shipped template was refused by the CLI's own shipped rule set, so
the dev server never started on a freshly generated project. The defect is live
on
main, not only on the published 17.0.0.Where it actually lives
The
apptemplate is not a file underpackages/create-objectstack/src/templates/— searching there returns nothing. All three built-in templates are emitted inline
from
srcFilesmaps insidepackages/cli/src/commands/init.ts(app,plugin,empty), and the init-time self-test sat just below them.Two halves, both in this PR
1. The templates author an OWD.
appandpluginnow declaresharingModel: 'private'on the object they emit — the rule's own recommendeddefault and the ADR-0090 D1 baseline (absence is not a decision). The rule is
untouched: no allowlist, no exemption, no threshold change. The template conforms
to the rule.
2.
init's self-test runs the author-time rules. It previously checked onlythat the rendered config loaded and carried a
manifest.namespace, which isexactly why a template that could not compile shipped. It now runs the author-time
rule registry over the generated project and refuses to report success when any
rule rejects it.
The rule set is the
buildone — the same setos devreaches when it spawnsos compile. That is deliberate and is what keeps this a shift-left rather than anew bar: nothing that compiles today stops compiling, and a template that cannot
compile now fails at generation time, in CI, instead of at a user's first
dev.The pipeline mirrors
compile.tsstep for step (normalize, lower callables, Zodparse, registry) so a rule cannot see a differently-prepared stack here than there.
✓ Scaffold validatedstill prints, and now names how many author-time rulespassed. Rule failures render in the same shape
os validate/os builduse(all failures at once, with rule id, path and hint) and keep the existing
"this is a CLI bug, please report it" remediation line, which is precisely right
when the CLI generated metadata its own rules refuse.
Per-template sweep — the assumption that was wrong
Once
initruns the rules, every built-in template has to pass them. Measuredbefore any fix, generating each template and running the
buildrule set (41rules) over the result:
appsecurity-owd-unsetatobjects[0].sharingModelpluginsecurity-owd-unsetatobjects[0].sharingModelemptypluginwas in the same state as the reportedapp. It is the same defect class— same rule, same missing field, same mechanical fix pinned by the same authored
baseline — so it is fixed here rather than deferred, and named explicitly rather
than folded in silently.
security-owd-unsetkeys on whether an object is a systemobject, not on the stack's manifest
type, so a plugin's object is judged exactlylike an app's.
Control checked rather than assumed:
create-objectstack'sblanktemplatealready declares
sharingModel: 'private'innote.object.ts. The CLI's owninline templates were the ones that never got that repair.
The pin
packages/cli/test/init-scaffold-authoring-rules.test.tsgenerates each templateand runs the author-time rules over the result, so a future template regression
fails in CI rather than at a user's first
dev— the defect class, not just thisinstance. It iterates
TEMPLATES, so a template added later is swept the day itlands without anyone remembering to extend the file.
To keep the pin honest it drives the command's real code path on both ends: the
file emitter (
writeTemplateSrcFiles) and the self-test itself(
validateScaffold) are now shared betweeninit.tsand the test, instead of thetest re-implementing either. A re-implementation would be free to drift, and the
drift would land in exactly the class the pin exists to catch. The pin also asserts
ruleCountis above zero, so a rule set that ran nothing cannot read as green.Reverse verification
Predicted in writing before running, then measured: delete
sharingModelfrom theapptemplate only and the suite must go red at 2 of 35, both onapp, namingsecurity-owd-unset;pluginandemptystay green; all 28 pre-existinginit.test.tstests stay green because none of them readssharingModelor runsa rule.
Measured: 33 passed / 2 failed, exactly that split, with the failure text
reproducing the incident's own message:
The fix was committed before the ablation, and restored from that commit
afterwards (
git diff HEADempty).Verification
All at
1b03083e2(final head, after mergingmain), heavy steps serialized onthe shared verify lock:
pnpm --filter @objectstack/cli test— 134 files, 1458 tests passed (baseline1452 + the 6 new ones)
pnpm --filter @objectstack/cli typecheck— exit 0node scripts/pm/dispatch-gates.mjs, all green:check:changeset-gate-self-tests,check:objectui-changeset,check:cross-package-test-inputs,check:nul-bytes,check:query-options-erasure,check:type-check-coverage,check:engine-double-contract,check:where-matcher,check-adr-0087-registration.mjs,check-changeset-no-major.mjs,check-empty-changeset.mjs,check-cross-package-test-inputs.mjs,check-affected-docs.mjscheck:type-check-debt --re-measurecaught the new test file adding 4 raw tscerrors to
@objectstack/cli's TEST_DEBT (146 to 150). Fixed rather thanledgered: two were TS2835 (relative imports need explicit
.jsextensions undermoduleResolution: NodeNext) and the other two — TS7006 and TS18046 — weredownstream of those imports resolving to
any. Adding the extensions cleared allfour and the entry sits back at its recorded 146.
Note on scope
initstill validates only when dependencies were installed (--install, or aninstall that succeeded), unchanged from before: the rendered config imports
@objectstack/spec, so with nonode_modulesthere is nothing to load and norules to run. Widening that is a separate change and is not attempted here.
Generated by Claude Code
Generated by Claude Code