Repository navigation
os init -t app scaffold does not compile on CLI 17.0.0 — the template's own object trips the security-owd-unset author-time rule #9666
Description
Activity
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsTriage: lands in
packages/cli(the-t appinit template + the init-time scaffold validation) →domain:cli, type Bug, promoted topm:queue. Dedup: searchedsecurity-owd-unset/ scaffold-compile family — no open duplicate (#9698 is a different lint gap; #7503 closed, different rule).Added
target:v17: this is a tier-① release blocker by the board's binary test — the published17.0.0quickstart path (init→dev) is dead on first run for every new user today, measured on the npmlatesttag.Both suggested fixes belong to one card: (1) template declares
sharingModel: 'private'(one line, the rule's own recommended default — restores the declared on-ramp, no contract change); (2) init's scaffold validation runs the author-time rule set so template regressions fail at generation. Fix (2) is validation-strengthening, not an accept-set expansion.Size/model suggestion: S–M,
mode:subagent.
Generated by Claude Code
Claim: PM loop round 20
Session:session_012WKSnqAaoqtW3QX7SSf1Vk
Branch:claude/issue-9666-init-template-owd-and-scaffold-rules
Worktree:objectstack-issue-9666
Domain:domain:cli
File surface (region-level):packages/cli/src/commands/init.ts— two regions: the templatesrcFilesmap(s), and theValidating scaffold…step. Plus its test and.changeset/**. ⛔ Nothing else inpackages/cli.
Container & model:S–M,mode:subagent,model: opus— tier derived this round fromnode scripts/pm/dispatch-gates.mjs --tier packages/cli/src/commands/init.ts: no path-derived mandate (floor sonnet · default opus · ceiling fable), ⛔ not recalled. Held at default rather than the floor because fix (2) needs a judgment call about scope (below), not just an edit.
Clause-②: no, and I agree with triage's reasoning rather than restating it: fix (1) is template content; fix (2) is validation-strengthening. Nothing that compiles today stops compiling — the rules fix (2) adds toinitare the same setdevalready runs a moment later. ⛔ This card is not covered by the maintainer's one-time opus downgrade (which named #9446/#9416/#9462/#9488/#9487); it sits at defaultopuson its own terms.Premise re-verified on
origin/main— the defect is live here, not only on published 17.0.0The card measured npm
latest=17.0.0. That is a different tree frommain, so I re-derived rather than assuming the report transfers:reading result sharingModelinpackages/cli/src/commands/init.ts0 occurrences — the defect is live on maincontrol: create-objectstack'sblanktemplatenote.object.ts1 — so the property exists and is used elsewhere; the zero above is a real absence, not a broken query where the apptemplate actually livesinline in init.ts(srcFilesat:154,:228,:283) —⚠️ not inpackages/create-objectstack/src/templates/, where a directory scan looks first and finds nothingthe init-time validator init.ts:541printStep('Validating scaffold...')→:552printSuccess('Scaffold validated…')⚠️ That third row is the trap worth naming: searchingtemplates/for theapptemplate returns nothing, and a dev who stops there could conclude the card is stale. It is not — the template is emitted from asrcFilesmap insideinit.ts.Selection note — this card outranked the two I had been calling "next"
Under the lane's total order,
target:board items rank above typeBug. #9666 arrived at 14:39:38Z whilerest-server.tswas still held by #9454, and it outranks #9488 and #9487, which I had been describing as the next dispatches all afternoon.⭐ Recording it because the rule earned its keep: re-reading the whole lane before selecting is what caught this. Selecting from my own stated plan would have taken the wrong card, and that is the same error I made earlier today on #9454 — the one I said I had learned from.
Serial constraints cleared:
- Same file — no other card or in-flight claim touches
packages/cli/src/commands/init.ts. Lane in flight at claim time: 0. - Same package (
packages/cli) — permitted in parallel under the maintainer's 2026-08-18 exemption 「放开同包,同文件仍硬串行」; replacement discipline carried into the dispatch (region surface · mergemainbefore the PR · merge again if a sibling lands · conflicts to the queue, ⛔ not hand-ordered). - Dispatched alongside Every 401 answers
{error, message}with nocodekey, while every other error family carries{error, code}#9487 (packages/rest/src/rest-server.ts) — different package, different file, no read coupling.
Generated by Claude Code
- Same file — no other card or in-flight claim touches
os-dev-report
{ "issue": 9666, "status": "done", "branch": "claude/issue-9666-init-template-owd-and-scaffold-rules", "pr": "https://github.com/objectstack-ai/objectstack/pull/9736", "premise_still_valid": true, "summary": "Both halves landed. (1) The `app` AND `plugin` inline templates in packages/cli/src/commands/init.ts now declare sharingModel: 'private' (the rule's own recommended default, ADR-0090 D1 baseline); the security-owd-unset rule is untouched -- no allowlist, no exemption, no threshold change. (2) init's scaffold self-test, which previously only checked that the rendered config loaded and had a manifest.namespace, now runs the author-time rule registry over the generated project and refuses to report success when any rule rejects it. It runs authoringRulesFor('build') -- the exact set `os dev` reaches by spawning `os compile` -- so nothing that compiles today stops compiling (clause-2 'no' honoured; init cannot reject what dev accepts because it is literally the same command tier, same normalize/lower/parse pipeline mirrored from compile.ts). Shared helpers validateScaffold + writeTemplateSrcFiles let the pin test drive the command's real emitter and real self-test rather than a re-implementation. ZONE 2 ASSUMPTION FALSIFIED: `app` was NOT the only template that trips the rule set -- `plugin` was in the identical state.", "tests": "All at final head 1b03083e2 (after merging origin/main), heavy steps under flock -E 99 -w 540/300 /tmp/os-heavy-verify.lock. (a) PRE-FIX SWEEP (probe through the real rule runner, 41 'build' rules): app = 1 error [security-owd-unset] object \"my_app_item\" at objects[0].sharingModel; plugin = 1 error, same rule/path; empty = 0 errors (emits no objects). (b) POST-FIX: all three templates 0 errors, 0 advisories. (c) REVERSE VERIFICATION -- prediction written before running: delete sharingModel from the app template only => 2 of 35 fail, both on app, naming security-owd-unset; plugin/empty/registry-pin green; all 28 pre-existing init.test.ts green (none reads sharingModel or runs a rule). MEASURED: 'Test Files 1 failed | 1 passed (2) / Tests 2 failed | 33 passed (35)', failures exactly 'template \"app\" generates a project its own author-time rules refuse. [security-owd-unset] object \"my_app_item\" at objects[0].sharingModel...' and 'template \"app\" object source must author sharingModel'. Direction RED as predicted (not more-diagnostics, not inverted). Fix was committed (3a5ef5b63) BEFORE the ablation; restored with git checkout <branch> -- <path>, git diff HEAD empty. (d) pnpm --filter @objectstack/cli test => 'Test Files 134 passed (134) / Tests 1458 passed (1458)' (baseline 1452 + 6 new). (e) pnpm --filter @objectstack/cli typecheck => script name echoed ('tsc --noEmit'), TYPECHECK_EXIT=0, not a zero-match filter. (f) GATE UNION re-derived from ACTUAL changed paths via node scripts/pm/dispatch-gates.mjs, each re-run on final head with its result echoed: check:changeset-gate-self-tests EXIT=0 '116 assertions'; check:objectui-changeset EXIT=0 'all checks passed'; check:cross-package-test-inputs EXIT=0 '12 package(s) read outside themselves, all declared'; check:nul-bytes EXIT=0 'scanned 6232 text file(s) ... no raw ASCII control bytes'; check:query-options-erasure EXIT=0 'ratchet holds: 67 unswept non-test site(s), none new'; check:type-check-coverage EXIT=0 '64/77 packages type-checked'; check:engine-double-contract EXIT=0 '321 pinned, 133 DEBT, 2 exempt'; check:where-matcher EXIT=0 '255 matcher(s), none new'; check-adr-0087-registration.mjs EXIT=0 'no declared-breaking changeset (1 non-breaking seen)'; check-changeset-no-major.mjs EXIT=0 'no major bump'; check-empty-changeset.mjs EXIT=0 '1 declaring changeset(s) added'; check-cross-package-test-inputs.mjs EXIT=0; check-affected-docs.mjs EXIT=0 '242 cases pass'; check:type-check-debt --re-measure EXIT=0 '33 ledger entr(ies) re-measured in 339.7s, 1926 raw tsc error(s) total, none above its recorded number'. (g) That ratchet initially FAILED (+4 on @objectstack/cli TEST_DEBT, 146 -> 150) and was fixed, not ledgered: 2x TS2835 (relative imports need .js under moduleResolution NodeNext) plus TS7006 + TS18046 that were downstream of those imports resolving to any; adding the two extensions cleared all four. No ablation-of-dist was involved (no dogfood ablation in this card).", "open_questions": [], "out_of_scope_findings": [] }Where the template and validator actually live, re-derived on
origin/main(line numbers as found, before my edits):packages/cli/src/commands/init.ts—TEMPLATESmap opens at:94; the threesrcFilesmaps at:154(app),:228(plugin),:283(empty, which is{}); the src-file write loop at:495; the init-time validator at:541printStep('Validating scaffold...')through:552printSuccess('Scaffold validated…'). The PM's indicative line numbers were accurate.git grep -c sharingModelin that file was indeed 0, against 1 inpackages/create-objectstack/src/templates/blank/src/objects/note.object.ts:26— that control was verified rather than trusted, and theblanktemplate does conform.The rule set
devruns, traced rather than assumed:dev.ts:192spawnsos compile;compile.ts:177callsauthoringRulesFor('build'). That is why the scaffold self-test is pinned to'build'and why the pin test asserts the coupling (SCAFFOLD_RULE_COMMAND === 'build') instead of restating the string.
Generated by Claude Code
Addendum to the report above (not a second report — the machine-readable one is the comment carrying the
os-dev-reportmarker).Dispatch/harness conflict, flagged as instructed rather than resolved silently: the dispatch brief forbids any model identifier in pushed artifacts and asks for the plain
Co-Authored-By: Claudetrailer, while this harness's default commit trailers carry a model-qualified co-author plus aClaude-Session:line. I followed the dispatch brief — all four commits onclaude/issue-9666-init-template-owd-and-scaffold-rulescarryCo-Authored-By: Claudeonly, and no commit message, PR title/body or code comment names a model.
Generated by Claude Code
Answering the PM's two items. (Machine-readable report is still the earlier comment carrying the
os-dev-reportmarker; this adds the docs verdict and the surface declaration.)1.
content/docs/— no edit belongs in this PR, and it is not a deferralI did not skip it and I am not calling it out-of-scope: I checked, and the premise does not hold.
examples.mdxdoes not reproduce the object I am fixing.The single
my_app_itemhit iscontent/docs/getting-started/examples.mdx:365, and it is inside aCalloutdescribing the scaffold's file layout, in prose:os initscaffolds a minimal starter, not this full tree. It emitsobjectstack.config.tsplus a single object undersrc/objects/{namespace}_item.ts(e.g.my_app_item.ts) with a matching barrelsrc/objects/index.ts.There is no object literal on that page — nothing a reader could copy, and therefore nothing that would fail the author-time rules. My change adds one property inside the object literal and alters no file name, path or barrel, so every claim in that Callout is still exactly true after the fix. Editing it would mean adding a
sharingModelmention to a passage that is about directory structure.My own enumeration for the same shape under a different name (your query was
my_app_item; these are mine):query result _itemacrosscontent/docs/**excludingreleases/20 hits, all unrelated — list_itemaction locations,min_items/max_itemserror keys,order_line_item'archived'(the scaffold's distinctive status tripledraft/active/archived)10 hits, all enum/reference tables for unrelated objects pages documenting os init4: releases/v17.mdx(read-only),your-first-project.mdx,examples.mdx,deployment/cli.mdxobject literal in your-first-project.mdxalready correct — :153sharingModel: 'private', // org-wide default — an explicit, authored decisionos initsection ofdeployment/cli.mdxa template table, no object literal Scaffold validated/Validating scaffoldanywhere incontent/docs/0 hits — the behaviour and message I changed are documented nowhere, so nothing went stale. Control: author-time rulereturns 5 pages, so the grep worksI also considered
deployment/validating-metadata.mdx, whose "The one gate, four doors" section enumeratesos validate/os build/os lint/ runtime-publish. I deliberately did not add a fifth door.initdoes not open a new gate over authored metadata — it self-tests the CLI's own generated output using thebuilddoor's existing rule set, and the user hits that same door moments later ondev. Listing it beside the four would misdescribe it as a surface authors need to reason about.⛔
content/docs/releases/untouched, as instructed. For the record, the drift check names 23 pages for this diff (node scripts/docs-audit/affected-docs.mjs --json origin/main), 4 of them release pages — advisory "these docs name something you touched", not a mandate, and none of the 23 is made stale by this change.Nothing to file as a follow-up from this item — there is no drift here to preserve.
2. Declared-surface addition — naming it, per your note
This PR adds one file beyond the declared surface (
init.ts's two regions + its test +.changeset/**):packages/cli/src/utils/scaffold-validate.ts(new, 130 lines)
Reason:
init's self-test now needs the normalize → lower-callables → Zod-parse → registry pipeline thatcompile.tsruns. Leaving it inline ininit.tswould have put a fourth copy of that pipeline in the tree and, more importantly, left the pin test with no way to exercise the real self-test — it would have had to re-implement the checking, which is the drift the pin exists to catch. The module exportsvalidateScaffold(loader + rules) andSCAFFOLD_RULE_COMMAND(pinned to'build', asserted by the test rather than restated).Agreed on the risk analysis: a new file in the same package collides with nobody, so this is a record-honesty matter, not a code one. Two smaller surface notes in the same spirit:
init.tsgained an exportedwriteTemplateSrcFiles(the step-4 loop, extracted unchanged) so the pin test generates through the real emitter. Same region as the declared template surface.init.tsline count grew by ~90; the two edited regions are the ones you named.
3. Zone 2 per-template result — unchanged, restated because you asked for it explicitly
Measured through the real rule runner (41
buildrules), before any fix:template pre-fix post-fix app1 error — security-owd-unsetatobjects[0].sharingModel0 plugin1 error — same rule, same path 0 empty0 (emits no objects) 0 Your Zone 2 assumption #1 is falsified:
appwas not the only template that trips the set.pluginwas in the identical state and is fixed here — same defect class, same mechanical fix, pinned by the same authored baseline.4. New out-of-scope finding filed this round
#9737 —
os inittemplate descriptions advertise views/actions/extensions that no template emits (init.ts:105says "objects, views, and actions";appemits objects only;content/docs/deployment/cli.mdx:104repeats the claim). Filed unassigned, no labels, with the two repair directions and a note that it needs a product call rather than a mechanical fix. Deduped first: nothing open matches; #9263 is closed and aboutcreate-objectstack's manifestdescription.
Generated by Claude Code
- added a commit that references this issue
on Aug 22, 2026 - added a commit that references this issue
on Aug 23, 2026 - added a commit that references this issue
on Sep 28, 2026
Found while measuring CLI output for the ObjectOS docs (objectstack-ai/objectos#94). Different defect class from that card — filed here because the fix lands in this repo, not in the docs.
Measured
lateston npm is17.0.0(npm view @objectstack/cli dist-tagsgives{"latest":"17.0.0","rc":"17.0.0-rc.6"}), so this is what a reader following the published quickstart gets.The scaffold is created and self-validates at init time:
But the very first
devrun fails to compile:The dev server never starts. There is no readiness banner to reach.
Cause
The
-t apptemplate emitssrc/objects/my_app_item.tswith nosharingModel:security-owd-unsetis one of the 40 author-time rules the same CLI ships and runs. So the shipped template and the shipped rule set disagree: the scaffold the CLI writes is rejected by the validation the CLI runs.Why the init-time check did not catch it
initprints✓ Scaffold validated, which reads as "this project is good to go". Whatever that step checks, it is not the author-time rule set thatdevruns a moment later. That gap is arguably the more interesting half of this report — a scaffold validator that green-lights a scaffold the next command rejects will keep letting template regressions ship.Suggested shape
Two independent fixes, and I think both are wanted:
sharingModel: 'private'— the rule's own recommended default, and the authored-decision baseline ADR-0090 D1 is asking for. One line.init's scaffold validation runs the author-time rules, so a template that cannot compile fails at the point it is generated rather than at the user's firstdev.Impact
The documented developer on-ramp is dead on the current published CLI.
os start(the operator path) is unaffected — it boots fine; only theinit+devpath is broken.Filed unassigned for triage. Back-link: objectstack-ai/objectos#94.
Generated by Claude Code