Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .changeset/assembled-views-vocabulary.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
---
"@objectstack/spec": minor
---

feat(spec): declare the `viewItems:` channel — a portable home for non-container view artifacts in runtime-assembled manifests (#5320, #8070)

Runtime-assembled manifests (`GET /packages/:id/export`, environment artifact
bundles) carry view artifacts the authored stack vocabulary refuses: expanded
`viewKind` items (the ADR-0017 dual-read keeps them registered beside their
container), tenant-authored standalone ViewItems, and flattened overlays.
Measured on #5320, 2 of 3 exported entries in the minimal single-container case
were stack-schema-refused yet metadata-door-legal — the export→import round trip
worked only through the runtime registration loop's undeclared wider acceptance.

Per the 2026-08-12 fork ruling (option B plus A's mechanical half), the bridge
is now declared instead of silent:

- **`AssembledViewArtifactSchema`** (`ui/assembled-views.zod.ts`) — one entry of
an assembled manifest's `viewItems:` collection: the non-container branches of
the `view` metadata vocabulary, built from `VIEW_METADATA_MEMBERS` minus
`container` so the two doors cannot drift. Strictly schema'd; no passthrough.
- **`partitionAssembledViewArtifacts`** — the producer-side re-aggregation
shared by every manifest assembler: containers travel in `views:`; expanded
items a travelling container re-derives exactly are folded away (reported in
`folded`); everything else — standalone items, overlays, items whose stored
body diverged from their container — travels in `viewItems:`.
- **`ObjectStackDefinition.viewItems`** is declared as an always-refusing key:
hand-authoring it in `defineStack` source fails `tsc` (input type `never`) and
parse, with the prescription (author containers in `views:`; author standalone
views through the metadata door). The channel is machine-assembled by design.
7 changes: 7 additions & 0 deletions packages/spec/api-surface/root.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,14 @@
"exports": [
"ADMIN_FULL_ACCESS (const)",
"ALL_CONVERSIONS (const)",
"ASSEMBLED_VIEW_ITEMS_KEY (const)",
"AUDIENCE_ANCHOR_POSITIONS (const)",
"Agent (type)",
"ApplyConversionsOptions (interface)",
"AssembledViewArtifact (type)",
"AssembledViewArtifactParsed (type)",
"AssembledViewArtifactSchema (const)",
"AssembledViewPartition (interface)",
"AuthoringKeySurface (type)",
"BUILTIN_IDENTITY_METADATA (const)",
"BUILTIN_IDENTITY_NAMES (const)",
Expand DownExpand Up@@ -166,6 +171,7 @@
"formatZodIssue (function)",
"isAggregatedViewContainer (function)",
"isKnownPlatformCapability (function)",
"isViewContainerShaped (function)",
"lintUnknownAuthoringKeys (function)",
"lintUnknownKeysAgainstSchema (function)",
"lintUnknownStackKeys (function)",
Expand All@@ -175,6 +181,7 @@
"normalizePluginMetadata (function)",
"normalizeStackInput (function)",
"objectStackErrorMap (const)",
"partitionAssembledViewArtifacts (function)",
"safeParsePretty (function)",
"suggestFieldType (function)",
"tmpl (function)"
Expand Down
7 changes: 7 additions & 0 deletions packages/spec/api-surface/ui.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@
"ACTION_LOCATIONS (const)",
"ACTION_PARAM_BUILTIN_KEYS (const)",
"AIChatWindowProps (const)",
"ASSEMBLED_VIEW_ITEMS_KEY (const)",
"Action (type)",
"ActionAi (type)",
"ActionAiParsed (type)",
Expand DownExpand Up@@ -42,6 +43,10 @@
"AppearanceConfigSchema (const)",
"AriaProps (type)",
"AriaPropsSchema (const)",
"AssembledViewArtifact (type)",
"AssembledViewArtifactParsed (type)",
"AssembledViewArtifactSchema (const)",
"AssembledViewPartition (interface)",
"BorderRadius (type)",
"BorderRadiusSchema (const)",
"BreakpointColumnMap (type)",
Expand DownExpand Up@@ -409,9 +414,11 @@
"isActionParamValuePresent (function)",
"isAggregatedViewContainer (function)",
"isRecordContextBlockType (function)",
"isViewContainerShaped (function)",
"normalizeFilterOperator (function)",
"normalizeInlineAction (function)",
"pageForm (const)",
"partitionAssembledViewArtifacts (function)",
"reactBlockTagFor (function)",
"reportForm (const)",
"reportSelectionOrder (function)",
Expand Down
7 changes: 7 additions & 0 deletions packages/spec/export-origins/root.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,9 +4,14 @@
"exports": {
"ADMIN_FULL_ACCESS": "src/identity/eval-user.zod.ts#ADMIN_FULL_ACCESS (const)",
"ALL_CONVERSIONS": "src/conversions/registry.ts#ALL_CONVERSIONS (const)",
"ASSEMBLED_VIEW_ITEMS_KEY": "src/ui/assembled-views.zod.ts#ASSEMBLED_VIEW_ITEMS_KEY (const)",
"AUDIENCE_ANCHOR_POSITIONS": "src/identity/position.zod.ts#AUDIENCE_ANCHOR_POSITIONS (const)",
"Agent": "src/ai/agent.zod.ts#Agent (type)",
"ApplyConversionsOptions": "src/conversions/apply.ts#ApplyConversionsOptions (interface)",
"AssembledViewArtifact": "src/ui/assembled-views.zod.ts#AssembledViewArtifact (type)",
"AssembledViewArtifactParsed": "src/ui/assembled-views.zod.ts#AssembledViewArtifactParsed (type)",
"AssembledViewArtifactSchema": "src/ui/assembled-views.zod.ts#AssembledViewArtifactSchema (const)",
"AssembledViewPartition": "src/ui/assembled-views.zod.ts#AssembledViewPartition (interface)",
"AuthoringKeySurface": "src/data/authoring-key-lint.ts#AuthoringKeySurface (type)",
"BUILTIN_IDENTITY_METADATA": "src/identity/eval-user.zod.ts#BUILTIN_IDENTITY_METADATA (const)",
"BUILTIN_IDENTITY_NAMES": "src/identity/eval-user.zod.ts#BUILTIN_IDENTITY_NAMES (const)",
Expand DownExpand Up@@ -166,6 +171,7 @@
"formatZodIssue": "src/shared/error-map.zod.ts#formatZodIssue (function)",
"isAggregatedViewContainer": "src/ui/view.zod.ts#isAggregatedViewContainer (function)",
"isKnownPlatformCapability": "src/kernel/platform-capabilities.ts#isKnownPlatformCapability (function)",
"isViewContainerShaped": "src/ui/assembled-views.zod.ts#isViewContainerShaped (function)",
"lintUnknownAuthoringKeys": "src/kernel/metadata-authoring-lint.ts#lintUnknownAuthoringKeys (function)",
"lintUnknownKeysAgainstSchema": "src/kernel/metadata-authoring-lint.ts#lintUnknownKeysAgainstSchema (function)",
"lintUnknownStackKeys": "src/kernel/metadata-authoring-lint.ts#lintUnknownStackKeys (function)",
Expand All@@ -175,6 +181,7 @@
"normalizePluginMetadata": "src/shared/metadata-collection.zod.ts#normalizePluginMetadata (function)",
"normalizeStackInput": "src/shared/metadata-collection.zod.ts#normalizeStackInput (function)",
"objectStackErrorMap": "src/shared/error-map.zod.ts#objectStackErrorMap (const)",
"partitionAssembledViewArtifacts": "src/ui/assembled-views.zod.ts#partitionAssembledViewArtifacts (function)",
"safeParsePretty": "src/shared/error-map.zod.ts#safeParsePretty (function)",
"suggestFieldType": "src/shared/suggestions.zod.ts#suggestFieldType (function)",
"tmpl": "src/shared/expression.zod.ts#tmpl (function)"
Expand Down
7 changes: 7 additions & 0 deletions packages/spec/export-origins/ui.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@
"ACTION_LOCATIONS": "src/ui/action.zod.ts#ACTION_LOCATIONS (const)",
"ACTION_PARAM_BUILTIN_KEYS": "src/ui/action-params.zod.ts#ACTION_PARAM_BUILTIN_KEYS (const)",
"AIChatWindowProps": "src/ui/component.zod.ts#AIChatWindowProps (const)",
"ASSEMBLED_VIEW_ITEMS_KEY": "src/ui/assembled-views.zod.ts#ASSEMBLED_VIEW_ITEMS_KEY (const)",
"Action": "src/ui/action.zod.ts#Action (type)",
"ActionAi": "src/ui/action.zod.ts#ActionAi (type)",
"ActionAiParsed": "src/ui/action.zod.ts#ActionAiParsed (type)",
Expand DownExpand Up@@ -42,6 +43,10 @@
"AppearanceConfigSchema": "src/ui/view.zod.ts#AppearanceConfigSchema (const)",
"AriaProps": "src/ui/i18n.zod.ts#AriaProps (type)",
"AriaPropsSchema": "src/ui/i18n.zod.ts#AriaPropsSchema (const)",
"AssembledViewArtifact": "src/ui/assembled-views.zod.ts#AssembledViewArtifact (type)",
"AssembledViewArtifactParsed": "src/ui/assembled-views.zod.ts#AssembledViewArtifactParsed (type)",
"AssembledViewArtifactSchema": "src/ui/assembled-views.zod.ts#AssembledViewArtifactSchema (const)",
"AssembledViewPartition": "src/ui/assembled-views.zod.ts#AssembledViewPartition (interface)",
"BorderRadius": "src/ui/theme.zod.ts#BorderRadius (type)",
"BorderRadiusSchema": "src/ui/theme.zod.ts#BorderRadiusSchema (const)",
"BreakpointColumnMap": "src/ui/responsive.zod.ts#BreakpointColumnMap (type)",
Expand DownExpand Up@@ -409,9 +414,11 @@
"isActionParamValuePresent": "src/ui/action-params.zod.ts#isActionParamValuePresent (function)",
"isAggregatedViewContainer": "src/ui/view.zod.ts#isAggregatedViewContainer (function)",
"isRecordContextBlockType": "src/ui/react-blocks.ts#isRecordContextBlockType (function)",
"isViewContainerShaped": "src/ui/assembled-views.zod.ts#isViewContainerShaped (function)",
"normalizeFilterOperator": "src/ui/view.zod.ts#normalizeFilterOperator (function)",
"normalizeInlineAction": "src/ui/action.zod.ts#normalizeInlineAction (function)",
"pageForm": "src/ui/page.form.ts#pageForm (const)",
"partitionAssembledViewArtifacts": "src/ui/assembled-views.zod.ts#partitionAssembledViewArtifacts (function)",
"reactBlockTagFor": "src/ui/react-blocks.ts#reactBlockTagFor (function)",
"reportForm": "src/ui/report.form.ts#reportForm (const)",
"reportSelectionOrder": "src/ui/report.zod.ts#reportSelectionOrder (function)",
Expand Down
6 changes: 6 additions & 0 deletions packages/spec/src/api/error-code-ledger.zod.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -344,6 +344,12 @@ export const ERROR_CODE_LEDGER = {
'ERR_BULK_RESULT_MISMATCH',
'ERR_DATASOURCE_UNAVAILABLE',
'ERR_DRIVER_CONNECT',
// [#5320] Third EMITTER of the code (metadata-protocol and plugin-security
// already register it) — the registration loop's `views:` tighten refuses a
// non-container entry, and the `viewItems:` channel refuses an entry the
// assembled vocabulary rejects, both 422/INVALID_METADATA. Provenance, not
// identity, per this file's header.
'INVALID_METADATA',
'ERR_READONLY_FIELD_REJECTED', // strictReadonlyWrites: the write would strip caller-supplied fields, so it was refused (#5126; since #6437 that covers the primary_key strip too — one code, `drops` carries the per-reason breakdown)
'ERR_SUMMARY_RECOMPUTE',
'VALIDATION_FAILED',
Expand Down
10 changes: 10 additions & 0 deletions packages/spec/src/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,16 @@ export * from './stack.zod';

// DX Helper Functions (re-exported for convenience)
export { defineView, defineForm, defineViewItem, isAggregatedViewContainer, expandViewContainer, expandViewContainerWithDiagnostics } from './ui/view.zod';
// [#5320] The assembled-manifest view channel: the `viewItems:` vocabulary, its
// producer-side partition, and the shared container classifier the registration
// loop's `views:` tighten judges by.
export {
ASSEMBLED_VIEW_ITEMS_KEY,
AssembledViewArtifactSchema,
isViewContainerShaped,
partitionAssembledViewArtifacts,
} from './ui/assembled-views.zod';
export type { AssembledViewArtifact, AssembledViewArtifactParsed, AssembledViewPartition } from './ui/assembled-views.zod';
export type { ExpandedViewItem, ViewKeyCollision, ExpandViewResult } from './ui/view.zod';
export { defineApp } from './ui/app.zod';
export { defineFlow } from './automation/flow.zod';
Expand Down
36 changes: 36 additions & 0 deletions packages/spec/src/stack.zod.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -249,6 +249,37 @@ export const ObjectStackDefinitionSchema = lazySchema(() => z.object({
// LayoutDispatcher, NavigationBuilder or ThemeProvider ever consumed it).
// Author external-user UI with apps/views + positions & permission sets.
views: z.array(ViewSchema).optional().describe('List Views'),
/**
* [#5320] MACHINE-ASSEMBLED ONLY — not an authoring surface.
*
* Runtime-assembled manifests (`GET /packages/:id/export`, environment
* artifact bundles) carry their non-container view artifacts — tenant-
* authored standalone ViewItems, flattened overlays, expanded items a
* travelling container cannot re-derive — under this key, judged by
* `AssembledViewArtifactSchema` (`ui/assembled-views.zod.ts`). Authored
* stack sources never write it: views are authored as `defineView`
* containers in `views:`, and a standalone view is authored through the
* metadata door (Studio / `PUT /api/v1/meta/view`), not in stack source.
*
* Declared here as an always-refusing key — the `retiredKey()` mechanism,
* applied to a key that is machine-only rather than removed — for the same
* reason the tombstones exist (#3855): this schema is not `.strict()`, so an
* UNDECLARED `viewItems:` would be silently stripped and the author would
* learn nothing. Declared-refusing, the mistake fails `tsc` (input type
* `never`) and the parse refusal carries the prescription itself.
*/
viewItems: z.never({
error: () =>
'`viewItems` is the machine-assembled channel for non-container view artifacts in '
+ 'runtime-assembled manifests (package export, environment artifacts) — it is not an '
+ 'authoring surface. Author views as `defineView` containers in `views:`; author a '
+ 'standalone view through the metadata door (Studio / `PUT /api/v1/meta/view`) instead '
+ 'of stack source.',
}).optional().describe(
'[MACHINE-ASSEMBLED] Non-container view artifacts of a runtime-assembled manifest '
+ '(standalone ViewItems, flattened overlays) — written by package export and artifact '
+ 'factories, refused in authored stack sources (#5320).',
),
pages: z.array(PageSchema).optional().describe('Custom Pages'),
dashboards: z.array(DashboardSchema).optional().describe('Dashboards'),
reports: z.array(ReportSchema).optional().describe('Analytics Reports'),
Expand DownExpand Up@@ -1674,6 +1705,11 @@ const COMPOSE_KEY_DISPOSITIONS: Record<keyof ObjectStackDefinition, ComposeDispo
objectExtensions: 'concat',
apps: 'concat',
views: 'concat',
// [#5320] Machine-assembled channel (never authorable — the schema types it
// `never`, so no authored stack reaches composition carrying it). Assembled
// manifests are not `composeStacks` inputs today; if two ever were, their
// non-container view artifacts would concatenate like every other collection.
viewItems: 'concat',
pages: 'concat',
dashboards: 'concat',
reports: 'concat',
Expand Down
Loading
Loading