Uh oh!
There was an error while loading. Please reload this page.
feat(lint): the two list-view field rules reach the flattened view overlay at the runtime publish gate (#9313) - #10234
Conversation
…erlay at the runtime publish gate (#9313) Both halves the card names, because either alone is a silent no-op: - dispatch: the reference-integrity suite's registry entry gains runtimeTypes view; the suite dispatches its MEMBERS by the gate-passed ctx.runtimeWriteType (default flow), so a view snapshot reaches exactly validateSearchableFields + validateSortableFields — never validateActionNameRefs, whose stack.actions universe no per-write snapshot carries (the measured phantom-refusal channel that makes whole-suite granularity wrong for this door); - walk: both rules gain the SELF rung — a views[] entry that IS a flattened list overlay (viewKind 'list', no nested config), the PUT /api/v1/meta/view shape — judging top-level sort/searchableFields with the same binding order and skips as every other list-view surface. CLI commands pass no runtimeWriteType and keep the full suite; flow snapshots keep every member (default declaration). The ViewItem RECORD shape (config.sort one level down) is recorded scope, not walked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
…erence-integrity-view-surface
The changeset was untracked when the original dev died on the fable quota; its text was preserved verbatim in issue comment 5342539755. Re-created from that block, not re-derived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
…ped phantom-channel pin (#9313) Measured by ablation: crossing validateActionNameRefs onto view left the flattened-overlay rides-along control GREEN, because that rule walks views[].list / views[].listViews.* and has no flattened-overlay rung — on that shape the crossing is a silent no-op, not a phantom refusal. The channel is real on CONTAINER view writes (measured: action-name-undefined on the per-write snapshot, clean on the full stack), so the behavioural control now uses a container body naming stack-level actions, and the source comments state the two measured failure shapes instead of one. Changeset FP numbers re-derived on this tree: 0 refusals / 0 advisories over 50 shipped view-door bodies (11 containers + 39 console-shaped personalization overlays) across four authoring lineages — the rescued text claimed 102 (10+92); the measured population replaces it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
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 f9832f51354ed1e4761038abc78c3f696dc079ce && git checkout f9832f51354ed1e4761038abc78c3f696dc079ce
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 68f65ff6048dba9c28bdbc708b789ae529eb8fa3 2c119f2ccde372125c795e345c00b66481164ee7 && git checkout -B drift-repro 68f65ff6048dba9c28bdbc708b789ae529eb8fa3 && git merge --no-ff 2c119f2ccde372125c795e345c00b66481164ee7
node scripts/docs-audit/affected-docs.mjs --json 68f65ff6048dba9c28bdbc708b789ae529eb8fa3
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9313
A standalone list view written through
PUT /api/v1/meta/view— the only door a Studio tenant or an MCP/AI author has — is now judged byvalidateSearchableFieldsandvalidateSortableFields. Both halves the card names, because either alone is a silent no-op:runtimeTypes: ['flow', 'view'], and the suite dispatches its members by the gate-passedctx.runtimeWriteType(ReferenceIntegrityRule.runtimeTypes, default['flow']);views[]entry that IS a flattened list overlay (viewKind: 'list', no nestedconfig), the shapeViewMetadataSchema's list-overlay member takes on the wire and the shape the gate snapshots asviews: [item]. Recogniser verified against the wire schema's own member discrimination inpackages/spec/src/ui/view.zod.ts(record =viewKindAND nestedconfig; container is strict and cannot carryviewKind).Provenance — a resumption, not a fresh authorship
The core of this PR is
f2e1822ce, the original dev's own commit, rescued unmodified by thedomain:enginePM seat after that dev died on the Fable-5 quota mid-wrap-up (issue comment 5342539755). This session (devx seat, maintainer instruction 「9313 插队」, running the requiredclaude-fable-5tier) mergedorigin/main(8d1fa008), re-created the untracked changeset verbatim from the rescue record, then audited and verified everything below. Amendments of my own are confined to2c119f2cand are labelled as such.The granularity decision, argued explicitly (a
domain:enginejudgement — hence draft)The card reserved "widen the whole suite vs split the entry" as the engine seat's decision and sanctioned the split path only if argued. The rescued commit takes the split: the entry's
runtimeTypessays which writes dispatch the suite; a new per-memberruntimeTypesaxis (default['flow'], i.e. the frozen #4463 P1 surface) says which members judge that per-write snapshot. Aviewwrite reaches exactly the two list-view field rules, which resolve only againststack.objects— a collectionRuntimeStackContextcarries.Why the whole suite is the wrong granularity for this door — measured, and the failure differs per body shape (both ways wrong):
viewwrite,validateActionNameRefs(error-tier) resolvesviews[].list/views[].listViews.*action names againststack.actions, which no per-write snapshot carries. Measured: a container naming a stack-level action yieldsaction-name-undefinedon the snapshot shape and is clean against the full stack — a phantom 422 on a legitimate write. (Latent in the shipped corpus: 0 of 11 shipped containers name a stack-level action today.)The rescued commit's comments claimed only the first channel; my audit measured both and corrected the comments (
authoring-rules.ts,reference-integrity-suite.ts) and the test controls to match.Wiring ratchet:
authoring-rule-wiring.test.tsis entry-level and holds — the rescued commit updates itsruntimeAuthoringRulesFor('view')census to['validatePresetComparands', 'validateReferenceIntegrity', ...FAMILY], and its invariants (surface question, declared types, snapshot mapping, same-array identity) all pass. The member-level axis is pinned byruntime-gate.view-writes.test.ts("exactly the two list-view field rules declareview"; "every member still judges flow snapshots") plus the behavioural container control below. The #7220 family invariant is untouched: the two list-view field rules cross together.Proof obligation — the walk rung discriminates (ablation, both legs confirmed on disk)
Resolution path stated: every test in
packages/lint/srcimports the rules relatively in-package (./validate-sortable-fields.js), so vitest executes the mutated source directly — noexportsmap, no dist, no alias in the path; neither leg needs a package build. Each leg's mutation/restoration was proven by grep counts on the anchored text (injected spelling and original spelling, both directions), not by editor exit codes.falsein both walk files; disk proof 1/1 injected, 0/0 original): 8 tests red — the three gate refusal tests + thedata.objectretarget test inruntime-gate.view-writes.test.ts, plus 2 rung unit tests in each walk file. Everything else stayed green. "the rule now dispatches for view" alone is therefore measurably NOT this card — the refusal tests fail without the rung.Tests 2153 passed (2153),TESTEXIT=0.validateActionNameRefsmember widened ontoview; disk proof 1/0): 2 tests red — the member-surface pin AND the new container-shaped behavioural control. The rescued commit's original flattened-overlay control stayed green under this crossing (its comment claimed it was "THE control" — measured vacuous as a discriminator, which is why2c119f2cadds the container control and re-labels the flattened one as a regression pin).False-positive budget (#4716 discipline; the #9313 claim re-derived)
Population stated: every shipped view-door body derivable from this repo — (A) all 11
defineViewcontainers registered in the four shipped stacks (examples/app-crm,examples/app-showcase,examples/app-todo,packages/qa/downstream-contract), replayed verbatim asviewwrites against each stack's own objects; (B) 39 console-shaped personalization overlays — one per shipped list surface (objectlistViews.*, containerlist, containerlistViews.*): stored config flattened to the top level, identity bound,sort[].idcarryingcrypto.randomUUID()row ids (#5074),isPinned/sortOrderriding along — i.e. the shapesaveMetaItemreally stores, not the authoring shape.sort-field-unknown), and the six-refusal shape is pinned permanently inruntime-gate.view-writes.test.ts.Lane C — reported separately, not part of the shipped-body budget: a synthetic console column-sort PUT for every displayed column of every shipped list surface (180 PUTs) yields 3 refusals, all
sort-field-unsortableon formula columns shipped in grids (crm_opportunity.expected_revenue×2,showcase_project.budget_remaining). Pre-widening, those PUTs persisted and the sort silently returned unordered rows under a success (the rule's own measured message); post-widening the PUT is refused loudly. No stored shipped body sorts by a formula field, so the rollback clause is not triggered — but this is the hot-path behaviour change quantified, and whether objectui's grid should stop offering the click on a formula column is recorded as an open question on the issue.Verification (all at
2c119f2c, after the final commit)pnpm exec vitest run(packages/lint):Tests 2153 passed (2153);pnpm exec tsc --noEmit: clean (TESTEXIT=0 TSCEXIT=0, captured before any pipe).node scripts/pm/dispatch-gates.mjs(no path args — variable set derived by the script from the merge base), all 15 exit 0, each verdict quoted from the gate's own output, including:check-cross-package-test-inputs"OK: 12 package(s) read outside themselves, all declared";check:slot-lookup"ratchet holds: 107 unswept site(s) …, none new";check:type-check-debt"33 ledger entr(ies) re-measured …, none above its recorded number / surplus: none";check:engine-double-contract"OK — 331 pinned, 133 in the DEBT ledger, 2 exempt";check:where-matcher"0 silently-wrong and 0 unjudged …; none new";check:nul-bytes"OK (scanned 6076 text file(s) …)". Baselines: none raised; every ratchet sits at its recorded number.runtimeWriteType, so whole-stack runs keep the full suite (pinned by the flow-door control and the "every member still judges flow" assertion).Draft status
⛔ Deliberately draft and not to be flipped ready by this seat: the per-member granularity split is a
domain:enginejudgement (issue comment 5342067628); it ships only with that seat's or the maintainer's acknowledgement.Generated by Claude Code
Generated by Claude Code