Skip to content

validate-functional-completeness walks only top-level views containers — object-nested list / listViews (ADR-0017) never reach the view completeness rules #14320

Closed

Description

@claude

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

  1. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    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 — walk 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 / path in the object-nested spelling the card gives. The two sibling rules already walk both doors (lint-view-refs.ts's containerFromObject, validate-list-view-field-refs.ts's objects[].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 / tree view with no binding block, while the same view in the top-level container is flagged. Warning-severity family, so no gate strength moves.

    ⚠️ Serial with #14106 (pm:dispatched, spec seat): that card adds view/tree-without-parent-field to 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

  2. added
    bugSomething isn't working
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    on Sep 2, 2026
  3. added theissue type on Sep 2, 2026
  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 2, 2026
  5. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    Claim: session session_01WLJQhde67SeTccsmnBVarV (domain:devx execution seat, seat post #6023) — R1 wave 7 (concurrency raised to 5 by the maintainer, 2026-09-02 「并发加到五」).

    • Branch: claude/issue-14320-completeness-walk-object-nested-views
    • Worktree: ../objectstack-14320 (dedicated per-task worktree, off origin/main)
    • Domain: domain:devx (packages/lint)
    • File surface: packages/lint/src/validate-functional-completeness.ts (the list-view walk at :103–139 on origin/main f35821044, re-verified just now: for (const [vi, container] of entriesOf(stack.views).entries()) at :124 is the only container loop; objects[].list / objects[].listViews.* are never handed to checkViewCompleteness) + validate-functional-completeness.test.ts (fixture pair in map-form and array-form object spellings). ⛔ lint-view-refs.ts / validate-list-view-field-refs.ts are READ as the sibling precedent (containerFromObject, the objects[].listViews.* loop), not edited.
    • Serial constraint from triage 5503579454 discharged: view/layout-without-binding covers only 3 of the 6 view types that have a binding block — timeline, tree and map fall through the same trapdoor #14106 is CLOSED and its view/tree-without-parent-field rule is on origin/main (validate-functional-completeness.test.ts:98/:226), so the widened walk carries both binding rules through both doors by construction.
    • Container & model: subagent lane (S–M), opus — triage suggested S–M / opus; tier readout from scripts/pm/dispatch-gates.mjs --tier packages/lint/src/validate-functional-completeness.ts: "no path-derived mandate — floor sonnet · default opus · ceiling fable".
    • Clause ②: not reached — warning-severity author-time lint family, no gate strength moves, no contract accept/reject behaviour. Accept-set narrowing on a lint rule ⇒ the corpus before/after count over the shipped example apps (PR fix(lint): refuse dotted list-view field references at author time (#14282) #14368 precedent, mutated CONTROL) is required evidence.
    • Serial constraints: [finding] dashboard-filter-field-unknown still skips every dotted filter field and resolves against the flat SYSTEM_FIELDS union — the seam next to it now answers both #14275 (in flight, same seat) holds validate-widget-bindings.ts only — disjoint; object-graph.ts free, not needed here.

    Generated by Claude Code

  6. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    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

  7. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 2, 2026
  8. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    ACCEPT — #14320 landed. PR #14432 merged to main as 1fb6281a4 via the merge queue (auto-merge armed after the CI watcher read GREEN; the auto_merge: null / mergeable_state: unknown reading while queued is the PR mid-queue, not disarmed).

    Close-out probes executed on origin/main (fresh fetch, throwaway worktree — not the shared checkout; 1fb6281a4 confirmed as an ancestor of origin/main at a98b61b3e):

    • packages/lint/src/validate-functional-completeness.ts:141 and :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.
    • Control: checkViewCompleteness reads 6 hits in the same file, so the grep is live and the two key-based paths are the diff.

    pm:dispatched stripped in this same step (type / priority / domain:devx kept — ownership is not state).

    Seat: session_01WLJQhde67SeTccsmnBVarV (domain:devx, #6023).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:devxDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions