@cynodia/axiom-agent-api 0.14.0-alpha.5 → 0.15.0-alpha.2
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/api.d.ts +11 -0
- package/dist/api.js +13 -0
- package/dist/authorization.d.ts +97 -0
- package/dist/authorization.js +189 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +2 -2
package/dist/api.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { MigrationImpact, SchemaInspection } from './migration.js';
|
|
|
6
6
|
import type { DistributedSemanticsInspection } from './distributed.js';
|
|
7
7
|
import type { LiveQueryAnalysis } from './live-query.js';
|
|
8
8
|
import type { WorkflowAnalysis } from './workflow.js';
|
|
9
|
+
import type { AuthorizationAnalysis } from './authorization.js';
|
|
9
10
|
/**
|
|
10
11
|
* The machine-facing interface to an application. Agents query semantics and apply
|
|
11
12
|
* structural transformations; they never edit generated code.
|
|
@@ -59,5 +60,15 @@ export declare class AgentAPI extends PresentationQueries {
|
|
|
59
60
|
* graph; live runtime state is `AxiomServer.getWorkflow(instanceId)`.
|
|
60
61
|
*/
|
|
61
62
|
analyzeWorkflow(workflowId: string): WorkflowAnalysis;
|
|
63
|
+
/**
|
|
64
|
+
* The authorization semantics of this application (spec15 §42, §43, §44): what protects
|
|
65
|
+
* every action / query / workflow surface, what each `AuthorizationPolicyDef` depends on
|
|
66
|
+
* (`PRINCIPAL` / `RESOURCE` fields, `OPERATION`, a secret-free rule summary), which
|
|
67
|
+
* surfaces have no explicit authorization boundary, and which workflow action steps run a
|
|
68
|
+
* policy the start principal is not statically proven to satisfy. Static over the graph;
|
|
69
|
+
* it never claims a principal is authorized where it cannot prove it, and exposes no
|
|
70
|
+
* runtime secret. Live decisions are the authority's job (`AUTHORIZATION_DENIED`).
|
|
71
|
+
*/
|
|
72
|
+
analyzeAuthorization(): AuthorizationAnalysis;
|
|
62
73
|
}
|
|
63
74
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/api.js
CHANGED
|
@@ -5,6 +5,7 @@ import { inspectSchema, migrationImpact } from './migration.js';
|
|
|
5
5
|
import { inspectDistributedSemantics } from './distributed.js';
|
|
6
6
|
import { analyzeLiveQuery } from './live-query.js';
|
|
7
7
|
import { analyzeWorkflow } from './workflow.js';
|
|
8
|
+
import { analyzeAuthorization } from './authorization.js';
|
|
8
9
|
/**
|
|
9
10
|
* The machine-facing interface to an application. Agents query semantics and apply
|
|
10
11
|
* structural transformations; they never edit generated code.
|
|
@@ -82,4 +83,16 @@ export class AgentAPI extends PresentationQueries {
|
|
|
82
83
|
analyzeWorkflow(workflowId) {
|
|
83
84
|
return analyzeWorkflow(this.graph, workflowId);
|
|
84
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The authorization semantics of this application (spec15 §42, §43, §44): what protects
|
|
88
|
+
* every action / query / workflow surface, what each `AuthorizationPolicyDef` depends on
|
|
89
|
+
* (`PRINCIPAL` / `RESOURCE` fields, `OPERATION`, a secret-free rule summary), which
|
|
90
|
+
* surfaces have no explicit authorization boundary, and which workflow action steps run a
|
|
91
|
+
* policy the start principal is not statically proven to satisfy. Static over the graph;
|
|
92
|
+
* it never claims a principal is authorized where it cannot prove it, and exposes no
|
|
93
|
+
* runtime secret. Live decisions are the authority's job (`AUTHORIZATION_DENIED`).
|
|
94
|
+
*/
|
|
95
|
+
analyzeAuthorization() {
|
|
96
|
+
return analyzeAuthorization(this.graph);
|
|
97
|
+
}
|
|
85
98
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type ApplicationGraph } from '@cynodia/axiom-core';
|
|
2
|
+
/**
|
|
3
|
+
* Static, graph-derivable authorization analysis (spec15 §42, §43, §44). The AgentAPI works
|
|
4
|
+
* over an `ApplicationGraph`, not a running authority, so it answers the *semantic*
|
|
5
|
+
* questions — what protects each surface, what a policy depends on, which surfaces have no
|
|
6
|
+
* explicit authorization boundary, whether a workflow can reach an action requiring
|
|
7
|
+
* permissions its start principal is not proven to hold. It never claims a principal *is*
|
|
8
|
+
* authorized where it cannot prove it (spec15 §50), and it reports policy **structure**, not
|
|
9
|
+
* any runtime secret (spec15 §83).
|
|
10
|
+
*
|
|
11
|
+
* Live decisions and their reasons are the authority's job (`AUTHORIZATION_DENIED` with
|
|
12
|
+
* `details.reason`).
|
|
13
|
+
*/
|
|
14
|
+
/** How one authorization policy reads its closed scope, plus a secret-free rendering. */
|
|
15
|
+
export interface AuthorizationPolicyAnalysis {
|
|
16
|
+
policyId: string;
|
|
17
|
+
/** Field ids read off `PRINCIPAL`. */
|
|
18
|
+
principalFields: string[];
|
|
19
|
+
/** Field ids read off `RESOURCE`. */
|
|
20
|
+
resourceFields: string[];
|
|
21
|
+
/** Whether the policy references `OPERATION`. */
|
|
22
|
+
readsOperation: boolean;
|
|
23
|
+
/** A verdict when `allow` is a constant `literal` (spec15 §8), else `null`. */
|
|
24
|
+
constant: 'always-allow' | 'always-deny' | null;
|
|
25
|
+
/** A structured, secret-free one-line rendering of the rule (spec15 §44). */
|
|
26
|
+
summary: string;
|
|
27
|
+
/** Any `AUTHORIZATION_*` validation problems on this policy. */
|
|
28
|
+
problems: string[];
|
|
29
|
+
}
|
|
30
|
+
export type OperationProtection = {
|
|
31
|
+
kind: 'policy';
|
|
32
|
+
policyId: string;
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'legacy-expression';
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'policy+legacy';
|
|
37
|
+
policyId: string;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'read-policy';
|
|
40
|
+
readPolicyId: string;
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'policy+read-policy';
|
|
43
|
+
policyId: string;
|
|
44
|
+
readPolicyId: string;
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'owner-fingerprint';
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'infrastructure';
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'public';
|
|
51
|
+
};
|
|
52
|
+
/** One protected (or unprotected) semantic surface. */
|
|
53
|
+
export interface OperationCoverage {
|
|
54
|
+
operation: string;
|
|
55
|
+
nodeId: string;
|
|
56
|
+
nodeKind: 'action' | 'query' | 'workflow';
|
|
57
|
+
protection: OperationProtection;
|
|
58
|
+
/** True when the surface has no explicit authorization boundary and could carry one. */
|
|
59
|
+
unresolved: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface WorkflowAuthorizationAnalysis {
|
|
62
|
+
workflowId: string;
|
|
63
|
+
start: 'policy' | 'public';
|
|
64
|
+
startPolicyId: string | null;
|
|
65
|
+
instanceAccess: 'policy' | 'owner-fingerprint';
|
|
66
|
+
instanceAccessPolicyId: string | null;
|
|
67
|
+
/** Each distinct `ActionDef` a step invokes, with that action's own protection. */
|
|
68
|
+
actionDependencies: Array<{
|
|
69
|
+
actionId: string;
|
|
70
|
+
protection: OperationProtection;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* spec15 §101 — action steps whose `ActionDef` requires a policy. Static analysis cannot
|
|
74
|
+
* prove the workflow's start principal satisfies that policy, so each is a
|
|
75
|
+
* privilege-amplification surface to review (the runtime enforces it per step, §10).
|
|
76
|
+
*/
|
|
77
|
+
privilegeReviewActions: string[];
|
|
78
|
+
}
|
|
79
|
+
export interface AuthorizationAnalysis {
|
|
80
|
+
/**
|
|
81
|
+
* Whether the graph carries any 0.15 authorization vocabulary — an `AuthorizationPolicyDef`
|
|
82
|
+
* or an `authorizationPolicy` / `startPolicy` / `instanceAccessPolicy` reference. When
|
|
83
|
+
* `true` the graph requires Server IR contract `axiom.server.v9`.
|
|
84
|
+
*/
|
|
85
|
+
usesAuthorizationVocabulary: boolean;
|
|
86
|
+
policies: AuthorizationPolicyAnalysis[];
|
|
87
|
+
operations: OperationCoverage[];
|
|
88
|
+
workflows: WorkflowAuthorizationAnalysis[];
|
|
89
|
+
/** Every semantic surface with no explicit authorization boundary (spec15 §43). */
|
|
90
|
+
unprotected: Array<{
|
|
91
|
+
nodeId: string;
|
|
92
|
+
nodeKind: string;
|
|
93
|
+
operation: string;
|
|
94
|
+
}>;
|
|
95
|
+
}
|
|
96
|
+
export declare function analyzeAuthorization(graph: ApplicationGraph): AuthorizationAnalysis;
|
|
97
|
+
//# sourceMappingURL=authorization.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { authorizationPolicyDependencies, authorizationPolicyProblems, workflowActionIds, } from '@cynodia/axiom-core';
|
|
2
|
+
function asString(value) {
|
|
3
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
4
|
+
}
|
|
5
|
+
/** A secret-free one-line rendering of common policy shapes; falls back to a dependency list. */
|
|
6
|
+
function summarize(policy, deps) {
|
|
7
|
+
if (deps.constant === 'always-allow')
|
|
8
|
+
return 'always allows';
|
|
9
|
+
if (deps.constant === 'always-deny')
|
|
10
|
+
return 'always denies';
|
|
11
|
+
const rendered = renderExpression(policy.allow);
|
|
12
|
+
if (rendered)
|
|
13
|
+
return `requires ${rendered}`;
|
|
14
|
+
const parts = [];
|
|
15
|
+
if (deps.principalFields.length)
|
|
16
|
+
parts.push(`PRINCIPAL.{${deps.principalFields.join(', ')}}`);
|
|
17
|
+
if (deps.resourceFields.length)
|
|
18
|
+
parts.push(`RESOURCE.{${deps.resourceFields.join(', ')}}`);
|
|
19
|
+
if (deps.readsOperation)
|
|
20
|
+
parts.push('OPERATION');
|
|
21
|
+
return parts.length ? `a predicate over ${parts.join(' and ')}` : 'a predicate';
|
|
22
|
+
}
|
|
23
|
+
/** Best-effort structural render — only shapes with no runtime-secret exposure. */
|
|
24
|
+
function renderExpression(expression) {
|
|
25
|
+
if (!expression || typeof expression !== 'object')
|
|
26
|
+
return null;
|
|
27
|
+
const e = expression;
|
|
28
|
+
switch (e.kind) {
|
|
29
|
+
case 'literal':
|
|
30
|
+
return JSON.stringify(e.value);
|
|
31
|
+
case 'ref':
|
|
32
|
+
return refName(String(e.targetId));
|
|
33
|
+
case 'field': {
|
|
34
|
+
const src = renderExpression(e.source);
|
|
35
|
+
return src ? `${src}.${String(e.fieldId)}` : null;
|
|
36
|
+
}
|
|
37
|
+
case 'unary': {
|
|
38
|
+
const operand = renderExpression(e.operand);
|
|
39
|
+
return operand ? `${String(e.operator)} ${operand}` : null;
|
|
40
|
+
}
|
|
41
|
+
case 'binary': {
|
|
42
|
+
const l = renderExpression(e.left);
|
|
43
|
+
const r = renderExpression(e.right);
|
|
44
|
+
return l && r ? `${l} ${binaryOp(String(e.operator))} ${r}` : null;
|
|
45
|
+
}
|
|
46
|
+
case 'call': {
|
|
47
|
+
const args = Array.isArray(e.arguments) ? e.arguments.map(renderExpression) : [];
|
|
48
|
+
return args.every((a) => a !== null) ? `${String(e.function)}(${args.join(', ')})` : null;
|
|
49
|
+
}
|
|
50
|
+
default:
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function refName(id) {
|
|
55
|
+
if (id === 'axiom_principal')
|
|
56
|
+
return 'PRINCIPAL';
|
|
57
|
+
if (id === 'axiom_resource')
|
|
58
|
+
return 'RESOURCE';
|
|
59
|
+
if (id === 'axiom_operation')
|
|
60
|
+
return 'OPERATION';
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
function binaryOp(op) {
|
|
64
|
+
return { eq: '==', neq: '!=', gt: '>', gte: '>=', lt: '<', lte: '<=', and: 'AND', or: 'OR' }[op] ?? op;
|
|
65
|
+
}
|
|
66
|
+
export function analyzeAuthorization(graph) {
|
|
67
|
+
const policyNodes = graph.getNodesByKind('authorization-policy');
|
|
68
|
+
const actions = graph.getNodesByKind('action');
|
|
69
|
+
const queries = graph.getNodesByKind('query');
|
|
70
|
+
const workflows = graph.getNodesByKind('workflow');
|
|
71
|
+
const readPolicyEntities = new Set(graph.getNodesByKind('read-policy').map((p) => String(p.entityId)));
|
|
72
|
+
const readPolicyByEntity = new Map(graph.getNodesByKind('read-policy').map((p) => [
|
|
73
|
+
String(p.entityId),
|
|
74
|
+
String(p.id),
|
|
75
|
+
]));
|
|
76
|
+
const policies = policyNodes.map((policy) => {
|
|
77
|
+
const deps = authorizationPolicyDependencies(policy);
|
|
78
|
+
return {
|
|
79
|
+
policyId: String(policy.id),
|
|
80
|
+
principalFields: deps.principalFields,
|
|
81
|
+
resourceFields: deps.resourceFields,
|
|
82
|
+
readsOperation: deps.readsOperation,
|
|
83
|
+
constant: deps.constant,
|
|
84
|
+
summary: summarize(policy, deps),
|
|
85
|
+
problems: authorizationPolicyProblems(policy).map((p) => `[${p.code}] ${p.message}`),
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
const operations = [];
|
|
89
|
+
const unprotected = [];
|
|
90
|
+
for (const action of actions) {
|
|
91
|
+
const policyId = asString(action.authorizationPolicy);
|
|
92
|
+
const hasLegacy = action.authorization !== undefined;
|
|
93
|
+
let protection;
|
|
94
|
+
if (policyId && hasLegacy)
|
|
95
|
+
protection = { kind: 'policy+legacy', policyId };
|
|
96
|
+
else if (policyId)
|
|
97
|
+
protection = { kind: 'policy', policyId };
|
|
98
|
+
else if (hasLegacy)
|
|
99
|
+
protection = { kind: 'legacy-expression' };
|
|
100
|
+
else
|
|
101
|
+
protection = { kind: 'public' };
|
|
102
|
+
const unresolved = protection.kind === 'public';
|
|
103
|
+
operations.push({ operation: 'action.invoke', nodeId: String(action.id), nodeKind: 'action', protection, unresolved });
|
|
104
|
+
if (unresolved)
|
|
105
|
+
unprotected.push({ nodeId: String(action.id), nodeKind: 'action', operation: 'action.invoke' });
|
|
106
|
+
}
|
|
107
|
+
for (const query of queries) {
|
|
108
|
+
const policyId = asString(query.authorizationPolicy);
|
|
109
|
+
const readPolicyId = asString(query.readPolicyId) ??
|
|
110
|
+
(readPolicyEntities.has(String(query.source))
|
|
111
|
+
? (readPolicyByEntity.get(String(query.source)) ?? null)
|
|
112
|
+
: null);
|
|
113
|
+
let protection;
|
|
114
|
+
if (policyId && readPolicyId)
|
|
115
|
+
protection = { kind: 'policy+read-policy', policyId, readPolicyId };
|
|
116
|
+
else if (policyId)
|
|
117
|
+
protection = { kind: 'policy', policyId };
|
|
118
|
+
else if (readPolicyId)
|
|
119
|
+
protection = { kind: 'read-policy', readPolicyId };
|
|
120
|
+
else
|
|
121
|
+
protection = { kind: 'public' };
|
|
122
|
+
const unresolved = protection.kind === 'public';
|
|
123
|
+
operations.push({ operation: 'query.read', nodeId: String(query.id), nodeKind: 'query', protection, unresolved });
|
|
124
|
+
if (unresolved)
|
|
125
|
+
unprotected.push({ nodeId: String(query.id), nodeKind: 'query', operation: 'query.read' });
|
|
126
|
+
}
|
|
127
|
+
const protectionOf = (actionId) => operations.find((o) => o.nodeKind === 'action' && o.nodeId === actionId)?.protection ?? { kind: 'public' };
|
|
128
|
+
const workflowAnalyses = workflows.map((workflow) => {
|
|
129
|
+
const startPolicyId = asString(workflow.startPolicy);
|
|
130
|
+
const instanceAccessPolicyId = asString(workflow.instanceAccessPolicy);
|
|
131
|
+
const actionIds = safeWorkflowActionIds(workflow).map(String);
|
|
132
|
+
const actionDependencies = [...new Set(actionIds)].map((actionId) => ({
|
|
133
|
+
actionId,
|
|
134
|
+
protection: protectionOf(actionId),
|
|
135
|
+
}));
|
|
136
|
+
const privilegeReviewActions = actionDependencies
|
|
137
|
+
.filter((d) => d.protection.kind === 'policy' || d.protection.kind === 'policy+legacy' || d.protection.kind === 'legacy-expression')
|
|
138
|
+
.map((d) => d.actionId);
|
|
139
|
+
// workflow.start
|
|
140
|
+
operations.push({
|
|
141
|
+
operation: 'workflow.start',
|
|
142
|
+
nodeId: String(workflow.id),
|
|
143
|
+
nodeKind: 'workflow',
|
|
144
|
+
protection: startPolicyId ? { kind: 'policy', policyId: startPolicyId } : { kind: 'public' },
|
|
145
|
+
unresolved: !startPolicyId,
|
|
146
|
+
});
|
|
147
|
+
if (!startPolicyId)
|
|
148
|
+
unprotected.push({ nodeId: String(workflow.id), nodeKind: 'workflow', operation: 'workflow.start' });
|
|
149
|
+
// workflow.cancel / .inspect / .history — owner-fingerprint is a defined default, not "unresolved".
|
|
150
|
+
for (const op of ['workflow.cancel', 'workflow.inspect', 'workflow.history']) {
|
|
151
|
+
operations.push({
|
|
152
|
+
operation: op,
|
|
153
|
+
nodeId: String(workflow.id),
|
|
154
|
+
nodeKind: 'workflow',
|
|
155
|
+
protection: instanceAccessPolicyId ? { kind: 'policy', policyId: instanceAccessPolicyId } : { kind: 'owner-fingerprint' },
|
|
156
|
+
unresolved: false,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
workflowId: String(workflow.id),
|
|
161
|
+
start: startPolicyId ? 'policy' : 'public',
|
|
162
|
+
startPolicyId,
|
|
163
|
+
instanceAccess: instanceAccessPolicyId ? 'policy' : 'owner-fingerprint',
|
|
164
|
+
instanceAccessPolicyId,
|
|
165
|
+
actionDependencies,
|
|
166
|
+
privilegeReviewActions,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
const usesAuthorizationVocabulary = policyNodes.length > 0 ||
|
|
170
|
+
actions.some((a) => a.authorizationPolicy !== undefined) ||
|
|
171
|
+
queries.some((q) => q.authorizationPolicy !== undefined) ||
|
|
172
|
+
workflows.some((w) => w.startPolicy !== undefined ||
|
|
173
|
+
w.instanceAccessPolicy !== undefined);
|
|
174
|
+
return {
|
|
175
|
+
usesAuthorizationVocabulary,
|
|
176
|
+
policies,
|
|
177
|
+
operations,
|
|
178
|
+
workflows: workflowAnalyses,
|
|
179
|
+
unprotected,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function safeWorkflowActionIds(workflow) {
|
|
183
|
+
try {
|
|
184
|
+
return workflowActionIds(workflow);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './migration.js';
|
|
|
4
4
|
export * from './distributed.js';
|
|
5
5
|
export * from './live-query.js';
|
|
6
6
|
export * from './workflow.js';
|
|
7
|
+
export * from './authorization.js';
|
|
7
8
|
export * from './presentation-queries.js';
|
|
8
9
|
export * from './transaction.js';
|
|
9
10
|
export * from './api.js';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from './migration.js';
|
|
|
4
4
|
export * from './distributed.js';
|
|
5
5
|
export * from './live-query.js';
|
|
6
6
|
export * from './workflow.js';
|
|
7
|
+
export * from './authorization.js';
|
|
7
8
|
export * from './presentation-queries.js';
|
|
8
9
|
export * from './transaction.js';
|
|
9
10
|
export * from './api.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cynodia/axiom-agent-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0-alpha.2",
|
|
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.
|
|
34
|
+
"@cynodia/axiom-core": "0.15.0-alpha.2"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc -b tsconfig.json tsconfig.test.json",
|