@cynodia/axiom-agent-api 0.16.0-alpha.1 → 0.16.0-alpha.3
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/capabilities.js +2 -1
- package/dist/distributed.js +2 -2
- package/dist/explain.js +5 -4
- package/dist/native-operations.js +5 -2
- package/dist/queries.js +5 -5
- package/dist/tooling-conformance.js +73 -2
- package/dist/transaction.js +5 -2
- package/package.json +2 -2
package/dist/capabilities.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { actionOperations } from '@cynodia/axiom-core';
|
|
1
2
|
import { analyzeLiveQuery } from './live-query.js';
|
|
2
3
|
/**
|
|
3
4
|
* Required-runtime-capability analysis (spec16 §30-31, §50-51). A graph declares no host or
|
|
@@ -65,7 +66,7 @@ export function analyzeCapabilities(graph) {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
for (const action of actions) {
|
|
68
|
-
const effectOps = (action
|
|
69
|
+
const effectOps = actionOperations(action).filter((op) => op.kind === 'integration-effect' || op.kind === 'blob-commit' || op.kind === 'blob-delete');
|
|
69
70
|
if (effectOps.length > 0) {
|
|
70
71
|
require('effect-execution', `ActionDef ${action.id} creates a logical effect (${effectOps.map((op) => op.kind).join(', ')})`);
|
|
71
72
|
}
|
package/dist/distributed.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { authorityCompatibilityKey, schemaFingerprint, semanticFingerprint, } from '@cynodia/axiom-core';
|
|
1
|
+
import { actionOperations, authorityCompatibilityKey, schemaFingerprint, semanticFingerprint, } from '@cynodia/axiom-core';
|
|
2
2
|
const EFFECT_OP_KINDS = new Set(['integration-effect', 'blob-commit', 'blob-delete']);
|
|
3
3
|
function actionsWithEffects(graph) {
|
|
4
|
-
return graph.getNodesByKind('action').filter((action) => (action
|
|
4
|
+
return graph.getNodesByKind('action').filter((action) => actionOperations(action).some((op) => EFFECT_OP_KINDS.has(op.kind)));
|
|
5
5
|
}
|
|
6
6
|
function scheduledTriggers(graph) {
|
|
7
7
|
return graph.getNodesByKind('trigger').filter((trigger) => trigger.when.kind === 'interval' || trigger.when.kind === 'delay');
|
package/dist/explain.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { actionOperations, } from '@cynodia/axiom-core';
|
|
1
2
|
import { GraphQueries } from './queries.js';
|
|
2
3
|
import { analyzeAuthorization } from './authorization.js';
|
|
3
4
|
import { analyzeWorkflow } from './workflow.js';
|
|
@@ -36,12 +37,12 @@ export function explainAction(graph, actionId) {
|
|
|
36
37
|
runsQueries.push(target.id);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
|
-
for (const operation of action
|
|
40
|
+
for (const operation of actionOperations(action)) {
|
|
40
41
|
if (operation.kind === 'blob-metadata' || operation.kind === 'blob-commit' || operation.kind === 'blob-delete') {
|
|
41
42
|
storagesTouched.add(operation.storageId);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
const nativeOperations = (action
|
|
45
|
+
const nativeOperations = actionOperations(action)
|
|
45
46
|
.filter((operation) => operation.kind === 'native')
|
|
46
47
|
.map((operation) => ({ implementationId: operation.implementationId, declaredEffects: operation.declaredEffects ?? [] }));
|
|
47
48
|
const analysisGaps = nativeOperations
|
|
@@ -92,7 +93,7 @@ export function explainAction(graph, actionId) {
|
|
|
92
93
|
invokedBy: { triggers: triggers.sort(), workflowSteps: workflowSteps.sort() },
|
|
93
94
|
clientInvocable: queries.isClientInvocable(actionId),
|
|
94
95
|
systemOnly: queries.isSystemOnly(actionId),
|
|
95
|
-
destructive: action.destructive === true || (action
|
|
96
|
+
destructive: action.destructive === true || actionOperations(action).some((op) => op.kind === 'remove'),
|
|
96
97
|
analysisComplete: analysisGaps.length === 0,
|
|
97
98
|
analysisGaps,
|
|
98
99
|
};
|
|
@@ -169,7 +170,7 @@ export function explainGraph(graph) {
|
|
|
169
170
|
const queryOps = authz.operations.filter((op) => op.nodeKind === 'query');
|
|
170
171
|
let opaqueBoundaries = 0;
|
|
171
172
|
for (const action of graph.getNodesByKind('action')) {
|
|
172
|
-
opaqueBoundaries += (action
|
|
173
|
+
opaqueBoundaries += actionOperations(action).filter((op) => op.kind === 'native').length;
|
|
173
174
|
}
|
|
174
175
|
return {
|
|
175
176
|
nodeCountsByKind,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { actionOperations, operationChildren } from '@cynodia/axiom-core';
|
|
1
2
|
/** Every `NativeOperation` in the graph, with the action that contains it (spec16 §46). */
|
|
2
3
|
export function listNativeOperations(graph) {
|
|
3
4
|
const found = [];
|
|
4
5
|
for (const action of graph.getNodesByKind('action')) {
|
|
5
|
-
for (const operation of collectNative(action
|
|
6
|
+
for (const operation of collectNative(actionOperations(action))) {
|
|
6
7
|
found.push({
|
|
7
8
|
actionId: action.id,
|
|
8
9
|
implementationId: operation.implementationId,
|
|
@@ -22,7 +23,9 @@ function collectNative(operations) {
|
|
|
22
23
|
found.push(operation);
|
|
23
24
|
}
|
|
24
25
|
else if (operation.kind === 'for-each') {
|
|
25
|
-
|
|
26
|
+
// A valid for-each may only contain set/insert/remove (validateGraph enforces this),
|
|
27
|
+
// so this recursion only matters for an already-invalid graph — still total, never a crash.
|
|
28
|
+
found.push(...collectNative(operationChildren(operation)));
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
return found;
|
package/dist/queries.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { allowedInvocationSources, isClientInvocable as actionIsClientInvocable, isSystemOnlyAction, isUINode, locationFieldIds, locationRootStateId, locationSelectorFieldIds, referencedIds, } from '@cynodia/axiom-core';
|
|
1
|
+
import { actionOperations, allowedInvocationSources, isClientInvocable as actionIsClientInvocable, isSystemOnlyAction, isUINode, locationFieldIds, locationRootStateId, locationSelectorFieldIds, referencedIds, } from '@cynodia/axiom-core';
|
|
2
2
|
import { expressionFieldIds, queryExpressions, queryIsAggregate, queryRowEntityId, sortKeyDirection, } from '@cynodia/axiom-core';
|
|
3
3
|
const READ_KINDS = ['reads', 'binds', 'derives-from'];
|
|
4
4
|
const WRITE_KINDS = ['writes'];
|
|
@@ -64,7 +64,7 @@ export class GraphQueries {
|
|
|
64
64
|
analysisGaps() {
|
|
65
65
|
const gaps = [];
|
|
66
66
|
for (const action of this.graph.getNodesByKind('action')) {
|
|
67
|
-
for (const operation of action
|
|
67
|
+
for (const operation of actionOperations(action)) {
|
|
68
68
|
if (operation.kind === 'native' && (operation.declaredEffects ?? []).length === 0) {
|
|
69
69
|
gaps.push(`${action.name ?? action.id} runs the native operation "${operation.implementationId}" without declaring its effects`);
|
|
70
70
|
}
|
|
@@ -82,7 +82,7 @@ export class GraphQueries {
|
|
|
82
82
|
if (touchesState) {
|
|
83
83
|
return true;
|
|
84
84
|
}
|
|
85
|
-
return (action
|
|
85
|
+
return actionOperations(action).some((operation) => 'value' in operation && operation.value.kind === 'object'
|
|
86
86
|
? operation.value.entityId === entityId
|
|
87
87
|
: false);
|
|
88
88
|
});
|
|
@@ -133,7 +133,7 @@ export class GraphQueries {
|
|
|
133
133
|
return this.graph
|
|
134
134
|
.getNodesByKind('action')
|
|
135
135
|
.filter((action) => action.destructive === true ||
|
|
136
|
-
(action
|
|
136
|
+
actionOperations(action).some((operation) => operation.kind === 'remove'));
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
139
|
* The neighbourhood of a node, optionally restricted to particular relationships.
|
|
@@ -657,7 +657,7 @@ export class GraphQueries {
|
|
|
657
657
|
getActionsUsingStorage(storageId) {
|
|
658
658
|
return this.graph
|
|
659
659
|
.getNodesByKind('action')
|
|
660
|
-
.filter((action) => (action
|
|
660
|
+
.filter((action) => actionOperations(action).some((operation) => (operation.kind === 'blob-metadata' ||
|
|
661
661
|
operation.kind === 'blob-commit' ||
|
|
662
662
|
operation.kind === 'blob-delete') &&
|
|
663
663
|
operation.storageId === storageId));
|
|
@@ -73,6 +73,41 @@ function fixtureGraph() {
|
|
|
73
73
|
});
|
|
74
74
|
return g;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* A self-contained entity + query + (optionally referenced) read policies, for the F3
|
|
78
|
+
* attach/detach/replace fixtures. `declarePolicies` names every `ReadPolicyDef` node to
|
|
79
|
+
* declare (both, for a "replace" scenario), independent of which one `readPolicyId` points
|
|
80
|
+
* at, so the diff reflects only the reference change, not a policy node appearing/vanishing.
|
|
81
|
+
*/
|
|
82
|
+
function readPolicyFixtureGraph(readPolicyId, ...declarePolicies) {
|
|
83
|
+
const g = new ApplicationGraph('ct-read-policy-graph', 'Read Policy Fixture');
|
|
84
|
+
const entityId = nodeId('read_policy_entity');
|
|
85
|
+
const identityField = fieldId('read_policy_entity_id');
|
|
86
|
+
g.addNode({
|
|
87
|
+
id: entityId,
|
|
88
|
+
kind: 'entity',
|
|
89
|
+
identityFieldId: identityField,
|
|
90
|
+
fields: [{ id: identityField, valueType: primitiveType('string'), required: true }],
|
|
91
|
+
});
|
|
92
|
+
for (const policyId of new Set([...(readPolicyId ? [readPolicyId] : []), ...declarePolicies])) {
|
|
93
|
+
g.addNode({
|
|
94
|
+
id: nodeId(policyId),
|
|
95
|
+
kind: 'read-policy',
|
|
96
|
+
entityId,
|
|
97
|
+
rowScopeId: nodeId('read_policy_row'),
|
|
98
|
+
predicate: literal(true),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
g.addNode({
|
|
102
|
+
id: nodeId('read_policy_query'),
|
|
103
|
+
kind: 'query',
|
|
104
|
+
source: entityId,
|
|
105
|
+
rowScopeId: nodeId('read_policy_query_row'),
|
|
106
|
+
pagination: { strategy: 'offset', maxPageSize: 50 },
|
|
107
|
+
...(readPolicyId ? { readPolicyId: nodeId(readPolicyId) } : {}),
|
|
108
|
+
});
|
|
109
|
+
return g;
|
|
110
|
+
}
|
|
76
111
|
export function toolingConformanceFixtures() {
|
|
77
112
|
return [
|
|
78
113
|
{
|
|
@@ -145,7 +180,7 @@ export function toolingConformanceFixtures() {
|
|
|
145
180
|
{
|
|
146
181
|
id: 'semantic-diff-authorization-change-is-tagged',
|
|
147
182
|
category: 'semantic-diff',
|
|
148
|
-
description: 'detaching a policy from an action is classified as an authorization change',
|
|
183
|
+
description: 'detaching a policy from an action is classified as an authorization change, additive with its own kind category (spec16pt2 §40)',
|
|
149
184
|
run: () => {
|
|
150
185
|
const before = fixtureGraph();
|
|
151
186
|
const after = fixtureGraph();
|
|
@@ -155,7 +190,43 @@ export function toolingConformanceFixtures() {
|
|
|
155
190
|
const diff = new AgentAPI(after).semanticDiff(before);
|
|
156
191
|
return diff.entries.find((e) => e.nodeId === String(A_BUMP))?.categories;
|
|
157
192
|
},
|
|
158
|
-
expected: ['authorization'],
|
|
193
|
+
expected: ['semantic', 'authorization'],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'semantic-diff-read-policy-attach-is-authorization',
|
|
197
|
+
category: 'semantic-diff',
|
|
198
|
+
description: 'spec16pt2 F3/§34-35: attaching a QueryDef.readPolicyId is classified query + authorization',
|
|
199
|
+
run: () => {
|
|
200
|
+
const before = readPolicyFixtureGraph(undefined);
|
|
201
|
+
const after = readPolicyFixtureGraph('read_policy_owner');
|
|
202
|
+
const diff = new AgentAPI(after).semanticDiff(before);
|
|
203
|
+
return diff.entries.find((e) => e.nodeId === 'read_policy_query')?.categories;
|
|
204
|
+
},
|
|
205
|
+
expected: ['query', 'authorization'],
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 'semantic-diff-read-policy-detach-is-authorization',
|
|
209
|
+
category: 'semantic-diff',
|
|
210
|
+
description: 'spec16pt2 F3 exact reproduction: detaching a QueryDef.readPolicyId is classified query + authorization',
|
|
211
|
+
run: () => {
|
|
212
|
+
const before = readPolicyFixtureGraph('read_policy_owner');
|
|
213
|
+
const after = readPolicyFixtureGraph(undefined);
|
|
214
|
+
const diff = new AgentAPI(after).semanticDiff(before);
|
|
215
|
+
return diff.entries.find((e) => e.nodeId === 'read_policy_query')?.categories;
|
|
216
|
+
},
|
|
217
|
+
expected: ['query', 'authorization'],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: 'semantic-diff-read-policy-replace-is-authorization',
|
|
221
|
+
category: 'semantic-diff',
|
|
222
|
+
description: 'spec16pt2 §36: replacing one read policy reference for another is classified authorization',
|
|
223
|
+
run: () => {
|
|
224
|
+
const before = readPolicyFixtureGraph('read_policy_owner', 'read_policy_other');
|
|
225
|
+
const after = readPolicyFixtureGraph('read_policy_other', 'read_policy_other');
|
|
226
|
+
const diff = new AgentAPI(after).semanticDiff(before);
|
|
227
|
+
return diff.entries.find((e) => e.nodeId === 'read_policy_query')?.categories.includes('authorization');
|
|
228
|
+
},
|
|
229
|
+
expected: true,
|
|
159
230
|
},
|
|
160
231
|
{
|
|
161
232
|
id: 'diagnostics-unresolved-reference-is-structured',
|
package/dist/transaction.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApplicationGraph, createFieldId, createNodeId, isUINode, randomHex, synchronizeEdges, validateGraph, } from '@cynodia/axiom-core';
|
|
1
|
+
import { ApplicationGraph, actionOperations, createFieldId, createNodeId, isUINode, randomHex, synchronizeEdges, validateGraph, } from '@cynodia/axiom-core';
|
|
2
2
|
import { PresentationQueries } from './presentation-queries.js';
|
|
3
3
|
export class TransactionError extends Error {
|
|
4
4
|
result;
|
|
@@ -157,7 +157,10 @@ export class Transaction extends PresentationQueries {
|
|
|
157
157
|
const updated = [];
|
|
158
158
|
for (const action of this.graph.getNodesByKind('action')) {
|
|
159
159
|
let changed = false;
|
|
160
|
-
|
|
160
|
+
// spec16pt2 §12-20: a hand-tampered or AI-generated ActionDef.operations can be
|
|
161
|
+
// malformed (non-array, or hold a malformed entry). `actionOperations` establishes
|
|
162
|
+
// both before this ever iterates, rather than assuming the declared type holds.
|
|
163
|
+
const operations = actionOperations(action).map((operation) => {
|
|
161
164
|
if (!('value' in operation) || operation.value.kind !== 'object') {
|
|
162
165
|
return operation;
|
|
163
166
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cynodia/axiom-agent-api",
|
|
3
|
-
"version": "0.16.0-alpha.
|
|
3
|
+
"version": "0.16.0-alpha.3",
|
|
4
4
|
"description": "Semantic queries and transactional graph transformations for AI agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AskTech AS",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@cynodia/axiom-core": "0.16.0-alpha.
|
|
34
|
+
"@cynodia/axiom-core": "0.16.0-alpha.3"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc -b tsconfig.json tsconfig.test.json",
|