Uh oh!
There was an error while loading. Please reload this page.
feat(lint): a standalone ViewItem record's nested config.sort / config.searchableFields reach the runtime publish gate (#10001) - #10558
Conversation
…ig.sort/searchableFields must be judged (#10001) The three record-rung refusal tests, the retarget/clean/round-trip controls, and the rung-split positive controls. Red on origin/main's walks by design — the walk rungs land in the following commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
…bleFields reach the runtime publish gate (#10001) The RECORD rung in both twin walks: a views[] entry carrying viewKind 'list' AND a record-shaped config — ViewMetadataSchema's member 1 (ViewItemWireSchema), the Studio-saved-view shape through PUT /api/v1/meta/view — is judged on config.sort / config.searchableFields against listViewObject(config) ?? entry.object, path views[i].config.*. The #9313 dispatch widening already routes view writes to exactly these two members; this closes the recorded follow-up scope, walk-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
…ewitem-record-config-rung
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 72cbba7a2eb0ea50493eb7a041ed4be716523aa8 && git checkout 72cbba7a2eb0ea50493eb7a041ed4be716523aa8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 68ca346446a47d94a1b60e7061a2a5dc60b29d27 dd90178ad2ac8490675c0e888c288faed92ed2d6 && git checkout -B drift-repro 68ca346446a47d94a1b60e7061a2a5dc60b29d27 && git merge --no-ff dd90178ad2ac8490675c0e888c288faed92ed2d6
node scripts/docs-audit/affected-docs.mjs --json 68ca346446a47d94a1b60e7061a2a5dc60b29d27
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10001
A standalone ViewItem RECORD —
ViewMetadataSchema's member 1 (ViewItemWireSchema,{ name, object, viewKind: 'list', config }), the shape a Studio-saved view takes throughPUT /api/v1/meta/viewand the shape objectui'supdateViewround-trips on every pin/reorder toggle ({ ...current, ...partial }, the #5074 trace) — is now judged byvalidateSortableFieldsandvalidateSearchableFieldson its nestedconfig.sort/config.searchableFields. Before this rung, a record write carryingconfig.sort: [{ field: 'amout' }]published through the runtime gate in silence and answered400 INVALID_SORT(#6994/#7095) on the view's first fetch, every load.Walk-only, as the card scoped it: #9313 (PR #10234, squash
adbcbfd86) already widened the reference-integrity suite entry and exactly these two members ontoviewwrites, so the dispatch existed and the record rung was the missing half. The per-member granularity wall is untouched: no further member crosses ontoview(the member-surface pin and the container behavioural control both still pass unchanged).The rung (both twin walks, mirrored)
In each walk's
views[]loop, the structural complement of #9313's self rung:view.viewKind === 'list' && isRec(view.config)— the wire union's own record/overlay member discrimination (the overlay rung keeps its!isRec(view.config)guard; a strict container carries neither key; aformrecord's FormViewSchema config declares no list-field surface);listViewObject(view.config) ?? viewObject— the config's owndata.object(ADR-0047 explicit retarget; a record's data binding lives on its config) ahead of the record's required top-levelobject;views[i].config.sort[…]/views[i].config.searchableFields[…], where-labelview "…" (ViewItem record);sortriding besideconfigin the stored body (saveMetaItempersists the original) is a key the wire schema strips and no runtime read path serves — judging it would be a false-positive channel. Pinned by the rung-split controls.The boundary marker, flipped (the anti-vacuity anchor)
runtime-gate.view-writes.test.tscarried the #9313 marker — "a ViewItem RECORD's nestedconfig.sortis not judged here — recorded scope, not a rung that fell off" — assertingerrors == []on a record with a badconfig.sort. That test is replaced by the record-rung block; the first commit of this branch (3c07cebfd, tests only) is the walks at origin/main state plus the flipped assertions, and the run at that commit is the required before-state:The
[]in the assertion message is the gate's own answer: the bad record published clean on origin/main. All 10 reds are #10001 tests; all 86 pre-existing tests (including the positive controls below) passed on the origin/main walks.The three controls, with before-states
views[0].config.*paths, retarget honored). Before-state: fail on origin/main walks (verbatim output above). Afterdc2d43620: green.sort-field-unknownatviews[0].sort[0]on a bad overlay (a config-rung leak moves the path; a double judgment adds a finding). Unit-level census twin: one bad sort per rung in a single stack (object listViews, container list, overlay, record) must yield exactly the four declared paths. The overlay/objects[].listViews/views[].listrefusal + clean tests from [finding] Both field-resolution gates for a list view (searchableFieldsandsort) are CLI-only — the reference-integrity suite dispatches at the runtime publish gate forflowalone, so a Studio/MCPviewwrite gets neither #9313 are byte-unchanged and pass in both runs. Plus the rung-split controls: a record carrying both a badconfig.sortand a stray bad top-levelsortyields exactly ONE finding, on the config path; aformrecord with a straysortinside its config is not judged.searchableFieldsandsort) are CLI-only — the reference-integrity suite dispatches at the runtime publish gate forflowalone, so a Studio/MCPviewwrite gets neither #9313 behavioural control is unchanged and passing: a container view write naming stack-level actions is not refused (validateActionNameRefsnever reaches the view door), and the member-surface pin still asserts exactly['validateSearchableFields', 'validateSortableFields']declareview.False-positive budget (#4716 discipline) — record-shaped corpus, population stated
Population: 39 record-shaped console round-trip bodies — one per shipped list surface in the four shipped stacks (
examples/app-crm9 = 3 containerlist+ 6 containerlistViews.*;examples/app-showcase26 = 6 + 20;examples/app-todo3 = 1 + 2;packages/qa/downstream-contract1 = 1 containerlist; the stacks ship zero object-levellistViewsand zerodefineViewItemrecords, so the container surfaces are the whole shipped list population — the same 39 surfaces #9313's lane B enumerated, record-shaped this time). Each body is the shapesaveMetaItemreally stores after a Studio save + console pin toggle:{ name, object: bound object (configdata.objectahead of the owner), viewKind: 'list', label, config: the surface's stored list config verbatim withsort[].idcarryingcrypto.randomUUID()row ids (#5074), isPinned, sortOrder }, replayed through the real dispatch path (runRuntimeAuthoringRules,type: 'view', context = the stack's own objects/permissions/books/datasets).dd90178ad). A lower bound, as every authored corpus is.sort-field-unknownatviews[0].config.sort[0].objects[].listViews.*+views[].list+views[].listViews.*across the four stack configs, wrap each as above, replay. The replay script imports the lint package source in-package (the same resolution the package's own tests use — no dist in the path).Verification (all at
dd90178ad, the final commit, after merging origin/mainceb33a9f1)pnpm --filter @objectstack/lint test:Tests 2194 passed (2194), TESTEXIT=0.pnpm exec tsc --noEmit(packages/lint): clean, TSCEXIT=0. All exits captured before any pipe (cmd > log 2>&1; EXIT=$?).pnpm --filter @objectstack/spec build+check:generated— "All 14 generated artifacts are up to date."node scripts/pm/dispatch-gates.mjs(no path args; change set derived by the script from merge baseceb33a9f1): 9 path-matched + 5 convention-triggered (new test files) +check:nul-bytesfrom standing orders — all exit 0. Verdict lines, each quoted from the gate's own output:check:changeset-gate-self-tests— "check-changeset-no-major --self-test: 116 assertions … + the wiring"check:cross-package-test-inputs(both invocations) — "OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob."check:objectui-changeset— "objectui-range --self-test: all checks passed"check:slot-lookup— "slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new"check-adr-0087-registration— "this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)."check-changeset-no-major— "This diff introduces nomajorbump."check-empty-changeset— exit 0check-affected-docs— "affected-docs self-test: 281 cases pass." (exit 0; the unreachable-rows table it prints is its standing repo report, not a finding on this diff)check:query-options-erasure— "test surface: 240 site(s) in 47 file(s) — at the ceiling … baseline key set verified against ceb33a9: no files added."check:type-check-coverage— "OK — 64/77 workspace packages type-checked (plus the root), 13 in the DEBT ledger (436 frozen raw errors …), 1 exempt."check:type-check-debt(afterturbo run build --filter=./packages/* --filter=./packages/*/*, BUILDEXIT=0) — "--re-measure: OK — 33 ledger entr(ies) re-measured in 344.3s, 1924 raw tsc error(s) total, none above its recorded number. surplus: none"check:engine-double-contract— "OK — 342 pinned, 133 in the DEBT ledger, 2 exempt."check:where-matcher— "269 matcher(s) discovered, 269 answer the combinator battery correctly or refuse it loudly (161 refuse). 0 silently-wrong and 0 unjudged … none new."check:nul-bytes— "OK (scanned 6179 text file(s) … no raw ASCII control bytes)."check:engine-split-ratiois report-only and this diff does not move engine code.Deliberately not done
view(fence 1; measured wrong in both directions on [finding] Both field-resolution gates for a list view (searchableFieldsandsort) are CLI-only — the reference-integrity suite dispatches at the runtime publish gate forflowalone, so a Studio/MCPviewwrite gets neither #9313).packages/spec/srcchange — the wire union's member discrimination already expresses the record shape; the rung reads it rather than re-deriving it.sort/searchableFieldsare not judged (see the rung notes) — a deliberate non-surface, pinned by tests, not an omission.Generated by Claude Code