Repository navigation
Description
Measured on origin/main (a39b02a6) while implementing #14106, by running runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }) — the exact path os validate takes (packages/cli/src/commands/validate.ts) — over the same two list views in the two authorable container shapes:
| where the list views live | gantt view with no gantt: block |
|---|---|
top-level views: [{ object: 'duly_task', listViews: { schedule: { type: 'gantt' } } }] |
view/layout-without-binding at views[0].listViews.schedule.gantt (as designed) |
object-nested objects: { duly_task: { …, listViews: { schedule: { type: 'gantt' } } } } |
nothing — 0 findings |
Why
packages/lint/src/validate-functional-completeness.ts walks objects[].fields for the field rules and only stack.views[] containers for the list-view rules. An object's built-in named list views (ObjectSchema.listViews, packages/spec/src/data/object.zod.ts — ADR-0017 nested "Object has-many View", ADR-0047 segmented tabs) and its default list slot are never handed to checkViewCompleteness. normalizeStackInput does not hoist them into stack.views either (views is not in MAP_SUPPORTED_FIELDS; the conversion layer does not move views), so on the normalized pre-parse stack the wrapper receives, object-nested list views are invisible to the whole view/* completeness family — view/layout-without-binding and, after #14106 lands, view/tree-without-parent-field.
The sibling reference-integrity rules already walk both doors: lint-view-refs.ts (containerFromObject) and validate-list-view-field-refs.ts (the objects[].listViews.* loop, resolving the object as data.object ?? obj.name). The completeness walk is the one member of the family that stopped at the top-level container, which is exactly the shape validate-functional-completeness.test.ts's own docblock warns about: "a completeness gate that walks half the stack is exactly that: green, and blind to the other half."
Expected
validateFunctionalCompleteness walks objects[].list and objects[].listViews.* through checkViewCompleteness with the object itself as the bound object (an inline data.object may retarget, per ADR-0047), reporting where as object "<name>" › listViews.<key> and path as objects[<i>].listViews.<key>.<block>, so an object-nested timeline / gantt / tree view authored without its binding gets the same warning the top-level container form gets today. A fixture pair in both spellings (map-form and array-form objects).
Scope note
Deliberately not folded into #14106 (that card is domain:spec, and its dispatch declared the lint edit as wiring only); this is a packages/lint walk defect and lands in the lint lane. #14106 remains open until its own PR merges; this issue does not depend on it.
Generated by Claude Code
Activity
huangyiirene commentedon Sep 2, 2026
Triage (R+90, triage seat, session
session_019kDRpB7D2XzVzkaLp57T5D):pm:queue·priority:p2·domain:devx· type Bug.Lands in:
packages/lint/src/validate-functional-completeness.ts— walkobjects[].listandobjects[].listViews.*throughcheckViewCompletenesswith the object itself as the bound object (an inlinedata.objectmay retarget, per ADR-0047), reportingwhere/pathin the object-nested spelling the card gives. The two sibling rules already walk both doors (lint-view-refs.ts'scontainerFromObject,validate-list-view-field-refs.ts'sobjects[].listViews.*loop) — this is the one family member that stopped at the top-level container, and its own test docblock names the failure: "a completeness gate that walks half the stack is exactly that: green, and blind to the other half." Fixture pair in both spellings (map-form and array-form objects) is part of the deliverable.p2: an author using the ADR-0017 nested door — the door the object schema itself offers — gets no diagnostic at all for a
gantt/timeline/treeview with no binding block, while the same view in the top-level container is flagged. Warning-severity family, so no gate strength moves.pm:dispatched, spec seat): that card addsview/tree-without-parent-fieldto the same rule family and its dispatch declares the lint edit as wiring only. This card widens the walk those rules run on ⇒ land after #14106 merges and re-derive, so the new rule is carried through both doors by construction rather than by a second edit.Size/model suggestion: S–M,
opus.Generated by Claude Code
claude commentedon Sep 2, 2026
Claim: session
session_01WLJQhde67SeTccsmnBVarV(domain:devx execution seat, seat post #6023) — R1 wave 7 (concurrency raised to 5 by the maintainer, 2026-09-02 「并发加到五」).claude/issue-14320-completeness-walk-object-nested-views../objectstack-14320(dedicated per-task worktree, offorigin/main)domain:devx(packages/lint)packages/lint/src/validate-functional-completeness.ts(the list-view walk at:103–139onorigin/mainf35821044, re-verified just now:for (const [vi, container] of entriesOf(stack.views).entries())at:124is the only container loop;objects[].list/objects[].listViews.*are never handed tocheckViewCompleteness) +validate-functional-completeness.test.ts(fixture pair in map-form and array-form object spellings). ⛔lint-view-refs.ts/validate-list-view-field-refs.tsare READ as the sibling precedent (containerFromObject, theobjects[].listViews.*loop), not edited.view/layout-without-bindingcovers only 3 of the 6 view types that have a binding block —timeline,treeandmapfall through the same trapdoor #14106 is CLOSED and itsview/tree-without-parent-fieldrule is onorigin/main(validate-functional-completeness.test.ts:98/:226), so the widened walk carries both binding rules through both doors by construction.opus; tier readout fromscripts/pm/dispatch-gates.mjs --tier packages/lint/src/validate-functional-completeness.ts: "no path-derived mandate — floor sonnet · default opus · ceiling fable".dashboard-filter-field-unknownstill skips every dotted filter field and resolves against the flatSYSTEM_FIELDSunion — the seam next to it now answers both #14275 (in flight, same seat) holdsvalidate-widget-bindings.tsonly — disjoint;object-graph.tsfree, not needed here.Generated by Claude Code
fix(lint): walk object-nested list / listViews through the view compl…
chore(changeset): patch @objectstack/lint for the widened completenes…
claude commentedon Sep 2, 2026
os-dev-report
{ "issue": 14320, "status": "done", "branch": "claude/issue-14320-completeness-walk-object-nested-views", "worktree": "/home/user/objectstack-14320", "pr": "https://github.com/objectstack-ai/objectstack/pull/14432", "pr_number": 14432, "head_sha": "050dd5716295e1b302f54c640121251c29142935", "premise_still_valid": true, "summary": "validateFunctionalCompleteness had exactly one container loop (stack.views), so objects[].list and objects[].listViews.* never reached checkViewCompleteness. Added the object-nested walk beside the existing objects[] field walk in the same file; the top-level walk and all severities are untouched. Bound-object resolution and the where/path grammar are mirrored from validate-list-view-field-refs (data.object retarget per ADR-0047, else the object's own name; where = object \"NAME\" > listViews.KEY, path = objects[i].listViews.KEY.BLOCK, positional object segment in both authorable spellings, as this file's own field walk already spells it). The serial-with-#14106 clause is DISCHARGED: that card is closed and view/tree-without-parent-field is on origin/main, so branching off current main carries both binding rules through the nested door by construction, pinned by tests. Merged origin/main cleanly mid-run; no conflicts, no deferred regeneration.", "files_changed": [ "packages/lint/src/validate-functional-completeness.ts", "packages/lint/src/validate-functional-completeness.test.ts", ".changeset/eighty-pumas-shave.md" ], "tests": "All at head 050dd5716; every exit code captured by redirect-then-capture, never through a pipe. (1) pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 -> PASS, 'Test Files 93 passed (93)' / 'Tests 2788 passed | 5 skipped (2793)'. (2) pnpm --filter @objectstack/lint typecheck -> exit 0. (3) REVERSE VERIFICATION, fix committed FIRST: implementation restored from base f35821044, restore leg pinned to HEAD under trap ... EXIT INT TERM with an absolute repo root; mutation confirmed on disk by blob hash 1de9c7df -> e4cec2e4 with nested-walk marker count 0; suite re-run gave 'Tests 9 failed | 16 passed (25)' vs 25/25 on head — expected turn-red direction. The 9 are exactly the new nested cases; the two negative fixtures and the two clean-stack acceptance cases correctly stay green on base (a blind walk reports nothing, including nothing false). Restoration proved byte-identical: git hash-object == git rev-parse HEAD:<path>, git status --porcelain empty, git diff HEAD empty. No dist involved — the test imports the module under test by intra-package relative path, so the ablation is source-level and needs no rebuild; the dependency closure was nonetheless built first (pnpm --filter '@objectstack/lint^...' build, exit 0). (4) ACCEPT-SET PROBE via the card's own entry runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }): app-todo and app-crm baseline 0 view/* findings BEFORE and 0 AFTER; the injected CONTROL (object-nested gantt with no gantt block) produced 0 new findings before and exactly 1 after, per app — 'view/layout-without-binding @ objects[0].listViews.os14320_control.gantt'. Zero new findings on the shipped apps besides CONTROL, so nothing in examples/ needed fixing or suppressing. app-showcase is NOT MEASURED dynamically (its dep closure is unbuilt here: Cannot find module .../@objectstack/connector-mcp/dist/index.js); a static scan settles all three instead — no example app authors an object-nested container at all (grep over examples/*/src/objects/ and *.object.ts: 0 matches), so this walk's population on the shipped apps is empty by construction.", "gates": { "derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, re-derived at head 050dd5716 AFTER the changeset commit (34 commands: 26 by path+kind, 8 more once the changeset existed). Not hand-listed; script read its own change set from the merge base.", "RAN-PASS": "31 of 34 — check:cross-package-test-inputs, check:doc-authoring, check:engine-double-contract, check:logger-receiver-detach, check:objectql-double-limit, check:page-declaration-shape, check:published-files, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:changeset-gate-self-tests, check:objectui-changeset, check:pm-half-states, check-ci-filter-parity, check-comment-mask-adoption, check-cross-package-test-inputs, check-keyed-text-bounds, check-plugin-teardown-shape, check-shard-attestation, check-system-context-census, check-undeclared-dep-imports, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, docs-audit/check-affected-docs, docs-audit/check-drift-comment, pm/check-half-states, pm/release-rehearsal-clone --self-test", "NOT-MEASURED": "3, all prerequisite refusals (exit 3, never a red) — check:dual-build-cjs-loads ('PREREQUISITE NOT MET ... Run pnpm build first', needs the whole workspace dist); check:type-check-debt (refuses on an unbuilt closure by design); check-test-completeness.mjs (no saved turbo test log to parse; its own text prescribes recording it as NOT MEASURED)", "UNRUN": "check:pm-dispatch-gates — not in the derived list for these paths, so not owed here. Repo-wide pnpm lint also UNRUN: it is a repo-scan CI owns and this diff adds no lint-relevant construct beyond ordinary TypeScript in two files the package suite and typecheck cover.", "typecheck_scope_caveat": "packages/lint/tsconfig.json excludes **/*.test.ts, so the green typecheck says NOTHING about the new test code — tsc --listFiles reports 0 occurrences of the edited test file. That exclusion is known ledgered state (check:type-check-coverage passes; the package carries a TEST_DEBT entry of 16). To confirm this PR cannot push that ratchet up while check:type-check-debt is unmeasurable here, the test surface was measured directly against the built closure with a throwaway include-tests project: 22 errors total, 0 in validate-functional-completeness.test.ts (the 22 = 16 ledgered + 6 TS6059 the ledger note itself attributes to a re-measure project's inherited rootDir). A targeted measurement of this PR's contribution, NOT a substitute for the gate." }, "before_after": "app-todo 0->0 baseline, CONTROL +0 before / +1 after. app-crm 0->0 baseline, CONTROL +0 before / +1 after. app-showcase NOT MEASURED dynamically, static population empty. No unexplained new finding on any shipped example app.", "changeset": "yes — .changeset/eighty-pumas-shave.md, patch on @objectstack/lint (published; publishConfig.access public, not private). User-visible: os validate / os build now emit findings on metadata that previously reported clean. skip-changeset does not apply.", "mcp_calls": "0 — all GitHub work went through repo-scoped REST with GITHUB_TOKEN (PR create, PR read-back, this comment). No MCP GitHub tool was called.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
claude commentedon Sep 2, 2026
ACCEPT — #14320 landed. PR #14432 merged to
mainas1fb6281a4via the merge queue (auto-merge armed after the CI watcher read GREEN; theauto_merge: null/mergeable_state: unknownreading while queued is the PR mid-queue, not disarmed).Close-out probes executed on
origin/main(fresh fetch, throwaway worktree — not the shared checkout;1fb6281a4confirmed as an ancestor oforigin/mainata98b61b3e):packages/lint/src/validate-functional-completeness.ts:141and:180— both list-view diagnostics now build their path with`…listViews${lv.key}`, i.e. the list-view key instead of the array index, so a reported path points at the view the author named.checkViewCompletenessreads 6 hits in the same file, so the grep is live and the two key-based paths are the diff.pm:dispatchedstripped in this same step (type / priority /domain:devxkept — ownership is not state).Seat:
session_01WLJQhde67SeTccsmnBVarV(domain:devx, #6023).Generated by Claude Code