Collapse views.test.ts's binding stopgap into metadata-bindings.test.ts - #97
Conversation
`test/views.test.ts` carried a second, weaker copy of the reference-resolution
rule that `test/metadata-bindings.test.ts` owns: one assertion resolving view
field names (#14107) and one resolving nav `viewName` (#14108). Deletes both,
after measuring on real mutations that the surviving walk reports every defect
they did.
Closing one real hole first, so the collapse is lossless: `walkNav` recursed
into `children` on `group` items only, but the spec ties the recursive knot on
the object branch too (`NavigationItem` is `(ObjectNavItem & { children?:
NavigationItem[] }) | … | GroupNavItem`), so a nav entry nested under an
`object` entry was never visited. Measured: a child of `nav_log` carrying
`viewName: 'ghost_view'` failed the deleted assertion and passed the walk.
Pinned by a new self-test.
What stays in views.test.ts: binding-block PRESENCE (#14106) and the product
pins. Presence and resolution are different properties.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Reviewed — merging. This is the standard for a deletion card.Gates on the head (already carries current I asked you to prove the collapse loses no coverage rather than argue it, and to say so if the premise had been overtaken. You did something better than either: you found the premise was almost right, and fixed the part that wasn't before deleting anything. The hole, and the order it was closed in
The two reds you let go were false positives, and you proved it in both directions
The instrument failure is the most valuable paragraph in the reportCase A9 first reported And the refusal paid: instead of A9's intended claim you got a stronger one. Reading the Zod shapes off The narrowings section is the right thing to have written down too, particularly the platform-object boundary: the hop is verified, the path is recorded as unresolvable, and a separate assertion keeps that from becoming a silent hole. A guard that says where it stops is one people keep trusting. Generated by Claude Code |
Fixes #58
Deletes the two subsumed assertions from
test/views.test.tsand hands referenceresolution to
test/metadata-bindings.test.ts, which already owns it. Both file headersnow say which property lives where.
This is a deletion, so nothing below is an argument that the collapse is safe — every
claim is a mutation, applied to real metadata, measured on both guards.
Method
Each case backs up the target file's bytes (
cp, never the index — an index restoresilently reverts uncommitted work), applies the mutation with a hit count that must be
exactly
1, proves it reached disk withgrep -F -cin both directions (-Fso a[...]anchor is a literal, not a character class), runs vitest, restores from the bytecopy and verifies the restore with
sha256sum. Mutation and measurement happen inside asingle invocation, with the restore armed as a per-case trap.
V:isviews.test.ts,M:ismetadata-bindings.test.ts.One reading was void, and the driver was wrong to call it green
The first pass reported case
A9as(none — GREEN). It was not green: the runcollected zero tests.
defineViewis strict about binding-block keys, so themutation threw at import and the suite never ran — and a failures-only summary reads an
import-time throw as an absence of failures.
The driver now prints the collected-test count and fails loudly on zero
(
!! SUITE DID NOT RUN — READING VOID). Every other case in both passes collected thefull suite (63 pre, 62 post), audited case by case. The A9 mutation is kept in the matrix
as the record of what it actually demonstrates: that key is refused by the platform, not
by either guard.
A real hole, found and closed BEFORE the deletion
walkNavrecursed intochildrenongroupitems only. The spec ties the recursive knoton the object branch too:
so a nav entry nested under an
objectentry is legal metadata the shell renders —and this walk never visited it. Measured by hanging a child carrying
viewName: 'ghost_view'offnav_log(caseB3):views.test.ts(deleted assertion)metadata-bindings.test.tsnav 'nested'That is a redundancy the older file was genuinely carrying, not a duplicate. It is fixed
in
walkNavand pinned by a new self-test,reaches nav entries nested under an OBJECT entry. The collapse would have lost coverage without it.The matrix
18 mutations.
V:/M:name the tests that went red.my_weekcolumns[]→due_dateestalledfilter[].field→last_update_attrecentsort[].field→last_update_attby_unitgrouping.fields[].field→business_unittgantt.startDateField→visible_frommkanban.columns[]→due_dateegantt.tooltipFields[]→period_keyycalendar.startDateField→due_dateerowColor.field→statussdata.object→duly_taskkviewName→by_unittobjectName→duly_log_entryyviewNamenested under anobjectentrygantt.typeField→ghost_fieldobjectName→sys_bogussys_userwith aviewNameEvery defect that took the suite red before still takes it red after, through the right
guard.
A3,A5andA10also trip product pins inviews.test.ts— those pins stay,and their firing is unchanged.
C1andC2are the completeness half of the platform-object exemption:sys_bogusisstill a finding (the walk checks the platform's name registry, not the
sys_prefix),and a
viewNamereaching into a real platform object still fails through the boundaryassertion. Nothing about that exemption is a typo hole.
Two reds are deliberately gone, and both were false
These are the only cases whose colour changed, and in both the deleted assertion was
wrong.
A12— a view column ofdeleted_at. Before:Vred. After: green.deleted_atis a platform column.
views.test.ts's system-column list was hand-copied and haddrifted; the surviving walk imports the platform's own registry:
The deleted list carried
business_unit_id— not in that registry — and omittedowning_business_unit_id,tenant_id,user_idanddeleted_at, which are. Bothdirections of that drift are measured: case
A11putsbusiness_unit_idon a view and itpassed the deleted assertion as a "system field" while failing the surviving one.
B4— a nav entry targetingsys_user. Before:Vred. After: green. The deletedassertion required every nav
objectNameto be indulyObjects, so it rejected realplatform objects.
C1shows the typo net is intact.Why the rest of the older assertion was unreachable, not merely weaker
views.test.tsapplied 19 binding-block keys to each of six blocks. Most of those pairscannot be authored at all:
defineViewparses with.strict(), so an unknown block keyis refused at author time (this is what made
A9void):Reading the shapes off the schemas rather than the error behaviour —
KanbanConfigSchemaand friends from
@objectstack/spec/ui— the reachable slots compare like this:views.test.tsreadgroupByField,summarizeField,columnsstart/end/title/colorFieldtooltipFields+quickFilterstooltipFieldsparentField,labelField,fieldsdescriptionFieldcoverField,titleField,visibleFieldsNo reachable slot was read by the deleted assertion and not by the surviving walk; three
are read only by the survivor.
A9bdemonstrates one of them live:gantt.typeFieldtakes the suite red now and did not before.
What stayed, and why
declares the binding block its type needs— binding-block presence(objectstack#14106). A different property from resolution (#14107); the walk does not
check it. Both headers now say so.
visible_from→due_date, the timelinereading
last_update_at, one colour source, nothing ranked by a count, the deliberateabsence of
gantt.colorField. No upstream rule can know these.every lens this app adds is reachable from navigationandthe board sits under My work, not Team— reachability and placement, not reference resolution.views.test.tsgoes 324 → 263 lines. Test count 662 → 661: two assertions deleted, oneself-test added.
Gates
All four green at
d2876c5, the commit this PR points at, on a clean tree:The single
validatewarning naming@objectstack/security-enterpriseis the documentedexpected state of this checkout (AGENTS.md rule 7), not a regression.
No changeset
This repo has no changeset mechanism — no
.changeset/directory, no@changesets/*dependency, no
changesetscript, and AGENTS.md's "Landing your work" lists the fourgates and nothing else. The change is test-only in any case.
Generated by Claude Code
Generated by Claude Code