@cynodia/axiom-core 0.16.0-alpha.3 → 0.17.0-alpha.1
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/dist/graph.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server-ir-admission.d.ts +84 -0
- package/dist/server-ir-admission.js +274 -0
- package/package.json +1 -1
package/dist/graph.js
CHANGED
|
@@ -23,7 +23,7 @@ export class ApplicationGraph {
|
|
|
23
23
|
/** Bumped by every change, so the derived edge index can never serve stale data. */
|
|
24
24
|
revision = 0;
|
|
25
25
|
semanticIndex;
|
|
26
|
-
constructor(id, name, version = '0.
|
|
26
|
+
constructor(id, name, version = '0.17.0') {
|
|
27
27
|
this.data = { id, name, version, nodes: {}, edges: {} };
|
|
28
28
|
}
|
|
29
29
|
get id() {
|
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export * from './derive-edges.js';
|
|
|
42
42
|
export * from './authority.js';
|
|
43
43
|
export * from './ir.js';
|
|
44
44
|
export * from './server-ir.js';
|
|
45
|
+
export * from './server-ir-admission.js';
|
|
45
46
|
export * from './authoring-schema.js';
|
|
46
47
|
export * from './semantic-diff.js';
|
|
47
48
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural admission of a serialized Server IR — the **totality boundary** every runtime
|
|
3
|
+
* that consumes untrusted `axiom.server.*` JSON crosses before any semantic execution
|
|
4
|
+
* (spec17 §15-§19, §80 F1-B).
|
|
5
|
+
*
|
|
6
|
+
* The frozen Server IR contract (`docs/AUTHORITY.md`) defines the semantic meaning of a
|
|
7
|
+
* *well-formed* document. A hand-built, AI-generated, network-delivered or corrupted
|
|
8
|
+
* document is not proof of shape: a `null`, a primitive or an array where a semantic node
|
|
9
|
+
* belongs, a non-array where a collection belongs, or an un-normalized action guard
|
|
10
|
+
* representation must all produce a **structured** rejection — never a native
|
|
11
|
+
* `TypeError` / panic, never a silently skipped check, never a partial execution.
|
|
12
|
+
*
|
|
13
|
+
* These functions are pure, allocate no host object, and are total over `unknown`. An
|
|
14
|
+
* independent runtime in another language needs only this contract and the shapes in
|
|
15
|
+
* `docs/AUTHORITY.md` to reproduce the same admission decision; it needs nothing else from
|
|
16
|
+
* this file.
|
|
17
|
+
*/
|
|
18
|
+
/** Stable diagnostic codes for a structurally invalid or non-normalized serialized Server IR. */
|
|
19
|
+
export declare const SERVER_IR_ADMISSION_CODES: {
|
|
20
|
+
/** The serialized Server IR is not a JSON object. */
|
|
21
|
+
readonly SERVER_IR_NOT_OBJECT: "SERVER_IR_NOT_OBJECT";
|
|
22
|
+
/** A Server IR collection is present but is not the array / object container its contract requires. */
|
|
23
|
+
readonly SERVER_IR_INVALID_COLLECTION: "SERVER_IR_INVALID_COLLECTION";
|
|
24
|
+
/**
|
|
25
|
+
* An entry of a semantic-node collection is `null`, a primitive, or otherwise not the
|
|
26
|
+
* structural shape its node kind requires. A key being present does not make an invalid
|
|
27
|
+
* value a valid node (spec17 §15-§16).
|
|
28
|
+
*/
|
|
29
|
+
readonly SERVER_IR_INVALID_NODE: "SERVER_IR_INVALID_NODE";
|
|
30
|
+
/**
|
|
31
|
+
* An executable action carries authoring `guards` that are not represented in the aligned,
|
|
32
|
+
* lowered `preconditions` / `failureModes` the authority evaluates. Compilation owns guard
|
|
33
|
+
* lowering; an authority does not lower at execution time and does not silently skip the
|
|
34
|
+
* unmatched guard semantics — it rejects the non-normalized document (spec17 §10-§14, §66).
|
|
35
|
+
*/
|
|
36
|
+
readonly SERVER_IR_NOT_NORMALIZED: "SERVER_IR_NOT_NORMALIZED";
|
|
37
|
+
};
|
|
38
|
+
export type ServerIRAdmissionCode = (typeof SERVER_IR_ADMISSION_CODES)[keyof typeof SERVER_IR_ADMISSION_CODES];
|
|
39
|
+
export interface ServerIRStructuralProblem {
|
|
40
|
+
code: ServerIRAdmissionCode;
|
|
41
|
+
/** A stable, non-secret dotted path into the document — e.g. `actions.a`, `states[3]`. */
|
|
42
|
+
path: string;
|
|
43
|
+
message: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A structurally invalid or non-normalized serialized Server IR. Carries every structural
|
|
47
|
+
* problem found, so a caller (or a conformance harness) can compare stable codes and paths
|
|
48
|
+
* rather than prose. Thrown by `createAxiomServer` before any state, provider, effect,
|
|
49
|
+
* event or workflow is touched.
|
|
50
|
+
*/
|
|
51
|
+
export declare class ServerIRError extends Error {
|
|
52
|
+
readonly problems: ServerIRStructuralProblem[];
|
|
53
|
+
constructor(problems: ServerIRStructuralProblem[]);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Every structural problem in a serialized Server IR — total over `unknown`. An empty array
|
|
57
|
+
* means the document is structurally admissible (it may still be semantically invalid;
|
|
58
|
+
* that is `validateGraph` / per-node validation's job). This never throws.
|
|
59
|
+
*/
|
|
60
|
+
export declare function serverIRStructuralProblems(ir: unknown): ServerIRStructuralProblem[];
|
|
61
|
+
/**
|
|
62
|
+
* Every guard-normalization problem in a serialized Server IR — total over `unknown`
|
|
63
|
+
* (spec17 §10-§14, §80 F2).
|
|
64
|
+
*
|
|
65
|
+
* A compiled Server IR carries each authoring guard lowered into `preconditions[i]` /
|
|
66
|
+
* `failureModes[i]` aligned by position, and retains `guards` alongside them. A document is
|
|
67
|
+
* **normalized** when, for every action, `preconditions` has at least one entry per guard
|
|
68
|
+
* and `canonicalJSON(preconditions[i]) === canonicalJSON(guards[i].condition)`. Anything
|
|
69
|
+
* else — a guard with no lowered precondition, or a precondition that does not match its
|
|
70
|
+
* guard's condition — means the executable representation would silently omit a check the
|
|
71
|
+
* document declares, and the authority MUST reject it fail-closed rather than execute the
|
|
72
|
+
* action.
|
|
73
|
+
*
|
|
74
|
+
* Actions with no `guards`, or `guards: []`, are always normalized (the positional
|
|
75
|
+
* `preconditions` / `failureModes` form is itself the normalized form).
|
|
76
|
+
*/
|
|
77
|
+
export declare function serverIRNormalizationProblems(ir: unknown): ServerIRStructuralProblem[];
|
|
78
|
+
/**
|
|
79
|
+
* Throws {@link ServerIRError} if a serialized Server IR is structurally invalid or not
|
|
80
|
+
* normalized. Runs structural admission first (a non-object / malformed-collection document
|
|
81
|
+
* is reported without also running the guard check against it).
|
|
82
|
+
*/
|
|
83
|
+
export declare function assertAdmissibleServerIR(ir: unknown): void;
|
|
84
|
+
//# sourceMappingURL=server-ir-admission.d.ts.map
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { canonicalJSON } from './schema-identity.js';
|
|
2
|
+
import { rawOperations } from './nodes.js';
|
|
3
|
+
/**
|
|
4
|
+
* Structural admission of a serialized Server IR — the **totality boundary** every runtime
|
|
5
|
+
* that consumes untrusted `axiom.server.*` JSON crosses before any semantic execution
|
|
6
|
+
* (spec17 §15-§19, §80 F1-B).
|
|
7
|
+
*
|
|
8
|
+
* The frozen Server IR contract (`docs/AUTHORITY.md`) defines the semantic meaning of a
|
|
9
|
+
* *well-formed* document. A hand-built, AI-generated, network-delivered or corrupted
|
|
10
|
+
* document is not proof of shape: a `null`, a primitive or an array where a semantic node
|
|
11
|
+
* belongs, a non-array where a collection belongs, or an un-normalized action guard
|
|
12
|
+
* representation must all produce a **structured** rejection — never a native
|
|
13
|
+
* `TypeError` / panic, never a silently skipped check, never a partial execution.
|
|
14
|
+
*
|
|
15
|
+
* These functions are pure, allocate no host object, and are total over `unknown`. An
|
|
16
|
+
* independent runtime in another language needs only this contract and the shapes in
|
|
17
|
+
* `docs/AUTHORITY.md` to reproduce the same admission decision; it needs nothing else from
|
|
18
|
+
* this file.
|
|
19
|
+
*/
|
|
20
|
+
/** Stable diagnostic codes for a structurally invalid or non-normalized serialized Server IR. */
|
|
21
|
+
export const SERVER_IR_ADMISSION_CODES = {
|
|
22
|
+
/** The serialized Server IR is not a JSON object. */
|
|
23
|
+
SERVER_IR_NOT_OBJECT: 'SERVER_IR_NOT_OBJECT',
|
|
24
|
+
/** A Server IR collection is present but is not the array / object container its contract requires. */
|
|
25
|
+
SERVER_IR_INVALID_COLLECTION: 'SERVER_IR_INVALID_COLLECTION',
|
|
26
|
+
/**
|
|
27
|
+
* An entry of a semantic-node collection is `null`, a primitive, or otherwise not the
|
|
28
|
+
* structural shape its node kind requires. A key being present does not make an invalid
|
|
29
|
+
* value a valid node (spec17 §15-§16).
|
|
30
|
+
*/
|
|
31
|
+
SERVER_IR_INVALID_NODE: 'SERVER_IR_INVALID_NODE',
|
|
32
|
+
/**
|
|
33
|
+
* An executable action carries authoring `guards` that are not represented in the aligned,
|
|
34
|
+
* lowered `preconditions` / `failureModes` the authority evaluates. Compilation owns guard
|
|
35
|
+
* lowering; an authority does not lower at execution time and does not silently skip the
|
|
36
|
+
* unmatched guard semantics — it rejects the non-normalized document (spec17 §10-§14, §66).
|
|
37
|
+
*/
|
|
38
|
+
SERVER_IR_NOT_NORMALIZED: 'SERVER_IR_NOT_NORMALIZED',
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A structurally invalid or non-normalized serialized Server IR. Carries every structural
|
|
42
|
+
* problem found, so a caller (or a conformance harness) can compare stable codes and paths
|
|
43
|
+
* rather than prose. Thrown by `createAxiomServer` before any state, provider, effect,
|
|
44
|
+
* event or workflow is touched.
|
|
45
|
+
*/
|
|
46
|
+
export class ServerIRError extends Error {
|
|
47
|
+
problems;
|
|
48
|
+
constructor(problems) {
|
|
49
|
+
super(`Server IR is structurally invalid:\n${problems
|
|
50
|
+
.map((problem) => ` [${problem.code}] ${problem.path}: ${problem.message}`)
|
|
51
|
+
.join('\n')}`);
|
|
52
|
+
this.name = 'ServerIRError';
|
|
53
|
+
this.problems = problems;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isPlainObject(value) {
|
|
57
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
58
|
+
}
|
|
59
|
+
/** A structurally acceptable semantic node: a plain object carrying a string `id`. */
|
|
60
|
+
function isNodeShaped(value) {
|
|
61
|
+
return isPlainObject(value) && typeof value.id === 'string';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Server IR collections that hold semantic nodes as a JSON **array**. `workflows` is
|
|
65
|
+
* deliberately excluded: `WorkflowDef` admission has its own total validator
|
|
66
|
+
* (`workflowStructuralProblems` / `WorkflowIRError`, spec14pt3-pt6) that `createAxiomServer`
|
|
67
|
+
* already runs, and it reports a richer structured result than a generic node-shape check.
|
|
68
|
+
*/
|
|
69
|
+
const ARRAY_NODE_COLLECTIONS = [
|
|
70
|
+
'entities',
|
|
71
|
+
'states',
|
|
72
|
+
'constraints',
|
|
73
|
+
'transitionConstraints',
|
|
74
|
+
'integrations',
|
|
75
|
+
'events',
|
|
76
|
+
'triggers',
|
|
77
|
+
'subscriptions',
|
|
78
|
+
'storages',
|
|
79
|
+
'queries',
|
|
80
|
+
'relationships',
|
|
81
|
+
'readPolicies',
|
|
82
|
+
'authorizationPolicies',
|
|
83
|
+
'migrations',
|
|
84
|
+
];
|
|
85
|
+
/** Server IR collections that hold semantic nodes as a JSON **object** keyed by id. */
|
|
86
|
+
const MAP_NODE_COLLECTIONS = ['actions', 'integrationOperations', 'expressionDefs'];
|
|
87
|
+
/** Collections a compiled Server IR always emits; a present non-array value is a defect. */
|
|
88
|
+
const REQUIRED_ARRAY_COLLECTIONS = [
|
|
89
|
+
'entities',
|
|
90
|
+
'states',
|
|
91
|
+
'constraints',
|
|
92
|
+
'transitionConstraints',
|
|
93
|
+
'observableStateIds',
|
|
94
|
+
];
|
|
95
|
+
/**
|
|
96
|
+
* Every structural problem in a serialized Server IR — total over `unknown`. An empty array
|
|
97
|
+
* means the document is structurally admissible (it may still be semantically invalid;
|
|
98
|
+
* that is `validateGraph` / per-node validation's job). This never throws.
|
|
99
|
+
*/
|
|
100
|
+
export function serverIRStructuralProblems(ir) {
|
|
101
|
+
const problems = [];
|
|
102
|
+
if (!isPlainObject(ir)) {
|
|
103
|
+
return [
|
|
104
|
+
{
|
|
105
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_NOT_OBJECT,
|
|
106
|
+
path: '(root)',
|
|
107
|
+
message: `Server IR must be a JSON object, received ${ir === null ? 'null' : Array.isArray(ir) ? 'array' : typeof ir}`,
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
// `fields` is a pre-indexed lookup object, not a node collection, but it is still iterated.
|
|
112
|
+
if (ir.fields !== undefined && !isPlainObject(ir.fields)) {
|
|
113
|
+
problems.push({
|
|
114
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_INVALID_COLLECTION,
|
|
115
|
+
path: 'fields',
|
|
116
|
+
message: 'fields must be an object keyed by field id',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
for (const key of REQUIRED_ARRAY_COLLECTIONS) {
|
|
120
|
+
const value = ir[key];
|
|
121
|
+
if (value !== undefined && !Array.isArray(value)) {
|
|
122
|
+
problems.push({
|
|
123
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_INVALID_COLLECTION,
|
|
124
|
+
path: key,
|
|
125
|
+
message: `${key} must be an array`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// `observableStateIds` holds ids, not nodes: every entry must be a string.
|
|
130
|
+
if (Array.isArray(ir.observableStateIds)) {
|
|
131
|
+
ir.observableStateIds.forEach((entry, index) => {
|
|
132
|
+
if (typeof entry !== 'string') {
|
|
133
|
+
problems.push({
|
|
134
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_INVALID_NODE,
|
|
135
|
+
path: `observableStateIds[${index}]`,
|
|
136
|
+
message: 'observableStateIds entries must be state id strings',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
for (const key of ARRAY_NODE_COLLECTIONS) {
|
|
142
|
+
const value = ir[key];
|
|
143
|
+
if (value === undefined)
|
|
144
|
+
continue;
|
|
145
|
+
if (!Array.isArray(value)) {
|
|
146
|
+
problems.push({
|
|
147
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_INVALID_COLLECTION,
|
|
148
|
+
path: key,
|
|
149
|
+
message: `${key} must be an array`,
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
value.forEach((entry, index) => {
|
|
154
|
+
if (!isNodeShaped(entry)) {
|
|
155
|
+
problems.push({
|
|
156
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_INVALID_NODE,
|
|
157
|
+
path: `${key}[${index}]`,
|
|
158
|
+
message: `${key}[${index}] is not a semantic node (a plain object with a string id)`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
for (const key of MAP_NODE_COLLECTIONS) {
|
|
164
|
+
const value = ir[key];
|
|
165
|
+
if (value === undefined)
|
|
166
|
+
continue;
|
|
167
|
+
if (!isPlainObject(value)) {
|
|
168
|
+
problems.push({
|
|
169
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_INVALID_COLLECTION,
|
|
170
|
+
path: key,
|
|
171
|
+
message: `${key} must be an object keyed by node id`,
|
|
172
|
+
});
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
for (const [entryKey, entry] of Object.entries(value)) {
|
|
176
|
+
if (!isNodeShaped(entry)) {
|
|
177
|
+
problems.push({
|
|
178
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_INVALID_NODE,
|
|
179
|
+
path: `${key}.${entryKey}`,
|
|
180
|
+
message: `${key}.${entryKey} is not a semantic node (a plain object with a string id)`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Operation shape, at admission: a `null` / primitive / array where an operation belongs
|
|
186
|
+
// must be a structured rejection, not a value silently dropped later (spec17 §14, §62).
|
|
187
|
+
if (isPlainObject(ir.actions)) {
|
|
188
|
+
for (const [actionKey, action] of Object.entries(ir.actions)) {
|
|
189
|
+
if (!isPlainObject(action))
|
|
190
|
+
continue;
|
|
191
|
+
walkOperationShape(rawOperations(action), `actions.${actionKey}.operations`, problems);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return problems;
|
|
195
|
+
}
|
|
196
|
+
function walkOperationShape(operations, path, problems) {
|
|
197
|
+
operations.forEach((operation, index) => {
|
|
198
|
+
if (!isPlainObject(operation) || typeof operation.kind !== 'string') {
|
|
199
|
+
problems.push({
|
|
200
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_INVALID_NODE,
|
|
201
|
+
path: `${path}[${index}]`,
|
|
202
|
+
message: 'operation is not a plain object with a string kind',
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (operation.kind === 'for-each') {
|
|
207
|
+
walkOperationShape(rawOperations(operation), `${path}[${index}].operations`, problems);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Every guard-normalization problem in a serialized Server IR — total over `unknown`
|
|
213
|
+
* (spec17 §10-§14, §80 F2).
|
|
214
|
+
*
|
|
215
|
+
* A compiled Server IR carries each authoring guard lowered into `preconditions[i]` /
|
|
216
|
+
* `failureModes[i]` aligned by position, and retains `guards` alongside them. A document is
|
|
217
|
+
* **normalized** when, for every action, `preconditions` has at least one entry per guard
|
|
218
|
+
* and `canonicalJSON(preconditions[i]) === canonicalJSON(guards[i].condition)`. Anything
|
|
219
|
+
* else — a guard with no lowered precondition, or a precondition that does not match its
|
|
220
|
+
* guard's condition — means the executable representation would silently omit a check the
|
|
221
|
+
* document declares, and the authority MUST reject it fail-closed rather than execute the
|
|
222
|
+
* action.
|
|
223
|
+
*
|
|
224
|
+
* Actions with no `guards`, or `guards: []`, are always normalized (the positional
|
|
225
|
+
* `preconditions` / `failureModes` form is itself the normalized form).
|
|
226
|
+
*/
|
|
227
|
+
export function serverIRNormalizationProblems(ir) {
|
|
228
|
+
const problems = [];
|
|
229
|
+
if (!isPlainObject(ir) || !isPlainObject(ir.actions)) {
|
|
230
|
+
return problems;
|
|
231
|
+
}
|
|
232
|
+
for (const [actionKey, action] of Object.entries(ir.actions)) {
|
|
233
|
+
if (!isPlainObject(action))
|
|
234
|
+
continue;
|
|
235
|
+
const guards = Array.isArray(action.guards) ? action.guards : undefined;
|
|
236
|
+
if (!guards || guards.length === 0)
|
|
237
|
+
continue;
|
|
238
|
+
const preconditions = Array.isArray(action.preconditions) ? action.preconditions : [];
|
|
239
|
+
if (preconditions.length < guards.length) {
|
|
240
|
+
problems.push({
|
|
241
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_NOT_NORMALIZED,
|
|
242
|
+
path: `actions.${actionKey}`,
|
|
243
|
+
message: `action carries ${guards.length} guard(s) but only ${preconditions.length} lowered precondition(s); Server IR must be normalized by compilation before execution`,
|
|
244
|
+
});
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
guards.forEach((guard, index) => {
|
|
248
|
+
const condition = isPlainObject(guard) ? guard.condition : undefined;
|
|
249
|
+
if (canonicalJSON(condition) !== canonicalJSON(preconditions[index])) {
|
|
250
|
+
problems.push({
|
|
251
|
+
code: SERVER_IR_ADMISSION_CODES.SERVER_IR_NOT_NORMALIZED,
|
|
252
|
+
path: `actions.${actionKey}.guards[${index}]`,
|
|
253
|
+
message: `guard ${index} is not represented at preconditions[${index}]; the lowered executable form does not preserve this guard's semantics`,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return problems;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Throws {@link ServerIRError} if a serialized Server IR is structurally invalid or not
|
|
262
|
+
* normalized. Runs structural admission first (a non-object / malformed-collection document
|
|
263
|
+
* is reported without also running the guard check against it).
|
|
264
|
+
*/
|
|
265
|
+
export function assertAdmissibleServerIR(ir) {
|
|
266
|
+
const structural = serverIRStructuralProblems(ir);
|
|
267
|
+
if (structural.length > 0) {
|
|
268
|
+
throw new ServerIRError(structural);
|
|
269
|
+
}
|
|
270
|
+
const normalization = serverIRNormalizationProblems(ir);
|
|
271
|
+
if (normalization.length > 0) {
|
|
272
|
+
throw new ServerIRError(normalization);
|
|
273
|
+
}
|
|
274
|
+
}
|