Skip to content

Commit a7586cd

Browse files
huangyiireneclaude
andauthored
spec: inline view arms of the runtime write door require the object binding (#8089)
* spec: inline view arms of the runtime write door require the object binding (#7741) Direction B per the maintainer ruling of 2026-08-12: the two flattened overlay members of ViewMetadataSchema now require object + viewKind — the exact pair the object-bound read paths filter on (GET /meta/view?object= in rest-server.ts and getViewsByObject() in metadata-manager.ts both match v.viewKind && v.object === obj) — so an inline config that could never be served is refused at the door, draft and active alike, with located guidance that reuses defineView's existing wrap prescription instead of forking a second copy. Personalization PUTs are unaffected: normalizeViewMetadata inherits viewKind/object/label from the shadowed registry entry (#2555) before validation, so a console pin/sort/hide PUT on a real view arrives bound. The body this refuses is the baseline-less one — the dead row QA run #7695 measured being stored and badged valid:true. Union membership (#6391) is preserved: four arms, same order, same JSON-Schema anyOf face; the arms' required set is the only move. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r * test: re-spell consumer view fixtures to the bound inline form (#7741) Fixture triage across the wire union's consumption radius: every consumer test writing an inline view body through saveMetaItem now carries the object + viewKind binding the arms require — the post-normalize shape a real shadowing write always had. Two deliberate inversions ride along: objectql's 'plain name-stamp with no entry to inherit from' pin (that save IS the #7741 dead row and now 422s with the located guidance, asserting code INVALID_METADATA + status 422), and the diagnostics lean-overlay pin (an unbound stored row is badged invalid now). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r * test: bind the two runtime control view fixtures (#7741) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d62f8eb commit a7586cd

41 files changed

Lines changed: 591 additions & 94 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
The runtime metadata write door for `view` now refuses an inline view config that carries no object binding. The two flattened-overlay members of `ViewMetadataSchema` require `object` and `viewKind` — the pair every object-bound read path (`GET /meta/view?object=`, the view switcher) matches stored rows on — so a flat body like `{ name, type: 'grid', columns: [...] }` is rejected at save time (draft and active alike) with located guidance naming the missing binding and the `defineView({ list: { … } })` wrap remedy, instead of being stored, reported valid, and served by nothing. Console personalization PUTs are unaffected: the write path inherits the binding from the registry entry the overlay shadows before validation. Union membership is unchanged — four arms, same order, same JSON-Schema `anyOf` face; the inline arms' required set is the only change.

packages/metadata-protocol/src/metadata-diagnostics.union-issues.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,14 @@ describe('#5599 a stored `view` that is not a view is no longer badged valid', (
189189

190190
it('a legitimately-lean overlay is still valid — no collateral badge', () => {
191191
// The precondition asks "is this a view at all", never "is it complete".
192-
expect(computeMetadataDiagnostics('view', { isPinned: true })).toEqual({ valid: true });
193-
expect(computeMetadataDiagnostics('view', { hidden: true })).toEqual({ valid: true });
192+
// [#7741] "lean" now still carries the object binding: the write path
193+
// inherits `object`/`viewKind` from the shadowed entry (#2555), so a
194+
// stored lean overlay of a REAL view looks exactly like this.
195+
expect(computeMetadataDiagnostics('view', { isPinned: true, object: 'task', viewKind: 'list' })).toEqual({ valid: true });
196+
expect(computeMetadataDiagnostics('view', { hidden: true, object: 'task', viewKind: 'list' })).toEqual({ valid: true });
197+
// …while a stored row with NO binding is a row no object-bound read
198+
// path can serve — the #7741 dead row — and is badged invalid now.
199+
expect(computeMetadataDiagnostics('view', { isPinned: true })?.valid).toBe(false);
194200
});
195201

196202
it('a stored row of pure identity is no longer valid either', () => {

packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ const viewBody = (name: string) => ({
195195
name,
196196
label: 'Project Tasks',
197197
object: 'proj_task',
198+
viewKind: 'list', // [#7741] the inline arm requires the object binding pair
198199
columns: [{ field: 'title', label: 'Title' }],
199200
});
200201

packages/metadata-protocol/src/protocol.adr0005-org-override-rollback.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ const VIEW = {
198198
name: 'probe_view',
199199
label: 'Probe',
200200
object: 'task',
201+
viewKind: 'list', // [#7741] the inline arm requires the object binding pair
201202
columns: [{ field: 'name', label: 'Name' }],
202203
};
203204

packages/metadata-protocol/src/protocol.code-only-types.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ describe('code-only metadata types are refused on every kernel (#5086)', () => {
330330
name: 'rc3_probe_view',
331331
label: 'Probe',
332332
object: 'task',
333+
viewKind: 'list', // [#7741] the inline arm requires the object binding pair
333334
columns: [{ field: 'name', label: 'Name' }],
334335
},
335336
});
@@ -452,6 +453,7 @@ describe('code-only metadata types are refused on every kernel (#5086)', () => {
452453
name: 'rc3_receipt_view',
453454
label: 'Receipt',
454455
object: 'task',
456+
viewKind: 'list', // [#7741] the inline arm requires the object binding pair
455457
columns: [{ field: 'name', label: 'Name' }],
456458
},
457459
},

packages/metadata-protocol/src/protocol.flow-org-override-closed.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ const VIEW = {
178178
name: 'overdue_grid',
179179
label: 'Overdue',
180180
object: 'task',
181+
viewKind: 'list', // [#7741] the inline arm requires the object binding pair
181182
columns: [{ field: 'name', label: 'Name' }],
182183
};
183184

packages/metadata-protocol/src/protocol.lock-gate-fail-closed.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ const save = (p: ObjectStackProtocolImplementation) =>
201201
p.saveMetaItem({
202202
type: 'view',
203203
name: 'v1',
204-
item: { name: 'v1', label: 'Edited', type: 'grid', columns: ['name'] },
204+
// [#7741] carries the object binding the inline arm now requires.
205+
item: { name: 'v1', label: 'Edited', type: 'grid', columns: ['name'], object: 'task', viewKind: 'list' },
205206
} as any);
206207

207208
const remove = (p: ObjectStackProtocolImplementation) =>

packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ const VIEW = {
266266
name: 'org_grid',
267267
label: 'Org grid',
268268
object: 'task',
269+
viewKind: 'list', // [#7741] the inline arm requires the object binding pair
269270
columns: [{ field: 'title', label: 'Title' }],
270271
};
271272

packages/metadata-protocol/src/protocol.save-flow-canonicalization.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,8 @@ describe('saveMetaItem canonicalizes flow bodies (#4542)', () => {
390390
type: 'view',
391391
name: 'case_grid',
392392
organizationId: 'org_alpha',
393-
item: { name: 'case_grid', type: 'grid', label: 'Cases', columns: ['id', 'title'] },
393+
// [#7741] carries the object binding the inline arm now requires.
394+
item: { name: 'case_grid', type: 'grid', label: 'Cases', columns: ['id', 'title'], object: 'case', viewKind: 'list' },
394395
});
395396
expect(spy).not.toHaveBeenCalled();
396397
});

packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ const VIEW = {
144144
name: 'rc5_probe_view',
145145
label: 'Probe',
146146
object: 'task',
147+
viewKind: 'list', // [#7741] the inline arm requires the object binding pair
147148
columns: [{ field: 'name', label: 'Name' }],
148149
};
149150

0 commit comments

Comments
 (0)