@inf-monkeys-tech/monkeys 1.0.72 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/lib/contracts/aggregate-commit.d.ts +155 -0
  2. package/lib/contracts/aggregate-commit.d.ts.map +1 -0
  3. package/lib/contracts/aggregate-commit.js +112 -0
  4. package/lib/contracts/aggregate-commit.js.map +1 -0
  5. package/lib/contracts/capability.d.ts +3 -0
  6. package/lib/contracts/capability.d.ts.map +1 -1
  7. package/lib/contracts/capability.js +1 -1
  8. package/lib/contracts/capability.js.map +1 -1
  9. package/lib/contracts/common.d.ts +2 -0
  10. package/lib/contracts/common.d.ts.map +1 -1
  11. package/lib/contracts/common.js +34 -1
  12. package/lib/contracts/common.js.map +1 -1
  13. package/lib/contracts/component-assembly.d.ts +118 -0
  14. package/lib/contracts/component-assembly.d.ts.map +1 -0
  15. package/lib/contracts/component-assembly.js +61 -0
  16. package/lib/contracts/component-assembly.js.map +1 -0
  17. package/lib/contracts/declarative-control-http.d.ts +101608 -44791
  18. package/lib/contracts/declarative-control-http.d.ts.map +1 -1
  19. package/lib/contracts/declarative-control-http.js +1358 -86
  20. package/lib/contracts/declarative-control-http.js.map +1 -1
  21. package/lib/contracts/declarative-control.d.ts +9668 -3376
  22. package/lib/contracts/declarative-control.d.ts.map +1 -1
  23. package/lib/contracts/declarative-control.js +2585 -186
  24. package/lib/contracts/declarative-control.js.map +1 -1
  25. package/lib/contracts/index.d.ts +6 -0
  26. package/lib/contracts/index.d.ts.map +1 -1
  27. package/lib/contracts/index.js +6 -0
  28. package/lib/contracts/index.js.map +1 -1
  29. package/lib/contracts/legacy-route.d.ts +15 -0
  30. package/lib/contracts/legacy-route.d.ts.map +1 -0
  31. package/lib/contracts/legacy-route.js +13 -0
  32. package/lib/contracts/legacy-route.js.map +1 -0
  33. package/lib/contracts/menu.d.ts +12 -0
  34. package/lib/contracts/menu.d.ts.map +1 -1
  35. package/lib/contracts/page-binding-dependencies.d.ts +25 -0
  36. package/lib/contracts/page-binding-dependencies.d.ts.map +1 -0
  37. package/lib/contracts/page-binding-dependencies.js +54 -0
  38. package/lib/contracts/page-binding-dependencies.js.map +1 -0
  39. package/lib/contracts/page-expression.d.ts +106 -0
  40. package/lib/contracts/page-expression.d.ts.map +1 -0
  41. package/lib/contracts/page-expression.js +136 -0
  42. package/lib/contracts/page-expression.js.map +1 -0
  43. package/lib/contracts/page-stream.d.ts +101 -0
  44. package/lib/contracts/page-stream.d.ts.map +1 -0
  45. package/lib/contracts/page-stream.js +66 -0
  46. package/lib/contracts/page-stream.js.map +1 -0
  47. package/lib/contracts/page.d.ts +24 -0
  48. package/lib/contracts/page.d.ts.map +1 -1
  49. package/lib/contracts/page.js +2 -0
  50. package/lib/contracts/page.js.map +1 -1
  51. package/lib/contracts/record-editor-draft.d.ts +95 -0
  52. package/lib/contracts/record-editor-draft.d.ts.map +1 -0
  53. package/lib/contracts/record-editor-draft.js +35 -0
  54. package/lib/contracts/record-editor-draft.js.map +1 -0
  55. package/lib/contracts/render.d.ts +16 -0
  56. package/lib/contracts/render.d.ts.map +1 -1
  57. package/lib/contracts/render.js +3 -1
  58. package/lib/contracts/render.js.map +1 -1
  59. package/lib/contracts/semantic.d.ts +48 -1
  60. package/lib/contracts/semantic.d.ts.map +1 -1
  61. package/lib/contracts/semantic.js +22 -19
  62. package/lib/contracts/semantic.js.map +1 -1
  63. package/lib/contracts/tenant.d.ts +112 -6
  64. package/lib/contracts/tenant.d.ts.map +1 -1
  65. package/lib/contracts/tenant.js +14 -1
  66. package/lib/contracts/tenant.js.map +1 -1
  67. package/lib/esm/contracts/index.mjs +6921 -3032
  68. package/lib/esm/contracts/index.mjs.map +1 -1
  69. package/lib/esm/index.mjs +11333 -4947
  70. package/lib/esm/index.mjs.map +1 -1
  71. package/lib/esm/runtime/index.mjs +11514 -4411
  72. package/lib/esm/runtime/index.mjs.map +1 -1
  73. package/lib/esm/schemas/index.mjs +7282 -2571
  74. package/lib/esm/schemas/index.mjs.map +1 -1
  75. package/lib/json-schema/aggregate-commit-request.schema.json +426 -0
  76. package/lib/json-schema/capability-manifest.schema.json +1 -0
  77. package/lib/json-schema/capability-registry.schema.json +1 -0
  78. package/lib/json-schema/declarative-route-owner-index.schema.json +1092 -0
  79. package/lib/json-schema/declarative-workbench-catalog.schema.json +769 -0
  80. package/lib/json-schema/design-capability-catalog-artifact.schema.json +2787 -0
  81. package/lib/json-schema/domain-command-definition.schema.json +18 -0
  82. package/lib/json-schema/domain-query-definition.schema.json +1406 -0
  83. package/lib/json-schema/index.json +7 -0
  84. package/lib/json-schema/legacy-route-takeover-authorization.schema.json +601 -0
  85. package/lib/json-schema/navigation-release.schema.json +296 -0
  86. package/lib/json-schema/navigation-runtime-bundle.schema.json +48 -8
  87. package/lib/json-schema/navigation.schema.json +47 -8
  88. package/lib/json-schema/overlay-node.schema.json +281 -0
  89. package/lib/json-schema/page-definition.schema.json +34 -0
  90. package/lib/json-schema/page-release.schema.json +1520 -626
  91. package/lib/json-schema/page-runtime-bundle.schema.json +5980 -401
  92. package/lib/json-schema/page-runtime-descriptor.schema.json +34 -0
  93. package/lib/json-schema/page.schema.json +7333 -1482
  94. package/lib/json-schema/permission-alternative-policy.schema.json +60 -0
  95. package/lib/json-schema/product-declaration.schema.json +53 -0
  96. package/lib/json-schema/render-node.schema.json +281 -0
  97. package/lib/json-schema/render-tree.schema.json +281 -0
  98. package/lib/json-schema/tenant-product-config.schema.json +88 -0
  99. package/lib/json-schema/tenant-runtime-config.schema.json +88 -0
  100. package/lib/json-schema/view-provider-descriptor.schema.json +1 -0
  101. package/lib/json-schema/workbench-release.schema.json +1520 -626
  102. package/lib/json-schema/workbench-runtime-bundle.schema.json +686 -20
  103. package/lib/json-schema/workbench.schema.json +1196 -0
  104. package/lib/runtime/component-assembly.d.ts +4 -0
  105. package/lib/runtime/component-assembly.d.ts.map +1 -0
  106. package/lib/runtime/component-assembly.js +126 -0
  107. package/lib/runtime/component-assembly.js.map +1 -0
  108. package/lib/runtime/declarative-control-compiler.d.ts +66 -5
  109. package/lib/runtime/declarative-control-compiler.d.ts.map +1 -1
  110. package/lib/runtime/declarative-control-compiler.js +862 -109
  111. package/lib/runtime/declarative-control-compiler.js.map +1 -1
  112. package/lib/runtime/index.d.ts +5 -0
  113. package/lib/runtime/index.d.ts.map +1 -1
  114. package/lib/runtime/index.js +7 -0
  115. package/lib/runtime/index.js.map +1 -1
  116. package/lib/runtime/page-expression.d.ts +9 -0
  117. package/lib/runtime/page-expression.d.ts.map +1 -0
  118. package/lib/runtime/page-expression.js +257 -0
  119. package/lib/runtime/page-expression.js.map +1 -0
  120. package/lib/runtime/page-stream.d.ts +24 -0
  121. package/lib/runtime/page-stream.d.ts.map +1 -0
  122. package/lib/runtime/page-stream.js +65 -0
  123. package/lib/runtime/page-stream.js.map +1 -0
  124. package/lib/runtime/record-editor-draft.d.ts +29 -0
  125. package/lib/runtime/record-editor-draft.d.ts.map +1 -0
  126. package/lib/runtime/record-editor-draft.js +161 -0
  127. package/lib/runtime/record-editor-draft.js.map +1 -0
  128. package/lib/runtime/target-render-model.d.ts +4 -0
  129. package/lib/runtime/target-render-model.d.ts.map +1 -0
  130. package/lib/runtime/target-render-model.js +28 -0
  131. package/lib/runtime/target-render-model.js.map +1 -0
  132. package/lib/runtime/tenant-config.d.ts +2 -0
  133. package/lib/runtime/tenant-config.d.ts.map +1 -1
  134. package/lib/runtime/tenant-config.js +6 -1
  135. package/lib/runtime/tenant-config.js.map +1 -1
  136. package/lib/schemas/index.d.ts +6957 -2171
  137. package/lib/schemas/index.d.ts.map +1 -1
  138. package/lib/schemas/index.js +86 -8
  139. package/lib/schemas/index.js.map +1 -1
  140. package/package.json +1 -1
@@ -1,11 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WorkbenchRuntimeBundleSchema = exports.ResolvedWorkbenchAppInstanceSchema = exports.PageRuntimeBundleSchema = exports.NavigationReleaseSchema = exports.UnavailableNavigationTargetSchema = exports.ResolvedNavigationTargetSchema = exports.NavigationSchema = exports.NavigationNodeSchema = exports.NavigationSeparatorNodeSchema = exports.NavigationTargetNodeSchema = exports.NavigationGroupNodeSchema = exports.WorkbenchReleaseSchema = exports.WorkbenchSchema = exports.WorkbenchAppInstanceSchema = exports.WorkbenchGroupSchema = exports.PageReleaseSchema = exports.PageSchema = exports.ActionBindingSchema = exports.OntologyBindingSchema = exports.CapabilityInstanceSchema = exports.BindingSourceSchema = exports.WorkbenchIdentitySchema = exports.PublicationPlanSchema = exports.PublicationPlanOperationSchema = exports.ReleaseEvidenceSchema = exports.ReleaseValidationSchema = exports.ReleaseDependencySchema = exports.ReleaseDependencyRoleSchema = exports.ReleaseOperationSchema = exports.CompiledRouteClaimSchema = exports.CompiledRouteMatcherSchema = exports.DeclarativeRouteClaimSchema = exports.RouteClaimSchema = exports.LegacyRouteClaimSchema = exports.RouteSpaceSchema = exports.RouteSpaceParameterSchema = exports.RoutePathTemplateSchema = exports.ManagementAccessSchema = exports.AccessPolicySchema = exports.ProductSurfaceSchema = exports.DeclarativeLifecycleSchema = exports.StableRefAliasMapSchema = exports.StableRefAliasSchema = exports.RevisionRefSchema = exports.StableRefSchema = exports.ReferenceVisibilitySchema = exports.TenantScopeSchema = exports.ScopedIdentityRefSchema = exports.I18nTextSchema = exports.DECLARATIVE_CONTROL_SCHEMA_VERSION = void 0;
4
- exports.DECLARATIVE_CONTROL_ONTOLOGY_DEFINITIONS = exports.DeclarativeControlOntologyDefinitionSchema = exports.NavigationRuntimeBundleSchema = exports.CompiledNavigationNodeSchema = exports.CompiledNavigationSeparatorNodeSchema = exports.CompiledNavigationTargetNodeSchema = exports.CompiledNavigationGroupNodeSchema = exports.CompiledNavigationResolvedTargetSchema = exports.CompiledNavigationDomainCommandTargetSchema = exports.CompiledNavigationRegisteredMenuActionTargetSchema = void 0;
3
+ exports.CursorWindowBindingSchema = exports.DomainQueryDefinitionSchema = exports.DomainQueryDataSourceSchema = exports.RegisteredServiceDomainQueryDataSourceSchema = exports.GovernedCatalogDomainQueryDataSourceSchema = exports.GovernedCatalogResourceKindSchema = exports.InteractionBindingSchema = exports.PageStateDefinitionSchema = exports.LegacyBrowserPreferenceImportSchema = exports.ResolvedPageEntryTransitionSchema = exports.PageEntryTransitionSchema = exports.PageStateActivationSchema = exports.PageStateConditionSchema = exports.BindingSourceSchema = exports.DeclarativeRouteClaimSchema = exports.LegacyRouteTakeoverAuthorizationSchema = exports.LegacySlotRestorationSchema = exports.WorkbenchIdentitySchema = exports.PublicationPlanSchema = exports.PublicationPlanOperationSchema = exports.ReleaseEvidenceSchema = exports.ReleaseValidationSchema = exports.ReleaseDependencySchema = exports.ReleaseDependencyRoleSchema = exports.ReleaseOperationSchema = exports.CompiledRouteClaimSchema = exports.CompiledRouteMatcherSchema = exports.RouteClaimSchema = exports.LegacyRouteClaimSchema = exports.RouteSpaceSchema = exports.RouteSpaceParameterSchema = exports.RoutePathTemplateSchema = exports.ManagementAccessSchema = exports.PermissionAlternativePolicySchema = exports.AccessPolicySchema = exports.DeclarativeRuntimeShellDescriptorSchema = exports.DeclarativeShellSurfaceChromeSchema = exports.DeclarativeShellHeaderChromeSchema = exports.ProductSurfaceSchema = exports.DeclarativeLifecycleSchema = exports.StableRefAliasMapSchema = exports.StableRefAliasSchema = exports.RevisionRefSchema = exports.StableRefSchema = exports.ReferenceVisibilitySchema = exports.TenantScopeSchema = exports.ScopedIdentityRefSchema = exports.I18nTextSchema = exports.DECLARATIVE_CONTROL_SCHEMA_VERSION = exports.analyzePageBindingDependencies = void 0;
4
+ exports.DECLARATIVE_CONTROL_ONTOLOGY_DEFINITIONS = exports.DeclarativeControlOntologyDefinitionSchema = exports.NavigationRuntimeBundleSchema = exports.CompiledNavigationNodeSchema = exports.CompiledNavigationSeparatorNodeSchema = exports.CompiledNavigationTargetNodeSchema = exports.CompiledNavigationGroupNodeSchema = exports.CompiledNavigationResolvedTargetSchema = exports.CompiledNavigationDomainCommandTargetSchema = exports.CompiledNavigationRegisteredMenuActionTargetSchema = exports.WorkbenchRuntimeBundleSchema = exports.WorkbenchRuntimeIdentitySchema = exports.ResolvedWorkbenchAppInstanceSchema = exports.PageRuntimeBundleSchema = exports.DeclarativeWorkbenchCatalogSchema = exports.DeclarativeWorkbenchCatalogEntrySchema = exports.DeclarativeRouteOwnerIndexSchema = exports.DeclarativeRouteOwnerIndexEntrySchema = exports.NavigationReleaseSchema = exports.UnavailableNavigationTargetSchema = exports.ResolvedNavigationTargetSchema = exports.NavigationSchema = exports.NavigationNodeSchema = exports.NavigationSeparatorNodeSchema = exports.NavigationTargetNodeSchema = exports.NavigationGroupNodeSchema = exports.WorkbenchReleaseSchema = exports.WorkbenchSchema = exports.WorkbenchAppInstanceSchema = exports.WorkbenchGroupSchema = exports.PageReleaseSchema = exports.PageSchema = exports.PageRouteStatePresentationSchema = exports.PageMigrationEquivalenceSchema = exports.PageMigrationEquivalenceDimensionSchema = exports.ActionBindingSchema = exports.ActionResultClipboardEffectSchema = exports.ActionResultBindingEffectSchema = exports.ActionResultBindingEffectSourceSchema = exports.OntologyBindingSchema = exports.CapabilityInstanceSchema = exports.QueryBindingSchema = exports.QueryResultStateBindingSchema = void 0;
5
5
  const zod_1 = require("zod");
6
6
  const common_1 = require("./common");
7
7
  const data_1 = require("./data");
8
+ const legacy_route_1 = require("./legacy-route");
9
+ const file_1 = require("./file");
8
10
  const render_1 = require("./render");
11
+ const page_expression_1 = require("./page-expression");
12
+ const page_binding_dependencies_1 = require("./page-binding-dependencies");
13
+ var page_binding_dependencies_2 = require("./page-binding-dependencies");
14
+ Object.defineProperty(exports, "analyzePageBindingDependencies", { enumerable: true, get: function () { return page_binding_dependencies_2.analyzePageBindingDependencies; } });
9
15
  exports.DECLARATIVE_CONTROL_SCHEMA_VERSION = 1;
10
16
  const uniqueArray = (values, identity) => {
11
17
  const seen = new Set();
@@ -17,6 +23,10 @@ const uniqueArray = (values, identity) => {
17
23
  }
18
24
  return undefined;
19
25
  };
26
+ const exactRevisionRef = (left, right) => stableRefIdentity(left) === stableRefIdentity(right) &&
27
+ left.revision === right.revision &&
28
+ left.schemaVersion === right.schemaVersion &&
29
+ left.contentHash === right.contentHash;
20
30
  exports.I18nTextSchema = zod_1.z
21
31
  .object({
22
32
  defaultLocale: common_1.LocaleIdentifierSchema,
@@ -54,7 +64,11 @@ exports.TenantScopeSchema = zod_1.z
54
64
  ];
55
65
  expectedKinds.forEach(([field, reference, expectedKind]) => {
56
66
  if (reference && reference.kind !== expectedKind) {
57
- context.addIssue({ code: 'custom', path: [field, 'kind'], message: `${field} must reference ${expectedKind}.` });
67
+ context.addIssue({
68
+ code: 'custom',
69
+ path: [field, 'kind'],
70
+ message: `${field} must reference ${expectedKind}.`,
71
+ });
58
72
  }
59
73
  });
60
74
  });
@@ -68,16 +82,21 @@ const StableRefShape = {
68
82
  };
69
83
  const validateReferenceScope = (value, context) => {
70
84
  if (value.visibility === 'tenant' && !value.tenantScope) {
71
- context.addIssue({ code: 'custom', path: ['tenantScope'], message: 'Tenant references require an explicit tenant scope.' });
85
+ context.addIssue({
86
+ code: 'custom',
87
+ path: ['tenantScope'],
88
+ message: 'Tenant references require an explicit tenant scope.',
89
+ });
72
90
  }
73
91
  if (value.visibility !== 'tenant' && value.tenantScope) {
74
- context.addIssue({ code: 'custom', path: ['tenantScope'], message: 'Global and public references cannot carry a tenant scope.' });
92
+ context.addIssue({
93
+ code: 'custom',
94
+ path: ['tenantScope'],
95
+ message: 'Global and public references cannot carry a tenant scope.',
96
+ });
75
97
  }
76
98
  };
77
- exports.StableRefSchema = zod_1.z
78
- .object(StableRefShape)
79
- .strict()
80
- .superRefine(validateReferenceScope);
99
+ exports.StableRefSchema = zod_1.z.object(StableRefShape).strict().superRefine(validateReferenceScope);
81
100
  exports.RevisionRefSchema = zod_1.z
82
101
  .object({
83
102
  ...StableRefShape,
@@ -90,10 +109,17 @@ exports.RevisionRefSchema = zod_1.z
90
109
  const scopedIdentityKey = (reference) => `${reference.kind}:${reference.id}:${reference.ownerRepo}`;
91
110
  const tenantScopeKey = (scope) => `${scopedIdentityKey(scope.tenantRef)}:${scopedIdentityKey(scope.dataSpaceRef)}:${scope.teamRef ? scopedIdentityKey(scope.teamRef) : ''}`;
92
111
  const stableRefIdentity = (reference) => `${reference.visibility}:${reference.tenantScope ? tenantScopeKey(reference.tenantScope) : ''}:${reference.kind}:${reference.id}:${reference.ownerRepo}`;
93
- const exactRevisionRef = (left, right) => stableRefIdentity(left) === stableRefIdentity(right)
94
- && left.revision === right.revision
95
- && left.schemaVersion === right.schemaVersion
96
- && left.contentHash === right.contentHash;
112
+ const validateTenantCompatibleReference = (reference, tenantScope, path, context) => {
113
+ if (reference.visibility === 'tenant' &&
114
+ reference.tenantScope &&
115
+ tenantScopeKey(reference.tenantScope) !== tenantScopeKey(tenantScope)) {
116
+ context.addIssue({
117
+ code: 'custom',
118
+ path: [...path, 'tenantScope'],
119
+ message: 'Reference belongs to another tenant scope.',
120
+ });
121
+ }
122
+ };
97
123
  exports.StableRefAliasSchema = zod_1.z
98
124
  .object({
99
125
  aliasRef: exports.StableRefSchema,
@@ -105,10 +131,18 @@ exports.StableRefAliasSchema = zod_1.z
105
131
  .strict()
106
132
  .superRefine((value, context) => {
107
133
  if (value.aliasRef.kind !== value.canonicalRef.kind) {
108
- context.addIssue({ code: 'custom', path: ['canonicalRef', 'kind'], message: 'Alias and canonical references must have the same kind.' });
134
+ context.addIssue({
135
+ code: 'custom',
136
+ path: ['canonicalRef', 'kind'],
137
+ message: 'Alias and canonical references must have the same kind.',
138
+ });
109
139
  }
110
140
  if (value.validUntil && value.validUntil <= value.validFrom) {
111
- context.addIssue({ code: 'custom', path: ['validUntil'], message: 'Alias validUntil must be later than validFrom.' });
141
+ context.addIssue({
142
+ code: 'custom',
143
+ path: ['validUntil'],
144
+ message: 'Alias validUntil must be later than validFrom.',
145
+ });
112
146
  }
113
147
  });
114
148
  exports.StableRefAliasMapSchema = zod_1.z
@@ -123,17 +157,71 @@ exports.StableRefAliasMapSchema = zod_1.z
123
157
  .superRefine((value, context) => {
124
158
  const duplicate = uniqueArray(value.aliases, (entry) => stableRefIdentity(entry.aliasRef));
125
159
  if (duplicate)
126
- context.addIssue({ code: 'custom', path: ['aliases'], message: `Duplicate alias reference: ${duplicate}` });
160
+ context.addIssue({
161
+ code: 'custom',
162
+ path: ['aliases'],
163
+ message: `Duplicate alias reference: ${duplicate}`,
164
+ });
127
165
  });
128
166
  exports.DeclarativeLifecycleSchema = zod_1.z.enum(['active', 'deprecated', 'retired']);
129
167
  exports.ProductSurfaceSchema = zod_1.z.enum(['studio', 'kernel']);
168
+ exports.DeclarativeShellHeaderChromeSchema = zod_1.z.discriminatedUnion('enabled', [
169
+ zod_1.z.object({ enabled: zod_1.z.literal(false) }).strict(),
170
+ zod_1.z
171
+ .object({
172
+ enabled: zod_1.z.literal(true),
173
+ navigationPlacement: zod_1.z.enum(['studio.headbar.primary', 'kernel.primary']),
174
+ })
175
+ .strict(),
176
+ ]);
177
+ exports.DeclarativeShellSurfaceChromeSchema = zod_1.z
178
+ .object({
179
+ surface: exports.ProductSurfaceSchema,
180
+ header: exports.DeclarativeShellHeaderChromeSchema,
181
+ })
182
+ .strict()
183
+ .superRefine((value, context) => {
184
+ if (!value.header.enabled)
185
+ return;
186
+ const expectedPlacement = value.surface === 'studio' ? 'studio.headbar.primary' : 'kernel.primary';
187
+ if (value.header.navigationPlacement !== expectedPlacement) {
188
+ context.addIssue({
189
+ code: 'custom',
190
+ path: ['header', 'navigationPlacement'],
191
+ message: `The ${value.surface} shell header must use its governed primary Navigation placement.`,
192
+ });
193
+ }
194
+ });
195
+ exports.DeclarativeRuntimeShellDescriptorSchema = zod_1.z
196
+ .object({
197
+ shellRevisionRef: exports.RevisionRefSchema,
198
+ header: exports.DeclarativeShellHeaderChromeSchema,
199
+ })
200
+ .strict()
201
+ .superRefine((value, context) => {
202
+ if (value.shellRevisionRef.kind !== 'shell') {
203
+ context.addIssue({
204
+ code: 'custom',
205
+ path: ['shellRevisionRef', 'kind'],
206
+ message: 'Runtime shell descriptors require an exact Shell revision.',
207
+ });
208
+ }
209
+ });
130
210
  const GroupRefSchema = exports.StableRefSchema.superRefine((value, context) => {
131
211
  if (value.kind !== 'group')
132
- context.addIssue({ code: 'custom', path: ['kind'], message: 'Access group references must use kind group.' });
212
+ context.addIssue({
213
+ code: 'custom',
214
+ path: ['kind'],
215
+ message: 'Access group references must use kind group.',
216
+ });
133
217
  });
134
218
  const AccessConditionRefSchema = exports.RevisionRefSchema.superRefine((value, context) => {
135
219
  if (value.kind !== 'access-condition') {
136
- context.addIssue({ code: 'custom', path: ['kind'], message: 'Access condition references must use kind access-condition.' });
220
+ context.addIssue({
221
+ code: 'custom',
222
+ path: ['kind'],
223
+ message: 'Access condition references must use kind access-condition.',
224
+ });
137
225
  }
138
226
  });
139
227
  exports.AccessPolicySchema = zod_1.z
@@ -152,12 +240,62 @@ exports.AccessPolicySchema = zod_1.z
152
240
  ['groupAnyOf', value.groupAnyOf, (candidate) => stableRefIdentity(candidate)],
153
241
  ['permissionAllOf', value.permissionAllOf, String],
154
242
  ['permissionAnyOf', value.permissionAnyOf, String],
155
- ['conditionAllOf', value.conditionAllOf, (candidate) => `${stableRefIdentity(candidate)}@${candidate.revision}`],
243
+ [
244
+ 'conditionAllOf',
245
+ value.conditionAllOf,
246
+ (candidate) => `${stableRefIdentity(candidate)}@${candidate.revision}`,
247
+ ],
156
248
  ];
157
249
  checks.forEach(([field, values, identity]) => {
158
250
  if (uniqueArray(values, identity))
159
- context.addIssue({ code: 'custom', path: [field], message: `${field} entries must be unique.` });
251
+ context.addIssue({
252
+ code: 'custom',
253
+ path: [field],
254
+ message: `${field} entries must be unique.`,
255
+ });
256
+ });
257
+ });
258
+ /**
259
+ * A finite set of permission conjunctions. An identity is authorized when it
260
+ * satisfies every permission in at least one alternative. This is deliberately
261
+ * separate from AccessPolicy: it models a versioned rollout compatibility
262
+ * bridge such as `new-permission OR (legacy-a AND legacy-b)` without turning
263
+ * individual legacy permissions into broad aliases.
264
+ */
265
+ exports.PermissionAlternativePolicySchema = zod_1.z
266
+ .object({
267
+ contract: zod_1.z.literal('PermissionAlternativePolicy'),
268
+ schemaVersion: zod_1.z.literal(exports.DECLARATIVE_CONTROL_SCHEMA_VERSION),
269
+ policyId: common_1.ContractIdentifierSchema,
270
+ alternatives: zod_1.z.array(zod_1.z.array(common_1.ContractIdentifierSchema).min(1)).min(1),
271
+ retirementGate: zod_1.z
272
+ .object({
273
+ capability: common_1.ContractIdentifierSchema,
274
+ notBefore: common_1.IsoDateTimeSchema.optional(),
275
+ })
276
+ .strict()
277
+ .optional(),
278
+ })
279
+ .strict()
280
+ .superRefine((value, context) => {
281
+ const canonical = value.alternatives.map((set) => [...new Set(set)].sort());
282
+ canonical.forEach((set, index) => {
283
+ if (set.length !== value.alternatives[index].length) {
284
+ context.addIssue({
285
+ code: 'custom',
286
+ path: ['alternatives', index],
287
+ message: 'A permission alternative cannot contain duplicate permissions.',
288
+ });
289
+ }
160
290
  });
291
+ const identities = canonical.map((set) => set.join('\0'));
292
+ if (new Set(identities).size !== identities.length) {
293
+ context.addIssue({
294
+ code: 'custom',
295
+ path: ['alternatives'],
296
+ message: 'Permission alternatives must be unique.',
297
+ });
298
+ }
161
299
  });
162
300
  exports.ManagementAccessSchema = zod_1.z
163
301
  .object({
@@ -175,13 +313,22 @@ exports.RoutePathTemplateSchema = zod_1.z
175
313
  .regex(/^\/(?!\/)/, 'Expected an application-relative path.')
176
314
  .superRefine((value, context) => {
177
315
  if (value.includes('?') || value.includes('#') || value.includes('\\')) {
178
- context.addIssue({ code: 'custom', message: 'Route paths cannot contain query strings, fragments, or backslashes.' });
316
+ context.addIssue({
317
+ code: 'custom',
318
+ message: 'Route paths cannot contain query strings, fragments, or backslashes.',
319
+ });
179
320
  }
180
321
  if (value.split('/').some((segment) => segment === '.' || segment === '..')) {
181
- context.addIssue({ code: 'custom', message: 'Route paths cannot contain traversal segments.' });
322
+ context.addIssue({
323
+ code: 'custom',
324
+ message: 'Route paths cannot contain traversal segments.',
325
+ });
182
326
  }
183
327
  if (/^[a-z][a-z0-9+.-]*:/i.test(value) || value.includes('://')) {
184
- context.addIssue({ code: 'custom', message: 'Route paths cannot contain a scheme or host.' });
328
+ context.addIssue({
329
+ code: 'custom',
330
+ message: 'Route paths cannot contain a scheme or host.',
331
+ });
185
332
  }
186
333
  });
187
334
  exports.RouteSpaceParameterSchema = zod_1.z
@@ -206,10 +353,18 @@ exports.RouteSpaceSchema = zod_1.z
206
353
  .strict()
207
354
  .superRefine((value, context) => {
208
355
  if (uniqueArray(value.parameters, (parameter) => parameter.name)) {
209
- context.addIssue({ code: 'custom', path: ['parameters'], message: 'RouteSpace parameter names must be unique.' });
356
+ context.addIssue({
357
+ code: 'custom',
358
+ path: ['parameters'],
359
+ message: 'RouteSpace parameter names must be unique.',
360
+ });
210
361
  }
211
362
  if (uniqueArray(value.reservedPaths, (path) => path)) {
212
- context.addIssue({ code: 'custom', path: ['reservedPaths'], message: 'RouteSpace reserved paths must be unique.' });
363
+ context.addIssue({
364
+ code: 'custom',
365
+ path: ['reservedPaths'],
366
+ message: 'RouteSpace reserved paths must be unique.',
367
+ });
213
368
  }
214
369
  });
215
370
  const RouteClaimBaseSchema = zod_1.z
@@ -222,27 +377,37 @@ const RouteClaimBaseSchema = zod_1.z
222
377
  .strict()
223
378
  .superRefine((value, context) => {
224
379
  if (value.routeSpaceRevisionRef.kind !== 'route-space') {
225
- context.addIssue({ code: 'custom', path: ['routeSpaceRevisionRef', 'kind'], message: 'Route claims must reference a route-space revision.' });
380
+ context.addIssue({
381
+ code: 'custom',
382
+ path: ['routeSpaceRevisionRef', 'kind'],
383
+ message: 'Route claims must reference a route-space revision.',
384
+ });
226
385
  }
227
386
  if (value.kind === 'redirect' && !value.redirectTargetRef) {
228
- context.addIssue({ code: 'custom', path: ['redirectTargetRef'], message: 'Redirect claims require a governed target reference.' });
387
+ context.addIssue({
388
+ code: 'custom',
389
+ path: ['redirectTargetRef'],
390
+ message: 'Redirect claims require a governed target reference.',
391
+ });
229
392
  }
230
393
  if (value.kind !== 'redirect' && value.redirectTargetRef) {
231
- context.addIssue({ code: 'custom', path: ['redirectTargetRef'], message: 'Only redirect claims may declare redirectTargetRef.' });
394
+ context.addIssue({
395
+ code: 'custom',
396
+ path: ['redirectTargetRef'],
397
+ message: 'Only redirect claims may declare redirectTargetRef.',
398
+ });
232
399
  }
233
400
  });
234
401
  /**
235
402
  * Read compatibility for schemaVersion 1 Page and Workbench records written
236
403
  * before route claims carried their owning product surface. New declarations
237
- * must use RouteClaimSchema; compilers infer this legacy form only from the
238
- * exact pinned RouteSpace.
404
+ * must use RouteClaimSchema; compilers materialize this legacy form only from
405
+ * the exact pinned RouteSpace surface.
239
406
  */
240
407
  exports.LegacyRouteClaimSchema = RouteClaimBaseSchema;
241
408
  exports.RouteClaimSchema = RouteClaimBaseSchema.safeExtend({
242
409
  surface: exports.ProductSurfaceSchema,
243
410
  }).strict();
244
- exports.DeclarativeRouteClaimSchema = exports.RouteClaimSchema;
245
- const LegacyDeclarativeRouteClaimSchema = exports.LegacyRouteClaimSchema;
246
411
  const LegacyCompiledRouteMatcherSchema = zod_1.z
247
412
  .object({
248
413
  caseSensitive: zod_1.z.boolean(),
@@ -256,32 +421,32 @@ exports.CompiledRouteMatcherSchema = LegacyCompiledRouteMatcherSchema.safeExtend
256
421
  exports.CompiledRouteClaimSchema = exports.RouteClaimSchema.safeExtend({
257
422
  normalizedPath: exports.RoutePathTemplateSchema,
258
423
  matcher: exports.CompiledRouteMatcherSchema,
259
- }).strict();
424
+ })
425
+ .strict()
426
+ .superRefine((value, context) => {
427
+ if (value.matcher.surface !== value.surface) {
428
+ context.addIssue({
429
+ code: 'custom',
430
+ path: ['matcher', 'surface'],
431
+ message: 'A compiled route matcher must match its claim surface.',
432
+ });
433
+ }
434
+ });
260
435
  const LegacyCompiledRouteClaimSchema = exports.LegacyRouteClaimSchema.safeExtend({
261
436
  normalizedPath: exports.RoutePathTemplateSchema,
262
437
  matcher: LegacyCompiledRouteMatcherSchema,
263
438
  }).strict();
264
- const validateDeclaredRouteSurfaces = (supportedSurfaces, routeClaims, context) => {
265
- if (uniqueArray(supportedSurfaces, (surface) => surface)) {
266
- context.addIssue({ code: 'custom', path: ['supportedSurfaces'], message: 'Supported surfaces must be unique.' });
267
- }
268
- const explicitClaims = routeClaims.filter((claim) => 'surface' in claim);
269
- if (explicitClaims.length > 0 && explicitClaims.length < routeClaims.length) {
270
- context.addIssue({ code: 'custom', path: ['routeClaims'], message: 'Route claims must either all declare surface or all omit it for legacy compatibility.' });
439
+ const validateReleaseRouteSurface = (target, context) => {
440
+ const claimSurface = 'surface' in target.routeClaim ? target.routeClaim.surface : undefined;
441
+ const matcherSurface = 'surface' in target.routeClaim.matcher ? target.routeClaim.matcher.surface : undefined;
442
+ if (claimSurface === undefined && matcherSurface === undefined)
271
443
  return;
272
- }
273
- routeClaims.forEach((claim, index) => {
274
- if ('surface' in claim && !supportedSurfaces.includes(claim.surface)) {
275
- context.addIssue({ code: 'custom', path: ['routeClaims', index, 'surface'], message: 'Route claim surface must be declared by supportedSurfaces.' });
276
- }
277
- });
278
- if (explicitClaims.length !== routeClaims.length)
444
+ if (claimSurface === target.surface && matcherSurface === target.surface)
279
445
  return;
280
- supportedSurfaces.forEach((surface) => {
281
- const canonicalCount = explicitClaims.filter((claim) => claim.surface === surface && claim.kind === 'canonical').length;
282
- if (canonicalCount !== 1) {
283
- context.addIssue({ code: 'custom', path: ['routeClaims'], message: `Surface ${surface} requires exactly one canonical route claim.` });
284
- }
446
+ context.addIssue({
447
+ code: 'custom',
448
+ path: ['target', 'routeClaim'],
449
+ message: 'A compiled Release route claim must either be fully legacy or bind both claim and matcher to the target surface.',
285
450
  });
286
451
  };
287
452
  exports.ReleaseOperationSchema = zod_1.z.enum(['activate', 'deactivate', 'rollback']);
@@ -299,6 +464,7 @@ exports.ReleaseDependencyRoleSchema = zod_1.z.enum([
299
464
  'capability',
300
465
  'provider',
301
466
  'ontology-definition',
467
+ 'query-definition',
302
468
  'view',
303
469
  'projection',
304
470
  'action',
@@ -327,6 +493,7 @@ const DependencyKindByRole = {
327
493
  capability: 'capability',
328
494
  provider: 'view-provider',
329
495
  'ontology-definition': 'ontology-definition',
496
+ 'query-definition': 'domain-query-definition',
330
497
  view: 'view',
331
498
  projection: 'projection',
332
499
  action: 'domain-command',
@@ -387,12 +554,20 @@ exports.ReleaseEvidenceSchema = zod_1.z
387
554
  ];
388
555
  expectedKinds.forEach(([field, reference, expectedKind]) => {
389
556
  if (reference.kind !== expectedKind) {
390
- context.addIssue({ code: 'custom', path: [field, 'kind'], message: `${field} must reference ${expectedKind}.` });
557
+ context.addIssue({
558
+ code: 'custom',
559
+ path: [field, 'kind'],
560
+ message: `${field} must reference ${expectedKind}.`,
561
+ });
391
562
  }
392
563
  });
393
564
  value.evidenceRefs.forEach((reference, index) => {
394
565
  if (reference.kind !== 'evidence')
395
- context.addIssue({ code: 'custom', path: ['evidenceRefs', index, 'kind'], message: 'Release evidenceRefs must reference evidence revisions.' });
566
+ context.addIssue({
567
+ code: 'custom',
568
+ path: ['evidenceRefs', index, 'kind'],
569
+ message: 'Release evidenceRefs must reference evidence revisions.',
570
+ });
396
571
  });
397
572
  });
398
573
  exports.PublicationPlanOperationSchema = zod_1.z
@@ -405,11 +580,20 @@ exports.PublicationPlanOperationSchema = zod_1.z
405
580
  .strict()
406
581
  .superRefine((value, context) => {
407
582
  if (value.releaseRevisionRef.kind !== value.releaseKind || value.releaseRevisionRef.id !== value.releaseSlotId) {
408
- context.addIssue({ code: 'custom', path: ['releaseRevisionRef'], message: 'Publication Plan operation must pin the exact release slot revision.' });
583
+ context.addIssue({
584
+ code: 'custom',
585
+ path: ['releaseRevisionRef'],
586
+ message: 'Publication Plan operation must pin the exact release slot revision.',
587
+ });
409
588
  }
410
- if (value.expectedHeadRevisionRef
411
- && (value.expectedHeadRevisionRef.kind !== value.releaseKind || value.expectedHeadRevisionRef.id !== value.releaseSlotId)) {
412
- context.addIssue({ code: 'custom', path: ['expectedHeadRevisionRef'], message: 'Expected head must identify the same release slot.' });
589
+ if (value.expectedHeadRevisionRef &&
590
+ (value.expectedHeadRevisionRef.kind !== value.releaseKind ||
591
+ value.expectedHeadRevisionRef.id !== value.releaseSlotId)) {
592
+ context.addIssue({
593
+ code: 'custom',
594
+ path: ['expectedHeadRevisionRef'],
595
+ message: 'Expected head must identify the same release slot.',
596
+ });
413
597
  }
414
598
  });
415
599
  exports.PublicationPlanSchema = zod_1.z
@@ -431,7 +615,12 @@ exports.PublicationPlanSchema = zod_1.z
431
615
  .strict()
432
616
  .superRefine((value, context) => {
433
617
  if (value.environmentRef.kind !== 'environment')
434
- context.addIssue({ code: 'custom', path: ['environmentRef', 'kind'], message: 'Publication Plan requires an environment reference.' });
618
+ context.addIssue({
619
+ code: 'custom',
620
+ path: ['environmentRef', 'kind'],
621
+ message: 'Publication Plan requires an environment reference.',
622
+ });
623
+ validateTenantCompatibleReference(value.environmentRef, value.tenantScope, ['environmentRef'], context);
435
624
  const governedRefs = [
436
625
  ['approvalPolicyRevisionRef', value.approvalPolicyRevisionRef, 'approval-policy'],
437
626
  ['approvalDecisionRevisionRef', value.approvalDecisionRevisionRef, 'approval-decision'],
@@ -439,22 +628,40 @@ exports.PublicationPlanSchema = zod_1.z
439
628
  ];
440
629
  governedRefs.forEach(([field, reference, expectedKind]) => {
441
630
  if (reference.kind !== expectedKind)
442
- context.addIssue({ code: 'custom', path: [field, 'kind'], message: `${field} must reference ${expectedKind}.` });
631
+ context.addIssue({
632
+ code: 'custom',
633
+ path: [field, 'kind'],
634
+ message: `${field} must reference ${expectedKind}.`,
635
+ });
636
+ validateTenantCompatibleReference(reference, value.tenantScope, [field], context);
443
637
  });
444
638
  value.operations.forEach((operation, index) => {
445
639
  [operation.releaseRevisionRef, operation.expectedHeadRevisionRef].forEach((reference, referenceIndex) => {
446
640
  if (reference?.tenantScope && tenantScopeKey(reference.tenantScope) !== tenantScopeKey(value.tenantScope)) {
447
641
  const field = referenceIndex === 0 ? 'releaseRevisionRef' : 'expectedHeadRevisionRef';
448
- context.addIssue({ code: 'custom', path: ['operations', index, field, 'tenantScope'], message: 'Publication Plan operation belongs to another tenant scope.' });
642
+ context.addIssue({
643
+ code: 'custom',
644
+ path: ['operations', index, field, 'tenantScope'],
645
+ message: 'Publication Plan operation belongs to another tenant scope.',
646
+ });
449
647
  }
450
648
  });
451
649
  });
452
650
  value.evidenceRefs.forEach((reference, index) => {
453
651
  if (reference.kind !== 'evidence')
454
- context.addIssue({ code: 'custom', path: ['evidenceRefs', index, 'kind'], message: 'Publication Plan evidenceRefs must reference evidence revisions.' });
652
+ context.addIssue({
653
+ code: 'custom',
654
+ path: ['evidenceRefs', index, 'kind'],
655
+ message: 'Publication Plan evidenceRefs must reference evidence revisions.',
656
+ });
657
+ validateTenantCompatibleReference(reference, value.tenantScope, ['evidenceRefs', index], context);
455
658
  });
456
659
  if (uniqueArray(value.operations, (operation) => `${operation.releaseKind}:${operation.releaseSlotId}`)) {
457
- context.addIssue({ code: 'custom', path: ['operations'], message: 'Publication Plan release slots must be unique.' });
660
+ context.addIssue({
661
+ code: 'custom',
662
+ path: ['operations'],
663
+ message: 'Publication Plan release slots must be unique.',
664
+ });
458
665
  }
459
666
  });
460
667
  const IdentitySchema = zod_1.z
@@ -464,105 +671,1266 @@ const IdentitySchema = zod_1.z
464
671
  tags: zod_1.z.array(common_1.ContractIdentifierSchema).default([]),
465
672
  })
466
673
  .strict();
467
- exports.WorkbenchIdentitySchema = IdentitySchema.extend({ iconRef: exports.StableRefSchema.optional() }).strict();
674
+ exports.WorkbenchIdentitySchema = IdentitySchema.extend({
675
+ iconRef: exports.StableRefSchema.optional(),
676
+ }).strict();
677
+ exports.LegacySlotRestorationSchema = zod_1.z
678
+ .object({
679
+ legacyAdapterRevisionRef: exports.RevisionRefSchema,
680
+ sourceRevisionRef: exports.RevisionRefSchema,
681
+ inspectedSourceContentHash: common_1.Sha256Schema,
682
+ })
683
+ .strict()
684
+ .superRefine((value, context) => {
685
+ if (value.sourceRevisionRef.contentHash !== value.inspectedSourceContentHash) {
686
+ context.addIssue({
687
+ code: 'custom',
688
+ path: ['inspectedSourceContentHash'],
689
+ message: 'Legacy restoration must pin the exact inspected source revision hash.',
690
+ });
691
+ }
692
+ });
693
+ /**
694
+ * Exact, inspect-produced authority for taking over one predeclared legacy
695
+ * route. It is not a general reserved-route bypass: compiler validation binds
696
+ * it to one canonical claim, one target resource and the exact inspected
697
+ * source/adaptor revisions.
698
+ */
699
+ exports.LegacyRouteTakeoverAuthorizationSchema = zod_1.z
700
+ .object({
701
+ contract: zod_1.z.literal('LegacyRouteTakeoverAuthorization'),
702
+ schemaVersion: zod_1.z.literal(exports.DECLARATIVE_CONTROL_SCHEMA_VERSION),
703
+ routeSpaceRevisionRef: exports.RevisionRefSchema,
704
+ normalizedPath: exports.RoutePathTemplateSchema,
705
+ legacyAdapterRevisionRef: exports.RevisionRefSchema,
706
+ sourceRevisionRef: exports.RevisionRefSchema,
707
+ inspectedSourceContentHash: common_1.Sha256Schema,
708
+ targetResourceRef: exports.StableRefSchema,
709
+ workbenchCatalogDefaults: zod_1.z
710
+ .object({ order: zod_1.z.number().int(), isDefaultCandidate: zod_1.z.boolean() })
711
+ .strict()
712
+ .optional(),
713
+ contentHash: common_1.Sha256Schema,
714
+ })
715
+ .strict()
716
+ .superRefine((value, context) => {
717
+ if (value.routeSpaceRevisionRef.kind !== 'route-space') {
718
+ context.addIssue({
719
+ code: 'custom',
720
+ path: ['routeSpaceRevisionRef', 'kind'],
721
+ message: 'A route takeover must pin a RouteSpace revision.',
722
+ });
723
+ }
724
+ if (value.legacyAdapterRevisionRef.kind !== 'legacy-route-adapter') {
725
+ context.addIssue({
726
+ code: 'custom',
727
+ path: ['legacyAdapterRevisionRef', 'kind'],
728
+ message: 'A route takeover must pin a legacy-route-adapter revision.',
729
+ });
730
+ }
731
+ if (!['page', 'workbench'].includes(value.targetResourceRef.kind)) {
732
+ context.addIssue({
733
+ code: 'custom',
734
+ path: ['targetResourceRef', 'kind'],
735
+ message: 'A route takeover target must be a Page or Workbench.',
736
+ });
737
+ }
738
+ if (value.sourceRevisionRef.contentHash !== value.inspectedSourceContentHash) {
739
+ context.addIssue({
740
+ code: 'custom',
741
+ path: ['inspectedSourceContentHash'],
742
+ message: 'A route takeover must pin the exact inspected legacy source hash.',
743
+ });
744
+ }
745
+ if (value.targetResourceRef.kind === 'workbench' && !value.workbenchCatalogDefaults) {
746
+ context.addIssue({
747
+ code: 'custom',
748
+ path: ['workbenchCatalogDefaults'],
749
+ message: 'A Workbench takeover must preserve exact legacy catalog order and default semantics.',
750
+ });
751
+ }
752
+ if (value.targetResourceRef.kind === 'page' && value.workbenchCatalogDefaults) {
753
+ context.addIssue({
754
+ code: 'custom',
755
+ path: ['workbenchCatalogDefaults'],
756
+ message: 'Page takeover provenance cannot carry Workbench catalog defaults.',
757
+ });
758
+ }
759
+ });
760
+ /** Authoring-only route evidence. Runtime compilation deliberately strips the takeover proof. */
761
+ exports.DeclarativeRouteClaimSchema = exports.RouteClaimSchema.safeExtend({
762
+ legacyRouteTakeoverAuthorization: exports.LegacyRouteTakeoverAuthorizationSchema.optional(),
763
+ }).strict();
764
+ const LegacyDeclarativeRouteClaimSchema = exports.LegacyRouteClaimSchema.safeExtend({
765
+ legacyRouteTakeoverAuthorization: exports.LegacyRouteTakeoverAuthorizationSchema.optional(),
766
+ }).strict();
767
+ const validateDeclaredRouteSurfaces = (supportedSurfaces, routeClaims, context) => {
768
+ if (uniqueArray(supportedSurfaces, (surface) => surface)) {
769
+ context.addIssue({
770
+ code: 'custom',
771
+ path: ['supportedSurfaces'],
772
+ message: 'Supported surfaces must be unique.',
773
+ });
774
+ }
775
+ const explicitClaims = routeClaims.filter((claim) => 'surface' in claim);
776
+ if (explicitClaims.length > 0 && explicitClaims.length < routeClaims.length) {
777
+ context.addIssue({
778
+ code: 'custom',
779
+ path: ['routeClaims'],
780
+ message: 'Route claims must either all declare surface or all omit it for legacy compatibility.',
781
+ });
782
+ return;
783
+ }
784
+ routeClaims.forEach((claim, index) => {
785
+ if ('surface' in claim && !supportedSurfaces.includes(claim.surface)) {
786
+ context.addIssue({
787
+ code: 'custom',
788
+ path: ['routeClaims', index, 'surface'],
789
+ message: 'Route claim surface must be declared by supportedSurfaces.',
790
+ });
791
+ }
792
+ });
793
+ if (explicitClaims.length !== routeClaims.length)
794
+ return;
795
+ supportedSurfaces.forEach((surface) => {
796
+ const canonicalCount = explicitClaims.filter((claim) => claim.surface === surface && claim.kind === 'canonical').length;
797
+ if (canonicalCount !== 1) {
798
+ context.addIssue({
799
+ code: 'custom',
800
+ path: ['routeClaims'],
801
+ message: `Surface ${surface} requires exactly one canonical route claim.`,
802
+ });
803
+ }
804
+ });
805
+ };
468
806
  const ReleaseBaseShape = {
469
807
  schemaVersion: zod_1.z.literal(exports.DECLARATIVE_CONTROL_SCHEMA_VERSION),
470
808
  releaseSlotId: common_1.ContractIdentifierSchema,
471
809
  tenantScope: exports.TenantScopeSchema,
472
810
  operation: exports.ReleaseOperationSchema,
811
+ legacyRestoration: exports.LegacySlotRestorationSchema.optional(),
473
812
  dependencySnapshot: zod_1.z.array(exports.ReleaseDependencySchema),
474
813
  evidence: exports.ReleaseEvidenceSchema,
475
814
  };
815
+ const validateLegacyRestoration = (value, expectedAdapterKind, context) => {
816
+ if (value.operation === 'activate' && value.legacyRestoration) {
817
+ context.addIssue({
818
+ code: 'custom',
819
+ path: ['legacyRestoration'],
820
+ message: 'An active declarative Release cannot also restore a legacy adapter.',
821
+ });
822
+ }
823
+ if (value.legacyRestoration?.legacyAdapterRevisionRef.kind !== undefined &&
824
+ value.legacyRestoration.legacyAdapterRevisionRef.kind !== expectedAdapterKind) {
825
+ context.addIssue({
826
+ code: 'custom',
827
+ path: ['legacyRestoration', 'legacyAdapterRevisionRef', 'kind'],
828
+ message: `Legacy restoration requires adapter kind ${expectedAdapterKind}.`,
829
+ });
830
+ }
831
+ };
476
832
  const validateDependencySnapshot = (snapshot, context) => {
477
833
  const duplicate = uniqueArray(snapshot, (dependency) => `${dependency.role}:${stableRefIdentity(dependency.revisionRef)}@${dependency.revisionRef.revision}`);
478
834
  if (duplicate)
479
- context.addIssue({ code: 'custom', path: ['dependencySnapshot'], message: `Duplicate dependency: ${duplicate}` });
835
+ context.addIssue({
836
+ code: 'custom',
837
+ path: ['dependencySnapshot'],
838
+ message: `Duplicate dependency: ${duplicate}`,
839
+ });
480
840
  };
481
- const validateReleaseExpectedHead = (value, expectedKind, context) => {
841
+ const validateReleaseGovernanceScope = (value, expectedKind, context) => {
482
842
  const expectedHead = value.evidence.expectedHeadRevisionRef;
483
843
  if (expectedHead && (expectedHead.kind !== expectedKind || expectedHead.id !== value.releaseSlotId)) {
484
- context.addIssue({ code: 'custom', path: ['evidence', 'expectedHeadRevisionRef'], message: 'Expected head must identify the same release slot.' });
844
+ context.addIssue({
845
+ code: 'custom',
846
+ path: ['evidence', 'expectedHeadRevisionRef'],
847
+ message: 'Expected head must identify the same release slot.',
848
+ });
485
849
  }
486
850
  if (expectedHead?.tenantScope && tenantScopeKey(expectedHead.tenantScope) !== tenantScopeKey(value.tenantScope)) {
487
- context.addIssue({ code: 'custom', path: ['evidence', 'expectedHeadRevisionRef', 'tenantScope'], message: 'Expected head belongs to another tenant scope.' });
851
+ context.addIssue({
852
+ code: 'custom',
853
+ path: ['evidence', 'expectedHeadRevisionRef', 'tenantScope'],
854
+ message: 'Expected head belongs to another tenant scope.',
855
+ });
488
856
  }
857
+ const governedEvidenceRefs = [
858
+ [['evidence', 'publicationPlanRef'], value.evidence.publicationPlanRef],
859
+ [['evidence', 'approvalPolicyRevisionRef'], value.evidence.approvalPolicyRevisionRef],
860
+ [['evidence', 'approvalDecisionRevisionRef'], value.evidence.approvalDecisionRevisionRef],
861
+ [['evidence', 'actorRef'], value.evidence.actorRef],
862
+ ...value.evidence.validation.diagnosticRefs.map((reference, index) => [['evidence', 'validation', 'diagnosticRefs', index], reference]),
863
+ ...value.evidence.evidenceRefs.map((reference, index) => [['evidence', 'evidenceRefs', index], reference]),
864
+ ...(expectedHead ? [[['evidence', 'expectedHeadRevisionRef'], expectedHead]] : []),
865
+ ...(value.legacyRestoration
866
+ ? [
867
+ [
868
+ ['legacyRestoration', 'legacyAdapterRevisionRef'],
869
+ value.legacyRestoration.legacyAdapterRevisionRef,
870
+ ],
871
+ [['legacyRestoration', 'sourceRevisionRef'], value.legacyRestoration.sourceRevisionRef],
872
+ ]
873
+ : []),
874
+ ];
875
+ governedEvidenceRefs.forEach(([path, reference]) => validateTenantCompatibleReference(reference, value.tenantScope, [...path], context));
489
876
  };
490
877
  exports.BindingSourceSchema = zod_1.z.discriminatedUnion('kind', [
491
- zod_1.z.object({ kind: zod_1.z.literal('constant'), value: zod_1.z.union([zod_1.z.string(), zod_1.z.number().finite(), zod_1.z.boolean(), zod_1.z.null()]) }).strict(),
492
- zod_1.z.object({ kind: zod_1.z.literal('route-parameter'), name: common_1.ContractIdentifierSchema }).strict(),
493
- zod_1.z.object({ kind: zod_1.z.literal('query-parameter'), name: common_1.ContractIdentifierSchema }).strict(),
494
- zod_1.z.object({ kind: zod_1.z.literal('identity'), name: zod_1.z.enum(['tenantId', 'teamId', 'userId']) }).strict(),
495
- zod_1.z.object({ kind: zod_1.z.literal('intent-field'), path: zod_1.z.string().trim().regex(/^[A-Za-z0-9_.-]+$/) }).strict(),
496
- zod_1.z.object({ kind: zod_1.z.literal('binding-field'), bindingId: common_1.ContractIdentifierSchema, path: zod_1.z.string().trim().regex(/^[A-Za-z0-9_.-]+$/) }).strict(),
878
+ zod_1.z.object({ kind: zod_1.z.literal('scope-field'), path: zod_1.z.string().trim().min(1).max(4096).regex(/^[A-Za-z0-9_.-]+$/).refine((path) => path.split('.').length <= 16 && path.split('.').every((part) => part.length > 0 && !['__proto__', 'prototype', 'constructor'].includes(part)), 'Scope paths require safe own members.') }).strict(),
879
+ zod_1.z
880
+ .object({
881
+ kind: zod_1.z.literal('binding-field'),
882
+ bindingId: common_1.ContractIdentifierSchema,
883
+ path: zod_1.z
884
+ .string()
885
+ .trim()
886
+ .regex(/^[A-Za-z0-9_.-]+$/),
887
+ })
888
+ .strict(),
889
+ zod_1.z
890
+ .object({
891
+ kind: zod_1.z.literal('constant'),
892
+ value: zod_1.z.union([zod_1.z.string(), zod_1.z.number().finite(), zod_1.z.boolean(), zod_1.z.null()]),
893
+ })
894
+ .strict(),
895
+ zod_1.z
896
+ .object({
897
+ kind: zod_1.z.literal('route-parameter'),
898
+ name: common_1.ContractIdentifierSchema,
899
+ })
900
+ .strict(),
901
+ zod_1.z
902
+ .object({
903
+ kind: zod_1.z.literal('query-parameter'),
904
+ name: common_1.ContractIdentifierSchema,
905
+ })
906
+ .strict(),
907
+ zod_1.z
908
+ .object({
909
+ kind: zod_1.z.literal('identity'),
910
+ name: zod_1.z.enum(['tenantId', 'teamId', 'userId']),
911
+ })
912
+ .strict(),
913
+ zod_1.z
914
+ .object({
915
+ kind: zod_1.z.literal('intent-field'),
916
+ path: zod_1.z
917
+ .string()
918
+ .trim()
919
+ .regex(/^[A-Za-z0-9_.-]+$/),
920
+ })
921
+ .strict(),
922
+ zod_1.z
923
+ .object({
924
+ kind: zod_1.z.literal('page-state'),
925
+ stateId: common_1.ContractIdentifierSchema,
926
+ })
927
+ .strict(),
497
928
  ]);
498
- exports.CapabilityInstanceSchema = zod_1.z
929
+ const ExpressionBindingSourceSchema = zod_1.z.object({ kind: zod_1.z.literal('expression'), expression: page_expression_1.PageExpressionSchema }).strict().superRefine((value, context) => {
930
+ if ((0, page_expression_1.collectPageExpressionReads)(value.expression).some(read => read.root === 'actions'))
931
+ context.addIssue({ code: 'custom', path: ['expression'], message: 'Action runtime state is presentation-only and cannot influence an input or state transition.' });
932
+ });
933
+ const FileFieldBindingSourceSchema = zod_1.z.object({ kind: zod_1.z.literal('file-field'), fileInputId: common_1.ContractIdentifierSchema, path: zod_1.z.enum(['fileName', 'content', 'canonicalUri']) }).strict();
934
+ const ActionFileInputSchema = zod_1.z.object({
935
+ inputId: common_1.ContractIdentifierSchema,
936
+ source: zod_1.z.discriminatedUnion('kind', [
937
+ zod_1.z.object({ kind: zod_1.z.literal('page-state'), stateId: common_1.ContractIdentifierSchema }).strict(),
938
+ zod_1.z.object({ kind: zod_1.z.literal('intent-field'), path: zod_1.z.string().trim().regex(/^[A-Za-z0-9_.-]+$/) }).strict(),
939
+ ]),
940
+ maxBytes: zod_1.z.number().int().min(1).max(5368709120),
941
+ upload: zod_1.z.object({ purpose: file_1.FilePurposeSchema }).strict().optional(),
942
+ accept: zod_1.z.string().min(1).max(256).optional(),
943
+ }).strict().superRefine((value, context) => {
944
+ if (!value.upload && value.maxBytes > 1048576)
945
+ context.addIssue({ code: 'custom', path: ['maxBytes'], message: 'Inline text files are limited to one MiB.' });
946
+ });
947
+ const ActionInputBindingSourceSchema = zod_1.z.discriminatedUnion('kind', [...exports.BindingSourceSchema.options, ExpressionBindingSourceSchema, FileFieldBindingSourceSchema]);
948
+ const InteractionBindingSourceSchema = zod_1.z.discriminatedUnion('kind', [
949
+ ExpressionBindingSourceSchema,
950
+ zod_1.z
951
+ .object({
952
+ kind: zod_1.z.literal('constant'),
953
+ value: zod_1.z.union([zod_1.z.string(), zod_1.z.number().finite(), zod_1.z.boolean(), zod_1.z.null()]),
954
+ })
955
+ .strict(),
956
+ zod_1.z
957
+ .object({
958
+ kind: zod_1.z.literal('intent-field'),
959
+ path: zod_1.z
960
+ .string()
961
+ .trim()
962
+ .regex(/^[A-Za-z0-9_.-]+$/),
963
+ })
964
+ .strict(),
965
+ zod_1.z
966
+ .object({
967
+ kind: zod_1.z.literal('result-field'),
968
+ path: zod_1.z
969
+ .string()
970
+ .trim()
971
+ .regex(/^[A-Za-z0-9_.-]+$/),
972
+ })
973
+ .strict(),
974
+ zod_1.z
975
+ .object({
976
+ kind: zod_1.z.literal('page-state'),
977
+ stateId: common_1.ContractIdentifierSchema,
978
+ })
979
+ .strict(),
980
+ ]);
981
+ const PageStateComparableLiteralSchema = zod_1.z.union([zod_1.z.string(), zod_1.z.number().finite(), zod_1.z.boolean(), zod_1.z.null()]);
982
+ exports.PageStateConditionSchema = zod_1.z.discriminatedUnion('predicate', [
983
+ zod_1.z
984
+ .object({
985
+ stateId: common_1.ContractIdentifierSchema,
986
+ predicate: zod_1.z.literal('equals'),
987
+ value: PageStateComparableLiteralSchema,
988
+ })
989
+ .strict(),
990
+ zod_1.z
991
+ .object({
992
+ stateId: common_1.ContractIdentifierSchema,
993
+ predicate: zod_1.z.literal('not-equals'),
994
+ value: PageStateComparableLiteralSchema,
995
+ })
996
+ .strict(),
997
+ zod_1.z
998
+ .object({
999
+ stateId: common_1.ContractIdentifierSchema,
1000
+ predicate: zod_1.z.literal('truthy'),
1001
+ })
1002
+ .strict(),
1003
+ ]);
1004
+ /**
1005
+ * A recursive boolean expression over governed Page state. Keeping leaf
1006
+ * conditions inside the original `conditions` array preserves the v1 flat
1007
+ * declaration while allowing groups to be nested without a parallel DSL.
1008
+ */
1009
+ exports.PageStateActivationSchema = zod_1.z.lazy(() => zod_1.z
499
1010
  .object({
500
- instanceId: common_1.ContractIdentifierSchema,
501
- nodeId: common_1.ContractIdentifierSchema,
502
- capabilityRevisionRef: exports.RevisionRefSchema,
503
- providerRevisionRef: exports.RevisionRefSchema,
504
- propertySchemaRevisionRef: exports.RevisionRefSchema,
505
- properties: common_1.JsonObjectSchema,
506
- allowedSideEffects: zod_1.z.array(zod_1.z.enum(['network', 'storage', 'navigation', 'worker', 'websocket'])),
1011
+ operator: zod_1.z.enum(['all', 'any']),
1012
+ conditions: zod_1.z.array(zod_1.z.union([exports.PageStateConditionSchema, exports.PageStateActivationSchema])).min(1),
507
1013
  })
508
- .strict();
509
- exports.OntologyBindingSchema = zod_1.z
1014
+ .strict());
1015
+ const forEachPageStateCondition = (activation, visit, path = []) => {
1016
+ activation.conditions.forEach((candidate, index) => {
1017
+ const candidatePath = [...path, 'conditions', index];
1018
+ if ('operator' in candidate) {
1019
+ forEachPageStateCondition(candidate, visit, candidatePath);
1020
+ return;
1021
+ }
1022
+ visit(candidate, candidatePath);
1023
+ });
1024
+ };
1025
+ const PageRouteValueSourceSchema = zod_1.z.discriminatedUnion('kind', [
1026
+ zod_1.z.object({ kind: zod_1.z.literal('route-parameter'), name: common_1.ContractIdentifierSchema }).strict(),
1027
+ zod_1.z
1028
+ .object({
1029
+ kind: zod_1.z.literal('constant'),
1030
+ value: zod_1.z.union([zod_1.z.string(), zod_1.z.number().finite(), zod_1.z.boolean()]),
1031
+ })
1032
+ .strict(),
1033
+ zod_1.z
1034
+ .object({
1035
+ kind: zod_1.z.literal('page-state'),
1036
+ stateId: common_1.ContractIdentifierSchema,
1037
+ })
1038
+ .strict(),
1039
+ ]);
1040
+ /**
1041
+ * Declares an ordered, state-driven route transition owned by one exact Page
1042
+ * revision. The compiler resolves targetRef to one active governed Release;
1043
+ * raw URLs and application-specific route switches are deliberately excluded.
1044
+ */
1045
+ exports.PageEntryTransitionSchema = zod_1.z
510
1046
  .object({
511
- bindingId: common_1.ContractIdentifierSchema,
512
- ontologyDefinitionRevisionRef: exports.RevisionRefSchema,
513
- viewRevisionRef: exports.RevisionRefSchema.optional(),
514
- projectionRevisionRef: exports.RevisionRefSchema.optional(),
515
- parameters: zod_1.z.record(common_1.ContractIdentifierSchema, exports.BindingSourceSchema),
516
- target: zod_1.z.object({ capabilityInstanceId: common_1.ContractIdentifierSchema, port: common_1.ContractIdentifierSchema }).strict(),
517
- renderModelSchemaRevisionRef: exports.RevisionRefSchema,
518
- pagination: zod_1.z.enum(['none', 'cursor', 'offset']),
519
- cache: zod_1.z.enum(['none', 'identity-scoped', 'tenant-scoped']),
520
- cancelOnChange: zod_1.z.boolean(),
1047
+ browsingContext: zod_1.z.enum(['same', 'new']).optional(),
1048
+ gestureSource: zod_1.z
1049
+ .object({ capabilityInstanceId: common_1.ContractIdentifierSchema, port: common_1.ContractIdentifierSchema })
1050
+ .strict()
1051
+ .optional(),
1052
+ transitionId: common_1.ContractIdentifierSchema,
1053
+ activation: exports.PageStateActivationSchema,
1054
+ targetRef: exports.StableRefSchema,
1055
+ pathParameters: zod_1.z.record(common_1.ContractIdentifierSchema, PageRouteValueSourceSchema),
1056
+ query: zod_1.z
1057
+ .object({
1058
+ preserve: zod_1.z.boolean(),
1059
+ remove: zod_1.z.array(common_1.ContractIdentifierSchema),
1060
+ set: zod_1.z.record(common_1.ContractIdentifierSchema, PageRouteValueSourceSchema),
1061
+ })
1062
+ .strict(),
521
1063
  })
522
1064
  .strict()
523
1065
  .superRefine((value, context) => {
524
- if ((value.viewRevisionRef ? 1 : 0) + (value.projectionRevisionRef ? 1 : 0) !== 1) {
525
- context.addIssue({ code: 'custom', path: ['viewRevisionRef'], message: 'Ontology bindings require exactly one View or Projection revision.' });
1066
+ if ((value.browsingContext === 'new') !== Boolean(value.gestureSource)) {
1067
+ context.addIssue({
1068
+ code: 'custom',
1069
+ path: ['gestureSource'],
1070
+ message: 'New-context entry transitions require an exact gesture source; same-context transitions must omit it.',
1071
+ });
526
1072
  }
527
- if (value.ontologyDefinitionRevisionRef.kind !== 'ontology-definition') {
528
- context.addIssue({ code: 'custom', path: ['ontologyDefinitionRevisionRef', 'kind'], message: 'Expected an ontology-definition revision.' });
1073
+ if (!['page', 'workbench'].includes(value.targetRef.kind)) {
1074
+ context.addIssue({
1075
+ code: 'custom',
1076
+ path: ['targetRef', 'kind'],
1077
+ message: 'Page entry transitions must reference a governed Page or Workbench.',
1078
+ });
1079
+ }
1080
+ if (new Set(value.query.remove).size !== value.query.remove.length) {
1081
+ context.addIssue({
1082
+ code: 'custom',
1083
+ path: ['query', 'remove'],
1084
+ message: 'Removed query parameter names must be unique.',
1085
+ });
1086
+ }
1087
+ });
1088
+ exports.ResolvedPageEntryTransitionSchema = exports.PageEntryTransitionSchema.safeExtend({
1089
+ targetRevisionRef: exports.RevisionRefSchema,
1090
+ targetReleaseRevisionRef: exports.RevisionRefSchema.optional(),
1091
+ targetAccessPolicy: exports.AccessPolicySchema,
1092
+ targetRouteClaim: exports.CompiledRouteClaimSchema,
1093
+ }).strict();
1094
+ exports.LegacyBrowserPreferenceImportSchema = zod_1.z
1095
+ .object({
1096
+ storageKey: zod_1.z.string().trim().min(1).max(256),
1097
+ jsonPath: zod_1.z
1098
+ .array(zod_1.z.union([zod_1.z.string().trim().min(1).max(128), zod_1.z.object({ stateId: common_1.ContractIdentifierSchema }).strict()]))
1099
+ .min(1)
1100
+ .max(16)
1101
+ .optional(),
1102
+ valueType: zod_1.z.enum(['string', 'number', 'boolean-01', 'json']),
1103
+ })
1104
+ .strict();
1105
+ exports.PageStateDefinitionSchema = zod_1.z
1106
+ .object({
1107
+ stateId: common_1.ContractIdentifierSchema,
1108
+ scopeNodeId: common_1.ContractIdentifierSchema.optional(),
1109
+ initialValue: page_expression_1.PageExpressionSchema.optional(),
1110
+ schemaRevisionRef: exports.RevisionRefSchema,
1111
+ defaultValue: common_1.JsonValueSchema.optional(),
1112
+ persistence: zod_1.z.enum(['none', 'session', 'url', 'tenant-preference', 'url-with-tenant-preference']),
1113
+ urlWriteback: zod_1.z.enum(['synchronize', 'never']).optional(),
1114
+ preferencePartitionStateId: common_1.ContractIdentifierSchema.optional(),
1115
+ legacyBrowserPreferenceImport: exports.LegacyBrowserPreferenceImportSchema.optional(),
1116
+ })
1117
+ .strict()
1118
+ .superRefine((value, context) => {
1119
+ if (value.scopeNodeId && value.persistence !== 'none')
1120
+ context.addIssue({ code: 'custom', path: ['persistence'], message: 'Repeated instance state must not persist outside its instance.' });
1121
+ if (value.initialValue && !value.scopeNodeId && (value.persistence !== 'none' || value.initialValue.kind !== 'read' || value.initialValue.root !== 'state' || value.initialValue.path.length !== 1))
1122
+ context.addIssue({ code: 'custom', path: ['initialValue'], message: 'Root initialization must directly read one persisted Page state into non-persistent state.' });
1123
+ if (value.schemaRevisionRef.kind !== 'schema') {
1124
+ context.addIssue({
1125
+ code: 'custom',
1126
+ path: ['schemaRevisionRef', 'kind'],
1127
+ message: 'Page state must pin an exact schema revision.',
1128
+ });
1129
+ }
1130
+ if (value.preferencePartitionStateId !== undefined &&
1131
+ value.persistence !== 'tenant-preference' &&
1132
+ value.persistence !== 'url-with-tenant-preference') {
1133
+ context.addIssue({
1134
+ code: 'custom',
1135
+ path: ['preferencePartitionStateId'],
1136
+ message: 'Preference partitioning requires tenant preference state.',
1137
+ });
1138
+ }
1139
+ if (value.urlWriteback !== undefined &&
1140
+ value.persistence !== 'url' &&
1141
+ value.persistence !== 'url-with-tenant-preference') {
1142
+ context.addIssue({
1143
+ code: 'custom',
1144
+ path: ['urlWriteback'],
1145
+ message: 'URL writeback is only valid for URL-backed Page state.',
1146
+ });
1147
+ }
1148
+ });
1149
+ exports.InteractionBindingSchema = zod_1.z
1150
+ .object({
1151
+ bindingId: common_1.ContractIdentifierSchema,
1152
+ source: zod_1.z
1153
+ .object({
1154
+ capabilityInstanceId: common_1.ContractIdentifierSchema,
1155
+ port: common_1.ContractIdentifierSchema,
1156
+ })
1157
+ .strict(),
1158
+ sourceIntentSchemaRevisionRef: exports.RevisionRefSchema,
1159
+ sourceResultSchemaRevisionRef: exports.RevisionRefSchema.optional(),
1160
+ targetStateId: common_1.ContractIdentifierSchema,
1161
+ transition: zod_1.z.enum(['set', 'merge', 'toggle', 'append', 'remove', 'reset']),
1162
+ inputMapping: zod_1.z.record(common_1.ContractIdentifierSchema, InteractionBindingSourceSchema),
1163
+ })
1164
+ .strict()
1165
+ .superRefine((value, context) => {
1166
+ if (value.sourceIntentSchemaRevisionRef.kind !== 'schema') {
1167
+ context.addIssue({
1168
+ code: 'custom',
1169
+ path: ['sourceIntentSchemaRevisionRef', 'kind'],
1170
+ message: 'Interaction source intents must pin a schema revision.',
1171
+ });
1172
+ }
1173
+ const mapsResult = Object.values(value.inputMapping).some((source) => source.kind === 'result-field' || (source.kind === 'expression' && (0, page_expression_1.collectPageExpressionReads)(source.expression).some((read) => read.root === 'result')));
1174
+ if (mapsResult && !value.sourceResultSchemaRevisionRef) {
1175
+ context.addIssue({
1176
+ code: 'custom',
1177
+ path: ['sourceResultSchemaRevisionRef'],
1178
+ message: 'Result-field interactions must pin the exact Action result schema revision.',
1179
+ });
1180
+ }
1181
+ if (value.sourceResultSchemaRevisionRef?.kind !== undefined &&
1182
+ value.sourceResultSchemaRevisionRef.kind !== 'schema') {
1183
+ context.addIssue({
1184
+ code: 'custom',
1185
+ path: ['sourceResultSchemaRevisionRef', 'kind'],
1186
+ message: 'Interaction source results must pin a schema revision.',
1187
+ });
1188
+ }
1189
+ if (!mapsResult && value.sourceResultSchemaRevisionRef) {
1190
+ context.addIssue({
1191
+ code: 'custom',
1192
+ path: ['sourceResultSchemaRevisionRef'],
1193
+ message: 'Interaction result schemas are only valid when inputMapping declares a result-field source.',
1194
+ });
1195
+ }
1196
+ const mappedInputs = Object.keys(value.inputMapping);
1197
+ if ((value.transition === 'toggle' || value.transition === 'reset') && mappedInputs.length > 0) {
1198
+ context.addIssue({
1199
+ code: 'custom',
1200
+ path: ['inputMapping'],
1201
+ message: `${value.transition} transitions cannot declare mapped inputs.`,
1202
+ });
1203
+ }
1204
+ if (value.transition !== 'toggle' && value.transition !== 'reset' && mappedInputs.length === 0) {
1205
+ context.addIssue({
1206
+ code: 'custom',
1207
+ path: ['inputMapping'],
1208
+ message: `${value.transition} transitions require at least one mapped input.`,
1209
+ });
1210
+ }
1211
+ });
1212
+ exports.GovernedCatalogResourceKindSchema = zod_1.z.enum(['tag', 'tag-group', 'feature-column']);
1213
+ exports.GovernedCatalogDomainQueryDataSourceSchema = zod_1.z
1214
+ .object({
1215
+ kind: zod_1.z.literal('governed-catalog'),
1216
+ resourceKind: exports.GovernedCatalogResourceKindSchema,
1217
+ })
1218
+ .strict();
1219
+ exports.RegisteredServiceDomainQueryDataSourceSchema = zod_1.z
1220
+ .object({
1221
+ kind: zod_1.z.literal('registered-service'),
1222
+ })
1223
+ .strict();
1224
+ exports.DomainQueryDataSourceSchema = zod_1.z.discriminatedUnion('kind', [
1225
+ zod_1.z.object({ kind: zod_1.z.literal('exact-view') }).strict(),
1226
+ zod_1.z
1227
+ .object({
1228
+ kind: zod_1.z.literal('tenant-catalog'),
1229
+ resource: zod_1.z.enum(['ontology', 'view']),
1230
+ ontologyIdParameter: common_1.ContractIdentifierSchema.optional(),
1231
+ })
1232
+ .strict()
1233
+ .superRefine((source, context) => {
1234
+ if (source.resource === 'view' && !source.ontologyIdParameter) {
1235
+ context.addIssue({
1236
+ code: 'custom',
1237
+ path: ['ontologyIdParameter'],
1238
+ message: 'A tenant View catalog must declare the input parameter that scopes it to one Ontology.',
1239
+ });
1240
+ }
1241
+ if (source.resource === 'ontology' && source.ontologyIdParameter) {
1242
+ context.addIssue({
1243
+ code: 'custom',
1244
+ path: ['ontologyIdParameter'],
1245
+ message: 'An Ontology catalog cannot be pre-scoped by an Ontology input.',
1246
+ });
1247
+ }
1248
+ }),
1249
+ zod_1.z
1250
+ .object({
1251
+ kind: zod_1.z.literal('tenant-current-view'),
1252
+ ontologyIdParameter: common_1.ContractIdentifierSchema,
1253
+ viewIdParameter: common_1.ContractIdentifierSchema,
1254
+ })
1255
+ .strict(),
1256
+ zod_1.z.object({ kind: zod_1.z.literal('page-state') }).strict(),
1257
+ exports.GovernedCatalogDomainQueryDataSourceSchema,
1258
+ exports.RegisteredServiceDomainQueryDataSourceSchema,
1259
+ ]);
1260
+ exports.DomainQueryDefinitionSchema = zod_1.z
1261
+ .object({
1262
+ contract: zod_1.z.literal('DomainQueryDefinition'),
1263
+ schemaVersion: zod_1.z.literal(exports.DECLARATIVE_CONTROL_SCHEMA_VERSION),
1264
+ queryId: common_1.ContractIdentifierSchema,
1265
+ tenantScope: exports.TenantScopeSchema,
1266
+ dataSource: exports.DomainQueryDataSourceSchema.optional(),
1267
+ handlerRef: common_1.EntityRefSchema.optional(),
1268
+ ontologyDefinitionRevisionRef: exports.RevisionRefSchema.optional(),
1269
+ viewRevisionRef: exports.RevisionRefSchema.optional(),
1270
+ canonicalDataViewRevisionRef: exports.RevisionRefSchema.optional(),
1271
+ inputSchemaRevisionRef: exports.RevisionRefSchema,
1272
+ resultSchemaRevisionRef: exports.RevisionRefSchema,
1273
+ accessPolicy: exports.AccessPolicySchema,
1274
+ lineageRequired: zod_1.z.literal(true),
1275
+ })
1276
+ .strict()
1277
+ .superRefine((value, context) => {
1278
+ const sourceKind = value.dataSource?.kind ?? 'exact-view';
1279
+ const exactDataRefs = [
1280
+ value.ontologyDefinitionRevisionRef,
1281
+ value.viewRevisionRef,
1282
+ value.canonicalDataViewRevisionRef,
1283
+ ];
1284
+ if (sourceKind === 'exact-view' && exactDataRefs.some((reference) => !reference)) {
1285
+ context.addIssue({
1286
+ code: 'custom',
1287
+ path: ['dataSource'],
1288
+ message: 'An exact-view Domain Query must pin Ontology, governed View, and canonical Data View revisions.',
1289
+ });
1290
+ }
1291
+ if (sourceKind !== 'exact-view' && exactDataRefs.some(Boolean)) {
1292
+ context.addIssue({
1293
+ code: 'custom',
1294
+ path: ['dataSource'],
1295
+ message: 'A non-View Domain Query cannot also pin one fixed View.',
1296
+ });
1297
+ }
1298
+ if (value.dataSource && (!value.handlerRef || value.handlerRef.version === undefined)) {
1299
+ context.addIssue({
1300
+ code: 'custom',
1301
+ path: ['handlerRef'],
1302
+ message: 'A source-aware Domain Query must pin its release-owned handler version.',
1303
+ });
1304
+ }
1305
+ const refs = [
1306
+ ...(sourceKind === 'exact-view' && value.ontologyDefinitionRevisionRef
1307
+ ? [['ontologyDefinitionRevisionRef', value.ontologyDefinitionRevisionRef, 'ontology-definition']]
1308
+ : []),
1309
+ ...(sourceKind === 'exact-view' && value.viewRevisionRef
1310
+ ? [['viewRevisionRef', value.viewRevisionRef, 'view']]
1311
+ : []),
1312
+ ...(sourceKind === 'exact-view' && value.canonicalDataViewRevisionRef
1313
+ ? [['canonicalDataViewRevisionRef', value.canonicalDataViewRevisionRef, 'view']]
1314
+ : []),
1315
+ ['inputSchemaRevisionRef', value.inputSchemaRevisionRef, 'schema'],
1316
+ ['resultSchemaRevisionRef', value.resultSchemaRevisionRef, 'schema'],
1317
+ ];
1318
+ refs.forEach(([field, reference, expectedKind]) => {
1319
+ if (reference.kind !== expectedKind) {
1320
+ context.addIssue({
1321
+ code: 'custom',
1322
+ path: [field, 'kind'],
1323
+ message: `${field} must reference ${expectedKind}.`,
1324
+ });
1325
+ }
1326
+ validateTenantCompatibleReference(reference, value.tenantScope, [field], context);
1327
+ });
1328
+ [...value.accessPolicy.groupAllOf, ...value.accessPolicy.groupAnyOf].forEach((reference, index) => validateTenantCompatibleReference(reference, value.tenantScope, ['accessPolicy', 'groups', index], context));
1329
+ value.accessPolicy.conditionAllOf.forEach((reference, index) => validateTenantCompatibleReference(reference, value.tenantScope, ['accessPolicy', 'conditionAllOf', index], context));
1330
+ if (value.viewRevisionRef &&
1331
+ value.canonicalDataViewRevisionRef &&
1332
+ exactRevisionRef(value.viewRevisionRef, value.canonicalDataViewRevisionRef)) {
1333
+ context.addIssue({
1334
+ code: 'custom',
1335
+ path: ['canonicalDataViewRevisionRef'],
1336
+ message: 'Governed View adapter and canonical data.view revisions must be distinct.',
1337
+ });
1338
+ }
1339
+ });
1340
+ exports.CursorWindowBindingSchema = zod_1.z
1341
+ .object({
1342
+ sourceCapabilityInstanceId: common_1.ContractIdentifierSchema.optional(),
1343
+ pageChangePort: common_1.ContractIdentifierSchema,
1344
+ pageChangeIntentSchemaRevisionRef: exports.RevisionRefSchema,
1345
+ })
1346
+ .strict()
1347
+ .superRefine((value, context) => {
1348
+ if (value.pageChangeIntentSchemaRevisionRef.kind !== 'schema') {
1349
+ context.addIssue({
1350
+ code: 'custom',
1351
+ path: ['pageChangeIntentSchemaRevisionRef', 'kind'],
1352
+ message: 'Cursor-window page-change intents must pin a schema revision.',
1353
+ });
1354
+ }
1355
+ });
1356
+ exports.QueryResultStateBindingSchema = zod_1.z
1357
+ .object({
1358
+ targetStateId: common_1.ContractIdentifierSchema,
1359
+ targetStateSchemaRevisionRef: exports.RevisionRefSchema,
1360
+ source: zod_1.z.object({ kind: zod_1.z.enum(['page-info-total', 'model-total']) }).strict(),
1361
+ })
1362
+ .strict()
1363
+ .superRefine((value, context) => {
1364
+ if (value.targetStateSchemaRevisionRef.kind !== 'schema') {
1365
+ context.addIssue({
1366
+ code: 'custom',
1367
+ path: ['targetStateSchemaRevisionRef', 'kind'],
1368
+ message: 'Query result state bindings must pin a Page-state schema revision.',
1369
+ });
529
1370
  }
530
1371
  });
1372
+ exports.QueryBindingSchema = zod_1.z
1373
+ .object({
1374
+ bindingId: common_1.ContractIdentifierSchema,
1375
+ queryDefinitionRevisionRef: exports.RevisionRefSchema,
1376
+ parameters: zod_1.z.record(common_1.ContractIdentifierSchema, exports.BindingSourceSchema),
1377
+ target: zod_1.z
1378
+ .object({
1379
+ capabilityInstanceId: common_1.ContractIdentifierSchema,
1380
+ port: common_1.ContractIdentifierSchema,
1381
+ })
1382
+ .strict(),
1383
+ renderModelSchemaRevisionRef: exports.RevisionRefSchema,
1384
+ targetProjection: zod_1.z.object({ expression: page_expression_1.PageExpressionSchema, schemaRevisionRef: exports.RevisionRefSchema }).strict().optional(),
1385
+ streamTargetProjection: zod_1.z.object({ expression: page_expression_1.PageExpressionSchema, schemaRevisionRef: exports.RevisionRefSchema }).strict().optional(),
1386
+ stateEffects: zod_1.z.array(zod_1.z.object({ targetStateId: common_1.ContractIdentifierSchema, value: page_expression_1.PageExpressionSchema, when: page_expression_1.PageExpressionSchema.optional() }).strict()).max(32).optional(),
1387
+ queryWhen: exports.PageStateActivationSchema.optional(),
1388
+ accessFailure: zod_1.z.literal('render-forbidden').optional(),
1389
+ resultStateBindings: zod_1.z.array(exports.QueryResultStateBindingSchema).max(32).optional(),
1390
+ execution: zod_1.z.enum(['server', 'local-state']).default('server'),
1391
+ pagination: zod_1.z.enum(['none', 'cursor', 'offset']),
1392
+ cursorWindow: exports.CursorWindowBindingSchema.optional(),
1393
+ cache: zod_1.z.enum(['none', 'identity-scoped', 'tenant-scoped']),
1394
+ cancelOnChange: zod_1.z.boolean(),
1395
+ refreshPolicy: zod_1.z
1396
+ .object({ intervalMs: zod_1.z.number().int().min(1000).max(300000), when: page_expression_1.PageExpressionSchema.optional() })
1397
+ .strict()
1398
+ .optional(),
1399
+ })
1400
+ .strict()
1401
+ .superRefine((value, context) => {
1402
+ if (value.targetProjection) {
1403
+ if (value.targetProjection.schemaRevisionRef.kind !== 'schema')
1404
+ context.addIssue({ code: 'custom', path: ['targetProjection', 'schemaRevisionRef'], message: 'Query target projections require an exact target schema revision.' });
1405
+ for (const read of (0, page_expression_1.collectExternalPageExpressionReads)(value.targetProjection.expression))
1406
+ if (read.root !== 'model')
1407
+ context.addIssue({ code: 'custom', path: ['targetProjection', 'expression'], message: 'Query target projections may read only the validated raw model.' });
1408
+ }
1409
+ if (value.streamTargetProjection) {
1410
+ const projection = value.streamTargetProjection;
1411
+ if (projection.schemaRevisionRef.kind !== 'schema')
1412
+ context.addIssue({ code: 'custom', path: ['streamTargetProjection', 'schemaRevisionRef'], message: 'Stream target projections require an exact target schema revision.' });
1413
+ for (const read of (0, page_expression_1.collectExternalPageExpressionReads)(projection.expression))
1414
+ if (read.root !== 'model')
1415
+ context.addIssue({ code: 'custom', path: ['streamTargetProjection', 'expression'], message: 'Stream target projections may read only the verified query and stream envelope.' });
1416
+ if (value.execution !== 'server' || value.pagination !== 'none')
1417
+ context.addIssue({ code: 'custom', path: ['streamTargetProjection'], message: 'Stream target projections require a non-paginated Server query.' });
1418
+ }
1419
+ const effects = value.stateEffects ?? [];
1420
+ if (new Set(effects.map((effect) => effect.targetStateId)).size !== effects.length)
1421
+ context.addIssue({ code: 'custom', path: ['stateEffects'], message: 'A query completion may update each state only once.' });
1422
+ for (const [index, effect] of effects.entries())
1423
+ for (const expression of [effect.value, ...(effect.when ? [effect.when] : [])]) {
1424
+ for (const read of (0, page_expression_1.collectPageExpressionReads)(expression))
1425
+ if (!['model', 'state', 'scope'].includes(read.root))
1426
+ context.addIssue({ code: 'custom', path: ['stateEffects', index], message: 'Query state effects may read only model, state, and scope.' });
1427
+ }
1428
+ if (effects.length && (value.execution !== 'server' || value.pagination !== 'none'))
1429
+ context.addIssue({ code: 'custom', path: ['stateEffects'], message: 'Query state effects require a non-paginated Server query.' });
1430
+ if (value.refreshPolicy?.when) {
1431
+ for (const read of (0, page_expression_1.collectExternalPageExpressionReads)(value.refreshPolicy.when)) {
1432
+ if (read.root !== 'model' && !(read.root === 'actions' && read.path.length === 2 && read.path[1] === 'pending'))
1433
+ context.addIssue({ code: 'custom', path: ['refreshPolicy', 'when'], message: 'Automatic refresh conditions may read only the verified raw model and declared action pending state.' });
1434
+ }
1435
+ }
1436
+ if (value.refreshPolicy && (value.execution !== 'server' || value.pagination !== 'none')) {
1437
+ context.addIssue({
1438
+ code: 'custom',
1439
+ path: ['refreshPolicy'],
1440
+ message: 'Automatic refresh requires a non-paginated Server query.',
1441
+ });
1442
+ }
1443
+ if (value.queryDefinitionRevisionRef.kind !== 'domain-query-definition') {
1444
+ context.addIssue({
1445
+ code: 'custom',
1446
+ path: ['queryDefinitionRevisionRef', 'kind'],
1447
+ message: 'Query bindings must pin a governed domain-query-definition revision.',
1448
+ });
1449
+ }
1450
+ if (value.renderModelSchemaRevisionRef.kind !== 'schema') {
1451
+ context.addIssue({
1452
+ code: 'custom',
1453
+ path: ['renderModelSchemaRevisionRef', 'kind'],
1454
+ message: 'Query bindings must pin a render-model schema revision.',
1455
+ });
1456
+ }
1457
+ if (value.execution === 'local-state' && value.pagination !== 'none') {
1458
+ context.addIssue({
1459
+ code: 'custom',
1460
+ path: ['pagination'],
1461
+ message: 'Local Page-state queries cannot paginate.',
1462
+ });
1463
+ }
1464
+ if (value.execution === 'local-state' && value.accessFailure) {
1465
+ context.addIssue({
1466
+ code: 'custom',
1467
+ path: ['accessFailure'],
1468
+ message: 'Only a Server query can render an independently governed access failure.',
1469
+ });
1470
+ }
1471
+ if (value.execution === 'local-state' && (value.resultStateBindings?.length ?? 0) > 0) {
1472
+ context.addIssue({
1473
+ code: 'custom',
1474
+ path: ['resultStateBindings'],
1475
+ message: 'Only Server queries can project governed result totals into Page state.',
1476
+ });
1477
+ }
1478
+ const targetStateIds = value.resultStateBindings?.map((binding) => binding.targetStateId) ?? [];
1479
+ if (new Set(targetStateIds).size !== targetStateIds.length) {
1480
+ context.addIssue({
1481
+ code: 'custom',
1482
+ path: ['resultStateBindings'],
1483
+ message: 'A Query binding can project a governed result total to each Page state at most once.',
1484
+ });
1485
+ }
1486
+ if (value.cursorWindow && value.pagination !== 'cursor') {
1487
+ context.addIssue({
1488
+ code: 'custom',
1489
+ path: ['cursorWindow'],
1490
+ message: 'Cursor-window navigation requires cursor pagination.',
1491
+ });
1492
+ }
1493
+ if (value.cursorWindow && value.execution !== 'server') {
1494
+ context.addIssue({
1495
+ code: 'custom',
1496
+ path: ['cursorWindow'],
1497
+ message: 'Cursor-window navigation requires Server query execution.',
1498
+ });
1499
+ }
1500
+ });
1501
+ exports.CapabilityInstanceSchema = zod_1.z
1502
+ .object({
1503
+ instanceId: common_1.ContractIdentifierSchema,
1504
+ nodeId: common_1.ContractIdentifierSchema,
1505
+ capabilityRevisionRef: exports.RevisionRefSchema,
1506
+ providerRevisionRef: exports.RevisionRefSchema,
1507
+ propertySchemaRevisionRef: exports.RevisionRefSchema,
1508
+ properties: common_1.JsonObjectSchema,
1509
+ propertyBindings: zod_1.z.array(page_expression_1.PagePropertyBindingSchema).max(128).optional(),
1510
+ eventPayloadBindings: zod_1.z.array(zod_1.z.object({ port: common_1.ContractIdentifierSchema, expression: page_expression_1.PageExpressionSchema }).strict()).max(32).optional(),
1511
+ eventEffects: zod_1.z.array(zod_1.z.object({
1512
+ sourcePort: common_1.ContractIdentifierSchema,
1513
+ sourceIntentSchemaRevisionRef: exports.RevisionRefSchema,
1514
+ refreshBindingIds: zod_1.z.array(common_1.ContractIdentifierSchema).min(1).max(128),
1515
+ }).strict()).max(32).optional(),
1516
+ accessPolicy: exports.AccessPolicySchema.optional(),
1517
+ activationWhen: exports.PageStateActivationSchema.optional(),
1518
+ allowedSideEffects: zod_1.z.array(zod_1.z.enum(['network', 'storage', 'navigation', 'clipboard', 'worker', 'websocket'])),
1519
+ })
1520
+ .strict();
1521
+ exports.OntologyBindingSchema = zod_1.z
1522
+ .object({
1523
+ bindingId: common_1.ContractIdentifierSchema,
1524
+ ontologyDefinitionRevisionRef: exports.RevisionRefSchema,
1525
+ viewRevisionRef: exports.RevisionRefSchema,
1526
+ canonicalDataViewRevisionRef: exports.RevisionRefSchema,
1527
+ parameters: zod_1.z.record(common_1.ContractIdentifierSchema, exports.BindingSourceSchema),
1528
+ target: zod_1.z
1529
+ .object({
1530
+ capabilityInstanceId: common_1.ContractIdentifierSchema,
1531
+ port: common_1.ContractIdentifierSchema,
1532
+ })
1533
+ .strict(),
1534
+ renderModelSchemaRevisionRef: exports.RevisionRefSchema,
1535
+ pagination: zod_1.z.enum(['none', 'cursor', 'offset']),
1536
+ cursorWindow: exports.CursorWindowBindingSchema.optional(),
1537
+ cache: zod_1.z.enum(['none', 'identity-scoped', 'tenant-scoped']),
1538
+ cancelOnChange: zod_1.z.boolean(),
1539
+ })
1540
+ .strict()
1541
+ .superRefine((value, context) => {
1542
+ if (value.viewRevisionRef.kind !== 'view') {
1543
+ context.addIssue({
1544
+ code: 'custom',
1545
+ path: ['viewRevisionRef', 'kind'],
1546
+ message: 'Ontology bindings require an exact governed View revision.',
1547
+ });
1548
+ }
1549
+ if (value.canonicalDataViewRevisionRef.kind !== 'view') {
1550
+ context.addIssue({
1551
+ code: 'custom',
1552
+ path: ['canonicalDataViewRevisionRef', 'kind'],
1553
+ message: 'Ontology bindings require an exact canonical data.view revision.',
1554
+ });
1555
+ }
1556
+ if (exactRevisionRef(value.viewRevisionRef, value.canonicalDataViewRevisionRef)) {
1557
+ context.addIssue({
1558
+ code: 'custom',
1559
+ path: ['canonicalDataViewRevisionRef'],
1560
+ message: 'Governed View adapter and canonical data.view revisions must be distinct.',
1561
+ });
1562
+ }
1563
+ if (value.ontologyDefinitionRevisionRef.kind !== 'ontology-definition') {
1564
+ context.addIssue({
1565
+ code: 'custom',
1566
+ path: ['ontologyDefinitionRevisionRef', 'kind'],
1567
+ message: 'Expected an ontology-definition revision.',
1568
+ });
1569
+ }
1570
+ if (value.cursorWindow && value.pagination !== 'cursor') {
1571
+ context.addIssue({
1572
+ code: 'custom',
1573
+ path: ['cursorWindow'],
1574
+ message: 'Cursor-window navigation requires cursor pagination.',
1575
+ });
1576
+ }
1577
+ });
1578
+ exports.ActionResultBindingEffectSourceSchema = zod_1.z.discriminatedUnion('kind', [
1579
+ zod_1.z
1580
+ .object({
1581
+ kind: zod_1.z.literal('result-field'),
1582
+ path: zod_1.z
1583
+ .string()
1584
+ .trim()
1585
+ .regex(/^[A-Za-z0-9_.-]+$/),
1586
+ })
1587
+ .strict(),
1588
+ zod_1.z.object({ kind: zod_1.z.literal('constant'), value: common_1.JsonValueSchema }).strict(),
1589
+ zod_1.z
1590
+ .object({
1591
+ kind: zod_1.z.literal('collection-length'),
1592
+ offset: zod_1.z.number().int().min(-1000).max(1000),
1593
+ })
1594
+ .strict(),
1595
+ ]);
1596
+ exports.ActionResultBindingEffectSchema = zod_1.z
1597
+ .object({
1598
+ bindingId: common_1.ContractIdentifierSchema,
1599
+ operation: zod_1.z.enum(['upsert-append', 'remove-exact']),
1600
+ collectionPath: zod_1.z.array(common_1.ContractIdentifierSchema).min(1).max(8),
1601
+ identityPath: zod_1.z.array(common_1.ContractIdentifierSchema).min(1).max(8),
1602
+ mappings: zod_1.z
1603
+ .array(zod_1.z
1604
+ .object({
1605
+ targetPath: zod_1.z.array(common_1.ContractIdentifierSchema).min(1).max(8),
1606
+ source: exports.ActionResultBindingEffectSourceSchema,
1607
+ })
1608
+ .strict())
1609
+ .min(1)
1610
+ .max(64),
1611
+ })
1612
+ .strict()
1613
+ .superRefine((value, context) => {
1614
+ const targetPaths = value.mappings.map(({ targetPath }) => targetPath.join('.'));
1615
+ if (new Set(targetPaths).size !== targetPaths.length) {
1616
+ context.addIssue({
1617
+ code: 'custom',
1618
+ path: ['mappings'],
1619
+ message: 'Result binding effect target paths must be unique.',
1620
+ });
1621
+ }
1622
+ if (!targetPaths.includes(value.identityPath.join('.'))) {
1623
+ context.addIssue({
1624
+ code: 'custom',
1625
+ path: ['identityPath'],
1626
+ message: 'A result binding effect must map the exact identity path of the projected item.',
1627
+ });
1628
+ }
1629
+ const identityMapping = value.mappings.find(({ targetPath }) => targetPath.join('.') === value.identityPath.join('.'));
1630
+ if (identityMapping?.source.kind === 'collection-length') {
1631
+ context.addIssue({
1632
+ code: 'custom',
1633
+ path: ['identityPath'],
1634
+ message: 'A projected item identity must come from the verified result or a declared constant.',
1635
+ });
1636
+ }
1637
+ if (value.operation === 'remove-exact' && value.mappings.length !== 1) {
1638
+ context.addIssue({
1639
+ code: 'custom',
1640
+ path: ['mappings'],
1641
+ message: 'An exact removal declares only the verified identity mapping used to remove one item.',
1642
+ });
1643
+ }
1644
+ });
1645
+ exports.ActionResultClipboardEffectSchema = zod_1.z
1646
+ .object({
1647
+ source: zod_1.z
1648
+ .object({
1649
+ kind: zod_1.z.literal('result-field'),
1650
+ path: zod_1.z
1651
+ .string()
1652
+ .trim()
1653
+ .regex(/^[A-Za-z0-9_.-]+$/),
1654
+ })
1655
+ .strict(),
1656
+ copiedToast: exports.I18nTextSchema.optional(),
1657
+ fallbackToast: exports.I18nTextSchema.superRefine((value, context) => {
1658
+ for (const [locale, message] of Object.entries(value.values)) {
1659
+ if (!message.includes('{value}')) {
1660
+ context.addIssue({
1661
+ code: 'custom',
1662
+ path: ['values', locale],
1663
+ message: 'Clipboard fallback feedback must visibly include the exact verified value placeholder.',
1664
+ });
1665
+ }
1666
+ }
1667
+ }),
1668
+ })
1669
+ .strict();
531
1670
  exports.ActionBindingSchema = zod_1.z
532
1671
  .object({
533
1672
  bindingId: common_1.ContractIdentifierSchema,
534
1673
  commandRevisionRef: exports.RevisionRefSchema,
535
- source: zod_1.z.object({ capabilityInstanceId: common_1.ContractIdentifierSchema, port: common_1.ContractIdentifierSchema }).strict(),
1674
+ source: zod_1.z
1675
+ .object({
1676
+ capabilityInstanceId: common_1.ContractIdentifierSchema,
1677
+ port: common_1.ContractIdentifierSchema,
1678
+ })
1679
+ .strict(),
536
1680
  sourceIntentSchemaRevisionRef: exports.RevisionRefSchema,
537
1681
  inputSchemaRevisionRef: exports.RevisionRefSchema,
538
- inputMapping: zod_1.z.record(common_1.ContractIdentifierSchema, exports.BindingSourceSchema),
1682
+ when: page_expression_1.PageExpressionSchema.optional(),
1683
+ fileInputs: zod_1.z.array(ActionFileInputSchema).min(1).max(4).optional(),
1684
+ inputMapping: zod_1.z.record(common_1.ContractIdentifierSchema, ActionInputBindingSourceSchema),
1685
+ sensitiveInputPaths: common_1.SensitiveResultPathsSchema.optional(),
539
1686
  accessPolicy: exports.AccessPolicySchema,
540
1687
  confirmation: exports.I18nTextSchema.optional(),
541
1688
  idempotencyKeySource: zod_1.z.enum(['interaction', 'record-and-interaction', 'caller-provided']),
542
1689
  compensationCommandRevisionRef: exports.RevisionRefSchema.optional(),
543
1690
  resultSchemaRevisionRef: exports.RevisionRefSchema,
544
- success: zod_1.z.object({
1691
+ success: zod_1.z
1692
+ .object({
1693
+ download: zod_1.z.discriminatedUnion('kind', [
1694
+ zod_1.z.object({ kind: zod_1.z.literal('native') }).strict(),
1695
+ zod_1.z.object({ kind: zod_1.z.literal('text-artifact'), fileNamePath: page_expression_1.PageValuePathSchema.min(1), mimeTypePath: page_expression_1.PageValuePathSchema.min(1), contentPath: page_expression_1.PageValuePathSchema.min(1), maxBytes: zod_1.z.number().int().min(1).max(1048576) }).strict(),
1696
+ ]).optional(),
545
1697
  refreshBindingIds: zod_1.z.array(common_1.ContractIdentifierSchema),
1698
+ ephemeralResult: zod_1.z
1699
+ .object({
1700
+ kind: zod_1.z.literal('one-time-value'),
1701
+ valuePath: common_1.SensitiveResultPathSchema,
1702
+ labelPath: common_1.SensitiveResultPathSchema,
1703
+ title: exports.I18nTextSchema,
1704
+ description: exports.I18nTextSchema,
1705
+ copySuccessToast: exports.I18nTextSchema,
1706
+ copyFailureToast: exports.I18nTextSchema,
1707
+ })
1708
+ .strict()
1709
+ .optional(),
1710
+ resultBindingEffects: zod_1.z.array(exports.ActionResultBindingEffectSchema).max(64).optional(),
1711
+ clipboard: exports.ActionResultClipboardEffectSchema.optional(),
546
1712
  navigationTargetRef: exports.StableRefSchema.optional(),
547
- }).strict(),
1713
+ toast: exports.I18nTextSchema.optional(),
1714
+ })
1715
+ .strict(),
548
1716
  recovery: zod_1.z.enum(['retry-safe', 'compensating-command', 'manual-review']),
549
1717
  lineageRequired: zod_1.z.literal(true),
550
1718
  })
551
1719
  .strict()
552
1720
  .superRefine((value, context) => {
1721
+ const fileIds = new Set();
1722
+ for (const file of value.fileInputs ?? []) {
1723
+ if (fileIds.has(file.inputId))
1724
+ context.addIssue({ code: 'custom', path: ['fileInputs'], message: 'File input ids must be unique.' });
1725
+ fileIds.add(file.inputId);
1726
+ if (!Object.values(value.inputMapping).some(source => source.kind === 'file-field' && source.fileInputId === file.inputId))
1727
+ context.addIssue({ code: 'custom', path: ['fileInputs'], message: 'File inputs require an explicit governed input mapping.' });
1728
+ }
1729
+ for (const [name, source] of Object.entries(value.inputMapping))
1730
+ if (source.kind === 'file-field') {
1731
+ const file = value.fileInputs?.find(file => file.inputId === source.fileInputId);
1732
+ if ((source.path === 'canonicalUri' && !file?.upload) || (source.path === 'content' && file?.upload))
1733
+ context.addIssue({ code: 'custom', path: ['inputMapping', name], message: 'File field must match its declared transport mode.' });
1734
+ if (!fileIds.has(source.fileInputId))
1735
+ context.addIssue({ code: 'custom', path: ['inputMapping', name], message: 'File fields must reference a declared file input.' });
1736
+ if (source.path === 'content' && !value.sensitiveInputPaths?.includes(`/${name}`))
1737
+ context.addIssue({ code: 'custom', path: ['sensitiveInputPaths'], message: 'File content must be declared as a sensitive command input.' });
1738
+ }
1739
+ if (value.when)
1740
+ for (const read of (0, page_expression_1.collectPageExpressionReads)(value.when)) {
1741
+ if (!['intent', 'state', 'scope'].includes(read.root))
1742
+ context.addIssue({ code: 'custom', path: ['when'], message: 'Action conditions may read only intent, declared Page state, and instance scope.' });
1743
+ }
1744
+ if (value.success.ephemeralResult) {
1745
+ const { valuePath, labelPath } = value.success.ephemeralResult;
1746
+ const segments = (path) => path.split('/').slice(1);
1747
+ const valueSegments = segments(valuePath);
1748
+ const labelSegments = segments(labelPath);
1749
+ const sharedLength = Math.min(valueSegments.length, labelSegments.length);
1750
+ if (valueSegments.slice(0, sharedLength).every((segment, index) => segment === labelSegments[index])) {
1751
+ context.addIssue({
1752
+ code: 'custom',
1753
+ path: ['success', 'ephemeralResult', 'labelPath'],
1754
+ message: 'One-time result labels cannot overlap the sensitive value path.',
1755
+ });
1756
+ }
1757
+ }
1758
+ if (value.success.ephemeralResult && (value.success.resultBindingEffects?.length || 0) > 0) {
1759
+ context.addIssue({
1760
+ code: 'custom',
1761
+ path: ['success', 'resultBindingEffects'],
1762
+ message: 'One-time result presentation cannot share a result with durable binding effects.',
1763
+ });
1764
+ }
553
1765
  if (value.commandRevisionRef.kind !== 'domain-command') {
554
- context.addIssue({ code: 'custom', path: ['commandRevisionRef', 'kind'], message: 'Action bindings must reference a domain-command revision.' });
1766
+ context.addIssue({
1767
+ code: 'custom',
1768
+ path: ['commandRevisionRef', 'kind'],
1769
+ message: 'Action bindings must reference a domain-command revision.',
1770
+ });
555
1771
  }
556
1772
  if (value.sourceIntentSchemaRevisionRef.kind !== 'schema') {
557
- context.addIssue({ code: 'custom', path: ['sourceIntentSchemaRevisionRef', 'kind'], message: 'Action source intents must pin a schema revision.' });
1773
+ context.addIssue({
1774
+ code: 'custom',
1775
+ path: ['sourceIntentSchemaRevisionRef', 'kind'],
1776
+ message: 'Action source intents must pin a schema revision.',
1777
+ });
558
1778
  }
559
1779
  if (value.inputSchemaRevisionRef.kind !== 'schema') {
560
- context.addIssue({ code: 'custom', path: ['inputSchemaRevisionRef', 'kind'], message: 'Action command inputs must pin a schema revision.' });
1780
+ context.addIssue({
1781
+ code: 'custom',
1782
+ path: ['inputSchemaRevisionRef', 'kind'],
1783
+ message: 'Action command inputs must pin a schema revision.',
1784
+ });
561
1785
  }
562
1786
  if (value.recovery === 'compensating-command' && !value.compensationCommandRevisionRef) {
563
- context.addIssue({ code: 'custom', path: ['compensationCommandRevisionRef'], message: 'Compensating recovery requires a compensation command.' });
1787
+ context.addIssue({
1788
+ code: 'custom',
1789
+ path: ['compensationCommandRevisionRef'],
1790
+ message: 'Compensating recovery requires a compensation command.',
1791
+ });
564
1792
  }
565
1793
  });
1794
+ exports.PageMigrationEquivalenceDimensionSchema = zod_1.z.enum([
1795
+ 'url',
1796
+ 'navigation',
1797
+ 'access',
1798
+ 'deep-link',
1799
+ 'layout',
1800
+ 'size',
1801
+ 'spacing',
1802
+ 'design-tokens',
1803
+ 'responsive',
1804
+ 'view-modes',
1805
+ 'search-filter',
1806
+ 'editing',
1807
+ 'detail',
1808
+ 'history',
1809
+ 'dialogs',
1810
+ 'keyboard',
1811
+ 'loading',
1812
+ 'empty',
1813
+ 'error',
1814
+ 'forbidden',
1815
+ ]);
1816
+ exports.PageMigrationEquivalenceSchema = zod_1.z
1817
+ .object({
1818
+ contract: zod_1.z.literal('PageMigrationEquivalence'),
1819
+ schemaVersion: zod_1.z.literal(1),
1820
+ policy: zod_1.z.literal('preserve-ui-ux'),
1821
+ baseline: zod_1.z
1822
+ .object({
1823
+ repository: common_1.ContractIdentifierSchema,
1824
+ revision: zod_1.z.string().regex(/^[0-9a-f]{40}$/),
1825
+ pageId: common_1.ContractIdentifierSchema,
1826
+ routePath: exports.RoutePathTemplateSchema,
1827
+ })
1828
+ .strict(),
1829
+ protectedDimensions: zod_1.z.array(exports.PageMigrationEquivalenceDimensionSchema).min(1),
1830
+ viewports: zod_1.z
1831
+ .array(zod_1.z
1832
+ .object({
1833
+ viewportId: common_1.ContractIdentifierSchema,
1834
+ width: zod_1.z.number().int().min(320).max(7680),
1835
+ height: zod_1.z.number().int().min(480).max(4320),
1836
+ })
1837
+ .strict())
1838
+ .min(2),
1839
+ scenarios: zod_1.z
1840
+ .array(zod_1.z
1841
+ .object({
1842
+ scenarioId: common_1.ContractIdentifierSchema,
1843
+ viewportIds: zod_1.z.array(common_1.ContractIdentifierSchema).min(1),
1844
+ })
1845
+ .strict())
1846
+ .min(1),
1847
+ evidence: zod_1.z
1848
+ .object({
1849
+ sideBySideVisual: zod_1.z.literal(true),
1850
+ domSnapshot: zod_1.z.literal(true),
1851
+ consoleErrors: zod_1.z.literal('none'),
1852
+ })
1853
+ .strict(),
1854
+ allowedDifferences: zod_1.z.array(zod_1.z
1855
+ .object({
1856
+ differenceId: common_1.ContractIdentifierSchema,
1857
+ requirementRevisionRef: exports.RevisionRefSchema,
1858
+ })
1859
+ .strict()),
1860
+ })
1861
+ .strict()
1862
+ .superRefine((value, context) => {
1863
+ if (uniqueArray(value.protectedDimensions, (candidate) => candidate)) {
1864
+ context.addIssue({
1865
+ code: 'custom',
1866
+ path: ['protectedDimensions'],
1867
+ message: 'Protected UI/UX dimensions must be unique.',
1868
+ });
1869
+ }
1870
+ if (uniqueArray(value.viewports, (candidate) => candidate.viewportId)) {
1871
+ context.addIssue({
1872
+ code: 'custom',
1873
+ path: ['viewports'],
1874
+ message: 'Migration equivalence viewport identities must be unique.',
1875
+ });
1876
+ }
1877
+ if (uniqueArray(value.scenarios, (candidate) => candidate.scenarioId)) {
1878
+ context.addIssue({
1879
+ code: 'custom',
1880
+ path: ['scenarios'],
1881
+ message: 'Migration equivalence scenario identities must be unique.',
1882
+ });
1883
+ }
1884
+ const viewportIds = new Set(value.viewports.map((viewport) => viewport.viewportId));
1885
+ value.scenarios.forEach((scenario, index) => {
1886
+ scenario.viewportIds.forEach((viewportId, viewportIndex) => {
1887
+ if (!viewportIds.has(viewportId)) {
1888
+ context.addIssue({
1889
+ code: 'custom',
1890
+ path: ['scenarios', index, 'viewportIds', viewportIndex],
1891
+ message: `Migration equivalence scenario references unknown viewport ${viewportId}.`,
1892
+ });
1893
+ }
1894
+ });
1895
+ });
1896
+ });
1897
+ exports.PageRouteStatePresentationSchema = zod_1.z
1898
+ .object({
1899
+ surface: exports.ProductSurfaceSchema,
1900
+ state: zod_1.z.literal('forbidden'),
1901
+ kind: zod_1.z.literal('status'),
1902
+ presentation: zod_1.z.enum(['panel', 'page']),
1903
+ title: exports.I18nTextSchema,
1904
+ description: exports.I18nTextSchema.optional(),
1905
+ })
1906
+ .strict()
1907
+ .superRefine((value, context) => {
1908
+ const localizedFields = [
1909
+ ['title', value.title, 160],
1910
+ ['description', value.description, 1000],
1911
+ ];
1912
+ localizedFields.forEach(([field, copy, maxLength]) => {
1913
+ if (!copy)
1914
+ return;
1915
+ const entries = Object.entries(copy.values);
1916
+ if (entries.length > 16) {
1917
+ context.addIssue({
1918
+ code: 'custom',
1919
+ path: [field, 'values'],
1920
+ message: 'Route-state presentation copy supports at most 16 locales.',
1921
+ });
1922
+ }
1923
+ entries.forEach(([locale, text]) => {
1924
+ if (text.length > maxLength) {
1925
+ context.addIssue({
1926
+ code: 'custom',
1927
+ path: [field, 'values', locale],
1928
+ message: `Route-state ${field} copy must contain at most ${maxLength} characters.`,
1929
+ });
1930
+ }
1931
+ });
1932
+ });
1933
+ });
566
1934
  exports.PageSchema = zod_1.z
567
1935
  .object({
568
1936
  contract: zod_1.z.literal('Page'),
@@ -572,11 +1940,18 @@ exports.PageSchema = zod_1.z
572
1940
  identity: IdentitySchema,
573
1941
  supportedSurfaces: zod_1.z.array(exports.ProductSurfaceSchema).min(1),
574
1942
  lifecycle: exports.DeclarativeLifecycleSchema,
1943
+ legacyRoutePolicy: legacy_route_1.LegacyRoutePolicySchema.optional(),
1944
+ migrationEquivalence: exports.PageMigrationEquivalenceSchema.optional(),
1945
+ routeStatePresentations: zod_1.z.array(exports.PageRouteStatePresentationSchema).max(8).optional(),
575
1946
  routeClaims: zod_1.z.array(zod_1.z.union([exports.DeclarativeRouteClaimSchema, LegacyDeclarativeRouteClaimSchema])).min(1),
576
1947
  shellRevisionRef: exports.RevisionRefSchema,
577
1948
  renderTree: render_1.RenderTreeSchema,
578
1949
  capabilityInstances: zod_1.z.array(exports.CapabilityInstanceSchema).min(1),
1950
+ stateDefinitions: zod_1.z.array(exports.PageStateDefinitionSchema).optional(),
1951
+ entryTransitions: zod_1.z.array(exports.PageEntryTransitionSchema).optional(),
1952
+ interactionBindings: zod_1.z.array(exports.InteractionBindingSchema).optional(),
579
1953
  ontologyBindings: zod_1.z.array(exports.OntologyBindingSchema),
1954
+ queryBindings: zod_1.z.array(exports.QueryBindingSchema).optional(),
580
1955
  actionBindings: zod_1.z.array(exports.ActionBindingSchema),
581
1956
  pageAccessPolicy: exports.AccessPolicySchema,
582
1957
  managementAccess: exports.ManagementAccessSchema,
@@ -588,34 +1963,590 @@ exports.PageSchema = zod_1.z
588
1963
  .strict()
589
1964
  .superRefine((value, context) => {
590
1965
  validateDeclaredRouteSurfaces(value.supportedSurfaces, value.routeClaims, context);
1966
+ const routeStatePresentations = value.routeStatePresentations ?? [];
1967
+ if (uniqueArray(routeStatePresentations, (candidate) => `${candidate.surface}:${candidate.state}`)) {
1968
+ context.addIssue({
1969
+ code: 'custom',
1970
+ path: ['routeStatePresentations'],
1971
+ message: 'Page route-state presentations must be unique per surface and state.',
1972
+ });
1973
+ }
1974
+ routeStatePresentations.forEach((presentation, index) => {
1975
+ if (!value.supportedSurfaces.includes(presentation.surface)) {
1976
+ context.addIssue({
1977
+ code: 'custom',
1978
+ path: ['routeStatePresentations', index, 'surface'],
1979
+ message: 'Page route-state presentation surface must be declared by supportedSurfaces.',
1980
+ });
1981
+ }
1982
+ });
1983
+ const stateDefinitions = value.stateDefinitions ?? [];
1984
+ const entryTransitions = value.entryTransitions ?? [];
1985
+ const interactionBindings = value.interactionBindings ?? [];
1986
+ const queryBindings = value.queryBindings ?? [];
1987
+ stateDefinitions.forEach((definition, index) => {
1988
+ const partitionStateId = definition.preferencePartitionStateId;
1989
+ const dynamicLegacySegments = definition.legacyBrowserPreferenceImport?.jsonPath?.filter((segment) => typeof segment !== 'string') ?? [];
1990
+ if (!partitionStateId) {
1991
+ if (dynamicLegacySegments.length > 0) {
1992
+ context.addIssue({
1993
+ code: 'custom',
1994
+ path: ['stateDefinitions', index, 'legacyBrowserPreferenceImport', 'jsonPath'],
1995
+ message: 'A dynamic legacy preference path requires a declared preference partition state.',
1996
+ });
1997
+ }
1998
+ return;
1999
+ }
2000
+ const partitionDefinition = stateDefinitions.find((candidate) => candidate.stateId === partitionStateId);
2001
+ if (partitionStateId === definition.stateId ||
2002
+ !partitionDefinition ||
2003
+ partitionDefinition.preferencePartitionStateId) {
2004
+ context.addIssue({
2005
+ code: 'custom',
2006
+ path: ['stateDefinitions', index, 'preferencePartitionStateId'],
2007
+ message: 'Preference partitioning must reference a distinct, non-partitioned Page state.',
2008
+ });
2009
+ }
2010
+ definition.legacyBrowserPreferenceImport?.jsonPath?.forEach((segment, segmentIndex) => {
2011
+ if (typeof segment !== 'string' && segment.stateId !== partitionStateId) {
2012
+ context.addIssue({
2013
+ code: 'custom',
2014
+ path: ['stateDefinitions', index, 'legacyBrowserPreferenceImport', 'jsonPath', segmentIndex],
2015
+ message: 'A dynamic legacy preference path must use the declared preference partition state.',
2016
+ });
2017
+ }
2018
+ });
2019
+ });
591
2020
  const uniqueness = [
592
2021
  ['capabilityInstances', value.capabilityInstances, (candidate) => String(candidate.instanceId)],
2022
+ ['stateDefinitions', stateDefinitions, (candidate) => String(candidate.stateId)],
2023
+ ['entryTransitions', entryTransitions, (candidate) => String(candidate.transitionId)],
2024
+ ['interactionBindings', interactionBindings, (candidate) => String(candidate.bindingId)],
593
2025
  ['ontologyBindings', value.ontologyBindings, (candidate) => String(candidate.bindingId)],
2026
+ ['queryBindings', queryBindings, (candidate) => String(candidate.bindingId)],
594
2027
  ['actionBindings', value.actionBindings, (candidate) => String(candidate.bindingId)],
595
2028
  ];
596
2029
  uniqueness.forEach(([field, values, identity]) => {
597
2030
  if (uniqueArray(values, identity))
598
- context.addIssue({ code: 'custom', path: [field], message: `${field} identities must be unique.` });
2031
+ context.addIssue({
2032
+ code: 'custom',
2033
+ path: [field],
2034
+ message: `${field} identities must be unique.`,
2035
+ });
2036
+ });
2037
+ const instanceIds = new Set(value.capabilityInstances.map((instance) => instance.instanceId));
2038
+ const stateIds = new Set(stateDefinitions.map((state) => state.stateId));
2039
+ value.capabilityInstances.forEach((instance, index) => {
2040
+ const ports = new Set();
2041
+ const payloadPorts = new Set();
2042
+ instance.eventPayloadBindings?.forEach((binding, bindingIndex) => {
2043
+ const path = ['capabilityInstances', index, 'eventPayloadBindings', bindingIndex];
2044
+ if (payloadPorts.has(binding.port))
2045
+ context.addIssue({ code: 'custom', path, message: 'An event payload can have only one declarative projection.' });
2046
+ payloadPorts.add(binding.port);
2047
+ for (const read of (0, page_expression_1.collectPageExpressionReads)(binding.expression)) {
2048
+ if (read.root === 'actions' || read.root === 'result' || (read.root === 'state' && !stateIds.has(read.path[0] ?? '')) || (read.root === 'bindings' && ![...value.ontologyBindings, ...queryBindings].some((candidate) => candidate.bindingId === read.path[0])))
2049
+ context.addIssue({ code: 'custom', path, message: 'Event payload projections require local event, model, scope, declared query results, or declared state.' });
2050
+ }
2051
+ });
2052
+ instance.eventEffects?.forEach((effect, effectIndex) => {
2053
+ const path = ['capabilityInstances', index, 'eventEffects', effectIndex];
2054
+ if (effect.sourceIntentSchemaRevisionRef.kind !== 'schema' || ports.has(effect.sourcePort))
2055
+ context.addIssue({ code: 'custom', path, message: 'Event effects require one exact source schema per output port.' });
2056
+ ports.add(effect.sourcePort);
2057
+ if (value.actionBindings.some((binding) => binding.source.capabilityInstanceId === instance.instanceId && binding.source.port === effect.sourcePort))
2058
+ context.addIssue({ code: 'custom', path, message: 'An Action output port must declare refresh effects through Action.success.' });
2059
+ for (const id of effect.refreshBindingIds)
2060
+ if (![...value.ontologyBindings, ...queryBindings].some((binding) => binding.bindingId === id))
2061
+ context.addIssue({ code: 'custom', path, message: 'Event effect references an unknown query binding.' });
2062
+ });
2063
+ });
2064
+ const repeatNodes = new Set(value.renderTree.nodes.filter((node) => node.repeat).map((node) => node.nodeId));
2065
+ stateDefinitions.forEach((definition, index) => {
2066
+ if (definition.scopeNodeId && !repeatNodes.has(definition.scopeNodeId))
2067
+ context.addIssue({ code: 'custom', path: ['stateDefinitions', index, 'scopeNodeId'], message: 'Instance state must reference an existing repeated RenderNode.' });
2068
+ if (definition.initialValue && !definition.scopeNodeId) {
2069
+ const expression = definition.initialValue;
2070
+ const source = expression.kind === 'read' ? stateDefinitions.find(state => state.stateId === expression.path[0] && !state.scopeNodeId) : undefined;
2071
+ if (!source || source.initialValue || !['tenant-preference', 'url-with-tenant-preference'].includes(source.persistence))
2072
+ context.addIssue({ code: 'custom', path: ['stateDefinitions', index, 'initialValue'], message: 'Root initialization requires an independently hydrated tenant preference; initialization chains are forbidden.' });
2073
+ }
2074
+ if (definition.initialValue) {
2075
+ for (const read of (0, page_expression_1.collectPageExpressionReads)(definition.initialValue)) {
2076
+ if (!['scope', 'state'].includes(read.root) || (read.root === 'state' && !stateDefinitions.some((state) => state.stateId === read.path[0] && !state.scopeNodeId)))
2077
+ context.addIssue({ code: 'custom', path: ['stateDefinitions', index, 'initialValue'], message: 'Instance initial values can read only their item scope and declared Page state.' });
2078
+ }
2079
+ }
2080
+ });
2081
+ value.renderTree.nodes.forEach((node, index) => {
2082
+ if (!node.repeat)
2083
+ return;
2084
+ for (const read of (0, page_expression_1.collectPageExpressionReads)(node.repeat.source)) {
2085
+ if (read.root === 'actions' || read.root === 'intent' || read.root === 'result' || read.root === 'model' || (read.root === 'state' && !stateIds.has(read.path[0] ?? '')) || (read.root === 'bindings' && ![...value.ontologyBindings, ...queryBindings].some((binding) => binding.bindingId === read.path[0])))
2086
+ context.addIssue({ code: 'custom', path: ['renderTree', 'nodes', index, 'repeat', 'source'], message: 'Repeated nodes require declared Page state, query results, or parent item scope.' });
2087
+ }
2088
+ });
2089
+ // A declaration can consume its own instance or ancestors, never an arbitrary sibling row.
2090
+ const renderNodesById = new Map(value.renderTree.nodes.map((node) => [node.nodeId, node]));
2091
+ const instanceNodes = new Map(value.capabilityInstances.map((instance) => [instance.instanceId, instance.nodeId]));
2092
+ const scopesForNode = (nodeId) => {
2093
+ const scopes = new Set();
2094
+ const visited = new Set();
2095
+ let node = nodeId ? renderNodesById.get(nodeId) : undefined;
2096
+ while (node && !visited.has(node.nodeId)) {
2097
+ visited.add(node.nodeId);
2098
+ if (node.repeat)
2099
+ scopes.add(node.nodeId);
2100
+ node = node.parentNodeId ? renderNodesById.get(node.parentNodeId) : undefined;
2101
+ }
2102
+ return scopes;
2103
+ };
2104
+ const queryOwners = new Map([...value.ontologyBindings, ...queryBindings].map((binding) => [
2105
+ binding.bindingId, [...scopesForNode(instanceNodes.get(binding.target.capabilityInstanceId))][0],
2106
+ ]));
2107
+ const actionScopes = new Map(value.actionBindings.map(binding => [binding.bindingId, scopesForNode(instanceNodes.get(binding.source.capabilityInstanceId))]));
2108
+ const actionOwners = new Map([...actionScopes].map(([id, scopes]) => [id, [...scopes][0]]));
2109
+ if (actionOwners.size !== value.actionBindings.length)
2110
+ context.addIssue({ code: 'custom', path: ['actionBindings'], message: 'Action binding identifiers must be unique.' });
2111
+ const checkScopedReads = (reads, scopes, path) => {
2112
+ for (const read of reads) {
2113
+ const owner = read.root === 'state' ? stateDefinitions.find((state) => state.stateId === read.path[0])?.scopeNodeId
2114
+ : read.root === 'bindings' ? queryOwners.get(read.path[0]) : read.root === 'actions' ? actionOwners.get(read.path[0]) : undefined;
2115
+ // A parent may observe only aggregate pending for its descendant action scopes.
2116
+ // Error codes and individual row state remain scoped; unrelated repeat branches cannot aggregate.
2117
+ const pendingAggregate = read.root === 'actions' && read.path.length === 2 && read.path[1] === 'pending'
2118
+ && actionScopes.has(read.path[0]) && [...scopes].every(scope => actionScopes.get(read.path[0]).has(scope));
2119
+ if (owner && !scopes.has(owner) && !pendingAggregate)
2120
+ context.addIssue({ code: 'custom', path, message: 'A binding cannot read state, query results, or individual action status from a sibling or descendant instance.' });
2121
+ }
2122
+ };
2123
+ const checkScopedSources = (sources, scopes, path) => {
2124
+ Object.entries(sources).forEach(([name, source]) => {
2125
+ if (source.kind === 'expression')
2126
+ checkScopedReads((0, page_expression_1.collectPageExpressionReads)(source.expression), scopes, [...path, name]);
2127
+ if (source.kind === 'page-state')
2128
+ checkScopedReads([{ kind: 'read', root: 'state', path: [source.stateId] }], scopes, [...path, name]);
2129
+ if (source.kind === 'binding-field')
2130
+ checkScopedReads([{ kind: 'read', root: 'bindings', path: [source.bindingId] }], scopes, [...path, name]);
2131
+ if (source.kind === 'scope-field' && scopes.size === 0)
2132
+ context.addIssue({ code: 'custom', path: [...path, name], message: 'A scope field requires a repeated instance.' });
2133
+ });
2134
+ };
2135
+ value.capabilityInstances.forEach((instance, index) => instance.eventPayloadBindings?.forEach((binding, bindingIndex) => checkScopedReads((0, page_expression_1.collectPageExpressionReads)(binding.expression), scopesForNode(instance.nodeId), ['capabilityInstances', index, 'eventPayloadBindings', bindingIndex])));
2136
+ value.capabilityInstances.forEach((instance, index) => instance.propertyBindings?.forEach((binding, bindingIndex) => {
2137
+ checkScopedReads((0, page_expression_1.collectPageExpressionReads)(binding.expression), scopesForNode(instance.nodeId), ['capabilityInstances', index, 'propertyBindings', bindingIndex]);
2138
+ }));
2139
+ for (const [collection, bindings] of [['ontologyBindings', value.ontologyBindings], ['queryBindings', queryBindings]]) {
2140
+ bindings.forEach((binding, index) => checkScopedSources(binding.parameters, scopesForNode(instanceNodes.get(binding.target.capabilityInstanceId)), [collection, index, 'parameters']));
2141
+ }
2142
+ value.actionBindings.forEach((binding, index) => {
2143
+ const scopes = scopesForNode(instanceNodes.get(binding.source.capabilityInstanceId));
2144
+ checkScopedSources(binding.inputMapping, scopes, ['actionBindings', index, 'inputMapping']);
2145
+ for (const [fileIndex, file] of (binding.fileInputs ?? []).entries()) {
2146
+ if (file.source.kind === 'page-state') {
2147
+ const stateId = file.source.stateId;
2148
+ const state = stateDefinitions.find(candidate => candidate.stateId === stateId);
2149
+ if (!state || state.persistence !== 'none')
2150
+ context.addIssue({ code: 'custom', path: ['actionBindings', index, 'fileInputs', fileIndex], message: 'File handles require declared nonpersistent Page state.' });
2151
+ checkScopedSources({ file: file.source }, scopes, ['actionBindings', index, 'fileInputs', fileIndex]);
2152
+ }
2153
+ }
2154
+ if (binding.when) {
2155
+ const reads = (0, page_expression_1.collectPageExpressionReads)(binding.when);
2156
+ checkScopedReads(reads, scopes, ['actionBindings', index, 'when']);
2157
+ for (const read of reads)
2158
+ if (read.root === 'state' && !stateIds.has(read.path[0]))
2159
+ context.addIssue({ code: 'custom', path: ['actionBindings', index, 'when'], message: 'Action condition references an unknown Page state.' });
2160
+ }
2161
+ });
2162
+ interactionBindings.forEach((binding, index) => {
2163
+ const sourceScopes = scopesForNode(instanceNodes.get(binding.source.capabilityInstanceId));
2164
+ const targetScope = stateDefinitions.find((state) => state.stateId === binding.targetStateId)?.scopeNodeId;
2165
+ if (targetScope && !sourceScopes.has(targetScope))
2166
+ context.addIssue({ code: 'custom', path: ['interactionBindings', index, 'targetStateId'], message: 'An interaction must target its own instance or an ancestor.' });
2167
+ checkScopedSources(binding.inputMapping, targetScope ? scopesForNode(targetScope) : new Set(), ['interactionBindings', index, 'inputMapping']);
2168
+ });
2169
+ value.renderTree.nodes.forEach((node, index) => {
2170
+ if (node.repeat)
2171
+ checkScopedReads((0, page_expression_1.collectPageExpressionReads)(node.repeat.source), scopesForNode(node.parentNodeId), ['renderTree', 'nodes', index, 'repeat', 'source']);
2172
+ });
2173
+ entryTransitions.forEach((transition, transitionIndex) => {
2174
+ if (transition.gestureSource &&
2175
+ (!instanceIds.has(transition.gestureSource.capabilityInstanceId) ||
2176
+ !interactionBindings.some((binding) => binding.source.capabilityInstanceId === transition.gestureSource.capabilityInstanceId &&
2177
+ binding.source.port === transition.gestureSource.port))) {
2178
+ context.addIssue({
2179
+ code: 'custom',
2180
+ path: ['entryTransitions', transitionIndex, 'gestureSource'],
2181
+ message: 'Entry gesture source must match an existing capability interaction source.',
2182
+ });
2183
+ }
2184
+ if (transition.targetRef.kind === 'page' && transition.targetRef.id === value.pageId) {
2185
+ context.addIssue({
2186
+ code: 'custom',
2187
+ path: ['entryTransitions', transitionIndex, 'targetRef'],
2188
+ message: 'A Page cannot route into itself on entry.',
2189
+ });
2190
+ }
2191
+ forEachPageStateCondition(transition.activation, (condition, conditionPath) => {
2192
+ if (!stateIds.has(condition.stateId)) {
2193
+ context.addIssue({
2194
+ code: 'custom',
2195
+ path: ['entryTransitions', transitionIndex, 'activation', ...conditionPath, 'stateId'],
2196
+ message: `Entry transition references unknown Page state ${condition.stateId}.`,
2197
+ });
2198
+ }
2199
+ });
2200
+ const declaredRouteParameters = new Set(value.routeClaims.flatMap((claim) => [...claim.pathTemplate.matchAll(/:([A-Za-z][A-Za-z0-9_-]*)/g)].map((match) => match[1])));
2201
+ for (const source of [...Object.values(transition.pathParameters), ...Object.values(transition.query.set)]) {
2202
+ if (source.kind === 'route-parameter' && !declaredRouteParameters.has(source.name)) {
2203
+ context.addIssue({ code: 'custom', path: ['entryTransitions', transitionIndex], message: `Entry transition references unknown source route parameter ${source.name}.` });
2204
+ }
2205
+ }
2206
+ const stateSources = [
2207
+ ...Object.values(transition.pathParameters).flatMap((source) => source.kind === 'page-state' ? [source.stateId] : []),
2208
+ ...Object.values(transition.query.set).flatMap((source) => source.kind === 'page-state' ? [source.stateId] : []),
2209
+ ];
2210
+ stateSources.forEach((stateId) => {
2211
+ if (!stateIds.has(stateId)) {
2212
+ context.addIssue({
2213
+ code: 'custom',
2214
+ path: ['entryTransitions', transitionIndex],
2215
+ message: `Entry transition references unknown Page state ${stateId}.`,
2216
+ });
2217
+ }
2218
+ });
2219
+ });
2220
+ const validatePageStateSources = (sources, path) => {
2221
+ Object.entries(sources).forEach(([name, source]) => {
2222
+ if (source.kind === 'expression') {
2223
+ for (const read of (0, page_expression_1.collectPageExpressionReads)(source.expression)) {
2224
+ if ((read.root === 'state' && (!read.path[0] || !stateIds.has(read.path[0]))) || read.root === 'model' || read.root === 'bindings' || (read.root === 'result' && path[0] !== 'interactionBindings')) {
2225
+ context.addIssue({ code: 'custom', path: [...path, name, 'expression'], message: 'Expression references unavailable context or an undeclared Page state.' });
2226
+ }
2227
+ }
2228
+ }
2229
+ if (source.kind === 'page-state' && !stateIds.has(source.stateId)) {
2230
+ context.addIssue({
2231
+ code: 'custom',
2232
+ path: [...path, name, 'stateId'],
2233
+ message: `Unknown Page state: ${source.stateId}`,
2234
+ });
2235
+ }
2236
+ });
2237
+ };
2238
+ interactionBindings.forEach((binding, index) => {
2239
+ if (!instanceIds.has(binding.source.capabilityInstanceId)) {
2240
+ context.addIssue({
2241
+ code: 'custom',
2242
+ path: ['interactionBindings', index, 'source', 'capabilityInstanceId'],
2243
+ message: 'Interaction binding sources an unknown capability instance.',
2244
+ });
2245
+ }
2246
+ if (!stateIds.has(binding.targetStateId)) {
2247
+ context.addIssue({
2248
+ code: 'custom',
2249
+ path: ['interactionBindings', index, 'targetStateId'],
2250
+ message: 'Interaction binding targets an unknown Page state.',
2251
+ });
2252
+ }
2253
+ validatePageStateSources(binding.inputMapping, ['interactionBindings', index, 'inputMapping']);
2254
+ if (Object.values(binding.inputMapping).some((source) => source.kind === 'result-field' || (source.kind === 'expression' && (0, page_expression_1.collectPageExpressionReads)(source.expression).some((read) => read.root === 'result')))) {
2255
+ const action = value.actionBindings.find((candidate) => candidate.source.capabilityInstanceId === binding.source.capabilityInstanceId &&
2256
+ candidate.source.port === binding.source.port);
2257
+ if (!action) {
2258
+ context.addIssue({
2259
+ code: 'custom',
2260
+ path: ['interactionBindings', index, 'source'],
2261
+ message: 'Result-field interactions must source one declared Action binding.',
2262
+ });
2263
+ }
2264
+ else if (!binding.sourceResultSchemaRevisionRef ||
2265
+ !exactRevisionRef(binding.sourceResultSchemaRevisionRef, action.resultSchemaRevisionRef)) {
2266
+ context.addIssue({
2267
+ code: 'custom',
2268
+ path: ['interactionBindings', index, 'sourceResultSchemaRevisionRef'],
2269
+ message: 'Interaction result schema must exactly match the sourced Action result schema.',
2270
+ });
2271
+ }
2272
+ }
2273
+ });
2274
+ value.capabilityInstances.forEach((instance, index) => {
2275
+ const paths = [];
2276
+ instance.propertyBindings?.forEach((binding, bindingIndex) => {
2277
+ const path = ['capabilityInstances', index, 'propertyBindings', bindingIndex];
2278
+ if (instance.capabilityRevisionRef.id.startsWith('monkeys.design.component.')) {
2279
+ const [group, member, field] = binding.targetPath;
2280
+ const messages = instance.properties.messages;
2281
+ const message = messages && typeof messages === 'object' && !Array.isArray(messages) ? messages[member] : undefined;
2282
+ const declaredMessage = message && typeof message === 'object' && !Array.isArray(message);
2283
+ const allowed = (group === 'props' && binding.targetPath.length >= 2) ||
2284
+ (group === 'messages' && declaredMessage && ((field === 'values' && binding.targetPath.length >= 3) ||
2285
+ (binding.targetPath.length === 3 && (field === 'key' || field === 'textI18n') &&
2286
+ Object.prototype.hasOwnProperty.call(message, field))));
2287
+ if (!allowed)
2288
+ context.addIssue({ code: 'custom', path: [...path, 'targetPath'], message: 'Component expressions can update property values or declared message fields, never executable wiring.' });
2289
+ }
2290
+ if (paths.some((previous) => previous.slice(0, Math.min(previous.length, binding.targetPath.length)).every((key, i) => key === binding.targetPath[i]))) {
2291
+ context.addIssue({ code: 'custom', path: [...path, 'targetPath'], message: 'Dynamic property targets must not overlap.' });
2292
+ }
2293
+ paths.push(binding.targetPath);
2294
+ (0, page_expression_1.visitPageExpression)(binding.expression, (expression) => {
2295
+ if (expression.kind !== 'read')
2296
+ return;
2297
+ if (expression.root === 'state' && (!expression.path[0] || !stateIds.has(expression.path[0]))) {
2298
+ context.addIssue({ code: 'custom', path: [...path, 'expression'], message: 'Property expressions must reference a declared Page state.' });
2299
+ }
2300
+ if (expression.root === 'bindings' && ![...value.ontologyBindings, ...queryBindings].some((candidate) => candidate.bindingId === expression.path[0])) {
2301
+ context.addIssue({ code: 'custom', path: [...path, 'expression'], message: 'Property expressions must reference a declared Page query binding.' });
2302
+ }
2303
+ if (expression.root === 'actions' && (expression.path.length !== 2 || !actionOwners.has(expression.path[0]) || !['pending', 'errorCode'].includes(expression.path[1]))) {
2304
+ context.addIssue({ code: 'custom', path: [...path, 'expression'], message: 'Property expressions may read only pending or errorCode from a declared Action binding.' });
2305
+ }
2306
+ if (expression.root === 'intent' || expression.root === 'result') {
2307
+ context.addIssue({ code: 'custom', path: [...path, 'expression'], message: 'Property expressions cannot read transient event or Action results.' });
2308
+ }
2309
+ });
2310
+ });
2311
+ if (!instance.activationWhen)
2312
+ return;
2313
+ forEachPageStateCondition(instance.activationWhen, (condition, conditionPath) => {
2314
+ if (!stateIds.has(condition.stateId)) {
2315
+ context.addIssue({
2316
+ code: 'custom',
2317
+ path: ['capabilityInstances', index, 'activationWhen', ...conditionPath, 'stateId'],
2318
+ message: 'Capability activation condition references an unknown Page state.',
2319
+ });
2320
+ }
2321
+ });
599
2322
  });
600
- const instanceIds = new Set(value.capabilityInstances.map((instance) => instance.instanceId));
601
2323
  value.ontologyBindings.forEach((binding, index) => {
602
2324
  if (!instanceIds.has(binding.target.capabilityInstanceId)) {
603
- context.addIssue({ code: 'custom', path: ['ontologyBindings', index, 'target', 'capabilityInstanceId'], message: 'Ontology binding targets an unknown capability instance.' });
2325
+ context.addIssue({
2326
+ code: 'custom',
2327
+ path: ['ontologyBindings', index, 'target', 'capabilityInstanceId'],
2328
+ message: 'Ontology binding targets an unknown capability instance.',
2329
+ });
2330
+ }
2331
+ validatePageStateSources(binding.parameters, ['ontologyBindings', index, 'parameters']);
2332
+ });
2333
+ queryBindings.forEach((binding, index) => {
2334
+ if (!instanceIds.has(binding.target.capabilityInstanceId)) {
2335
+ context.addIssue({
2336
+ code: 'custom',
2337
+ path: ['queryBindings', index, 'target', 'capabilityInstanceId'],
2338
+ message: 'Query binding targets an unknown capability instance.',
2339
+ });
2340
+ }
2341
+ validatePageStateSources(binding.parameters, ['queryBindings', index, 'parameters']);
2342
+ if (binding.queryWhen) {
2343
+ forEachPageStateCondition(binding.queryWhen, (condition, conditionPath) => {
2344
+ if (!stateIds.has(condition.stateId)) {
2345
+ context.addIssue({
2346
+ code: 'custom',
2347
+ path: ['queryBindings', index, 'queryWhen', ...conditionPath, 'stateId'],
2348
+ message: 'Query activation condition references an unknown Page state.',
2349
+ });
2350
+ }
2351
+ });
2352
+ }
2353
+ if (binding.refreshPolicy?.when) {
2354
+ const path = ['queryBindings', index, 'refreshPolicy', 'when'];
2355
+ const reads = (0, page_expression_1.collectExternalPageExpressionReads)(binding.refreshPolicy.when);
2356
+ checkScopedReads(reads, scopesForNode(instanceNodes.get(binding.target.capabilityInstanceId)), path);
2357
+ for (const read of reads)
2358
+ if (read.root === 'actions' && !actionScopes.has(read.path[0]))
2359
+ context.addIssue({ code: 'custom', path, message: 'Automatic refresh condition references an unknown action binding.' });
2360
+ }
2361
+ const effectOwners = new Set(binding.stateEffects?.map((effect) => stateDefinitions.find((definition) => definition.stateId === effect.targetStateId)?.scopeNodeId ?? '') ?? []);
2362
+ if (effectOwners.size > 1)
2363
+ context.addIssue({ code: 'custom', path: ['queryBindings', index, 'stateEffects'], message: 'A query completion must update a single state owner atomically.' });
2364
+ binding.stateEffects?.forEach((effect, effectIndex) => {
2365
+ const path = ['queryBindings', index, 'stateEffects', effectIndex];
2366
+ const state = stateDefinitions.find((definition) => definition.stateId === effect.targetStateId);
2367
+ const scopes = scopesForNode(instanceNodes.get(binding.target.capabilityInstanceId));
2368
+ if (!state || (state.scopeNodeId && !scopes.has(state.scopeNodeId)))
2369
+ context.addIssue({ code: 'custom', path: [...path, 'targetStateId'], message: 'A query effect must target declared state in its own scope or an ancestor.' });
2370
+ for (const expression of [effect.value, ...(effect.when ? [effect.when] : [])]) {
2371
+ const reads = (0, page_expression_1.collectPageExpressionReads)(expression);
2372
+ checkScopedReads(reads, state?.scopeNodeId ? scopesForNode(state.scopeNodeId) : new Set(), path);
2373
+ for (const read of reads)
2374
+ if (read.root === 'state' && !stateIds.has(read.path[0]))
2375
+ context.addIssue({ code: 'custom', path, message: 'Query effect references an unknown Page state.' });
2376
+ }
2377
+ });
2378
+ binding.resultStateBindings?.forEach((resultBinding, resultIndex) => {
2379
+ const stateDefinition = stateDefinitions.find((definition) => definition.stateId === resultBinding.targetStateId);
2380
+ if (!stateDefinition) {
2381
+ context.addIssue({
2382
+ code: 'custom',
2383
+ path: ['queryBindings', index, 'resultStateBindings', resultIndex, 'targetStateId'],
2384
+ message: 'Query result binding targets an unknown Page state.',
2385
+ });
2386
+ return;
2387
+ }
2388
+ if (!exactRevisionRef(resultBinding.targetStateSchemaRevisionRef, stateDefinition.schemaRevisionRef)) {
2389
+ context.addIssue({
2390
+ code: 'custom',
2391
+ path: ['queryBindings', index, 'resultStateBindings', resultIndex, 'targetStateSchemaRevisionRef'],
2392
+ message: 'Query result binding must pin the exact target Page-state schema revision.',
2393
+ });
2394
+ }
2395
+ if (stateDefinition.persistence !== 'none') {
2396
+ context.addIssue({
2397
+ code: 'custom',
2398
+ path: ['queryBindings', index, 'resultStateBindings', resultIndex, 'targetStateId'],
2399
+ message: 'Query result state projections must target non-persistent Page state.',
2400
+ });
2401
+ }
2402
+ });
2403
+ });
2404
+ const dependencyBindings = [...value.ontologyBindings, ...queryBindings];
2405
+ (0, page_binding_dependencies_1.analyzePageBindingDependencies)(dependencyBindings).errors.forEach((error) => {
2406
+ const ontologyIndex = value.ontologyBindings.findIndex((binding) => binding.bindingId === error.bindingId);
2407
+ const collection = ontologyIndex >= 0 ? 'ontologyBindings' : 'queryBindings';
2408
+ const index = ontologyIndex >= 0 ? ontologyIndex : queryBindings.findIndex((binding) => binding.bindingId === error.bindingId);
2409
+ context.addIssue({ code: 'custom', path: [collection, index, 'parameters'], message: `Page binding dependency ${error.code}: ${error.dependencyId ?? error.bindingId}` });
2410
+ });
2411
+ const projectedQueryStateIds = new Set();
2412
+ queryBindings.forEach((binding, index) => {
2413
+ binding.resultStateBindings?.forEach((resultBinding, resultIndex) => {
2414
+ if (projectedQueryStateIds.has(resultBinding.targetStateId)) {
2415
+ context.addIssue({
2416
+ code: 'custom',
2417
+ path: ['queryBindings', index, 'resultStateBindings', resultIndex, 'targetStateId'],
2418
+ message: 'One Page state can have only one Server Query result owner.',
2419
+ });
2420
+ }
2421
+ projectedQueryStateIds.add(resultBinding.targetStateId);
2422
+ });
2423
+ });
2424
+ queryBindings.forEach((binding, index) => {
2425
+ if (binding.execution !== 'server')
2426
+ return;
2427
+ const readStateIds = new Set(Object.values(binding.parameters).flatMap((source) => (source.kind === 'page-state' ? [source.stateId] : [])));
2428
+ if (binding.queryWhen) {
2429
+ forEachPageStateCondition(binding.queryWhen, (condition) => readStateIds.add(condition.stateId));
2430
+ }
2431
+ const feedbackStateId = [...readStateIds].find((stateId) => projectedQueryStateIds.has(stateId));
2432
+ if (feedbackStateId) {
2433
+ context.addIssue({
2434
+ code: 'custom',
2435
+ path: ['queryBindings', index],
2436
+ message: `Server Query bindings cannot read projected Query result state: ${feedbackStateId}`,
2437
+ });
2438
+ }
2439
+ });
2440
+ const cursorWindowBindings = [
2441
+ ...value.ontologyBindings.map((binding, index) => ({
2442
+ binding,
2443
+ index,
2444
+ collection: 'ontologyBindings',
2445
+ })),
2446
+ ...queryBindings.map((binding, index) => ({
2447
+ binding,
2448
+ index,
2449
+ collection: 'queryBindings',
2450
+ })),
2451
+ ].filter((candidate) => candidate.binding.cursorWindow !== undefined);
2452
+ const cursorWindowSources = new Set();
2453
+ cursorWindowBindings.forEach(({ binding, index, collection }) => {
2454
+ const sourceId = binding.cursorWindow.sourceCapabilityInstanceId ?? binding.target.capabilityInstanceId;
2455
+ if (!instanceIds.has(sourceId))
2456
+ context.addIssue({ code: 'custom', path: [collection, index, 'cursorWindow', 'sourceCapabilityInstanceId'], message: 'Cursor-window source must reference a declared capability instance.' });
2457
+ const sourceScopes = [...scopesForNode(instanceNodes.get(sourceId))];
2458
+ const targetScopes = [...scopesForNode(instanceNodes.get(binding.target.capabilityInstanceId))];
2459
+ if (sourceScopes.length !== targetScopes.length || sourceScopes.some((id, index) => id !== targetScopes[index]))
2460
+ context.addIssue({ code: 'custom', path: [collection, index, 'cursorWindow', 'sourceCapabilityInstanceId'], message: 'Cursor-window source and query target must share the same repeated scope.' });
2461
+ const sourceKey = `${sourceId}:${binding.cursorWindow.pageChangePort}`;
2462
+ if (cursorWindowSources.has(sourceKey)) {
2463
+ context.addIssue({
2464
+ code: 'custom',
2465
+ path: [collection, index, 'cursorWindow', 'pageChangePort'],
2466
+ message: 'A capability output port can govern at most one cursor-window binding.',
2467
+ });
2468
+ }
2469
+ cursorWindowSources.add(sourceKey);
2470
+ const conflictingInteraction = interactionBindings.some((candidate) => candidate.source.capabilityInstanceId === sourceId &&
2471
+ candidate.source.port === binding.cursorWindow.pageChangePort);
2472
+ const conflictingAction = value.actionBindings.some((candidate) => candidate.source.capabilityInstanceId === sourceId &&
2473
+ candidate.source.port === binding.cursorWindow.pageChangePort);
2474
+ if (conflictingInteraction || conflictingAction) {
2475
+ context.addIssue({
2476
+ code: 'custom',
2477
+ path: [collection, index, 'cursorWindow', 'pageChangePort'],
2478
+ message: 'Cursor-window page-change ports cannot also drive Page state or Domain Actions.',
2479
+ });
604
2480
  }
605
2481
  });
606
2482
  value.actionBindings.forEach((binding, index) => {
607
2483
  if (!instanceIds.has(binding.source.capabilityInstanceId)) {
608
- context.addIssue({ code: 'custom', path: ['actionBindings', index, 'source', 'capabilityInstanceId'], message: 'Action binding sources an unknown capability instance.' });
2484
+ context.addIssue({
2485
+ code: 'custom',
2486
+ path: ['actionBindings', index, 'source', 'capabilityInstanceId'],
2487
+ message: 'Action binding sources an unknown capability instance.',
2488
+ });
609
2489
  }
2490
+ validatePageStateSources(binding.inputMapping, ['actionBindings', index, 'inputMapping']);
610
2491
  binding.success.refreshBindingIds.forEach((bindingId) => {
611
- if (!value.ontologyBindings.some((candidate) => candidate.bindingId === bindingId)) {
612
- context.addIssue({ code: 'custom', path: ['actionBindings', index, 'success', 'refreshBindingIds'], message: `Unknown refresh binding: ${bindingId}` });
2492
+ if (!value.ontologyBindings.some((candidate) => candidate.bindingId === bindingId) &&
2493
+ !queryBindings.some((candidate) => candidate.bindingId === bindingId)) {
2494
+ context.addIssue({
2495
+ code: 'custom',
2496
+ path: ['actionBindings', index, 'success', 'refreshBindingIds'],
2497
+ message: `Unknown refresh binding: ${bindingId}`,
2498
+ });
613
2499
  }
614
2500
  });
2501
+ const effectBindingIds = binding.success.resultBindingEffects?.map((effect) => effect.bindingId) ?? [];
2502
+ if (new Set(effectBindingIds).size !== effectBindingIds.length) {
2503
+ context.addIssue({
2504
+ code: 'custom',
2505
+ path: ['actionBindings', index, 'success', 'resultBindingEffects'],
2506
+ message: 'An Action can declare at most one result effect for each target binding.',
2507
+ });
2508
+ }
2509
+ binding.success.resultBindingEffects?.forEach((effect, effectIndex) => {
2510
+ if (!value.ontologyBindings.some((candidate) => candidate.bindingId === effect.bindingId) &&
2511
+ !queryBindings.some((candidate) => candidate.bindingId === effect.bindingId)) {
2512
+ context.addIssue({
2513
+ code: 'custom',
2514
+ path: ['actionBindings', index, 'success', 'resultBindingEffects', effectIndex, 'bindingId'],
2515
+ message: `Unknown result effect binding: ${effect.bindingId}`,
2516
+ });
2517
+ }
2518
+ if (binding.success.refreshBindingIds.includes(effect.bindingId)) {
2519
+ context.addIssue({
2520
+ code: 'custom',
2521
+ path: ['actionBindings', index, 'success', 'resultBindingEffects', effectIndex, 'bindingId'],
2522
+ message: 'A result-projected binding cannot also be refreshed by the same Action.',
2523
+ });
2524
+ }
2525
+ });
2526
+ if (binding.success.clipboard) {
2527
+ const sourceInstance = value.capabilityInstances.find((instance) => instance.instanceId === binding.source.capabilityInstanceId);
2528
+ if (!sourceInstance?.allowedSideEffects.includes('clipboard'))
2529
+ context.addIssue({
2530
+ code: 'custom',
2531
+ path: ['actionBindings', index, 'success', 'clipboard'],
2532
+ message: 'An Action result clipboard effect requires clipboard permission on its source capability instance.',
2533
+ });
2534
+ }
615
2535
  });
616
2536
  if (value.shellRevisionRef.kind !== 'shell')
617
- context.addIssue({ code: 'custom', path: ['shellRevisionRef', 'kind'], message: 'Page shellRevisionRef must reference a shell.' });
2537
+ context.addIssue({
2538
+ code: 'custom',
2539
+ path: ['shellRevisionRef', 'kind'],
2540
+ message: 'Page shellRevisionRef must reference a shell.',
2541
+ });
618
2542
  value.capabilityInstances.forEach((instance, index) => {
2543
+ if (instance.accessPolicy) {
2544
+ [
2545
+ ...instance.accessPolicy.groupAllOf,
2546
+ ...instance.accessPolicy.groupAnyOf,
2547
+ ...instance.accessPolicy.conditionAllOf,
2548
+ ].forEach((reference, refIndex) => validateTenantCompatibleReference(reference, value.tenantScope, ['capabilityInstances', index, 'accessPolicy', refIndex], context));
2549
+ }
619
2550
  const refs = [
620
2551
  ['capabilityRevisionRef', instance.capabilityRevisionRef, 'capability'],
621
2552
  ['providerRevisionRef', instance.providerRevisionRef, 'view-provider'],
@@ -623,46 +2554,95 @@ exports.PageSchema = zod_1.z
623
2554
  ];
624
2555
  refs.forEach(([field, reference, expectedKind]) => {
625
2556
  if (reference.kind !== expectedKind)
626
- context.addIssue({ code: 'custom', path: ['capabilityInstances', index, field, 'kind'], message: `${field} must reference ${expectedKind}.` });
2557
+ context.addIssue({
2558
+ code: 'custom',
2559
+ path: ['capabilityInstances', index, field, 'kind'],
2560
+ message: `${field} must reference ${expectedKind}.`,
2561
+ });
627
2562
  });
628
2563
  });
629
2564
  value.tokenRevisionRefs.forEach((reference, index) => {
630
2565
  if (reference.kind !== 'design-token')
631
- context.addIssue({ code: 'custom', path: ['tokenRevisionRefs', index, 'kind'], message: 'Page token references must use kind design-token.' });
2566
+ context.addIssue({
2567
+ code: 'custom',
2568
+ path: ['tokenRevisionRefs', index, 'kind'],
2569
+ message: 'Page token references must use kind design-token.',
2570
+ });
632
2571
  });
633
2572
  if (value.performanceBudgetRef.kind !== 'performance-budget')
634
- context.addIssue({ code: 'custom', path: ['performanceBudgetRef', 'kind'], message: 'Page requires a performance-budget revision.' });
2573
+ context.addIssue({
2574
+ code: 'custom',
2575
+ path: ['performanceBudgetRef', 'kind'],
2576
+ message: 'Page requires a performance-budget revision.',
2577
+ });
635
2578
  if (value.observationPolicyRevisionRef.kind !== 'observation-policy')
636
- context.addIssue({ code: 'custom', path: ['observationPolicyRevisionRef', 'kind'], message: 'Page requires an observation-policy revision.' });
2579
+ context.addIssue({
2580
+ code: 'custom',
2581
+ path: ['observationPolicyRevisionRef', 'kind'],
2582
+ message: 'Page requires an observation-policy revision.',
2583
+ });
637
2584
  });
638
2585
  exports.PageReleaseSchema = zod_1.z
639
2586
  .object({
640
2587
  contract: zod_1.z.literal('PageRelease'),
641
2588
  ...ReleaseBaseShape,
642
2589
  pageRevisionRef: exports.RevisionRefSchema,
643
- target: zod_1.z.object({
2590
+ target: zod_1.z
2591
+ .object({
644
2592
  environmentRef: exports.StableRefSchema,
645
2593
  surface: exports.ProductSurfaceSchema,
646
2594
  routeSpaceRevisionRef: exports.RevisionRefSchema,
647
2595
  normalizedPath: exports.RoutePathTemplateSchema,
648
2596
  routeClaim: zod_1.z.union([exports.CompiledRouteClaimSchema, LegacyCompiledRouteClaimSchema]),
649
- }).strict(),
2597
+ legacyRouteTakeoverAuthorization: exports.LegacyRouteTakeoverAuthorizationSchema.optional(),
2598
+ })
2599
+ .strict(),
650
2600
  })
651
2601
  .strict()
652
2602
  .superRefine((value, context) => {
2603
+ validateLegacyRestoration(value, 'legacy-route-adapter', context);
653
2604
  validateDependencySnapshot(value.dependencySnapshot, context);
654
- validateReleaseExpectedHead(value, 'page-release', context);
2605
+ validateReleaseGovernanceScope(value, 'page-release', context);
655
2606
  if (value.pageRevisionRef.kind !== 'page')
656
- context.addIssue({ code: 'custom', path: ['pageRevisionRef', 'kind'], message: 'Expected a page revision.' });
2607
+ context.addIssue({
2608
+ code: 'custom',
2609
+ path: ['pageRevisionRef', 'kind'],
2610
+ message: 'Expected a page revision.',
2611
+ });
657
2612
  if (value.target.environmentRef.kind !== 'environment')
658
- context.addIssue({ code: 'custom', path: ['target', 'environmentRef', 'kind'], message: 'Page release target requires an environment reference.' });
2613
+ context.addIssue({
2614
+ code: 'custom',
2615
+ path: ['target', 'environmentRef', 'kind'],
2616
+ message: 'Page release target requires an environment reference.',
2617
+ });
659
2618
  if (value.target.routeSpaceRevisionRef.kind !== 'route-space')
660
- context.addIssue({ code: 'custom', path: ['target', 'routeSpaceRevisionRef', 'kind'], message: 'Page release target requires a route-space revision.' });
661
- if (value.target.normalizedPath !== value.target.routeClaim.normalizedPath || !exactRevisionRef(value.target.routeSpaceRevisionRef, value.target.routeClaim.routeSpaceRevisionRef)) {
662
- context.addIssue({ code: 'custom', path: ['target', 'routeClaim'], message: 'Page Release route authority must pin the exact compiled claim and normalized path.' });
2619
+ context.addIssue({
2620
+ code: 'custom',
2621
+ path: ['target', 'routeSpaceRevisionRef', 'kind'],
2622
+ message: 'Page release target requires a route-space revision.',
2623
+ });
2624
+ validateReleaseRouteSurface(value.target, context);
2625
+ if (value.target.normalizedPath !== value.target.routeClaim.normalizedPath ||
2626
+ !exactRevisionRef(value.target.routeSpaceRevisionRef, value.target.routeClaim.routeSpaceRevisionRef)) {
2627
+ context.addIssue({
2628
+ code: 'custom',
2629
+ path: ['target', 'routeClaim'],
2630
+ message: 'Page Release route authority must pin the exact compiled claim and normalized path.',
2631
+ });
663
2632
  }
664
- if ('surface' in value.target.routeClaim && value.target.routeClaim.surface !== value.target.surface) {
665
- context.addIssue({ code: 'custom', path: ['target', 'routeClaim', 'surface'], message: 'Page Release route claim must match the target surface.' });
2633
+ const takeover = value.target.legacyRouteTakeoverAuthorization;
2634
+ if (takeover &&
2635
+ (value.target.routeClaim.kind !== 'canonical' ||
2636
+ takeover.normalizedPath !== value.target.normalizedPath ||
2637
+ !exactRevisionRef(takeover.routeSpaceRevisionRef, value.target.routeSpaceRevisionRef) ||
2638
+ takeover.targetResourceRef.kind !== 'page' ||
2639
+ takeover.targetResourceRef.id !== value.pageRevisionRef.id ||
2640
+ takeover.targetResourceRef.ownerRepo !== value.pageRevisionRef.ownerRepo)) {
2641
+ context.addIssue({
2642
+ code: 'custom',
2643
+ path: ['target', 'legacyRouteTakeoverAuthorization'],
2644
+ message: 'A Page route takeover must bind the exact canonical claim, RouteSpace and target Page.',
2645
+ });
666
2646
  }
667
2647
  });
668
2648
  exports.WorkbenchGroupSchema = zod_1.z
@@ -685,7 +2665,12 @@ exports.WorkbenchAppInstanceSchema = zod_1.z
685
2665
  input: common_1.JsonObjectSchema,
686
2666
  groupId: common_1.ContractIdentifierSchema,
687
2667
  order: zod_1.z.number().int(),
688
- display: zod_1.z.object({ name: exports.I18nTextSchema.optional(), iconRef: exports.StableRefSchema.optional() }).strict(),
2668
+ display: zod_1.z
2669
+ .object({
2670
+ name: exports.I18nTextSchema.optional(),
2671
+ iconRef: exports.StableRefSchema.optional(),
2672
+ })
2673
+ .strict(),
689
2674
  accessPolicy: exports.AccessPolicySchema,
690
2675
  })
691
2676
  .strict();
@@ -703,19 +2688,29 @@ exports.WorkbenchSchema = zod_1.z
703
2688
  routeClaims: zod_1.z.array(zod_1.z.union([exports.DeclarativeRouteClaimSchema, LegacyDeclarativeRouteClaimSchema])).min(1),
704
2689
  groups: zod_1.z.array(exports.WorkbenchGroupSchema).min(1),
705
2690
  appInstances: zod_1.z.array(exports.WorkbenchAppInstanceSchema).min(1),
706
- defaultEntry: zod_1.z.object({ groupId: common_1.ContractIdentifierSchema, instanceId: common_1.ContractIdentifierSchema, showDefaultGroup: zod_1.z.boolean() }).strict(),
707
- layout: zod_1.z.object({
2691
+ defaultEntry: zod_1.z
2692
+ .object({
2693
+ groupId: common_1.ContractIdentifierSchema,
2694
+ instanceId: common_1.ContractIdentifierSchema,
2695
+ showDefaultGroup: zod_1.z.boolean(),
2696
+ })
2697
+ .strict(),
2698
+ layout: zod_1.z
2699
+ .object({
708
2700
  hostCapabilityRevisionRef: exports.RevisionRefSchema,
709
2701
  hostProviderRevisionRef: exports.RevisionRefSchema,
710
2702
  layoutPolicyRevisionRef: exports.RevisionRefSchema,
711
2703
  tokenRevisionRefs: zod_1.z.array(exports.RevisionRefSchema).min(1),
712
- }).strict(),
2704
+ })
2705
+ .strict(),
713
2706
  workbenchAccessPolicy: exports.AccessPolicySchema,
714
2707
  managementAccess: exports.ManagementAccessSchema,
715
- personalPreferencesPolicy: zod_1.z.object({
2708
+ personalPreferencesPolicy: zod_1.z
2709
+ .object({
716
2710
  allowed: zod_1.z.array(zod_1.z.enum(['collapsed-groups', 'recent-entry', 'layout-density'])),
717
2711
  authority: zod_1.z.literal('identity-overlay'),
718
- }).strict(),
2712
+ })
2713
+ .strict(),
719
2714
  performanceBudgetRef: exports.RevisionRefSchema,
720
2715
  observationPolicyRevisionRef: exports.RevisionRefSchema,
721
2716
  privacyClassification: zod_1.z.enum(['public', 'internal', 'confidential', 'restricted']),
@@ -724,22 +2719,42 @@ exports.WorkbenchSchema = zod_1.z
724
2719
  .superRefine((value, context) => {
725
2720
  validateDeclaredRouteSurfaces(value.supportedSurfaces, value.routeClaims, context);
726
2721
  if (value.purpose === 'template' && value.sourceTemplateRevisionRef) {
727
- context.addIssue({ code: 'custom', path: ['sourceTemplateRevisionRef'], message: 'Templates cannot derive from another template at runtime.' });
2722
+ context.addIssue({
2723
+ code: 'custom',
2724
+ path: ['sourceTemplateRevisionRef'],
2725
+ message: 'Templates cannot derive from another template at runtime.',
2726
+ });
728
2727
  }
729
2728
  const duplicateGroup = uniqueArray(value.groups, (group) => group.groupId);
730
2729
  if (duplicateGroup)
731
- context.addIssue({ code: 'custom', path: ['groups'], message: `Duplicate Workbench group: ${duplicateGroup}` });
2730
+ context.addIssue({
2731
+ code: 'custom',
2732
+ path: ['groups'],
2733
+ message: `Duplicate Workbench group: ${duplicateGroup}`,
2734
+ });
732
2735
  const duplicateInstance = uniqueArray(value.appInstances, (instance) => instance.instanceId);
733
2736
  if (duplicateInstance)
734
- context.addIssue({ code: 'custom', path: ['appInstances'], message: `Duplicate Workbench instance: ${duplicateInstance}` });
2737
+ context.addIssue({
2738
+ code: 'custom',
2739
+ path: ['appInstances'],
2740
+ message: `Duplicate Workbench instance: ${duplicateInstance}`,
2741
+ });
735
2742
  const groupIds = new Set(value.groups.map((group) => group.groupId));
736
2743
  value.appInstances.forEach((instance, index) => {
737
2744
  if (!groupIds.has(instance.groupId))
738
- context.addIssue({ code: 'custom', path: ['appInstances', index, 'groupId'], message: 'Workbench app references an unknown group.' });
2745
+ context.addIssue({
2746
+ code: 'custom',
2747
+ path: ['appInstances', index, 'groupId'],
2748
+ message: 'Workbench app references an unknown group.',
2749
+ });
739
2750
  });
740
2751
  const defaultInstance = value.appInstances.find((instance) => instance.instanceId === value.defaultEntry.instanceId);
741
2752
  if (!defaultInstance || defaultInstance.groupId !== value.defaultEntry.groupId) {
742
- context.addIssue({ code: 'custom', path: ['defaultEntry'], message: 'Workbench default entry must reference an app in the declared group.' });
2753
+ context.addIssue({
2754
+ code: 'custom',
2755
+ path: ['defaultEntry'],
2756
+ message: 'Workbench default entry must reference an app in the declared group.',
2757
+ });
743
2758
  }
744
2759
  const targetKindByInstanceKind = {
745
2760
  'built-in-application': 'built-in-application',
@@ -751,11 +2766,23 @@ exports.WorkbenchSchema = zod_1.z
751
2766
  };
752
2767
  value.appInstances.forEach((instance, index) => {
753
2768
  if (instance.targetRevisionRef.kind !== targetKindByInstanceKind[instance.kind])
754
- context.addIssue({ code: 'custom', path: ['appInstances', index, 'targetRevisionRef', 'kind'], message: `Workbench ${instance.kind} target has an incompatible reference kind.` });
2769
+ context.addIssue({
2770
+ code: 'custom',
2771
+ path: ['appInstances', index, 'targetRevisionRef', 'kind'],
2772
+ message: `Workbench ${instance.kind} target has an incompatible reference kind.`,
2773
+ });
755
2774
  if (instance.providerRevisionRef.kind !== 'view-provider')
756
- context.addIssue({ code: 'custom', path: ['appInstances', index, 'providerRevisionRef', 'kind'], message: 'Workbench app provider must use kind view-provider.' });
2775
+ context.addIssue({
2776
+ code: 'custom',
2777
+ path: ['appInstances', index, 'providerRevisionRef', 'kind'],
2778
+ message: 'Workbench app provider must use kind view-provider.',
2779
+ });
757
2780
  if (instance.inputSchemaRevisionRef.kind !== 'schema')
758
- context.addIssue({ code: 'custom', path: ['appInstances', index, 'inputSchemaRevisionRef', 'kind'], message: 'Workbench app input must pin a schema revision.' });
2781
+ context.addIssue({
2782
+ code: 'custom',
2783
+ path: ['appInstances', index, 'inputSchemaRevisionRef', 'kind'],
2784
+ message: 'Workbench app input must pin a schema revision.',
2785
+ });
759
2786
  });
760
2787
  const layoutRefs = [
761
2788
  ['hostCapabilityRevisionRef', value.layout.hostCapabilityRevisionRef, 'capability'],
@@ -764,11 +2791,19 @@ exports.WorkbenchSchema = zod_1.z
764
2791
  ];
765
2792
  layoutRefs.forEach(([field, reference, expectedKind]) => {
766
2793
  if (reference.kind !== expectedKind)
767
- context.addIssue({ code: 'custom', path: ['layout', field, 'kind'], message: `${field} must reference ${expectedKind}.` });
2794
+ context.addIssue({
2795
+ code: 'custom',
2796
+ path: ['layout', field, 'kind'],
2797
+ message: `${field} must reference ${expectedKind}.`,
2798
+ });
768
2799
  });
769
2800
  value.layout.tokenRevisionRefs.forEach((reference, index) => {
770
2801
  if (reference.kind !== 'design-token')
771
- context.addIssue({ code: 'custom', path: ['layout', 'tokenRevisionRefs', index, 'kind'], message: 'Workbench token references must use kind design-token.' });
2802
+ context.addIssue({
2803
+ code: 'custom',
2804
+ path: ['layout', 'tokenRevisionRefs', index, 'kind'],
2805
+ message: 'Workbench token references must use kind design-token.',
2806
+ });
772
2807
  });
773
2808
  });
774
2809
  exports.WorkbenchReleaseSchema = zod_1.z
@@ -776,32 +2811,65 @@ exports.WorkbenchReleaseSchema = zod_1.z
776
2811
  contract: zod_1.z.literal('WorkbenchRelease'),
777
2812
  ...ReleaseBaseShape,
778
2813
  workbenchRevisionRef: exports.RevisionRefSchema,
779
- target: zod_1.z.object({
2814
+ target: zod_1.z
2815
+ .object({
780
2816
  environmentRef: exports.StableRefSchema,
781
2817
  surface: exports.ProductSurfaceSchema,
782
2818
  workbenchId: common_1.ContractIdentifierSchema,
783
2819
  routeSpaceRevisionRef: exports.RevisionRefSchema,
784
2820
  normalizedPath: exports.RoutePathTemplateSchema,
785
2821
  routeClaim: zod_1.z.union([exports.CompiledRouteClaimSchema, LegacyCompiledRouteClaimSchema]),
2822
+ legacyRouteTakeoverAuthorization: exports.LegacyRouteTakeoverAuthorizationSchema.optional(),
786
2823
  order: zod_1.z.number().int(),
787
2824
  isDefaultCandidate: zod_1.z.boolean(),
788
- }).strict(),
2825
+ })
2826
+ .strict(),
789
2827
  })
790
2828
  .strict()
791
2829
  .superRefine((value, context) => {
2830
+ validateLegacyRestoration(value, 'legacy-workbench-adapter', context);
792
2831
  validateDependencySnapshot(value.dependencySnapshot, context);
793
- validateReleaseExpectedHead(value, 'workbench-release', context);
2832
+ validateReleaseGovernanceScope(value, 'workbench-release', context);
794
2833
  if (value.workbenchRevisionRef.kind !== 'workbench')
795
- context.addIssue({ code: 'custom', path: ['workbenchRevisionRef', 'kind'], message: 'Expected a workbench revision.' });
2834
+ context.addIssue({
2835
+ code: 'custom',
2836
+ path: ['workbenchRevisionRef', 'kind'],
2837
+ message: 'Expected a workbench revision.',
2838
+ });
796
2839
  if (value.target.environmentRef.kind !== 'environment')
797
- context.addIssue({ code: 'custom', path: ['target', 'environmentRef', 'kind'], message: 'Workbench release target requires an environment reference.' });
2840
+ context.addIssue({
2841
+ code: 'custom',
2842
+ path: ['target', 'environmentRef', 'kind'],
2843
+ message: 'Workbench release target requires an environment reference.',
2844
+ });
798
2845
  if (value.target.routeSpaceRevisionRef.kind !== 'route-space')
799
- context.addIssue({ code: 'custom', path: ['target', 'routeSpaceRevisionRef', 'kind'], message: 'Workbench release target requires a route-space revision.' });
800
- if (value.target.normalizedPath !== value.target.routeClaim.normalizedPath || !exactRevisionRef(value.target.routeSpaceRevisionRef, value.target.routeClaim.routeSpaceRevisionRef)) {
801
- context.addIssue({ code: 'custom', path: ['target', 'routeClaim'], message: 'Workbench Release route authority must pin the exact compiled claim and normalized path.' });
2846
+ context.addIssue({
2847
+ code: 'custom',
2848
+ path: ['target', 'routeSpaceRevisionRef', 'kind'],
2849
+ message: 'Workbench release target requires a route-space revision.',
2850
+ });
2851
+ validateReleaseRouteSurface(value.target, context);
2852
+ if (value.target.normalizedPath !== value.target.routeClaim.normalizedPath ||
2853
+ !exactRevisionRef(value.target.routeSpaceRevisionRef, value.target.routeClaim.routeSpaceRevisionRef)) {
2854
+ context.addIssue({
2855
+ code: 'custom',
2856
+ path: ['target', 'routeClaim'],
2857
+ message: 'Workbench Release route authority must pin the exact compiled claim and normalized path.',
2858
+ });
802
2859
  }
803
- if ('surface' in value.target.routeClaim && value.target.routeClaim.surface !== value.target.surface) {
804
- context.addIssue({ code: 'custom', path: ['target', 'routeClaim', 'surface'], message: 'Workbench Release route claim must match the target surface.' });
2860
+ const takeover = value.target.legacyRouteTakeoverAuthorization;
2861
+ if (takeover &&
2862
+ (value.target.routeClaim.kind !== 'canonical' ||
2863
+ takeover.normalizedPath !== value.target.normalizedPath ||
2864
+ !exactRevisionRef(takeover.routeSpaceRevisionRef, value.target.routeSpaceRevisionRef) ||
2865
+ takeover.targetResourceRef.kind !== 'workbench' ||
2866
+ takeover.targetResourceRef.id !== value.workbenchRevisionRef.id ||
2867
+ takeover.targetResourceRef.ownerRepo !== value.workbenchRevisionRef.ownerRepo)) {
2868
+ context.addIssue({
2869
+ code: 'custom',
2870
+ path: ['target', 'legacyRouteTakeoverAuthorization'],
2871
+ message: 'A Workbench route takeover must bind the exact canonical claim, RouteSpace and target Workbench.',
2872
+ });
805
2873
  }
806
2874
  });
807
2875
  const NavigationNodeBaseShape = {
@@ -810,15 +2878,18 @@ const NavigationNodeBaseShape = {
810
2878
  order: zod_1.z.number().int(),
811
2879
  disabled: zod_1.z.boolean().optional(),
812
2880
  };
813
- exports.NavigationGroupNodeSchema = zod_1.z.object({
2881
+ exports.NavigationGroupNodeSchema = zod_1.z
2882
+ .object({
814
2883
  ...NavigationNodeBaseShape,
815
2884
  kind: zod_1.z.literal('group'),
816
2885
  label: exports.I18nTextSchema,
817
2886
  iconRef: exports.StableRefSchema.optional(),
818
2887
  collapsedByDefault: zod_1.z.boolean(),
819
2888
  audience: exports.AccessPolicySchema,
820
- }).strict();
821
- exports.NavigationTargetNodeSchema = zod_1.z.object({
2889
+ })
2890
+ .strict();
2891
+ exports.NavigationTargetNodeSchema = zod_1.z
2892
+ .object({
822
2893
  ...NavigationNodeBaseShape,
823
2894
  kind: zod_1.z.literal('target'),
824
2895
  label: exports.I18nTextSchema,
@@ -827,11 +2898,14 @@ exports.NavigationTargetNodeSchema = zod_1.z.object({
827
2898
  targetRef: exports.StableRefSchema,
828
2899
  parameterMapping: zod_1.z.record(common_1.ContractIdentifierSchema, exports.BindingSourceSchema),
829
2900
  audience: exports.AccessPolicySchema,
830
- }).strict();
831
- exports.NavigationSeparatorNodeSchema = zod_1.z.object({
2901
+ })
2902
+ .strict();
2903
+ exports.NavigationSeparatorNodeSchema = zod_1.z
2904
+ .object({
832
2905
  ...NavigationNodeBaseShape,
833
2906
  kind: zod_1.z.literal('separator'),
834
- }).strict();
2907
+ })
2908
+ .strict();
835
2909
  exports.NavigationNodeSchema = zod_1.z.discriminatedUnion('kind', [
836
2910
  exports.NavigationGroupNodeSchema,
837
2911
  exports.NavigationTargetNodeSchema,
@@ -841,7 +2915,11 @@ const validateNavigationTree = (nodes, context) => {
841
2915
  const byId = new Map();
842
2916
  nodes.forEach((node, index) => {
843
2917
  if (byId.has(node.nodeId))
844
- context.addIssue({ code: 'custom', path: ['nodes', index, 'nodeId'], message: `Duplicate Navigation node: ${node.nodeId}` });
2918
+ context.addIssue({
2919
+ code: 'custom',
2920
+ path: ['nodes', index, 'nodeId'],
2921
+ message: `Duplicate Navigation node: ${node.nodeId}`,
2922
+ });
845
2923
  else
846
2924
  byId.set(node.nodeId, { node, index });
847
2925
  });
@@ -849,20 +2927,36 @@ const validateNavigationTree = (nodes, context) => {
849
2927
  nodes.forEach((node, index) => {
850
2928
  const orderKey = `${node.parentNodeId ?? ''}:${node.order}`;
851
2929
  if (siblingOrders.has(orderKey))
852
- context.addIssue({ code: 'custom', path: ['nodes', index, 'order'], message: 'Sibling Navigation order values must be unique.' });
2930
+ context.addIssue({
2931
+ code: 'custom',
2932
+ path: ['nodes', index, 'order'],
2933
+ message: 'Sibling Navigation order values must be unique.',
2934
+ });
853
2935
  siblingOrders.add(orderKey);
854
2936
  if (node.parentNodeId) {
855
2937
  const parent = byId.get(node.parentNodeId)?.node;
856
2938
  if (!parent)
857
- context.addIssue({ code: 'custom', path: ['nodes', index, 'parentNodeId'], message: 'Navigation node has an unknown parent.' });
2939
+ context.addIssue({
2940
+ code: 'custom',
2941
+ path: ['nodes', index, 'parentNodeId'],
2942
+ message: 'Navigation node has an unknown parent.',
2943
+ });
858
2944
  else if (parent.kind !== 'group')
859
- context.addIssue({ code: 'custom', path: ['nodes', index, 'parentNodeId'], message: 'Navigation parents must be groups.' });
2945
+ context.addIssue({
2946
+ code: 'custom',
2947
+ path: ['nodes', index, 'parentNodeId'],
2948
+ message: 'Navigation parents must be groups.',
2949
+ });
860
2950
  }
861
2951
  const ancestors = new Set([node.nodeId]);
862
2952
  let parentId = node.parentNodeId;
863
2953
  while (parentId) {
864
2954
  if (ancestors.has(parentId)) {
865
- context.addIssue({ code: 'custom', path: ['nodes', index, 'parentNodeId'], message: `Navigation cycle contains ${node.nodeId}.` });
2955
+ context.addIssue({
2956
+ code: 'custom',
2957
+ path: ['nodes', index, 'parentNodeId'],
2958
+ message: `Navigation cycle contains ${node.nodeId}.`,
2959
+ });
866
2960
  break;
867
2961
  }
868
2962
  ancestors.add(parentId);
@@ -889,8 +2983,13 @@ exports.NavigationSchema = zod_1.z
889
2983
  .superRefine((value, context) => {
890
2984
  validateNavigationTree(value.nodes, context);
891
2985
  value.nodes.forEach((node, index) => {
892
- if (node.kind === 'target' && !['page', 'workbench', 'menu-action', 'domain-command'].includes(node.targetRef.kind)) {
893
- context.addIssue({ code: 'custom', path: ['nodes', index, 'targetRef', 'kind'], message: 'Navigation targets must reference Page, Workbench, registered Menu Action, or DomainCommand identities.' });
2986
+ if (node.kind === 'target' &&
2987
+ !['page', 'workbench', 'menu-action', 'domain-command'].includes(node.targetRef.kind)) {
2988
+ context.addIssue({
2989
+ code: 'custom',
2990
+ path: ['nodes', index, 'targetRef', 'kind'],
2991
+ message: 'Navigation targets must reference Page, Workbench, registered Menu Action, or DomainCommand identities.',
2992
+ });
894
2993
  }
895
2994
  });
896
2995
  });
@@ -914,34 +3013,51 @@ exports.NavigationReleaseSchema = zod_1.z
914
3013
  contract: zod_1.z.literal('NavigationRelease'),
915
3014
  ...ReleaseBaseShape,
916
3015
  navigationRevisionRef: exports.RevisionRefSchema,
917
- target: zod_1.z.object({
3016
+ target: zod_1.z
3017
+ .object({
918
3018
  environmentRef: exports.StableRefSchema,
919
3019
  surface: exports.ProductSurfaceSchema,
920
3020
  placement: common_1.ContractIdentifierSchema,
921
- }).strict(),
3021
+ })
3022
+ .strict(),
922
3023
  resolvedTargets: zod_1.z.array(exports.ResolvedNavigationTargetSchema),
923
3024
  unavailableTargets: zod_1.z.array(exports.UnavailableNavigationTargetSchema).optional(),
924
3025
  })
925
3026
  .strict()
926
3027
  .superRefine((value, context) => {
3028
+ validateLegacyRestoration(value, 'legacy-navigation-adapter', context);
927
3029
  validateDependencySnapshot(value.dependencySnapshot, context);
928
- validateReleaseExpectedHead(value, 'navigation-release', context);
3030
+ validateReleaseGovernanceScope(value, 'navigation-release', context);
929
3031
  if (value.navigationRevisionRef.kind !== 'navigation')
930
- context.addIssue({ code: 'custom', path: ['navigationRevisionRef', 'kind'], message: 'Expected a navigation revision.' });
3032
+ context.addIssue({
3033
+ code: 'custom',
3034
+ path: ['navigationRevisionRef', 'kind'],
3035
+ message: 'Expected a navigation revision.',
3036
+ });
931
3037
  if (value.target.environmentRef.kind !== 'environment')
932
- context.addIssue({ code: 'custom', path: ['target', 'environmentRef', 'kind'], message: 'Navigation release target requires an environment reference.' });
3038
+ context.addIssue({
3039
+ code: 'custom',
3040
+ path: ['target', 'environmentRef', 'kind'],
3041
+ message: 'Navigation release target requires an environment reference.',
3042
+ });
933
3043
  if (uniqueArray(value.resolvedTargets, (target) => target.nodeId))
934
- context.addIssue({ code: 'custom', path: ['resolvedTargets'], message: 'Resolved Navigation node IDs must be unique.' });
3044
+ context.addIssue({
3045
+ code: 'custom',
3046
+ path: ['resolvedTargets'],
3047
+ message: 'Resolved Navigation node IDs must be unique.',
3048
+ });
935
3049
  if (uniqueArray([...value.resolvedTargets, ...(value.unavailableTargets ?? [])], (target) => target.nodeId)) {
936
3050
  context.addIssue({ code: 'custom', path: ['unavailableTargets'], message: 'Navigation target resolutions must have unique node IDs.' });
937
3051
  }
938
3052
  });
939
- const RuntimeDiagnosticSchema = zod_1.z.object({
3053
+ const RuntimeDiagnosticSchema = zod_1.z
3054
+ .object({
940
3055
  code: common_1.ContractIdentifierSchema,
941
3056
  severity: zod_1.z.enum(['info', 'warning', 'error']),
942
3057
  path: zod_1.z.string().trim().min(1),
943
3058
  message: zod_1.z.string().trim().min(1),
944
- }).strict();
3059
+ })
3060
+ .strict();
945
3061
  const RuntimeBundleBaseShape = {
946
3062
  schemaVersion: zod_1.z.literal(exports.DECLARATIVE_CONTROL_SCHEMA_VERSION),
947
3063
  tenantScope: exports.TenantScopeSchema,
@@ -953,40 +3069,264 @@ const RuntimeBundleBaseShape = {
953
3069
  rebuildable: zod_1.z.literal(true),
954
3070
  diagnostics: zod_1.z.array(RuntimeDiagnosticSchema),
955
3071
  };
956
- exports.PageRuntimeBundleSchema = zod_1.z.object({
3072
+ const validateRuntimeBundleReference = (reference, tenantScope, path, expectedKind, expectedId, context) => {
3073
+ if (reference.kind !== expectedKind) {
3074
+ context.addIssue({
3075
+ code: 'custom',
3076
+ path: [path, 'kind'],
3077
+ message: `${path} must reference ${expectedKind}.`,
3078
+ });
3079
+ }
3080
+ if (expectedId !== undefined && reference.id !== expectedId) {
3081
+ context.addIssue({
3082
+ code: 'custom',
3083
+ path: [path, 'id'],
3084
+ message: `${path} must identify ${expectedId}.`,
3085
+ });
3086
+ }
3087
+ validateTenantCompatibleReference(reference, tenantScope, [path], context);
3088
+ };
3089
+ const validateRuntimeBundleBase = (value, expectedReleaseKind, context) => {
3090
+ validateRuntimeBundleReference(value.releaseRevisionRef, value.tenantScope, 'releaseRevisionRef', expectedReleaseKind, undefined, context);
3091
+ validateRuntimeBundleReference(value.compilerRevisionRef, value.tenantScope, 'compilerRevisionRef', 'compiler', undefined, context);
3092
+ };
3093
+ exports.DeclarativeRouteOwnerIndexEntrySchema = zod_1.z
3094
+ .object({
3095
+ resourceKind: zod_1.z.enum(['page', 'workbench']),
3096
+ releaseSlotId: common_1.ContractIdentifierSchema,
3097
+ activeReleaseRevisionRef: exports.RevisionRefSchema,
3098
+ routeClaim: exports.CompiledRouteClaimSchema,
3099
+ authority: zod_1.z.enum(['declarative', 'legacy', 'unavailable']),
3100
+ legacyRestoration: exports.LegacySlotRestorationSchema.optional(),
3101
+ })
3102
+ .strict()
3103
+ .superRefine((value, context) => {
3104
+ if (value.authority === 'legacy' && !value.legacyRestoration) {
3105
+ context.addIssue({
3106
+ code: 'custom',
3107
+ path: ['legacyRestoration'],
3108
+ message: 'A restored legacy route must pin its exact inspected source and adapter revisions.',
3109
+ });
3110
+ }
3111
+ if (value.authority !== 'legacy' && value.legacyRestoration) {
3112
+ context.addIssue({
3113
+ code: 'custom',
3114
+ path: ['legacyRestoration'],
3115
+ message: 'Only a restored legacy route can carry legacy restoration provenance.',
3116
+ });
3117
+ }
3118
+ });
3119
+ exports.DeclarativeRouteOwnerIndexSchema = zod_1.z
3120
+ .object({
3121
+ contract: zod_1.z.literal('DeclarativeRouteOwnerIndex'),
3122
+ schemaVersion: zod_1.z.literal(exports.DECLARATIVE_CONTROL_SCHEMA_VERSION),
3123
+ tenantScope: exports.TenantScopeSchema,
3124
+ environmentRef: exports.StableRefSchema,
3125
+ surface: exports.ProductSurfaceSchema,
3126
+ generation: zod_1.z.number().int().positive(),
3127
+ entries: zod_1.z.array(exports.DeclarativeRouteOwnerIndexEntrySchema).max(4096),
3128
+ contentHash: common_1.Sha256Schema,
3129
+ rebuildable: zod_1.z.literal(true),
3130
+ })
3131
+ .strict()
3132
+ .superRefine((value, context) => {
3133
+ const identities = value.entries.map((entry) => `${entry.resourceKind}:${entry.releaseSlotId}`);
3134
+ if (new Set(identities).size !== identities.length) {
3135
+ context.addIssue({
3136
+ code: 'custom',
3137
+ path: ['entries'],
3138
+ message: 'Route owner index entries must be unique by Release slot.',
3139
+ });
3140
+ }
3141
+ if (identities.some((identity, index) => index > 0 && identities[index - 1].localeCompare(identity) > 0)) {
3142
+ context.addIssue({
3143
+ code: 'custom',
3144
+ path: ['entries'],
3145
+ message: 'Route owner index entries must use deterministic Release-slot order.',
3146
+ });
3147
+ }
3148
+ });
3149
+ exports.DeclarativeWorkbenchCatalogEntrySchema = zod_1.z
3150
+ .object({
3151
+ workbenchId: common_1.ContractIdentifierSchema,
3152
+ authority: zod_1.z.enum(['declarative', 'legacy', 'unavailable']),
3153
+ identity: exports.WorkbenchIdentitySchema,
3154
+ order: zod_1.z.number().int(),
3155
+ isDefaultCandidate: zod_1.z.boolean(),
3156
+ activeReleaseRevisionRef: exports.RevisionRefSchema.optional(),
3157
+ legacyAdapterRevisionRef: exports.RevisionRefSchema.optional(),
3158
+ })
3159
+ .strict()
3160
+ .superRefine((value, context) => {
3161
+ if (value.authority === 'declarative') {
3162
+ if (value.activeReleaseRevisionRef?.kind !== 'workbench-release') {
3163
+ context.addIssue({
3164
+ code: 'custom',
3165
+ path: ['activeReleaseRevisionRef'],
3166
+ message: 'A declarative Workbench catalog entry requires an exact Workbench Release.',
3167
+ });
3168
+ }
3169
+ if (value.legacyAdapterRevisionRef) {
3170
+ context.addIssue({
3171
+ code: 'custom',
3172
+ path: ['legacyAdapterRevisionRef'],
3173
+ message: 'A declarative Workbench catalog entry cannot carry a legacy adapter.',
3174
+ });
3175
+ }
3176
+ return;
3177
+ }
3178
+ if (value.activeReleaseRevisionRef) {
3179
+ context.addIssue({
3180
+ code: 'custom',
3181
+ path: ['activeReleaseRevisionRef'],
3182
+ message: 'A non-declarative Workbench entry cannot expose a Workbench Release.',
3183
+ });
3184
+ }
3185
+ if (value.authority === 'legacy' && value.legacyAdapterRevisionRef?.kind !== 'legacy-workbench-adapter') {
3186
+ context.addIssue({
3187
+ code: 'custom',
3188
+ path: ['legacyAdapterRevisionRef'],
3189
+ message: 'A legacy Workbench entry requires an exact legacy-workbench-adapter revision.',
3190
+ });
3191
+ }
3192
+ if (value.authority === 'unavailable' && value.legacyAdapterRevisionRef) {
3193
+ context.addIssue({
3194
+ code: 'custom',
3195
+ path: ['legacyAdapterRevisionRef'],
3196
+ message: 'An unavailable Workbench entry cannot carry a legacy adapter.',
3197
+ });
3198
+ }
3199
+ });
3200
+ exports.DeclarativeWorkbenchCatalogSchema = zod_1.z
3201
+ .object({
3202
+ contract: zod_1.z.literal('DeclarativeWorkbenchCatalog'),
3203
+ schemaVersion: zod_1.z.literal(exports.DECLARATIVE_CONTROL_SCHEMA_VERSION),
3204
+ tenantScope: exports.TenantScopeSchema,
3205
+ environmentRef: exports.StableRefSchema,
3206
+ surface: exports.ProductSurfaceSchema,
3207
+ generation: zod_1.z.number().int().positive(),
3208
+ entries: zod_1.z.array(exports.DeclarativeWorkbenchCatalogEntrySchema).max(4096),
3209
+ contentHash: common_1.Sha256Schema,
3210
+ rebuildable: zod_1.z.literal(true),
3211
+ })
3212
+ .strict()
3213
+ .superRefine((value, context) => {
3214
+ if (value.environmentRef.kind !== 'environment') {
3215
+ context.addIssue({
3216
+ code: 'custom',
3217
+ path: ['environmentRef', 'kind'],
3218
+ message: 'A Workbench catalog requires an Environment reference.',
3219
+ });
3220
+ }
3221
+ const ids = value.entries.map((entry) => entry.workbenchId);
3222
+ if (new Set(ids).size !== ids.length) {
3223
+ context.addIssue({
3224
+ code: 'custom',
3225
+ path: ['entries'],
3226
+ message: 'Workbench catalog identities must be unique.',
3227
+ });
3228
+ }
3229
+ if (value.entries.filter((entry) => entry.authority !== 'unavailable' && entry.isDefaultCandidate).length > 1) {
3230
+ context.addIssue({
3231
+ code: 'custom',
3232
+ path: ['entries'],
3233
+ message: 'The effective Workbench catalog may have at most one default.',
3234
+ });
3235
+ }
3236
+ const sorted = [...value.entries].sort((left, right) => left.order - right.order || left.workbenchId.localeCompare(right.workbenchId));
3237
+ if (sorted.some((entry, index) => entry.workbenchId !== value.entries[index]?.workbenchId)) {
3238
+ context.addIssue({
3239
+ code: 'custom',
3240
+ path: ['entries'],
3241
+ message: 'Workbench catalog entries must use deterministic order plus Workbench identity.',
3242
+ });
3243
+ }
3244
+ });
3245
+ exports.PageRuntimeBundleSchema = zod_1.z
3246
+ .object({
957
3247
  contract: zod_1.z.literal('PageRuntimeBundle'),
958
3248
  ...RuntimeBundleBaseShape,
959
3249
  pageId: common_1.ContractIdentifierSchema,
960
3250
  pageRevisionRef: exports.RevisionRefSchema,
961
3251
  surface: exports.ProductSurfaceSchema,
962
3252
  routeClaims: zod_1.z.array(exports.CompiledRouteClaimSchema),
3253
+ routeStatePresentations: zod_1.z.array(exports.PageRouteStatePresentationSchema).default([]),
963
3254
  shellRevisionRef: exports.RevisionRefSchema,
3255
+ shellDescriptor: exports.DeclarativeRuntimeShellDescriptorSchema,
964
3256
  renderTree: render_1.RenderTreeSchema,
965
3257
  capabilityInstances: zod_1.z.array(exports.CapabilityInstanceSchema),
3258
+ deniedCapabilityInstanceIds: zod_1.z.array(common_1.ContractIdentifierSchema).default([]),
3259
+ capabilityAccessEvaluated: zod_1.z.literal(true).optional(),
3260
+ stateDefinitions: zod_1.z.array(exports.PageStateDefinitionSchema).default([]),
3261
+ entryTransitions: zod_1.z.array(exports.ResolvedPageEntryTransitionSchema).default([]),
3262
+ interactionBindings: zod_1.z.array(exports.InteractionBindingSchema).default([]),
966
3263
  ontologyBindings: zod_1.z.array(exports.OntologyBindingSchema),
3264
+ queryBindings: zod_1.z.array(exports.QueryBindingSchema).default([]),
967
3265
  actionBindings: zod_1.z.array(exports.ActionBindingSchema),
968
3266
  pageAccessPolicy: exports.AccessPolicySchema,
969
3267
  tokenRevisionRefs: zod_1.z.array(exports.RevisionRefSchema),
970
3268
  performanceBudgetRef: exports.RevisionRefSchema,
971
3269
  observationPolicyRevisionRef: exports.RevisionRefSchema,
972
3270
  privacyClassification: zod_1.z.enum(['public', 'internal', 'confidential', 'restricted']),
973
- }).strict();
3271
+ })
3272
+ .strict()
3273
+ .superRefine((value, context) => {
3274
+ const instanceIds = new Set(value.capabilityInstances.map((instance) => instance.instanceId));
3275
+ if (new Set(value.deniedCapabilityInstanceIds).size !== value.deniedCapabilityInstanceIds.length ||
3276
+ value.deniedCapabilityInstanceIds.some((id) => !instanceIds.has(id))) {
3277
+ context.addIssue({
3278
+ code: 'custom',
3279
+ path: ['deniedCapabilityInstanceIds'],
3280
+ message: 'Denied capability identities must be unique declared instances.',
3281
+ });
3282
+ }
3283
+ validateRuntimeBundleBase(value, 'page-release', context);
3284
+ value.routeClaims.forEach((claim, index) => {
3285
+ if (claim.surface !== value.surface) {
3286
+ context.addIssue({
3287
+ code: 'custom',
3288
+ path: ['routeClaims', index, 'surface'],
3289
+ message: 'Runtime route claims must match the bundle surface.',
3290
+ });
3291
+ }
3292
+ });
3293
+ validateRuntimeBundleReference(value.pageRevisionRef, value.tenantScope, 'pageRevisionRef', 'page', value.pageId, context);
3294
+ value.routeStatePresentations.forEach((presentation, index) => {
3295
+ if (presentation.surface !== value.surface) {
3296
+ context.addIssue({
3297
+ code: 'custom',
3298
+ path: ['routeStatePresentations', index, 'surface'],
3299
+ message: 'Runtime route-state presentations must match the compiled Page surface.',
3300
+ });
3301
+ }
3302
+ });
3303
+ });
974
3304
  exports.ResolvedWorkbenchAppInstanceSchema = exports.WorkbenchAppInstanceSchema.extend({
975
3305
  targetAccessPolicy: exports.AccessPolicySchema,
976
3306
  }).strict();
977
- const WorkbenchRuntimeTargetSchema = exports.WorkbenchReleaseSchema.shape.target.safeExtend({
3307
+ exports.WorkbenchRuntimeIdentitySchema = zod_1.z
3308
+ .object({
3309
+ name: exports.I18nTextSchema,
3310
+ description: exports.I18nTextSchema.optional(),
3311
+ })
3312
+ .strict();
3313
+ const WorkbenchRuntimeTargetSchema = exports.WorkbenchReleaseSchema.shape.target
3314
+ .safeExtend({
978
3315
  routeClaim: exports.CompiledRouteClaimSchema,
979
- }).strict();
980
- exports.WorkbenchRuntimeBundleSchema = zod_1.z.object({
3316
+ })
3317
+ .strict();
3318
+ exports.WorkbenchRuntimeBundleSchema = zod_1.z
3319
+ .object({
981
3320
  contract: zod_1.z.literal('WorkbenchRuntimeBundle'),
982
3321
  ...RuntimeBundleBaseShape,
983
3322
  workbenchId: common_1.ContractIdentifierSchema,
984
3323
  workbenchRevisionRef: exports.RevisionRefSchema,
3324
+ identity: exports.WorkbenchRuntimeIdentitySchema,
985
3325
  surface: exports.ProductSurfaceSchema,
986
3326
  routeClaims: zod_1.z.array(exports.CompiledRouteClaimSchema),
987
3327
  groups: zod_1.z.array(exports.WorkbenchGroupSchema),
988
3328
  appInstances: zod_1.z.array(exports.ResolvedWorkbenchAppInstanceSchema),
989
- defaultEntry: exports.WorkbenchSchema.shape.defaultEntry,
3329
+ defaultEntry: exports.WorkbenchSchema.shape.defaultEntry.nullable(),
990
3330
  layout: exports.WorkbenchSchema.shape.layout,
991
3331
  workbenchAccessPolicy: exports.AccessPolicySchema,
992
3332
  personalPreferencesPolicy: exports.WorkbenchSchema.shape.personalPreferencesPolicy,
@@ -994,7 +3334,54 @@ exports.WorkbenchRuntimeBundleSchema = zod_1.z.object({
994
3334
  performanceBudgetRef: exports.RevisionRefSchema,
995
3335
  observationPolicyRevisionRef: exports.RevisionRefSchema,
996
3336
  privacyClassification: zod_1.z.enum(['public', 'internal', 'confidential', 'restricted']),
997
- }).strict();
3337
+ })
3338
+ .strict()
3339
+ .superRefine((value, context) => {
3340
+ validateRuntimeBundleBase(value, 'workbench-release', context);
3341
+ value.routeClaims.forEach((claim, index) => {
3342
+ if (claim.surface !== value.surface) {
3343
+ context.addIssue({
3344
+ code: 'custom',
3345
+ path: ['routeClaims', index, 'surface'],
3346
+ message: 'Runtime route claims must match the bundle surface.',
3347
+ });
3348
+ }
3349
+ });
3350
+ validateRuntimeBundleReference(value.workbenchRevisionRef, value.tenantScope, 'workbenchRevisionRef', 'workbench', value.workbenchId, context);
3351
+ if (value.appInstances.length === 0) {
3352
+ if (value.defaultEntry !== null) {
3353
+ context.addIssue({
3354
+ code: 'custom',
3355
+ path: ['defaultEntry'],
3356
+ message: 'An empty Workbench runtime bundle must use a null default entry.',
3357
+ });
3358
+ }
3359
+ return;
3360
+ }
3361
+ if (value.defaultEntry === null) {
3362
+ context.addIssue({
3363
+ code: 'custom',
3364
+ path: ['defaultEntry'],
3365
+ message: 'A non-empty Workbench runtime bundle requires a default entry.',
3366
+ });
3367
+ return;
3368
+ }
3369
+ const defaultInstance = value.appInstances.find((instance) => instance.instanceId === value.defaultEntry?.instanceId);
3370
+ if (!defaultInstance || defaultInstance.groupId !== value.defaultEntry.groupId) {
3371
+ context.addIssue({
3372
+ code: 'custom',
3373
+ path: ['defaultEntry'],
3374
+ message: 'Workbench runtime default entry must reference a visible app in the declared group.',
3375
+ });
3376
+ }
3377
+ if (!value.groups.some((group) => group.groupId === value.defaultEntry?.groupId)) {
3378
+ context.addIssue({
3379
+ code: 'custom',
3380
+ path: ['defaultEntry', 'groupId'],
3381
+ message: 'Workbench runtime default entry must reference a visible group.',
3382
+ });
3383
+ }
3384
+ });
998
3385
  const CompiledNavigationTargetSchema = exports.ResolvedNavigationTargetSchema.extend({
999
3386
  kind: zod_1.z.literal('route'),
1000
3387
  accessPolicy: exports.AccessPolicySchema,
@@ -1047,7 +3434,8 @@ exports.CompiledNavigationNodeSchema = zod_1.z.discriminatedUnion('kind', [
1047
3434
  exports.CompiledNavigationTargetNodeSchema,
1048
3435
  exports.CompiledNavigationSeparatorNodeSchema,
1049
3436
  ]);
1050
- exports.NavigationRuntimeBundleSchema = zod_1.z.object({
3437
+ exports.NavigationRuntimeBundleSchema = zod_1.z
3438
+ .object({
1051
3439
  contract: zod_1.z.literal('NavigationRuntimeBundle'),
1052
3440
  ...RuntimeBundleBaseShape,
1053
3441
  navigationId: common_1.ContractIdentifierSchema,
@@ -1057,16 +3445,23 @@ exports.NavigationRuntimeBundleSchema = zod_1.z.object({
1057
3445
  nodes: zod_1.z.array(exports.CompiledNavigationNodeSchema),
1058
3446
  performanceBudgetRef: exports.RevisionRefSchema,
1059
3447
  observationPolicyRevisionRef: exports.RevisionRefSchema,
1060
- }).strict();
3448
+ })
3449
+ .strict()
3450
+ .superRefine((value, context) => {
3451
+ validateRuntimeBundleBase(value, 'navigation-release', context);
3452
+ validateRuntimeBundleReference(value.navigationRevisionRef, value.tenantScope, 'navigationRevisionRef', 'navigation', value.navigationId, context);
3453
+ });
1061
3454
  exports.DeclarativeControlOntologyDefinitionSchema = data_1.OntologyDefinitionSchema.extend({
1062
3455
  classification: zod_1.z.literal('control'),
1063
- discovery: zod_1.z.object({
3456
+ discovery: zod_1.z
3457
+ .object({
1064
3458
  ordinaryDataBrowser: zod_1.z.literal(false),
1065
3459
  businessSearch: zod_1.z.literal(false),
1066
3460
  businessStatistics: zod_1.z.literal(false),
1067
3461
  ordinaryExport: zod_1.z.literal(false),
1068
3462
  genericWrite: zod_1.z.literal(false),
1069
- }).strict(),
3463
+ })
3464
+ .strict(),
1070
3465
  }).strict();
1071
3466
  const controlDefinition = (ontologyId, bodySchemaRef) => exports.DeclarativeControlOntologyDefinitionSchema.parse({
1072
3467
  contract: 'OntologyDefinition',
@@ -1074,7 +3469,11 @@ const controlDefinition = (ontologyId, bodySchemaRef) => exports.DeclarativeCont
1074
3469
  dataSpaceId: 'monkeys.control',
1075
3470
  ownerRepo: 'monkeys-js-sdk',
1076
3471
  bodySchemaRef,
1077
- authority: { service: 'monkeys-data-server', storage: 'domain-record', scope: 'tenant' },
3472
+ authority: {
3473
+ service: 'monkeys-data-server',
3474
+ storage: 'domain-record',
3475
+ scope: 'tenant',
3476
+ },
1078
3477
  relationKinds: [],
1079
3478
  metricKinds: [],
1080
3479
  classification: 'control',