@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.
- package/lib/contracts/aggregate-commit.d.ts +155 -0
- package/lib/contracts/aggregate-commit.d.ts.map +1 -0
- package/lib/contracts/aggregate-commit.js +112 -0
- package/lib/contracts/aggregate-commit.js.map +1 -0
- package/lib/contracts/capability.d.ts +3 -0
- package/lib/contracts/capability.d.ts.map +1 -1
- package/lib/contracts/capability.js +1 -1
- package/lib/contracts/capability.js.map +1 -1
- package/lib/contracts/common.d.ts +2 -0
- package/lib/contracts/common.d.ts.map +1 -1
- package/lib/contracts/common.js +34 -1
- package/lib/contracts/common.js.map +1 -1
- package/lib/contracts/component-assembly.d.ts +118 -0
- package/lib/contracts/component-assembly.d.ts.map +1 -0
- package/lib/contracts/component-assembly.js +61 -0
- package/lib/contracts/component-assembly.js.map +1 -0
- package/lib/contracts/declarative-control-http.d.ts +101608 -44791
- package/lib/contracts/declarative-control-http.d.ts.map +1 -1
- package/lib/contracts/declarative-control-http.js +1358 -86
- package/lib/contracts/declarative-control-http.js.map +1 -1
- package/lib/contracts/declarative-control.d.ts +9668 -3376
- package/lib/contracts/declarative-control.d.ts.map +1 -1
- package/lib/contracts/declarative-control.js +2585 -186
- package/lib/contracts/declarative-control.js.map +1 -1
- package/lib/contracts/index.d.ts +6 -0
- package/lib/contracts/index.d.ts.map +1 -1
- package/lib/contracts/index.js +6 -0
- package/lib/contracts/index.js.map +1 -1
- package/lib/contracts/legacy-route.d.ts +15 -0
- package/lib/contracts/legacy-route.d.ts.map +1 -0
- package/lib/contracts/legacy-route.js +13 -0
- package/lib/contracts/legacy-route.js.map +1 -0
- package/lib/contracts/menu.d.ts +12 -0
- package/lib/contracts/menu.d.ts.map +1 -1
- package/lib/contracts/page-binding-dependencies.d.ts +25 -0
- package/lib/contracts/page-binding-dependencies.d.ts.map +1 -0
- package/lib/contracts/page-binding-dependencies.js +54 -0
- package/lib/contracts/page-binding-dependencies.js.map +1 -0
- package/lib/contracts/page-expression.d.ts +106 -0
- package/lib/contracts/page-expression.d.ts.map +1 -0
- package/lib/contracts/page-expression.js +136 -0
- package/lib/contracts/page-expression.js.map +1 -0
- package/lib/contracts/page-stream.d.ts +101 -0
- package/lib/contracts/page-stream.d.ts.map +1 -0
- package/lib/contracts/page-stream.js +66 -0
- package/lib/contracts/page-stream.js.map +1 -0
- package/lib/contracts/page.d.ts +24 -0
- package/lib/contracts/page.d.ts.map +1 -1
- package/lib/contracts/page.js +2 -0
- package/lib/contracts/page.js.map +1 -1
- package/lib/contracts/record-editor-draft.d.ts +95 -0
- package/lib/contracts/record-editor-draft.d.ts.map +1 -0
- package/lib/contracts/record-editor-draft.js +35 -0
- package/lib/contracts/record-editor-draft.js.map +1 -0
- package/lib/contracts/render.d.ts +16 -0
- package/lib/contracts/render.d.ts.map +1 -1
- package/lib/contracts/render.js +3 -1
- package/lib/contracts/render.js.map +1 -1
- package/lib/contracts/semantic.d.ts +48 -1
- package/lib/contracts/semantic.d.ts.map +1 -1
- package/lib/contracts/semantic.js +22 -19
- package/lib/contracts/semantic.js.map +1 -1
- package/lib/contracts/tenant.d.ts +112 -6
- package/lib/contracts/tenant.d.ts.map +1 -1
- package/lib/contracts/tenant.js +14 -1
- package/lib/contracts/tenant.js.map +1 -1
- package/lib/esm/contracts/index.mjs +6921 -3032
- package/lib/esm/contracts/index.mjs.map +1 -1
- package/lib/esm/index.mjs +11333 -4947
- package/lib/esm/index.mjs.map +1 -1
- package/lib/esm/runtime/index.mjs +11514 -4411
- package/lib/esm/runtime/index.mjs.map +1 -1
- package/lib/esm/schemas/index.mjs +7282 -2571
- package/lib/esm/schemas/index.mjs.map +1 -1
- package/lib/json-schema/aggregate-commit-request.schema.json +426 -0
- package/lib/json-schema/capability-manifest.schema.json +1 -0
- package/lib/json-schema/capability-registry.schema.json +1 -0
- package/lib/json-schema/declarative-route-owner-index.schema.json +1092 -0
- package/lib/json-schema/declarative-workbench-catalog.schema.json +769 -0
- package/lib/json-schema/design-capability-catalog-artifact.schema.json +2787 -0
- package/lib/json-schema/domain-command-definition.schema.json +18 -0
- package/lib/json-schema/domain-query-definition.schema.json +1406 -0
- package/lib/json-schema/index.json +7 -0
- package/lib/json-schema/legacy-route-takeover-authorization.schema.json +601 -0
- package/lib/json-schema/navigation-release.schema.json +296 -0
- package/lib/json-schema/navigation-runtime-bundle.schema.json +48 -8
- package/lib/json-schema/navigation.schema.json +47 -8
- package/lib/json-schema/overlay-node.schema.json +281 -0
- package/lib/json-schema/page-definition.schema.json +34 -0
- package/lib/json-schema/page-release.schema.json +1520 -626
- package/lib/json-schema/page-runtime-bundle.schema.json +5980 -401
- package/lib/json-schema/page-runtime-descriptor.schema.json +34 -0
- package/lib/json-schema/page.schema.json +7333 -1482
- package/lib/json-schema/permission-alternative-policy.schema.json +60 -0
- package/lib/json-schema/product-declaration.schema.json +53 -0
- package/lib/json-schema/render-node.schema.json +281 -0
- package/lib/json-schema/render-tree.schema.json +281 -0
- package/lib/json-schema/tenant-product-config.schema.json +88 -0
- package/lib/json-schema/tenant-runtime-config.schema.json +88 -0
- package/lib/json-schema/view-provider-descriptor.schema.json +1 -0
- package/lib/json-schema/workbench-release.schema.json +1520 -626
- package/lib/json-schema/workbench-runtime-bundle.schema.json +686 -20
- package/lib/json-schema/workbench.schema.json +1196 -0
- package/lib/runtime/component-assembly.d.ts +4 -0
- package/lib/runtime/component-assembly.d.ts.map +1 -0
- package/lib/runtime/component-assembly.js +126 -0
- package/lib/runtime/component-assembly.js.map +1 -0
- package/lib/runtime/declarative-control-compiler.d.ts +66 -5
- package/lib/runtime/declarative-control-compiler.d.ts.map +1 -1
- package/lib/runtime/declarative-control-compiler.js +862 -109
- package/lib/runtime/declarative-control-compiler.js.map +1 -1
- package/lib/runtime/index.d.ts +5 -0
- package/lib/runtime/index.d.ts.map +1 -1
- package/lib/runtime/index.js +7 -0
- package/lib/runtime/index.js.map +1 -1
- package/lib/runtime/page-expression.d.ts +9 -0
- package/lib/runtime/page-expression.d.ts.map +1 -0
- package/lib/runtime/page-expression.js +257 -0
- package/lib/runtime/page-expression.js.map +1 -0
- package/lib/runtime/page-stream.d.ts +24 -0
- package/lib/runtime/page-stream.d.ts.map +1 -0
- package/lib/runtime/page-stream.js +65 -0
- package/lib/runtime/page-stream.js.map +1 -0
- package/lib/runtime/record-editor-draft.d.ts +29 -0
- package/lib/runtime/record-editor-draft.d.ts.map +1 -0
- package/lib/runtime/record-editor-draft.js +161 -0
- package/lib/runtime/record-editor-draft.js.map +1 -0
- package/lib/runtime/target-render-model.d.ts +4 -0
- package/lib/runtime/target-render-model.d.ts.map +1 -0
- package/lib/runtime/target-render-model.js +28 -0
- package/lib/runtime/target-render-model.js.map +1 -0
- package/lib/runtime/tenant-config.d.ts +2 -0
- package/lib/runtime/tenant-config.d.ts.map +1 -1
- package/lib/runtime/tenant-config.js +6 -1
- package/lib/runtime/tenant-config.js.map +1 -1
- package/lib/schemas/index.d.ts +6957 -2171
- package/lib/schemas/index.d.ts.map +1 -1
- package/lib/schemas/index.js +86 -8
- package/lib/schemas/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
-
[
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
|
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
|
-
})
|
|
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
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
-
|
|
412
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
835
|
+
context.addIssue({
|
|
836
|
+
code: 'custom',
|
|
837
|
+
path: ['dependencySnapshot'],
|
|
838
|
+
message: `Duplicate dependency: ${duplicate}`,
|
|
839
|
+
});
|
|
480
840
|
};
|
|
481
|
-
const
|
|
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({
|
|
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({
|
|
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('
|
|
492
|
-
zod_1.z
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
|
|
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
|
-
|
|
501
|
-
|
|
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
|
-
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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.
|
|
525
|
-
context.addIssue({
|
|
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.
|
|
528
|
-
context.addIssue({
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2605
|
+
validateReleaseGovernanceScope(value, 'page-release', context);
|
|
655
2606
|
if (value.pageRevisionRef.kind !== 'page')
|
|
656
|
-
context.addIssue({
|
|
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({
|
|
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({
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
665
|
-
|
|
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
|
|
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
|
|
707
|
-
|
|
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
|
-
})
|
|
2704
|
+
})
|
|
2705
|
+
.strict(),
|
|
713
2706
|
workbenchAccessPolicy: exports.AccessPolicySchema,
|
|
714
2707
|
managementAccess: exports.ManagementAccessSchema,
|
|
715
|
-
personalPreferencesPolicy: zod_1.z
|
|
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
|
-
})
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
|
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
|
-
})
|
|
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
|
-
|
|
2832
|
+
validateReleaseGovernanceScope(value, 'workbench-release', context);
|
|
794
2833
|
if (value.workbenchRevisionRef.kind !== 'workbench')
|
|
795
|
-
context.addIssue({
|
|
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({
|
|
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({
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
|
|
804
|
-
|
|
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
|
|
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
|
-
})
|
|
821
|
-
|
|
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
|
-
})
|
|
831
|
-
|
|
2901
|
+
})
|
|
2902
|
+
.strict();
|
|
2903
|
+
exports.NavigationSeparatorNodeSchema = zod_1.z
|
|
2904
|
+
.object({
|
|
832
2905
|
...NavigationNodeBaseShape,
|
|
833
2906
|
kind: zod_1.z.literal('separator'),
|
|
834
|
-
})
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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' &&
|
|
893
|
-
|
|
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
|
|
3016
|
+
target: zod_1.z
|
|
3017
|
+
.object({
|
|
918
3018
|
environmentRef: exports.StableRefSchema,
|
|
919
3019
|
surface: exports.ProductSurfaceSchema,
|
|
920
3020
|
placement: common_1.ContractIdentifierSchema,
|
|
921
|
-
})
|
|
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
|
-
|
|
3030
|
+
validateReleaseGovernanceScope(value, 'navigation-release', context);
|
|
929
3031
|
if (value.navigationRevisionRef.kind !== 'navigation')
|
|
930
|
-
context.addIssue({
|
|
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({
|
|
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({
|
|
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
|
|
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
|
-
})
|
|
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
|
-
|
|
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
|
-
})
|
|
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
|
-
|
|
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
|
-
})
|
|
980
|
-
|
|
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
|
-
})
|
|
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
|
|
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
|
-
})
|
|
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
|
|
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
|
-
})
|
|
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: {
|
|
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',
|