@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 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.16.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
@@ -42,5 +42,6 @@ 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';
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom-core",
3
- "version": "0.16.0-alpha.3",
3
+ "version": "0.17.0-alpha.1",
4
4
  "description": "Application Graph, semantic types, locations and validation for Axiom.",
5
5
  "license": "MIT",
6
6
  "author": "AskTech AS",