fix(lint): walk object-nested list / listViews through the view completeness rules - #14432

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views
Sep 2, 2026
Merged

fix(lint): walk object-nested list / listViews through the view completeness rules#14432
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14320

What

validateFunctionalCompleteness walked exactly one container loop — stack.views — so objects[].list and objects[].listViews.* (the ADR-0017 "Object has-many View" spelling) never reached checkViewCompleteness. This adds the second walk, beside the existing objects[] field walk, in the same file.

The two sibling rules in this family already walk both doors: lint-view-refs.ts's containerFromObject pulls list / listViews straight off the object definition, and validate-list-view-field-refs.ts has an objects[].listViews.* loop resolving the bound object as data.object ?? <the object's own name>. This was 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."

Resolution semantics and location grammar — mirrored, not invented

  • Bound object = the list view's own data.object retarget (ADR-0047) first, else the object the container belongs to. That is validate-list-view-field-refs's listViewObject(lv) ?? objName on the identical rung.
  • where = object "NAME" › listViews.KEY (and object "NAME" › list for the default slot) — the card's Expected spelling, and the sibling's.
  • path = objects[i].listViews.KEY.BLOCK. The object segment is positional in both authorable spellings, which is how this file's own field walk directly above already spells it (objects[i].fields.NAME) and how the sibling spells it. No new path grammar.
  • The top-level container walk is untouched, and severities are unchanged (the warning family; no gate strength moved).

Serial-with-#14106 clause: discharged

Triage made this card serial with #14106 so the new view/tree-without-parent-field rule would be carried through both doors by construction rather than by a second edit. #14106 is closed and that rule is on origin/main; this branch is cut from current main, so the widened walk carries bothview/layout-without-binding and view/tree-without-parent-field through the nested door with no extra wiring. Pinned by a test that reaches the tree rule through objects[].listViews.* and by the map-form self-lookup case that stays silent.

Accept-set evidence — before/after on the shipped example apps

Probe entry point is the card's own repro path, runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }), counting view/* findings per app. CONTROL = the same stack with an object-nested gantt list view injected onto its first object, stripped of its gantt block.

example appbaseline BEFORE (f358210)baseline AFTER (050dd57)CONTROL beforeCONTROL after
app-todo000 (+0)1 (+1)
app-crm000 (+0)1 (+1)
app-showcaseNOT MEASUREDNOT MEASUREDNOT MEASUREDNOT MEASURED

CONTROL finding on head, verbatim:

view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "todo_task" › listViews.os14320_control)
view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "crm_account" › listViews.os14320_control)

Zero new findings on the shipped apps besides CONTROL, so nothing in examples/** needed fixing or suppressing.

app-showcase could not be loaded by the probe in this container (Cannot find module .../@objectstack/connector-mcp/dist/index.js — its dependency closure is not built here), so its row is honestly NOT MEASURED rather than reported green. What settles it for all three apps instead is a static scan of the population this change can reach: no example app authors an object-nested container at all

grep -rn "listViews|^\s*list:" --include=*.ts examples/*/src/objects/ → 0 matches
grep -rln "listViews" --include=*.object.ts examples/ → 0 files

Every listViews in examples/ sits in a top-level defineView container, which the old walk already covered. So the widened walk's population on the shipped apps is empty by construction, which is exactly what the two measured rows show.

Reverse verification

The fix was committed first, then the implementation file was restored from the base commit (git checkout f35821044 -- <path>, restore leg pinned to HEAD under a trap … EXIT INT TERM with an absolute repo root), the ablation confirmed on disk by blob hash (1de9c7df… → e4cec2e4…, nested-walk marker count 0), and the suite re-run:

Ablated (base implementation): Tests 9 failed | 16 passed (25)
Head: Tests 25 passed (25)

Direction is the expected one, turn-red. The 9 are exactly the new object-nested cases; the two negative fixtures and the two "clean stack passes" acceptance cases correctly stay green on base too — a blind walk reports nothing, including nothing false. Restoration verified byte-identical (git hash-object == git rev-parse HEAD:<path>, git status --porcelain empty, git diff HEAD empty). No dist/ is involved: the tests import the module under test by intra-package relative path, so the ablation is source-level.

Tests

Fixture pair in both object spellings plus the negative half, in validate-functional-completeness.test.ts:

  • object-nested listViews.plangantt with no gantt block — array-form objects → view/layout-without-binding @ objects[0].listViews.plan.gantt
  • the same, name-keyed map-form objects → same rule, same location grammar
  • the object-nested default list slot → objects[0].list.timeline
  • view/tree-without-parent-field through the nested door, plus the self-lookup case that stays silent and the ADR-0047 data.object retarget
  • negative: a nested container whose views all declare their bindings → 0 findings (a widened walk that over-reported would be worse than the blind one)
  • nested and top-level copies reported independently
  • junk robustness: list: null, listViews: 'nope', list: 7, listViews: [null, 'x']
  • an end-to-end #14320 acceptance block driving both spellings through runAuthoringRules('validate')andrunAuthoringRules('build'), with the bound twin proving the fixtures fail for the right reason

Verification readout

All commands run at head 050dd5716; exit codes captured by redirect-then-capture, never through a pipe.

whatresult
pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 (whole package)PASSTest Files 93 passed (93) · Tests 2788 passed | 5 skipped (2793)
pnpm --filter @objectstack/lint typecheckPASS (exit 0)
derived gate families (scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)31 of 34 RAN-PASS, 3 NOT MEASURED

The three NOT MEASURED are prerequisite refusals (exit 3), not reds:

  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET … Run pnpm build first; needs the whole workspace dist/.
  • pnpm check:type-check-debt — refuses on an unbuilt closure by design (its own header measures packages/lint at 19 built vs 147 unbuilt).
  • node scripts/check-test-completeness.mjs — with no argument it has no saved turbo run test log to parse; its own text says to record it as NOT MEASURED.

⚠️ Note on typecheck scope: packages/lint/tsconfig.json excludes **/*.test.ts, so the green above 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 make sure this PR does not push that ratchet up while check:type-check-debt is unmeasurable here, the test surface was measured directly with a throwaway project that includes test files, against the built dependency closure: 22 errors total, 0 of them in validate-functional-completeness.test.ts (the 22 are 16 ledgered plus 6 TS6059 that the ledger note itself attributes to a re-measure project's inherited rootDir, not to this package). So the new test code contributes zero. That is a targeted measurement of this PR's contribution, not a substitute for the gate, which stays NOT MEASURED.

Repo-wide pnpm lint was not run locally — it is a repo-scan CI owns, and this change adds no lint-relevant construct beyond ordinary TypeScript in two files the package's own suite and typecheck already cover.

Changeset

patch on @objectstack/lint (a published package) — .changeset/eighty-pumas-shave.md. User-visible: os validate / os build now emit findings on metadata that previously reported clean. skip-changeset does not apply.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55da6d5fea720f0e48103c2f538d577ef1d1fc89 — the merge of head 050dd5716295e1b302f54c640121251c29142935 into base 96b627d135826253981cfa01d74b2832ccdea194, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55da6d5fea720f0e48103c2f538d577ef1d1fc89 && git checkout 55da6d5fea720f0e48103c2f538d577ef1d1fc89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96b627d135826253981cfa01d74b2832ccdea194 050dd5716295e1b302f54c640121251c29142935 && git checkout -B drift-repro 96b627d135826253981cfa01d74b2832ccdea194 && git merge --no-ff 050dd5716295e1b302f54c640121251c29142935
node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 06:59
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 1fb6281Sep 2, 2026
35 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14320-completeness-walk-object-nested-views branch September 2, 2026 07:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(lint): walk object-nested list / listViews through the view completeness rules - #14432

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views
Sep 2, 2026
Merged

fix(lint): walk object-nested list / listViews through the view completeness rules#14432
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14320

What

validateFunctionalCompleteness walked exactly one container loop — stack.views — so objects[].list and objects[].listViews.* (the ADR-0017 "Object has-many View" spelling) never reached checkViewCompleteness. This adds the second walk, beside the existing objects[] field walk, in the same file.

The two sibling rules in this family already walk both doors: lint-view-refs.ts's containerFromObject pulls list / listViews straight off the object definition, and validate-list-view-field-refs.ts has an objects[].listViews.* loop resolving the bound object as data.object ?? <the object's own name>. This was 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."

Resolution semantics and location grammar — mirrored, not invented

  • Bound object = the list view's own data.object retarget (ADR-0047) first, else the object the container belongs to. That is validate-list-view-field-refs's listViewObject(lv) ?? objName on the identical rung.
  • where = object "NAME" › listViews.KEY (and object "NAME" › list for the default slot) — the card's Expected spelling, and the sibling's.
  • path = objects[i].listViews.KEY.BLOCK. The object segment is positional in both authorable spellings, which is how this file's own field walk directly above already spells it (objects[i].fields.NAME) and how the sibling spells it. No new path grammar.
  • The top-level container walk is untouched, and severities are unchanged (the warning family; no gate strength moved).

Serial-with-#14106 clause: discharged

Triage made this card serial with #14106 so the new view/tree-without-parent-field rule would be carried through both doors by construction rather than by a second edit. #14106 is closed and that rule is on origin/main; this branch is cut from current main, so the widened walk carries bothview/layout-without-binding and view/tree-without-parent-field through the nested door with no extra wiring. Pinned by a test that reaches the tree rule through objects[].listViews.* and by the map-form self-lookup case that stays silent.

Accept-set evidence — before/after on the shipped example apps

Probe entry point is the card's own repro path, runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }), counting view/* findings per app. CONTROL = the same stack with an object-nested gantt list view injected onto its first object, stripped of its gantt block.

example appbaseline BEFORE (f358210)baseline AFTER (050dd57)CONTROL beforeCONTROL after
app-todo000 (+0)1 (+1)
app-crm000 (+0)1 (+1)
app-showcaseNOT MEASUREDNOT MEASUREDNOT MEASUREDNOT MEASURED

CONTROL finding on head, verbatim:

view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "todo_task" › listViews.os14320_control)
view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "crm_account" › listViews.os14320_control)

Zero new findings on the shipped apps besides CONTROL, so nothing in examples/** needed fixing or suppressing.

app-showcase could not be loaded by the probe in this container (Cannot find module .../@objectstack/connector-mcp/dist/index.js — its dependency closure is not built here), so its row is honestly NOT MEASURED rather than reported green. What settles it for all three apps instead is a static scan of the population this change can reach: no example app authors an object-nested container at all

grep -rn "listViews|^\s*list:" --include=*.ts examples/*/src/objects/ → 0 matches
grep -rln "listViews" --include=*.object.ts examples/ → 0 files

Every listViews in examples/ sits in a top-level defineView container, which the old walk already covered. So the widened walk's population on the shipped apps is empty by construction, which is exactly what the two measured rows show.

Reverse verification

The fix was committed first, then the implementation file was restored from the base commit (git checkout f35821044 -- <path>, restore leg pinned to HEAD under a trap … EXIT INT TERM with an absolute repo root), the ablation confirmed on disk by blob hash (1de9c7df… → e4cec2e4…, nested-walk marker count 0), and the suite re-run:

Ablated (base implementation): Tests 9 failed | 16 passed (25)
Head: Tests 25 passed (25)

Direction is the expected one, turn-red. The 9 are exactly the new object-nested cases; the two negative fixtures and the two "clean stack passes" acceptance cases correctly stay green on base too — a blind walk reports nothing, including nothing false. Restoration verified byte-identical (git hash-object == git rev-parse HEAD:<path>, git status --porcelain empty, git diff HEAD empty). No dist/ is involved: the tests import the module under test by intra-package relative path, so the ablation is source-level.

Tests

Fixture pair in both object spellings plus the negative half, in validate-functional-completeness.test.ts:

  • object-nested listViews.plangantt with no gantt block — array-form objects → view/layout-without-binding @ objects[0].listViews.plan.gantt
  • the same, name-keyed map-form objects → same rule, same location grammar
  • the object-nested default list slot → objects[0].list.timeline
  • view/tree-without-parent-field through the nested door, plus the self-lookup case that stays silent and the ADR-0047 data.object retarget
  • negative: a nested container whose views all declare their bindings → 0 findings (a widened walk that over-reported would be worse than the blind one)
  • nested and top-level copies reported independently
  • junk robustness: list: null, listViews: 'nope', list: 7, listViews: [null, 'x']
  • an end-to-end #14320 acceptance block driving both spellings through runAuthoringRules('validate')andrunAuthoringRules('build'), with the bound twin proving the fixtures fail for the right reason

Verification readout

All commands run at head 050dd5716; exit codes captured by redirect-then-capture, never through a pipe.

whatresult
pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 (whole package)PASSTest Files 93 passed (93) · Tests 2788 passed | 5 skipped (2793)
pnpm --filter @objectstack/lint typecheckPASS (exit 0)
derived gate families (scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)31 of 34 RAN-PASS, 3 NOT MEASURED

The three NOT MEASURED are prerequisite refusals (exit 3), not reds:

  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET … Run pnpm build first; needs the whole workspace dist/.
  • pnpm check:type-check-debt — refuses on an unbuilt closure by design (its own header measures packages/lint at 19 built vs 147 unbuilt).
  • node scripts/check-test-completeness.mjs — with no argument it has no saved turbo run test log to parse; its own text says to record it as NOT MEASURED.

⚠️ Note on typecheck scope: packages/lint/tsconfig.json excludes **/*.test.ts, so the green above 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 make sure this PR does not push that ratchet up while check:type-check-debt is unmeasurable here, the test surface was measured directly with a throwaway project that includes test files, against the built dependency closure: 22 errors total, 0 of them in validate-functional-completeness.test.ts (the 22 are 16 ledgered plus 6 TS6059 that the ledger note itself attributes to a re-measure project's inherited rootDir, not to this package). So the new test code contributes zero. That is a targeted measurement of this PR's contribution, not a substitute for the gate, which stays NOT MEASURED.

Repo-wide pnpm lint was not run locally — it is a repo-scan CI owns, and this change adds no lint-relevant construct beyond ordinary TypeScript in two files the package's own suite and typecheck already cover.

Changeset

patch on @objectstack/lint (a published package) — .changeset/eighty-pumas-shave.md. User-visible: os validate / os build now emit findings on metadata that previously reported clean. skip-changeset does not apply.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55da6d5fea720f0e48103c2f538d577ef1d1fc89 — the merge of head 050dd5716295e1b302f54c640121251c29142935 into base 96b627d135826253981cfa01d74b2832ccdea194, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55da6d5fea720f0e48103c2f538d577ef1d1fc89 && git checkout 55da6d5fea720f0e48103c2f538d577ef1d1fc89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96b627d135826253981cfa01d74b2832ccdea194 050dd5716295e1b302f54c640121251c29142935 && git checkout -B drift-repro 96b627d135826253981cfa01d74b2832ccdea194 && git merge --no-ff 050dd5716295e1b302f54c640121251c29142935
node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 06:59
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 1fb6281Sep 2, 2026
35 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14320-completeness-walk-object-nested-views branch September 2, 2026 07:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(lint): walk object-nested list / listViews through the view completeness rules - #14432

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views
Sep 2, 2026
Merged

fix(lint): walk object-nested list / listViews through the view completeness rules#14432
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14320

What

validateFunctionalCompleteness walked exactly one container loop — stack.views — so objects[].list and objects[].listViews.* (the ADR-0017 "Object has-many View" spelling) never reached checkViewCompleteness. This adds the second walk, beside the existing objects[] field walk, in the same file.

The two sibling rules in this family already walk both doors: lint-view-refs.ts's containerFromObject pulls list / listViews straight off the object definition, and validate-list-view-field-refs.ts has an objects[].listViews.* loop resolving the bound object as data.object ?? <the object's own name>. This was 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."

Resolution semantics and location grammar — mirrored, not invented

  • Bound object = the list view's own data.object retarget (ADR-0047) first, else the object the container belongs to. That is validate-list-view-field-refs's listViewObject(lv) ?? objName on the identical rung.
  • where = object "NAME" › listViews.KEY (and object "NAME" › list for the default slot) — the card's Expected spelling, and the sibling's.
  • path = objects[i].listViews.KEY.BLOCK. The object segment is positional in both authorable spellings, which is how this file's own field walk directly above already spells it (objects[i].fields.NAME) and how the sibling spells it. No new path grammar.
  • The top-level container walk is untouched, and severities are unchanged (the warning family; no gate strength moved).

Serial-with-#14106 clause: discharged

Triage made this card serial with #14106 so the new view/tree-without-parent-field rule would be carried through both doors by construction rather than by a second edit. #14106 is closed and that rule is on origin/main; this branch is cut from current main, so the widened walk carries bothview/layout-without-binding and view/tree-without-parent-field through the nested door with no extra wiring. Pinned by a test that reaches the tree rule through objects[].listViews.* and by the map-form self-lookup case that stays silent.

Accept-set evidence — before/after on the shipped example apps

Probe entry point is the card's own repro path, runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }), counting view/* findings per app. CONTROL = the same stack with an object-nested gantt list view injected onto its first object, stripped of its gantt block.

example appbaseline BEFORE (f358210)baseline AFTER (050dd57)CONTROL beforeCONTROL after
app-todo000 (+0)1 (+1)
app-crm000 (+0)1 (+1)
app-showcaseNOT MEASUREDNOT MEASUREDNOT MEASUREDNOT MEASURED

CONTROL finding on head, verbatim:

view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "todo_task" › listViews.os14320_control)
view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "crm_account" › listViews.os14320_control)

Zero new findings on the shipped apps besides CONTROL, so nothing in examples/** needed fixing or suppressing.

app-showcase could not be loaded by the probe in this container (Cannot find module .../@objectstack/connector-mcp/dist/index.js — its dependency closure is not built here), so its row is honestly NOT MEASURED rather than reported green. What settles it for all three apps instead is a static scan of the population this change can reach: no example app authors an object-nested container at all

grep -rn "listViews|^\s*list:" --include=*.ts examples/*/src/objects/ → 0 matches
grep -rln "listViews" --include=*.object.ts examples/ → 0 files

Every listViews in examples/ sits in a top-level defineView container, which the old walk already covered. So the widened walk's population on the shipped apps is empty by construction, which is exactly what the two measured rows show.

Reverse verification

The fix was committed first, then the implementation file was restored from the base commit (git checkout f35821044 -- <path>, restore leg pinned to HEAD under a trap … EXIT INT TERM with an absolute repo root), the ablation confirmed on disk by blob hash (1de9c7df… → e4cec2e4…, nested-walk marker count 0), and the suite re-run:

Ablated (base implementation): Tests 9 failed | 16 passed (25)
Head: Tests 25 passed (25)

Direction is the expected one, turn-red. The 9 are exactly the new object-nested cases; the two negative fixtures and the two "clean stack passes" acceptance cases correctly stay green on base too — a blind walk reports nothing, including nothing false. Restoration verified byte-identical (git hash-object == git rev-parse HEAD:<path>, git status --porcelain empty, git diff HEAD empty). No dist/ is involved: the tests import the module under test by intra-package relative path, so the ablation is source-level.

Tests

Fixture pair in both object spellings plus the negative half, in validate-functional-completeness.test.ts:

  • object-nested listViews.plangantt with no gantt block — array-form objects → view/layout-without-binding @ objects[0].listViews.plan.gantt
  • the same, name-keyed map-form objects → same rule, same location grammar
  • the object-nested default list slot → objects[0].list.timeline
  • view/tree-without-parent-field through the nested door, plus the self-lookup case that stays silent and the ADR-0047 data.object retarget
  • negative: a nested container whose views all declare their bindings → 0 findings (a widened walk that over-reported would be worse than the blind one)
  • nested and top-level copies reported independently
  • junk robustness: list: null, listViews: 'nope', list: 7, listViews: [null, 'x']
  • an end-to-end #14320 acceptance block driving both spellings through runAuthoringRules('validate')andrunAuthoringRules('build'), with the bound twin proving the fixtures fail for the right reason

Verification readout

All commands run at head 050dd5716; exit codes captured by redirect-then-capture, never through a pipe.

whatresult
pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 (whole package)PASSTest Files 93 passed (93) · Tests 2788 passed | 5 skipped (2793)
pnpm --filter @objectstack/lint typecheckPASS (exit 0)
derived gate families (scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)31 of 34 RAN-PASS, 3 NOT MEASURED

The three NOT MEASURED are prerequisite refusals (exit 3), not reds:

  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET … Run pnpm build first; needs the whole workspace dist/.
  • pnpm check:type-check-debt — refuses on an unbuilt closure by design (its own header measures packages/lint at 19 built vs 147 unbuilt).
  • node scripts/check-test-completeness.mjs — with no argument it has no saved turbo run test log to parse; its own text says to record it as NOT MEASURED.

⚠️ Note on typecheck scope: packages/lint/tsconfig.json excludes **/*.test.ts, so the green above 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 make sure this PR does not push that ratchet up while check:type-check-debt is unmeasurable here, the test surface was measured directly with a throwaway project that includes test files, against the built dependency closure: 22 errors total, 0 of them in validate-functional-completeness.test.ts (the 22 are 16 ledgered plus 6 TS6059 that the ledger note itself attributes to a re-measure project's inherited rootDir, not to this package). So the new test code contributes zero. That is a targeted measurement of this PR's contribution, not a substitute for the gate, which stays NOT MEASURED.

Repo-wide pnpm lint was not run locally — it is a repo-scan CI owns, and this change adds no lint-relevant construct beyond ordinary TypeScript in two files the package's own suite and typecheck already cover.

Changeset

patch on @objectstack/lint (a published package) — .changeset/eighty-pumas-shave.md. User-visible: os validate / os build now emit findings on metadata that previously reported clean. skip-changeset does not apply.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55da6d5fea720f0e48103c2f538d577ef1d1fc89 — the merge of head 050dd5716295e1b302f54c640121251c29142935 into base 96b627d135826253981cfa01d74b2832ccdea194, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55da6d5fea720f0e48103c2f538d577ef1d1fc89 && git checkout 55da6d5fea720f0e48103c2f538d577ef1d1fc89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96b627d135826253981cfa01d74b2832ccdea194 050dd5716295e1b302f54c640121251c29142935 && git checkout -B drift-repro 96b627d135826253981cfa01d74b2832ccdea194 && git merge --no-ff 050dd5716295e1b302f54c640121251c29142935
node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 06:59
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 1fb6281Sep 2, 2026
35 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14320-completeness-walk-object-nested-views branch September 2, 2026 07:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(lint): walk object-nested list / listViews through the view completeness rules - #14432

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views
Sep 2, 2026
Merged

fix(lint): walk object-nested list / listViews through the view completeness rules#14432
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14320

What

validateFunctionalCompleteness walked exactly one container loop — stack.views — so objects[].list and objects[].listViews.* (the ADR-0017 "Object has-many View" spelling) never reached checkViewCompleteness. This adds the second walk, beside the existing objects[] field walk, in the same file.

The two sibling rules in this family already walk both doors: lint-view-refs.ts's containerFromObject pulls list / listViews straight off the object definition, and validate-list-view-field-refs.ts has an objects[].listViews.* loop resolving the bound object as data.object ?? <the object's own name>. This was 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."

Resolution semantics and location grammar — mirrored, not invented

  • Bound object = the list view's own data.object retarget (ADR-0047) first, else the object the container belongs to. That is validate-list-view-field-refs's listViewObject(lv) ?? objName on the identical rung.
  • where = object "NAME" › listViews.KEY (and object "NAME" › list for the default slot) — the card's Expected spelling, and the sibling's.
  • path = objects[i].listViews.KEY.BLOCK. The object segment is positional in both authorable spellings, which is how this file's own field walk directly above already spells it (objects[i].fields.NAME) and how the sibling spells it. No new path grammar.
  • The top-level container walk is untouched, and severities are unchanged (the warning family; no gate strength moved).

Serial-with-#14106 clause: discharged

Triage made this card serial with #14106 so the new view/tree-without-parent-field rule would be carried through both doors by construction rather than by a second edit. #14106 is closed and that rule is on origin/main; this branch is cut from current main, so the widened walk carries bothview/layout-without-binding and view/tree-without-parent-field through the nested door with no extra wiring. Pinned by a test that reaches the tree rule through objects[].listViews.* and by the map-form self-lookup case that stays silent.

Accept-set evidence — before/after on the shipped example apps

Probe entry point is the card's own repro path, runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }), counting view/* findings per app. CONTROL = the same stack with an object-nested gantt list view injected onto its first object, stripped of its gantt block.

example appbaseline BEFORE (f358210)baseline AFTER (050dd57)CONTROL beforeCONTROL after
app-todo000 (+0)1 (+1)
app-crm000 (+0)1 (+1)
app-showcaseNOT MEASUREDNOT MEASUREDNOT MEASUREDNOT MEASURED

CONTROL finding on head, verbatim:

view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "todo_task" › listViews.os14320_control)
view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "crm_account" › listViews.os14320_control)

Zero new findings on the shipped apps besides CONTROL, so nothing in examples/** needed fixing or suppressing.

app-showcase could not be loaded by the probe in this container (Cannot find module .../@objectstack/connector-mcp/dist/index.js — its dependency closure is not built here), so its row is honestly NOT MEASURED rather than reported green. What settles it for all three apps instead is a static scan of the population this change can reach: no example app authors an object-nested container at all

grep -rn "listViews|^\s*list:" --include=*.ts examples/*/src/objects/ → 0 matches
grep -rln "listViews" --include=*.object.ts examples/ → 0 files

Every listViews in examples/ sits in a top-level defineView container, which the old walk already covered. So the widened walk's population on the shipped apps is empty by construction, which is exactly what the two measured rows show.

Reverse verification

The fix was committed first, then the implementation file was restored from the base commit (git checkout f35821044 -- <path>, restore leg pinned to HEAD under a trap … EXIT INT TERM with an absolute repo root), the ablation confirmed on disk by blob hash (1de9c7df… → e4cec2e4…, nested-walk marker count 0), and the suite re-run:

Ablated (base implementation): Tests 9 failed | 16 passed (25)
Head: Tests 25 passed (25)

Direction is the expected one, turn-red. The 9 are exactly the new object-nested cases; the two negative fixtures and the two "clean stack passes" acceptance cases correctly stay green on base too — a blind walk reports nothing, including nothing false. Restoration verified byte-identical (git hash-object == git rev-parse HEAD:<path>, git status --porcelain empty, git diff HEAD empty). No dist/ is involved: the tests import the module under test by intra-package relative path, so the ablation is source-level.

Tests

Fixture pair in both object spellings plus the negative half, in validate-functional-completeness.test.ts:

  • object-nested listViews.plangantt with no gantt block — array-form objects → view/layout-without-binding @ objects[0].listViews.plan.gantt
  • the same, name-keyed map-form objects → same rule, same location grammar
  • the object-nested default list slot → objects[0].list.timeline
  • view/tree-without-parent-field through the nested door, plus the self-lookup case that stays silent and the ADR-0047 data.object retarget
  • negative: a nested container whose views all declare their bindings → 0 findings (a widened walk that over-reported would be worse than the blind one)
  • nested and top-level copies reported independently
  • junk robustness: list: null, listViews: 'nope', list: 7, listViews: [null, 'x']
  • an end-to-end #14320 acceptance block driving both spellings through runAuthoringRules('validate')andrunAuthoringRules('build'), with the bound twin proving the fixtures fail for the right reason

Verification readout

All commands run at head 050dd5716; exit codes captured by redirect-then-capture, never through a pipe.

whatresult
pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 (whole package)PASSTest Files 93 passed (93) · Tests 2788 passed | 5 skipped (2793)
pnpm --filter @objectstack/lint typecheckPASS (exit 0)
derived gate families (scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)31 of 34 RAN-PASS, 3 NOT MEASURED

The three NOT MEASURED are prerequisite refusals (exit 3), not reds:

  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET … Run pnpm build first; needs the whole workspace dist/.
  • pnpm check:type-check-debt — refuses on an unbuilt closure by design (its own header measures packages/lint at 19 built vs 147 unbuilt).
  • node scripts/check-test-completeness.mjs — with no argument it has no saved turbo run test log to parse; its own text says to record it as NOT MEASURED.

⚠️ Note on typecheck scope: packages/lint/tsconfig.json excludes **/*.test.ts, so the green above 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 make sure this PR does not push that ratchet up while check:type-check-debt is unmeasurable here, the test surface was measured directly with a throwaway project that includes test files, against the built dependency closure: 22 errors total, 0 of them in validate-functional-completeness.test.ts (the 22 are 16 ledgered plus 6 TS6059 that the ledger note itself attributes to a re-measure project's inherited rootDir, not to this package). So the new test code contributes zero. That is a targeted measurement of this PR's contribution, not a substitute for the gate, which stays NOT MEASURED.

Repo-wide pnpm lint was not run locally — it is a repo-scan CI owns, and this change adds no lint-relevant construct beyond ordinary TypeScript in two files the package's own suite and typecheck already cover.

Changeset

patch on @objectstack/lint (a published package) — .changeset/eighty-pumas-shave.md. User-visible: os validate / os build now emit findings on metadata that previously reported clean. skip-changeset does not apply.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55da6d5fea720f0e48103c2f538d577ef1d1fc89 — the merge of head 050dd5716295e1b302f54c640121251c29142935 into base 96b627d135826253981cfa01d74b2832ccdea194, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55da6d5fea720f0e48103c2f538d577ef1d1fc89 && git checkout 55da6d5fea720f0e48103c2f538d577ef1d1fc89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96b627d135826253981cfa01d74b2832ccdea194 050dd5716295e1b302f54c640121251c29142935 && git checkout -B drift-repro 96b627d135826253981cfa01d74b2832ccdea194 && git merge --no-ff 050dd5716295e1b302f54c640121251c29142935
node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 06:59
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 1fb6281Sep 2, 2026
35 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14320-completeness-walk-object-nested-views branch September 2, 2026 07:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(lint): walk object-nested list / listViews through the view completeness rules - #14432

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views
Sep 2, 2026
Merged

fix(lint): walk object-nested list / listViews through the view completeness rules#14432
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14320

What

validateFunctionalCompleteness walked exactly one container loop — stack.views — so objects[].list and objects[].listViews.* (the ADR-0017 "Object has-many View" spelling) never reached checkViewCompleteness. This adds the second walk, beside the existing objects[] field walk, in the same file.

The two sibling rules in this family already walk both doors: lint-view-refs.ts's containerFromObject pulls list / listViews straight off the object definition, and validate-list-view-field-refs.ts has an objects[].listViews.* loop resolving the bound object as data.object ?? <the object's own name>. This was 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."

Resolution semantics and location grammar — mirrored, not invented

  • Bound object = the list view's own data.object retarget (ADR-0047) first, else the object the container belongs to. That is validate-list-view-field-refs's listViewObject(lv) ?? objName on the identical rung.
  • where = object "NAME" › listViews.KEY (and object "NAME" › list for the default slot) — the card's Expected spelling, and the sibling's.
  • path = objects[i].listViews.KEY.BLOCK. The object segment is positional in both authorable spellings, which is how this file's own field walk directly above already spells it (objects[i].fields.NAME) and how the sibling spells it. No new path grammar.
  • The top-level container walk is untouched, and severities are unchanged (the warning family; no gate strength moved).

Serial-with-#14106 clause: discharged

Triage made this card serial with #14106 so the new view/tree-without-parent-field rule would be carried through both doors by construction rather than by a second edit. #14106 is closed and that rule is on origin/main; this branch is cut from current main, so the widened walk carries bothview/layout-without-binding and view/tree-without-parent-field through the nested door with no extra wiring. Pinned by a test that reaches the tree rule through objects[].listViews.* and by the map-form self-lookup case that stays silent.

Accept-set evidence — before/after on the shipped example apps

Probe entry point is the card's own repro path, runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }), counting view/* findings per app. CONTROL = the same stack with an object-nested gantt list view injected onto its first object, stripped of its gantt block.

example appbaseline BEFORE (f358210)baseline AFTER (050dd57)CONTROL beforeCONTROL after
app-todo000 (+0)1 (+1)
app-crm000 (+0)1 (+1)
app-showcaseNOT MEASUREDNOT MEASUREDNOT MEASUREDNOT MEASURED

CONTROL finding on head, verbatim:

view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "todo_task" › listViews.os14320_control)
view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "crm_account" › listViews.os14320_control)

Zero new findings on the shipped apps besides CONTROL, so nothing in examples/** needed fixing or suppressing.

app-showcase could not be loaded by the probe in this container (Cannot find module .../@objectstack/connector-mcp/dist/index.js — its dependency closure is not built here), so its row is honestly NOT MEASURED rather than reported green. What settles it for all three apps instead is a static scan of the population this change can reach: no example app authors an object-nested container at all

grep -rn "listViews|^\s*list:" --include=*.ts examples/*/src/objects/ → 0 matches
grep -rln "listViews" --include=*.object.ts examples/ → 0 files

Every listViews in examples/ sits in a top-level defineView container, which the old walk already covered. So the widened walk's population on the shipped apps is empty by construction, which is exactly what the two measured rows show.

Reverse verification

The fix was committed first, then the implementation file was restored from the base commit (git checkout f35821044 -- <path>, restore leg pinned to HEAD under a trap … EXIT INT TERM with an absolute repo root), the ablation confirmed on disk by blob hash (1de9c7df… → e4cec2e4…, nested-walk marker count 0), and the suite re-run:

Ablated (base implementation): Tests 9 failed | 16 passed (25)
Head: Tests 25 passed (25)

Direction is the expected one, turn-red. The 9 are exactly the new object-nested cases; the two negative fixtures and the two "clean stack passes" acceptance cases correctly stay green on base too — a blind walk reports nothing, including nothing false. Restoration verified byte-identical (git hash-object == git rev-parse HEAD:<path>, git status --porcelain empty, git diff HEAD empty). No dist/ is involved: the tests import the module under test by intra-package relative path, so the ablation is source-level.

Tests

Fixture pair in both object spellings plus the negative half, in validate-functional-completeness.test.ts:

  • object-nested listViews.plangantt with no gantt block — array-form objects → view/layout-without-binding @ objects[0].listViews.plan.gantt
  • the same, name-keyed map-form objects → same rule, same location grammar
  • the object-nested default list slot → objects[0].list.timeline
  • view/tree-without-parent-field through the nested door, plus the self-lookup case that stays silent and the ADR-0047 data.object retarget
  • negative: a nested container whose views all declare their bindings → 0 findings (a widened walk that over-reported would be worse than the blind one)
  • nested and top-level copies reported independently
  • junk robustness: list: null, listViews: 'nope', list: 7, listViews: [null, 'x']
  • an end-to-end #14320 acceptance block driving both spellings through runAuthoringRules('validate')andrunAuthoringRules('build'), with the bound twin proving the fixtures fail for the right reason

Verification readout

All commands run at head 050dd5716; exit codes captured by redirect-then-capture, never through a pipe.

whatresult
pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 (whole package)PASSTest Files 93 passed (93) · Tests 2788 passed | 5 skipped (2793)
pnpm --filter @objectstack/lint typecheckPASS (exit 0)
derived gate families (scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)31 of 34 RAN-PASS, 3 NOT MEASURED

The three NOT MEASURED are prerequisite refusals (exit 3), not reds:

  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET … Run pnpm build first; needs the whole workspace dist/.
  • pnpm check:type-check-debt — refuses on an unbuilt closure by design (its own header measures packages/lint at 19 built vs 147 unbuilt).
  • node scripts/check-test-completeness.mjs — with no argument it has no saved turbo run test log to parse; its own text says to record it as NOT MEASURED.

⚠️ Note on typecheck scope: packages/lint/tsconfig.json excludes **/*.test.ts, so the green above 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 make sure this PR does not push that ratchet up while check:type-check-debt is unmeasurable here, the test surface was measured directly with a throwaway project that includes test files, against the built dependency closure: 22 errors total, 0 of them in validate-functional-completeness.test.ts (the 22 are 16 ledgered plus 6 TS6059 that the ledger note itself attributes to a re-measure project's inherited rootDir, not to this package). So the new test code contributes zero. That is a targeted measurement of this PR's contribution, not a substitute for the gate, which stays NOT MEASURED.

Repo-wide pnpm lint was not run locally — it is a repo-scan CI owns, and this change adds no lint-relevant construct beyond ordinary TypeScript in two files the package's own suite and typecheck already cover.

Changeset

patch on @objectstack/lint (a published package) — .changeset/eighty-pumas-shave.md. User-visible: os validate / os build now emit findings on metadata that previously reported clean. skip-changeset does not apply.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55da6d5fea720f0e48103c2f538d577ef1d1fc89 — the merge of head 050dd5716295e1b302f54c640121251c29142935 into base 96b627d135826253981cfa01d74b2832ccdea194, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55da6d5fea720f0e48103c2f538d577ef1d1fc89 && git checkout 55da6d5fea720f0e48103c2f538d577ef1d1fc89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96b627d135826253981cfa01d74b2832ccdea194 050dd5716295e1b302f54c640121251c29142935 && git checkout -B drift-repro 96b627d135826253981cfa01d74b2832ccdea194 && git merge --no-ff 050dd5716295e1b302f54c640121251c29142935
node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 06:59
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 1fb6281Sep 2, 2026
35 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14320-completeness-walk-object-nested-views branch September 2, 2026 07:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(lint): walk object-nested list / listViews through the view completeness rules - #14432

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views
Sep 2, 2026
Merged

fix(lint): walk object-nested list / listViews through the view completeness rules#14432
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14320

What

validateFunctionalCompleteness walked exactly one container loop — stack.views — so objects[].list and objects[].listViews.* (the ADR-0017 "Object has-many View" spelling) never reached checkViewCompleteness. This adds the second walk, beside the existing objects[] field walk, in the same file.

The two sibling rules in this family already walk both doors: lint-view-refs.ts's containerFromObject pulls list / listViews straight off the object definition, and validate-list-view-field-refs.ts has an objects[].listViews.* loop resolving the bound object as data.object ?? <the object's own name>. This was 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."

Resolution semantics and location grammar — mirrored, not invented

  • Bound object = the list view's own data.object retarget (ADR-0047) first, else the object the container belongs to. That is validate-list-view-field-refs's listViewObject(lv) ?? objName on the identical rung.
  • where = object "NAME" › listViews.KEY (and object "NAME" › list for the default slot) — the card's Expected spelling, and the sibling's.
  • path = objects[i].listViews.KEY.BLOCK. The object segment is positional in both authorable spellings, which is how this file's own field walk directly above already spells it (objects[i].fields.NAME) and how the sibling spells it. No new path grammar.
  • The top-level container walk is untouched, and severities are unchanged (the warning family; no gate strength moved).

Serial-with-#14106 clause: discharged

Triage made this card serial with #14106 so the new view/tree-without-parent-field rule would be carried through both doors by construction rather than by a second edit. #14106 is closed and that rule is on origin/main; this branch is cut from current main, so the widened walk carries bothview/layout-without-binding and view/tree-without-parent-field through the nested door with no extra wiring. Pinned by a test that reaches the tree rule through objects[].listViews.* and by the map-form self-lookup case that stays silent.

Accept-set evidence — before/after on the shipped example apps

Probe entry point is the card's own repro path, runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }), counting view/* findings per app. CONTROL = the same stack with an object-nested gantt list view injected onto its first object, stripped of its gantt block.

example appbaseline BEFORE (f358210)baseline AFTER (050dd57)CONTROL beforeCONTROL after
app-todo000 (+0)1 (+1)
app-crm000 (+0)1 (+1)
app-showcaseNOT MEASUREDNOT MEASUREDNOT MEASUREDNOT MEASURED

CONTROL finding on head, verbatim:

view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "todo_task" › listViews.os14320_control)
view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "crm_account" › listViews.os14320_control)

Zero new findings on the shipped apps besides CONTROL, so nothing in examples/** needed fixing or suppressing.

app-showcase could not be loaded by the probe in this container (Cannot find module .../@objectstack/connector-mcp/dist/index.js — its dependency closure is not built here), so its row is honestly NOT MEASURED rather than reported green. What settles it for all three apps instead is a static scan of the population this change can reach: no example app authors an object-nested container at all

grep -rn "listViews|^\s*list:" --include=*.ts examples/*/src/objects/ → 0 matches
grep -rln "listViews" --include=*.object.ts examples/ → 0 files

Every listViews in examples/ sits in a top-level defineView container, which the old walk already covered. So the widened walk's population on the shipped apps is empty by construction, which is exactly what the two measured rows show.

Reverse verification

The fix was committed first, then the implementation file was restored from the base commit (git checkout f35821044 -- <path>, restore leg pinned to HEAD under a trap … EXIT INT TERM with an absolute repo root), the ablation confirmed on disk by blob hash (1de9c7df… → e4cec2e4…, nested-walk marker count 0), and the suite re-run:

Ablated (base implementation): Tests 9 failed | 16 passed (25)
Head: Tests 25 passed (25)

Direction is the expected one, turn-red. The 9 are exactly the new object-nested cases; the two negative fixtures and the two "clean stack passes" acceptance cases correctly stay green on base too — a blind walk reports nothing, including nothing false. Restoration verified byte-identical (git hash-object == git rev-parse HEAD:<path>, git status --porcelain empty, git diff HEAD empty). No dist/ is involved: the tests import the module under test by intra-package relative path, so the ablation is source-level.

Tests

Fixture pair in both object spellings plus the negative half, in validate-functional-completeness.test.ts:

  • object-nested listViews.plangantt with no gantt block — array-form objects → view/layout-without-binding @ objects[0].listViews.plan.gantt
  • the same, name-keyed map-form objects → same rule, same location grammar
  • the object-nested default list slot → objects[0].list.timeline
  • view/tree-without-parent-field through the nested door, plus the self-lookup case that stays silent and the ADR-0047 data.object retarget
  • negative: a nested container whose views all declare their bindings → 0 findings (a widened walk that over-reported would be worse than the blind one)
  • nested and top-level copies reported independently
  • junk robustness: list: null, listViews: 'nope', list: 7, listViews: [null, 'x']
  • an end-to-end #14320 acceptance block driving both spellings through runAuthoringRules('validate')andrunAuthoringRules('build'), with the bound twin proving the fixtures fail for the right reason

Verification readout

All commands run at head 050dd5716; exit codes captured by redirect-then-capture, never through a pipe.

whatresult
pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 (whole package)PASSTest Files 93 passed (93) · Tests 2788 passed | 5 skipped (2793)
pnpm --filter @objectstack/lint typecheckPASS (exit 0)
derived gate families (scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)31 of 34 RAN-PASS, 3 NOT MEASURED

The three NOT MEASURED are prerequisite refusals (exit 3), not reds:

  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET … Run pnpm build first; needs the whole workspace dist/.
  • pnpm check:type-check-debt — refuses on an unbuilt closure by design (its own header measures packages/lint at 19 built vs 147 unbuilt).
  • node scripts/check-test-completeness.mjs — with no argument it has no saved turbo run test log to parse; its own text says to record it as NOT MEASURED.

⚠️ Note on typecheck scope: packages/lint/tsconfig.json excludes **/*.test.ts, so the green above 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 make sure this PR does not push that ratchet up while check:type-check-debt is unmeasurable here, the test surface was measured directly with a throwaway project that includes test files, against the built dependency closure: 22 errors total, 0 of them in validate-functional-completeness.test.ts (the 22 are 16 ledgered plus 6 TS6059 that the ledger note itself attributes to a re-measure project's inherited rootDir, not to this package). So the new test code contributes zero. That is a targeted measurement of this PR's contribution, not a substitute for the gate, which stays NOT MEASURED.

Repo-wide pnpm lint was not run locally — it is a repo-scan CI owns, and this change adds no lint-relevant construct beyond ordinary TypeScript in two files the package's own suite and typecheck already cover.

Changeset

patch on @objectstack/lint (a published package) — .changeset/eighty-pumas-shave.md. User-visible: os validate / os build now emit findings on metadata that previously reported clean. skip-changeset does not apply.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55da6d5fea720f0e48103c2f538d577ef1d1fc89 — the merge of head 050dd5716295e1b302f54c640121251c29142935 into base 96b627d135826253981cfa01d74b2832ccdea194, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55da6d5fea720f0e48103c2f538d577ef1d1fc89 && git checkout 55da6d5fea720f0e48103c2f538d577ef1d1fc89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96b627d135826253981cfa01d74b2832ccdea194 050dd5716295e1b302f54c640121251c29142935 && git checkout -B drift-repro 96b627d135826253981cfa01d74b2832ccdea194 && git merge --no-ff 050dd5716295e1b302f54c640121251c29142935
node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 06:59
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 1fb6281Sep 2, 2026
35 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14320-completeness-walk-object-nested-views branch September 2, 2026 07:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(lint): walk object-nested list / listViews through the view completeness rules - #14432

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views
Sep 2, 2026
Merged

fix(lint): walk object-nested list / listViews through the view completeness rules#14432
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14320

What

validateFunctionalCompleteness walked exactly one container loop — stack.views — so objects[].list and objects[].listViews.* (the ADR-0017 "Object has-many View" spelling) never reached checkViewCompleteness. This adds the second walk, beside the existing objects[] field walk, in the same file.

The two sibling rules in this family already walk both doors: lint-view-refs.ts's containerFromObject pulls list / listViews straight off the object definition, and validate-list-view-field-refs.ts has an objects[].listViews.* loop resolving the bound object as data.object ?? <the object's own name>. This was 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."

Resolution semantics and location grammar — mirrored, not invented

  • Bound object = the list view's own data.object retarget (ADR-0047) first, else the object the container belongs to. That is validate-list-view-field-refs's listViewObject(lv) ?? objName on the identical rung.
  • where = object "NAME" › listViews.KEY (and object "NAME" › list for the default slot) — the card's Expected spelling, and the sibling's.
  • path = objects[i].listViews.KEY.BLOCK. The object segment is positional in both authorable spellings, which is how this file's own field walk directly above already spells it (objects[i].fields.NAME) and how the sibling spells it. No new path grammar.
  • The top-level container walk is untouched, and severities are unchanged (the warning family; no gate strength moved).

Serial-with-#14106 clause: discharged

Triage made this card serial with #14106 so the new view/tree-without-parent-field rule would be carried through both doors by construction rather than by a second edit. #14106 is closed and that rule is on origin/main; this branch is cut from current main, so the widened walk carries bothview/layout-without-binding and view/tree-without-parent-field through the nested door with no extra wiring. Pinned by a test that reaches the tree rule through objects[].listViews.* and by the map-form self-lookup case that stays silent.

Accept-set evidence — before/after on the shipped example apps

Probe entry point is the card's own repro path, runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }), counting view/* findings per app. CONTROL = the same stack with an object-nested gantt list view injected onto its first object, stripped of its gantt block.

example appbaseline BEFORE (f358210)baseline AFTER (050dd57)CONTROL beforeCONTROL after
app-todo000 (+0)1 (+1)
app-crm000 (+0)1 (+1)
app-showcaseNOT MEASUREDNOT MEASUREDNOT MEASUREDNOT MEASURED

CONTROL finding on head, verbatim:

view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "todo_task" › listViews.os14320_control)
view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "crm_account" › listViews.os14320_control)

Zero new findings on the shipped apps besides CONTROL, so nothing in examples/** needed fixing or suppressing.

app-showcase could not be loaded by the probe in this container (Cannot find module .../@objectstack/connector-mcp/dist/index.js — its dependency closure is not built here), so its row is honestly NOT MEASURED rather than reported green. What settles it for all three apps instead is a static scan of the population this change can reach: no example app authors an object-nested container at all

grep -rn "listViews|^\s*list:" --include=*.ts examples/*/src/objects/ → 0 matches
grep -rln "listViews" --include=*.object.ts examples/ → 0 files

Every listViews in examples/ sits in a top-level defineView container, which the old walk already covered. So the widened walk's population on the shipped apps is empty by construction, which is exactly what the two measured rows show.

Reverse verification

The fix was committed first, then the implementation file was restored from the base commit (git checkout f35821044 -- <path>, restore leg pinned to HEAD under a trap … EXIT INT TERM with an absolute repo root), the ablation confirmed on disk by blob hash (1de9c7df… → e4cec2e4…, nested-walk marker count 0), and the suite re-run:

Ablated (base implementation): Tests 9 failed | 16 passed (25)
Head: Tests 25 passed (25)

Direction is the expected one, turn-red. The 9 are exactly the new object-nested cases; the two negative fixtures and the two "clean stack passes" acceptance cases correctly stay green on base too — a blind walk reports nothing, including nothing false. Restoration verified byte-identical (git hash-object == git rev-parse HEAD:<path>, git status --porcelain empty, git diff HEAD empty). No dist/ is involved: the tests import the module under test by intra-package relative path, so the ablation is source-level.

Tests

Fixture pair in both object spellings plus the negative half, in validate-functional-completeness.test.ts:

  • object-nested listViews.plangantt with no gantt block — array-form objects → view/layout-without-binding @ objects[0].listViews.plan.gantt
  • the same, name-keyed map-form objects → same rule, same location grammar
  • the object-nested default list slot → objects[0].list.timeline
  • view/tree-without-parent-field through the nested door, plus the self-lookup case that stays silent and the ADR-0047 data.object retarget
  • negative: a nested container whose views all declare their bindings → 0 findings (a widened walk that over-reported would be worse than the blind one)
  • nested and top-level copies reported independently
  • junk robustness: list: null, listViews: 'nope', list: 7, listViews: [null, 'x']
  • an end-to-end #14320 acceptance block driving both spellings through runAuthoringRules('validate')andrunAuthoringRules('build'), with the bound twin proving the fixtures fail for the right reason

Verification readout

All commands run at head 050dd5716; exit codes captured by redirect-then-capture, never through a pipe.

whatresult
pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 (whole package)PASSTest Files 93 passed (93) · Tests 2788 passed | 5 skipped (2793)
pnpm --filter @objectstack/lint typecheckPASS (exit 0)
derived gate families (scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)31 of 34 RAN-PASS, 3 NOT MEASURED

The three NOT MEASURED are prerequisite refusals (exit 3), not reds:

  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET … Run pnpm build first; needs the whole workspace dist/.
  • pnpm check:type-check-debt — refuses on an unbuilt closure by design (its own header measures packages/lint at 19 built vs 147 unbuilt).
  • node scripts/check-test-completeness.mjs — with no argument it has no saved turbo run test log to parse; its own text says to record it as NOT MEASURED.

⚠️ Note on typecheck scope: packages/lint/tsconfig.json excludes **/*.test.ts, so the green above 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 make sure this PR does not push that ratchet up while check:type-check-debt is unmeasurable here, the test surface was measured directly with a throwaway project that includes test files, against the built dependency closure: 22 errors total, 0 of them in validate-functional-completeness.test.ts (the 22 are 16 ledgered plus 6 TS6059 that the ledger note itself attributes to a re-measure project's inherited rootDir, not to this package). So the new test code contributes zero. That is a targeted measurement of this PR's contribution, not a substitute for the gate, which stays NOT MEASURED.

Repo-wide pnpm lint was not run locally — it is a repo-scan CI owns, and this change adds no lint-relevant construct beyond ordinary TypeScript in two files the package's own suite and typecheck already cover.

Changeset

patch on @objectstack/lint (a published package) — .changeset/eighty-pumas-shave.md. User-visible: os validate / os build now emit findings on metadata that previously reported clean. skip-changeset does not apply.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55da6d5fea720f0e48103c2f538d577ef1d1fc89 — the merge of head 050dd5716295e1b302f54c640121251c29142935 into base 96b627d135826253981cfa01d74b2832ccdea194, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55da6d5fea720f0e48103c2f538d577ef1d1fc89 && git checkout 55da6d5fea720f0e48103c2f538d577ef1d1fc89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96b627d135826253981cfa01d74b2832ccdea194 050dd5716295e1b302f54c640121251c29142935 && git checkout -B drift-repro 96b627d135826253981cfa01d74b2832ccdea194 && git merge --no-ff 050dd5716295e1b302f54c640121251c29142935
node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 06:59
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 1fb6281Sep 2, 2026
35 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14320-completeness-walk-object-nested-views branch September 2, 2026 07:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(lint): walk object-nested list / listViews through the view completeness rules - #14432

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views
Sep 2, 2026
Merged

fix(lint): walk object-nested list / listViews through the view completeness rules#14432
baozhoutao merged 3 commits into
mainfrom
claude/issue-14320-completeness-walk-object-nested-views

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14320

What

validateFunctionalCompleteness walked exactly one container loop — stack.views — so objects[].list and objects[].listViews.* (the ADR-0017 "Object has-many View" spelling) never reached checkViewCompleteness. This adds the second walk, beside the existing objects[] field walk, in the same file.

The two sibling rules in this family already walk both doors: lint-view-refs.ts's containerFromObject pulls list / listViews straight off the object definition, and validate-list-view-field-refs.ts has an objects[].listViews.* loop resolving the bound object as data.object ?? <the object's own name>. This was 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."

Resolution semantics and location grammar — mirrored, not invented

  • Bound object = the list view's own data.object retarget (ADR-0047) first, else the object the container belongs to. That is validate-list-view-field-refs's listViewObject(lv) ?? objName on the identical rung.
  • where = object "NAME" › listViews.KEY (and object "NAME" › list for the default slot) — the card's Expected spelling, and the sibling's.
  • path = objects[i].listViews.KEY.BLOCK. The object segment is positional in both authorable spellings, which is how this file's own field walk directly above already spells it (objects[i].fields.NAME) and how the sibling spells it. No new path grammar.
  • The top-level container walk is untouched, and severities are unchanged (the warning family; no gate strength moved).

Serial-with-#14106 clause: discharged

Triage made this card serial with #14106 so the new view/tree-without-parent-field rule would be carried through both doors by construction rather than by a second edit. #14106 is closed and that rule is on origin/main; this branch is cut from current main, so the widened walk carries bothview/layout-without-binding and view/tree-without-parent-field through the nested door with no extra wiring. Pinned by a test that reaches the tree rule through objects[].listViews.* and by the map-form self-lookup case that stays silent.

Accept-set evidence — before/after on the shipped example apps

Probe entry point is the card's own repro path, runAuthoringRules('validate', { normalized: normalizeStackInput(stack) }), counting view/* findings per app. CONTROL = the same stack with an object-nested gantt list view injected onto its first object, stripped of its gantt block.

example appbaseline BEFORE (f358210)baseline AFTER (050dd57)CONTROL beforeCONTROL after
app-todo000 (+0)1 (+1)
app-crm000 (+0)1 (+1)
app-showcaseNOT MEASUREDNOT MEASUREDNOT MEASUREDNOT MEASURED

CONTROL finding on head, verbatim:

view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "todo_task" › listViews.os14320_control)
view/layout-without-binding @ objects[0].listViews.os14320_control.gantt (object "crm_account" › listViews.os14320_control)

Zero new findings on the shipped apps besides CONTROL, so nothing in examples/** needed fixing or suppressing.

app-showcase could not be loaded by the probe in this container (Cannot find module .../@objectstack/connector-mcp/dist/index.js — its dependency closure is not built here), so its row is honestly NOT MEASURED rather than reported green. What settles it for all three apps instead is a static scan of the population this change can reach: no example app authors an object-nested container at all

grep -rn "listViews|^\s*list:" --include=*.ts examples/*/src/objects/ → 0 matches
grep -rln "listViews" --include=*.object.ts examples/ → 0 files

Every listViews in examples/ sits in a top-level defineView container, which the old walk already covered. So the widened walk's population on the shipped apps is empty by construction, which is exactly what the two measured rows show.

Reverse verification

The fix was committed first, then the implementation file was restored from the base commit (git checkout f35821044 -- <path>, restore leg pinned to HEAD under a trap … EXIT INT TERM with an absolute repo root), the ablation confirmed on disk by blob hash (1de9c7df… → e4cec2e4…, nested-walk marker count 0), and the suite re-run:

Ablated (base implementation): Tests 9 failed | 16 passed (25)
Head: Tests 25 passed (25)

Direction is the expected one, turn-red. The 9 are exactly the new object-nested cases; the two negative fixtures and the two "clean stack passes" acceptance cases correctly stay green on base too — a blind walk reports nothing, including nothing false. Restoration verified byte-identical (git hash-object == git rev-parse HEAD:<path>, git status --porcelain empty, git diff HEAD empty). No dist/ is involved: the tests import the module under test by intra-package relative path, so the ablation is source-level.

Tests

Fixture pair in both object spellings plus the negative half, in validate-functional-completeness.test.ts:

  • object-nested listViews.plangantt with no gantt block — array-form objects → view/layout-without-binding @ objects[0].listViews.plan.gantt
  • the same, name-keyed map-form objects → same rule, same location grammar
  • the object-nested default list slot → objects[0].list.timeline
  • view/tree-without-parent-field through the nested door, plus the self-lookup case that stays silent and the ADR-0047 data.object retarget
  • negative: a nested container whose views all declare their bindings → 0 findings (a widened walk that over-reported would be worse than the blind one)
  • nested and top-level copies reported independently
  • junk robustness: list: null, listViews: 'nope', list: 7, listViews: [null, 'x']
  • an end-to-end #14320 acceptance block driving both spellings through runAuthoringRules('validate')andrunAuthoringRules('build'), with the bound twin proving the fixtures fail for the right reason

Verification readout

All commands run at head 050dd5716; exit codes captured by redirect-then-capture, never through a pipe.

whatresult
pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 (whole package)PASSTest Files 93 passed (93) · Tests 2788 passed | 5 skipped (2793)
pnpm --filter @objectstack/lint typecheckPASS (exit 0)
derived gate families (scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands)31 of 34 RAN-PASS, 3 NOT MEASURED

The three NOT MEASURED are prerequisite refusals (exit 3), not reds:

  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET … Run pnpm build first; needs the whole workspace dist/.
  • pnpm check:type-check-debt — refuses on an unbuilt closure by design (its own header measures packages/lint at 19 built vs 147 unbuilt).
  • node scripts/check-test-completeness.mjs — with no argument it has no saved turbo run test log to parse; its own text says to record it as NOT MEASURED.

⚠️ Note on typecheck scope: packages/lint/tsconfig.json excludes **/*.test.ts, so the green above 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 make sure this PR does not push that ratchet up while check:type-check-debt is unmeasurable here, the test surface was measured directly with a throwaway project that includes test files, against the built dependency closure: 22 errors total, 0 of them in validate-functional-completeness.test.ts (the 22 are 16 ledgered plus 6 TS6059 that the ledger note itself attributes to a re-measure project's inherited rootDir, not to this package). So the new test code contributes zero. That is a targeted measurement of this PR's contribution, not a substitute for the gate, which stays NOT MEASURED.

Repo-wide pnpm lint was not run locally — it is a repo-scan CI owns, and this change adds no lint-relevant construct beyond ordinary TypeScript in two files the package's own suite and typecheck already cover.

Changeset

patch on @objectstack/lint (a published package) — .changeset/eighty-pumas-shave.md. User-visible: os validate / os build now emit findings on metadata that previously reported clean. skip-changeset does not apply.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55da6d5fea720f0e48103c2f538d577ef1d1fc89 — the merge of head 050dd5716295e1b302f54c640121251c29142935 into base 96b627d135826253981cfa01d74b2832ccdea194, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 55da6d5fea720f0e48103c2f538d577ef1d1fc89 && git checkout 55da6d5fea720f0e48103c2f538d577ef1d1fc89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96b627d135826253981cfa01d74b2832ccdea194 050dd5716295e1b302f54c640121251c29142935 && git checkout -B drift-repro 96b627d135826253981cfa01d74b2832ccdea194 && git merge --no-ff 050dd5716295e1b302f54c640121251c29142935
node scripts/docs-audit/affected-docs.mjs --json 96b627d135826253981cfa01d74b2832ccdea194

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 06:59
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 1fb6281Sep 2, 2026
35 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14320-completeness-walk-object-nested-views branch September 2, 2026 07:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@baozhoutao@claude