fix(lint)!: refuse a view container whose object names no object - #20253
objectstack-fleet[bot] merged 6 commits into
Conversation
The container's own `object` is the key getViewsByObject() indexes by, and nothing resolved it at authoring time. It now joins the object-reference ladder beside the relationship-target leg, resolved against the same set (own objects plus packages[]), and names the namespace-prefixed spelling when that is the one declared. Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV Co-authored-by: Claude <noreply@anthropic.com>
…wing Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV Co-authored-by: Claude <noreply@anthropic.com>
…ew-container-object-refused
Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 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 — 4 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 7750764a9f6433145424620213c4eca3e6e125e3 && git checkout 7750764a9f6433145424620213c4eca3e6e125e3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 443b2f4fdce681f1d3656475c9c72f3ffacfee4c 60808317dca031fc55bff733d60cafe762283988 && git checkout -B drift-repro 443b2f4fdce681f1d3656475c9c72f3ffacfee4c && git merge --no-ff 60808317dca031fc55bff733d60cafe762283988
node scripts/docs-audit/affected-docs.mjs --json 443b2f4fdce681f1d3656475c9c72f3ffacfee4c
|
…rites The scaffold-validates harness judged `os g view` in a stack that declared no object, so once the author-time rules resolve a view container's `object` the harness's own omission read as the template's defect. Every `namesObject` generator is now validated beside the object scaffold for the same name, materialized through the same loader. Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20216
Clause-②: no (narrowing)
Landing order: PR #20228 → PR #20229 → this PR. #20228 has merged. #20229 (the
packages[]read inartifactProvidedObjectNames, still in a rework round) edits the same file. This PR does not touch that hunk, its import line, orpackages/lint/src/object-graph.ts, and a localgit merge-treeof this branch against #20229's heada4b05d6writes a clean tree. The seat enqueues this PR after #20229 merges, and this branch mergesorigin/mainthen.CI fix round:
Test Core (6/6), and a file-surface amendmentpackages/cli/test/generate-scaffold-validates.test.ts, "os g 'view'writes a stackos validateaccepts", gotobject-reference-unknown at views[0].object: view container object "probe_thing" ….probe_thing, the objectos g object probe_thingwrites, but no object was in the stack. The new leg refused it correctly: the omission was the harness's, not the template's. Reproduced red locally before the fix (1 failed, 17 passed).namesObjectgenerator other thanobjectitself (view, action, flow, app) is now judged beside the object scaffold for the same name, materialized through the samebundle-requireloader. A new pin asserts the view's binding and the seeded object'snameare the same spelling, and that a non-binding generator (dashboard) andobjectitself carry no seeded object. ⛔ The rule is not weakened,probe_thingis not special-cased, and no test is skipped.packages/cli/test/generate-scaffold-validates.test.ts, test fixture only. No other CLI fixture needed a change (see the runs below).probe_thing. They passed before the seeding and still pass with it.What changed
A view container's own
object(ViewSchema.object) is the key the runtime indexes views by (getViewsByObject()/GET /meta/view?object=). Nothing resolved it at authoring time:defineStack'svalidateCrossReferencesreads a container'slist.data/form.databindings, never the container's own key.packages/lint/src/validate-object-references.tsgains a view-container leg beside the relationship-target leg. It uses the samecheckladder and the sameresolvableset: the stack's objects plus what itspackages[]provide.object-reference-unknownatviews[N].object.object-reference-unregistered-platformadvisory.Defined objects: ...);manifest.namespaceprefix, the hint names that object:write "my_app_order_line", not "order_line".os validate,os buildandos lintall run it at the same tier.object(its binding falls back tolist.data.object/form.data.object/name, which is a different reference);viewwrite at the runtime publish gate, and the runtime is untouched.packages/spec/src/stack.zod.ts.Measured at the public door (CLI built at this branch)
The scratch project has
manifest.namespace: 'my_app', an objectmy_app_order_line, and a view container withobject: 'order_line'.os validateos builddist/)object: 'order_line'object-reference-unknown at views[0].object, hint namesmy_app_order_lineobject: 'my_app_order_line'(control)os generate view order_linein the same namespaced project (after PR #20214) writesobject: 'my_app_order_line', which passes.Census of producers (H2)
The instrument is one TypeScript-AST scan at
0d60f88760. It readsexamples/**,packages/**(tests and fixtures included),skills/**, and the ts/js code fences incontent/docs/**md/mdx (generatedreferences/excluded). It covers 7242 files and counts object literals that carry a view-container slot (list/form/listViews/formViews). A name resolves when it is declared by an object literal (name+fields) anywhere in the corpus, or when it is a platform-provided object.objectobjectresolve.objectat all; they bind throughlist.data.object.objectexpressions in code, not stored views: schema/strictObjectdefinitions inview.zod.ts, walkers invalidate-translation-references.ts/validate-translatable-sections.ts/protocol.ts, a helper in a rest measurement test, and the parameterised helper in this PR's own test.packages/cli/test/format-zod-union.test.ts(union_probe_obj). That specimen fails schema parse first, which that file asserts as exactly oneinvalid_unionissue.os validateexits at the schema step, so author-time rules never run on it. No pin flips.object-reference-unknownand the rule's message across the repo found no pin on a view container. The pins inpackages/cli(artifact-packages.test.ts,build-multi-package-artifact.e2e.test.ts,union-fold-command-parity.test.ts) have fixtures with no containerobject, so none flips. ②: nothing flipped, so no load-bearing re-pin was owed.Tests (at
60808317dcunless marked)pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 109 files / 4242 tests passed.pnpm --filter @objectstack/lint typecheck: exit 0 (tsc --noEmitpluscheck:test-typecheck: OK).pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 227 files / 3219 tests passed. This includes the fixedgenerate-scaffold-validates.test.ts(19 of 19).--project integration): 21 files / 196 tests passed.pnpm --filter @objectstack/cli typecheck: exit 0 (check:test-typecheck: OK).OS_TEST_TIERS=nightly), 7 e2e files on views or scaffolds: 6 files passed. One test failed ingenerate-agent-retired.e2e.test.ts("os g object … --dry-runstill previews a typed object file"). It expectsimport * as Data from '@objectstack/spec/data', but the object template writesimport { ObjectSchema }since fix(cli): os init and os generate object declare the scaffolded object with ObjectSchema.create #20195. That failure is independent of this PR: neither file differs from the merge base (0 diff lines).validate-object-references.test.ts(two newdescribeblocks, appended so they stay clear of fix(lint): refuse a present non-arraypackagesat all five stack.packages readers #20229's hunk):object: 'order_line'in amy_appstack is refused, namingmy_app_order_line; the prefixed container is the control;viewsis read;packages[]sibling's object passes, and the same package alone is refused (control);sys_userpasses andsys_approval_processadvises;views: [], and a container with noobjectstay silent;runAuthoringRulesforvalidate,buildandlint.scripts/ablation-replace.mjs, anchorconst bound = strName(view.object);, anchor count 1 to 0, blob27699c9to03f6f47): 8 refusal pins red; 48 green, including both controls (prefixed container, silence). Restored with blob equal to HEAD andgit diff HEADempty. Taken at9fa1ff4c61. Since then only comment lines changed insrc.@objectstack/lintrebuilt,ablation-dist-preflightfound the marker in 4 built files, andos validateexited 0. Then the restore leg: blob equal to HEAD, lint rebuilt, preflight--absentconfirmed the marker gone from all 14 built files with a clean tree, andos validateexited 1 again.Gates (derived by
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat60808317dc)check:cli-test-child-env, green) and all run at60808317dc.--ranreconciliation with exit codes: 59 run, 1 NOT MEASURED, 0 unrun.check-adr-0087-registration(dispositionnot-required (no-migration-prescription)accepted),check-changeset-no-major,check-empty-changeset,check:doc-authoring,check:nul-bytes,check-closing-keyword-parity,check:published-files,check:type-check-coverage,check:test-source-alias.check:type-check-debtnow measures green (none above its recorded number).check:dual-build-cjs-loads(exit 3, PREREQUISITE NOT MET: it needs every package'sdist/, and this box built the lint and CLI closures only). Declared to CI.packages/clisuites to CI without running them, and the census missedos g viewbecause its container sits inside a template string the AST scan cannot see. That is the red this round fixes. The CLI unit project now runs in full here.Changeset
.changeset/20216-view-container-object-refused.md:@objectstack/lint: minor, carrying a BREAKING banner andClause-②: no (narrowing);not-required (no-migration-prescription): nothing authorable moves in spec, and which object an author meant is a fact about their stack, not a mechanical conversion.The table is a behaviour table (door: FROM exit 0, TO exit 1). My first draft headed it "FROM → TO", and the ADR-0087 gate read that heading as a rewrite prescription and refused the exemption. The heading now says "before and after", and the table carries no rewrite row.
Acceptance notes (not filed)
reference, action params, dataset base object) could give the same hint for the same missing-prefix miss. I noted it and did not widen it here. Carrier: none.objectandlist.data.objectname different objects is not judged by any rule. It is out of this card's scope, and I found no instance in the census.Generated by Claude Code