Uh oh!
There was an error while loading. Please reload this page.
fix(cli): os generate scaffolds object, view, action and app in the shapes os validate accepts - #14548
Conversation
…d red-first `KNOWN_UNVALIDATED_SCAFFOLDS` is a shrink-only ledger carrying an anti-staleness assertion: a kind listed in it must still FAIL. Deleting the four entries BEFORE touching a template is therefore the instrument, not bookkeeping — it turns the suite red for `object` / `view` / `action` / `app` and prints each kind's refusal verbatim, which is the baseline the repairs in the next commit are measured against. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…date accepts (#14336) Four of the seven `os generate` templates wrote artifacts the platform's own validator refuses, so an author following the documented path got a file their own toolchain rejected. Re-measured on this base through the #14087 harness: objects[0].sharingModel security-owd-unset — declares no sharingModel (OWD) views[0].list unrecognized key(s) on this list view: `pageSize` views[0] unrecognized key(s) on this view container: `type`, `objectName` actions[0].type invalid option: expected "script"|"url"|"modal"|"flow"|"api"|"form" actions[0] unrecognized key(s) on this action: `handler` apps[0].navigation expected array, received object `object` authors `sharingModel: 'private'` — not a new decision, but the one #9666 already took for the `os init` templates, emitted with the same explanation so both authoring doors agree. `view` emits a view CONTAINER rather than a flat list view: the container's slots are `list` / `form` / `listViews` / `formViews`, `type` belongs to a single view, and the object binding is `object`. The flat shape mattered past the refusal — it parses to an EMPTY container, so zero views register and the Console renders nothing. `pageSize` moves to `pagination`, the schema that declares it. `action` emits `type: 'flow'` with `target` naming the flow, which is what its `handler: { type: 'flow', target }` block was trying to express; `custom` is not an ActionType and the second handler slot was removed in protocol 17. `app` emits `navigation` as the array it is declared as, carrying one real `type: 'object'` entry instead of a `{ type: 'sidebar', items: [] }` wrapper that is not on the authoring surface. The `action` and `app` entries target the names `os g flow NAME` and `os g object NAME` write, so the scaffolds compose instead of each standing alone. `KNOWN_UNVALIDATED_SCAFFOLDS` is now empty, so the clean pin covers the whole roster. The ledger stays shrink-only: a red there is a template to fix, never a line to add. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…nerate-scaffolds-validate
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 22 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 100d0cf12fb102526c31f8e8db5e67461485a754 && git checkout 100d0cf12fb102526c31f8e8db5e67461485a754
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4a378701156050de0608f728d479c0be038ea836 467e26e9cdb5735f15101bdace858dd5b860fcc5 && git checkout -B drift-repro 4a378701156050de0608f728d479c0be038ea836 && git merge --no-ff 467e26e9cdb5735f15101bdace858dd5b860fcc5
node scripts/docs-audit/affected-docs.mjs --json 4a378701156050de0608f728d479c0be038ea836
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#14336
Four of the seven
os generatetemplates wrote artifacts the platform's own validator refuses. The generator is the path the docs point at, and a.strict()refusal enumerates what is allowed rather than saying where a key moved to — so an author following the documented path got a file their ownos validaterejected, with an error that did not say where to put the key instead.#14087fixedflowand recorded these four inKNOWN_UNVALIDATED_SCAFFOLDS, a shrink-only ledger with an anti-staleness assertion: a ledgered kind must still FAIL. This PR repairs all four templates and deletes all four entries, which empties the ledger.The four ledger deletions
All four lines are removed from
KNOWN_UNVALIDATED_SCAFFOLDSinpackages/cli/test/generate-scaffold-validates.test.ts, leaving the table empty. ⛔ No kind was added to it.object— "parses, then fails the author-time rules:security-owd-unset(no sharingModel authored)."sharingModel: 'private'view— "unrecognizedpageSizeon the list view, andtype/objectNameon the view container."pageSizeunderpaginationaction— "type: 'custom'is not an Action type, andhandleris not an Action key."type: 'flow'+targetapp— "navigationtakes an array of nav items; the scaffold writes a{ type, items }object."navigationas an array of nav itemsBecause the table is empty, the harness's clean pin now ranges over the whole roster: every generator must write a stack
os validateaccepts, and a template that stops validating is red on the day it lands.Per kind — the refusal before, the shape after
Refusals re-measured on this branch's base through the
#14087harness (schema parse afternormalizeStackInputand the unknown-key lints, thenrunAuthoringRules('validate')), not copied from the card.object— the one that is not shape driftBefore:
The object parses fine and is refused one layer later, by an error-severity author-time rule asking for a security decision. Per triage, this is not a new authoring decision:
#9666already took it for theos inittemplates. Measured on this base, bothos initsites emitsharingModel: 'private'— so this emits the same value, with the same comment block naming ADR-0090 and the sharing-rules doc, and the two doors an author can arrive through agree. (Had that template emitted none, or a different value, the disagreement would have been reported instead of a third answer invented.)After:
view— a container, not a flat list viewBefore:
The template wrote both spellings at once — a flat list view's keys on the container and a
listblock. That mattered past the refusal: a flat view parses to an empty container, so zero views register and the Console renders nothing (which is whydefineViewhas carried a bespoke guard for it).pageSizeisPaginationConfigSchema's key, reached through the list view'spagination. The object binding isobject— the keygetViewsByObject()reads;objectNameis the spelling on the query surface.After:
name/label/objecton the container, onelistholdingtype/columns/sort/pagination: { pageSize: 25 }. The const is renamed toNAMEViewsand its JSDoc follows, because the artifact is a container now, not "a list view".action— a declared type and the singletargetslotBefore:
customis not anActionType, andhandleris not an Action key — the second handler slot was removed in protocol 17 precisely so no consumer has two places to disagree about. What the oldhandler: { type: 'flow', target: 'NAME_flow' }block was trying to express is exactlytype: 'flow'withtargetnaming the flow, so that is what it now says.ActionSchema's own refinement makestargetrequired for every type butscript, so this cannot drift back to an action bound to nothing.After:
type: 'flow',target: 'NAME_flow'— the nameos g flow NAMEwrites.app— navigation is an arrayBefore:
AppSchema.navigationisz.array(NavigationItemSchema).optional(). There is nosidebarwrapper on the authoring surface: the array is the sidebar tree, and it nests throughtype: 'group'items carryingchildren.After: one real
type: 'object'entry (id/type/label/objectName) rather than an empty array — the entry shape is the thing an author copies to add the second one, and an app with no navigation renders a shell with nothing in it. It points at the objectos g object NAMEwrites.The
actionandapprepairs deliberately target the names the sibling generators write, so the scaffolds compose instead of each standing alone.Scope
packages/specwas read but not changed — none of the four needed a spec change; each repair moves the scaffold onto a shape the schema already documents.dashboardandskillwere clean and stay clean; theflowtemplate (#14087) is untouched, and the harness still assertsflowis absent from the ledger. The migration codegen region ofgenerate.tsis untouched.Verification
All runs serialized through this container's shared verify lock. Every exit code captured with the redirect-then-capture form, and each gate's verdict read from the line the gate itself prints — never from a bare
$?behind a pipe.Red-first, then green (the ledger is the instrument, so it was emptied before any template was touched — first commit on the branch):
The four reds printed exactly the refusals quoted per kind above — re-measured here, not copied from the card.
Ablation — one refused shape re-introduced on disk (
type: 'custom'back into the action template), predicted direction stated before running: red foractiononly.The mutation was proven on disk by grep counts of both the injected and the removed text plus a blob-hash comparison — not by the editor's exit code — and the restore leg got the same treatment. The script carried
trap ... EXIT INT TERMwith absolute paths throughout. No rebuild leg was needed and none was skipped: the harness reaches the template through a relative../src/commands/generate.jsimport that vitest transpiles from source, so nodist/sits between the edit and the measurement.Gate union — re-derived AFTER the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(the first derivation printed STALE TREE, soorigin/mainwas merged in and it was derived again; the second run printed no staleness warning and the union was unchanged). It reports the change set as exactly the three files this PR touches. All 37 were run:The five are all "nothing was measured" by the gate's own words, and all name the same missing prerequisite — a whole-repo build, or a saved
turbo run testlog, i.e. inputs CI has and a single dev container does not:check:dual-build-cjs-loads— "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ ... ⛔ This is NOT a pass: nothing was measured."check:test-completeness— "Nothing was measured: this gate exited before parsing a single summary line ... ⛔ It is NOT a finding."pm/check-half-states— "Treat this exit as an unread instrument, never as a quiet board."check:i18n/check:i18n-coverage— "Nothing was checked / Nothing was measured", both naming the same ten-package build closure. Neither can be moved by this diff: it adds no label to any authored artifact and touches no translation bundle — the scaffold labels live inside a template string, not in metadata any extract config loads.⛔ Reported as NOT MEASURED, never folded into the green count. Notably green and directly relevant to the test-file edit:
check:type-check-debt,check:type-check-coverage,check:type-source-resolutionandcheck:test-source-aliasall pass, so emptying the ledger moved no type-check debt.Package-level, on the final tree
467e26e9cd:packages/cli/tsconfig.jsonhasinclude: ["src"], sopnpm --filter @objectstack/cli typechecktype-checks the repaired generator but not the edited test file. What covers the test tree here is the type-check debt/coverage gate family, and those pass unchanged (above). Recorded as a boundary, not as coverage.Notes for review
objectrepair was the one place a wrong answer would have been invented rather than measured. Triage's stop-and-report condition —os initemitting nosharingModel, or a different one — did not fire: both sites emit'private', so this PR emits'private'and copies their comment block verbatim rather than writing a new rationale.KNOWN_UNVALIDATED_SCAFFOLDS; it is now empty. The anti-staleness assertion and theflow-absent assertion are both retained.packages/spec/**is unchanged. No scaffold needed a spec change; if one had, this would have stopped and reported instead.🤖 Generated with Claude Code
https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
Generated by Claude Code