@cynodia/axiom-agent-api 0.15.0-alpha.3 → 0.16.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 CHANGED
@@ -1,12 +1,22 @@
1
- import type { ApplicationGraph, SchemaDiff, ValidationResult } from '@cynodia/axiom-core';
1
+ import type { ApplicationGraph, SchemaDiff, SemanticDiff, ServerIRContract, ValidationResult } from '@cynodia/axiom-core';
2
2
  import { PresentationQueries } from './presentation-queries.js';
3
3
  import { Transaction } from './transaction.js';
4
4
  import type { ChangeSet } from './changes.js';
5
+ import type { GraphChange } from './changes.js';
5
6
  import type { MigrationImpact, SchemaInspection } from './migration.js';
6
7
  import type { DistributedSemanticsInspection } from './distributed.js';
7
8
  import type { LiveQueryAnalysis } from './live-query.js';
8
9
  import type { WorkflowAnalysis } from './workflow.js';
9
10
  import type { AuthorizationAnalysis } from './authorization.js';
11
+ import type { InventoryQuery, SemanticInventory } from './inventory.js';
12
+ import { explainDependency, transitiveDependencies, transitiveDependents } from './dependencies.js';
13
+ import type { DependencyProvenance, TransitiveDependencyResult } from './dependencies.js';
14
+ import { explainAction, explainQueryFull, explainState, explainWorkflowFull } from './explain.js';
15
+ import type { ActionExplanation, FullQueryExplanation, FullWorkflowExplanation, GraphSummary, StateExplanation } from './explain.js';
16
+ import type { CapabilityAnalysis } from './capabilities.js';
17
+ import type { NativeOperationOccurrence, NativeOperationSummary } from './native-operations.js';
18
+ import type { AuthorizationDecisionExplanation, AuthorizationDecisionRequest } from './authorization-decision.js';
19
+ import type { GraphEditRequest, GraphEditResult } from './graph-edit.js';
10
20
  /**
11
21
  * The machine-facing interface to an application. Agents query semantics and apply
12
22
  * structural transformations; they never edit generated code.
@@ -70,5 +80,61 @@ export declare class AgentAPI extends PresentationQueries {
70
80
  * runtime secret. Live decisions are the authority's job (`AUTHORIZATION_DENIED`).
71
81
  */
72
82
  analyzeAuthorization(): AuthorizationAnalysis;
83
+ /** Every graph node, by id and kind, with dependency/dependent counts (spec16 §9-11, §113-115). */
84
+ inventory(query?: InventoryQuery): SemanticInventory;
85
+ /** Every node transitively reachable from `id` by following outgoing edges (spec16 §12-13). */
86
+ getTransitiveDependencies(id: Parameters<typeof transitiveDependencies>[1], kinds?: Parameters<typeof transitiveDependencies>[2]): TransitiveDependencyResult;
87
+ /** Every node that transitively depends on `id` (spec16 §12-13). */
88
+ getTransitiveDependents(id: Parameters<typeof transitiveDependents>[1], kinds?: Parameters<typeof transitiveDependents>[2]): TransitiveDependencyResult;
89
+ /** Why a dependency edge exists between two nodes (spec16 §14). */
90
+ explainDependency(from: Parameters<typeof explainDependency>[1], to: Parameters<typeof explainDependency>[2]): DependencyProvenance | undefined;
91
+ /** A structured explanation of an `ActionDef`: reads, writes, effects, authorization, invokers (spec16 §17). */
92
+ explainAction(actionId: Parameters<typeof explainAction>[1]): ActionExplanation | undefined;
93
+ /** A structured explanation of a `StateDef`: type, persistence, readers, writers, constraints (spec16 §21). */
94
+ explainState(stateId: Parameters<typeof explainState>[1]): StateExplanation | undefined;
95
+ /** `explainQuery` plus its authorization surface and live-query capability (spec16 §18). */
96
+ explainQuery(queryId: Parameters<typeof explainQueryFull>[1]): FullQueryExplanation | undefined;
97
+ /** `analyzeWorkflow` plus its authorization surface (spec16 §19-20). */
98
+ explainWorkflow(workflowId: Parameters<typeof explainWorkflowFull>[1]): FullWorkflowExplanation;
99
+ /** A structural, domain-neutral graph summary: counts, executable roots, security boundaries (spec16 §161-162). */
100
+ explainGraph(): GraphSummary;
101
+ /** Required runtime/provider capabilities, with provenance for each (spec16 §30-31, §50-51). */
102
+ analyzeCapabilities(): CapabilityAnalysis;
103
+ /** Every `NativeOperation` in the graph — the one boundary static analysis cannot see through (spec16 §46-49). */
104
+ listNativeOperations(): NativeOperationOccurrence[];
105
+ /** How many `NativeOperation`s exist, and how many are fully opaque (spec16 §49). */
106
+ summarizeNativeOperations(): NativeOperationSummary;
107
+ /**
108
+ * A concrete authorization decision, evaluated through the same evaluator the authority
109
+ * uses, with zero mutation and zero effect (spec16 §26, §136-141). Advisory only — the
110
+ * real operation always re-authorizes on the authority (spec16 §138).
111
+ */
112
+ explainAuthorizationDecision(request: AuthorizationDecisionRequest): AuthorizationDecisionExplanation | undefined;
113
+ /** The Server IR contract this graph currently requires (spec16 §32-33, §122). */
114
+ requiredServerContract(): ServerIRContract;
115
+ /**
116
+ * The canonical semantic difference between `other` and this graph: added/removed/changed
117
+ * nodes classified into categories, plus compatibility impact — does it move
118
+ * `semanticFingerprint`, `schemaFingerprint`, or the required Server IR contract
119
+ * (spec16 §34-38, §150-160).
120
+ */
121
+ semanticDiff(other: ApplicationGraph): SemanticDiff;
122
+ /**
123
+ * Propose a structured, portable edit set against a private clone: check preconditions,
124
+ * apply, validate, and compute the semantic diff — all without mutating this graph
125
+ * (spec16 §79-87). Accept a valid result with {@link acceptEdit}.
126
+ */
127
+ proposeEdit(request: GraphEditRequest): GraphEditResult;
128
+ /**
129
+ * Commits a previously-proposed, valid candidate into this graph and records it in the
130
+ * change history — the only way a `proposeEdit` result becomes live (spec16 §145).
131
+ * Refuses a result that did not validate; re-validates the candidate as a defensive check
132
+ * against a candidate built by hand.
133
+ */
134
+ acceptEdit(result: GraphEditResult, options?: {
135
+ reason?: string;
136
+ actor?: string;
137
+ }): ChangeSet;
73
138
  }
139
+ export type { GraphChange };
74
140
  //# sourceMappingURL=api.d.ts.map
package/dist/api.js CHANGED
@@ -1,4 +1,4 @@
1
- import { diffSchema, validateGraph } from '@cynodia/axiom-core';
1
+ import { diffSchema, randomHex, requiredServerContractForGraph, semanticDiff as computeSemanticDiff, validateGraph } from '@cynodia/axiom-core';
2
2
  import { PresentationQueries } from './presentation-queries.js';
3
3
  import { Transaction } from './transaction.js';
4
4
  import { inspectSchema, migrationImpact } from './migration.js';
@@ -6,6 +6,13 @@ import { inspectDistributedSemantics } from './distributed.js';
6
6
  import { analyzeLiveQuery } from './live-query.js';
7
7
  import { analyzeWorkflow } from './workflow.js';
8
8
  import { analyzeAuthorization } from './authorization.js';
9
+ import { semanticInventory } from './inventory.js';
10
+ import { explainDependency, transitiveDependencies, transitiveDependents } from './dependencies.js';
11
+ import { explainAction, explainGraph, explainQueryFull, explainState, explainWorkflowFull } from './explain.js';
12
+ import { analyzeCapabilities } from './capabilities.js';
13
+ import { listNativeOperations, summarizeNativeOperations } from './native-operations.js';
14
+ import { explainAuthorizationDecision } from './authorization-decision.js';
15
+ import { proposeGraphEdit } from './graph-edit.js';
9
16
  /**
10
17
  * The machine-facing interface to an application. Agents query semantics and apply
11
18
  * structural transformations; they never edit generated code.
@@ -95,4 +102,107 @@ export class AgentAPI extends PresentationQueries {
95
102
  analyzeAuthorization() {
96
103
  return analyzeAuthorization(this.graph);
97
104
  }
105
+ // ------------------------------------------------------------------ spec16: inspection
106
+ /** Every graph node, by id and kind, with dependency/dependent counts (spec16 §9-11, §113-115). */
107
+ inventory(query) {
108
+ return semanticInventory(this.graph, query);
109
+ }
110
+ /** Every node transitively reachable from `id` by following outgoing edges (spec16 §12-13). */
111
+ getTransitiveDependencies(id, kinds) {
112
+ return transitiveDependencies(this.graph, id, kinds);
113
+ }
114
+ /** Every node that transitively depends on `id` (spec16 §12-13). */
115
+ getTransitiveDependents(id, kinds) {
116
+ return transitiveDependents(this.graph, id, kinds);
117
+ }
118
+ /** Why a dependency edge exists between two nodes (spec16 §14). */
119
+ explainDependency(from, to) {
120
+ return explainDependency(this.graph, from, to);
121
+ }
122
+ /** A structured explanation of an `ActionDef`: reads, writes, effects, authorization, invokers (spec16 §17). */
123
+ explainAction(actionId) {
124
+ return explainAction(this.graph, actionId);
125
+ }
126
+ /** A structured explanation of a `StateDef`: type, persistence, readers, writers, constraints (spec16 §21). */
127
+ explainState(stateId) {
128
+ return explainState(this.graph, stateId);
129
+ }
130
+ /** `explainQuery` plus its authorization surface and live-query capability (spec16 §18). */
131
+ explainQuery(queryId) {
132
+ return explainQueryFull(this.graph, queryId);
133
+ }
134
+ /** `analyzeWorkflow` plus its authorization surface (spec16 §19-20). */
135
+ explainWorkflow(workflowId) {
136
+ return explainWorkflowFull(this.graph, workflowId);
137
+ }
138
+ /** A structural, domain-neutral graph summary: counts, executable roots, security boundaries (spec16 §161-162). */
139
+ explainGraph() {
140
+ return explainGraph(this.graph);
141
+ }
142
+ /** Required runtime/provider capabilities, with provenance for each (spec16 §30-31, §50-51). */
143
+ analyzeCapabilities() {
144
+ return analyzeCapabilities(this.graph);
145
+ }
146
+ /** Every `NativeOperation` in the graph — the one boundary static analysis cannot see through (spec16 §46-49). */
147
+ listNativeOperations() {
148
+ return listNativeOperations(this.graph);
149
+ }
150
+ /** How many `NativeOperation`s exist, and how many are fully opaque (spec16 §49). */
151
+ summarizeNativeOperations() {
152
+ return summarizeNativeOperations(this.graph);
153
+ }
154
+ /**
155
+ * A concrete authorization decision, evaluated through the same evaluator the authority
156
+ * uses, with zero mutation and zero effect (spec16 §26, §136-141). Advisory only — the
157
+ * real operation always re-authorizes on the authority (spec16 §138).
158
+ */
159
+ explainAuthorizationDecision(request) {
160
+ return explainAuthorizationDecision(this.graph, request);
161
+ }
162
+ /** The Server IR contract this graph currently requires (spec16 §32-33, §122). */
163
+ requiredServerContract() {
164
+ return requiredServerContractForGraph(this.graph);
165
+ }
166
+ /**
167
+ * The canonical semantic difference between `other` and this graph: added/removed/changed
168
+ * nodes classified into categories, plus compatibility impact — does it move
169
+ * `semanticFingerprint`, `schemaFingerprint`, or the required Server IR contract
170
+ * (spec16 §34-38, §150-160).
171
+ */
172
+ semanticDiff(other) {
173
+ return computeSemanticDiff(other, this.graph);
174
+ }
175
+ /**
176
+ * Propose a structured, portable edit set against a private clone: check preconditions,
177
+ * apply, validate, and compute the semantic diff — all without mutating this graph
178
+ * (spec16 §79-87). Accept a valid result with {@link acceptEdit}.
179
+ */
180
+ proposeEdit(request) {
181
+ return proposeGraphEdit(this.graph, request);
182
+ }
183
+ /**
184
+ * Commits a previously-proposed, valid candidate into this graph and records it in the
185
+ * change history — the only way a `proposeEdit` result becomes live (spec16 §145).
186
+ * Refuses a result that did not validate; re-validates the candidate as a defensive check
187
+ * against a candidate built by hand.
188
+ */
189
+ acceptEdit(result, options = {}) {
190
+ if (!result.applied || !result.candidate) {
191
+ throw new Error('acceptEdit: refusing to accept an edit that did not validate');
192
+ }
193
+ const revalidated = validateGraph(result.candidate);
194
+ if (!revalidated.valid) {
195
+ throw new Error('acceptEdit: the candidate no longer validates');
196
+ }
197
+ this.graph.restore(result.candidate.toJSON());
198
+ const change = {
199
+ id: `change_${randomHex(6)}`,
200
+ timestamp: Date.now(),
201
+ operations: [],
202
+ ...(options.reason ? { reason: options.reason } : {}),
203
+ ...(options.actor ? { actor: options.actor } : {}),
204
+ };
205
+ this.changeLog.push(change);
206
+ return change;
207
+ }
98
208
  }
@@ -0,0 +1,33 @@
1
+ import { type ApplicationGraph, type AuthorizationOperation, type NodeId } from '@cynodia/axiom-core';
2
+ /**
3
+ * Concrete authorization decision explanation (spec16 §26, §136-141), evaluated through the
4
+ * **same** canonical, security-absence-aware evaluator the authority uses
5
+ * (`evaluateAuthorizationExpression` / `decideAuthorization`, spec15pt3) — never a second,
6
+ * tooling-only interpretation (spec16 §197). It performs no mutation, no provider call and no
7
+ * effect: it is a pure function of the policy expression and the supplied principal /
8
+ * resource, exactly like an ordinary static analysis (spec16 §133, §136).
9
+ *
10
+ * This is advisory. The real operation always re-authorizes on the authority; a prior
11
+ * tooling result is never a token (spec16 §138).
12
+ */
13
+ export interface AuthorizationDecisionRequest {
14
+ actionId?: NodeId;
15
+ queryId?: NodeId;
16
+ principal?: Record<string, unknown> | null;
17
+ resource?: Record<string, unknown> | null;
18
+ }
19
+ export interface AuthorizationPartResult {
20
+ evaluated: boolean;
21
+ ok?: boolean;
22
+ value?: unknown;
23
+ }
24
+ export interface AuthorizationDecisionExplanation {
25
+ operation: AuthorizationOperation;
26
+ decision: 'ALLOW' | 'DENY';
27
+ reason: string;
28
+ policyId: string | null;
29
+ policyResult: AuthorizationPartResult | null;
30
+ legacyResult: AuthorizationPartResult | null;
31
+ }
32
+ export declare function explainAuthorizationDecision(graph: ApplicationGraph, request: AuthorizationDecisionRequest): AuthorizationDecisionExplanation | undefined;
33
+ //# sourceMappingURL=authorization-decision.d.ts.map
@@ -0,0 +1,58 @@
1
+ import { decideAuthorization, evaluateAuthorizationExpression, } from '@cynodia/axiom-core';
2
+ function partResult(part) {
3
+ return { evaluated: true, ok: part.ok, ...(part.ok ? { value: part.value } : {}) };
4
+ }
5
+ export function explainAuthorizationDecision(graph, request) {
6
+ let operation;
7
+ let policyId;
8
+ let legacyExpression;
9
+ if (request.actionId !== undefined) {
10
+ const action = graph.getNode(request.actionId);
11
+ if (!action || action.kind !== 'action')
12
+ return undefined;
13
+ operation = 'action.invoke';
14
+ policyId = action.authorizationPolicy;
15
+ legacyExpression = action.authorization;
16
+ }
17
+ else if (request.queryId !== undefined) {
18
+ const query = graph.getNode(request.queryId);
19
+ if (!query || query.kind !== 'query')
20
+ return undefined;
21
+ operation = 'query.read';
22
+ policyId = query.authorizationPolicy;
23
+ }
24
+ else {
25
+ return undefined;
26
+ }
27
+ const context = { principal: request.principal ?? null, resource: request.resource ?? null, operation };
28
+ let policyPart;
29
+ let policyResult = null;
30
+ if (policyId !== undefined) {
31
+ const policy = graph.getNode(policyId);
32
+ policyPart = policy && policy.kind === 'authorization-policy'
33
+ ? evaluateAuthorizationExpression(policy.allow, context, 'policy')
34
+ : { ok: false };
35
+ policyResult = partResult(policyPart);
36
+ }
37
+ let legacyPart;
38
+ let legacyResult = null;
39
+ if (legacyExpression !== undefined) {
40
+ // Static analysis has no running StateDef to resolve a legacy expression's external
41
+ // refs against, so an id outside the closed PRINCIPAL/RESOURCE/OPERATION scope fails
42
+ // closed here exactly as it would with no resolver at runtime (spec15pt3).
43
+ legacyPart = evaluateAuthorizationExpression(legacyExpression, context, 'legacy-action');
44
+ legacyResult = partResult(legacyPart);
45
+ }
46
+ const result = decideAuthorization({
47
+ ...(policyPart ? { policy: policyPart } : {}),
48
+ ...(legacyPart ? { legacy: legacyPart } : {}),
49
+ });
50
+ return {
51
+ operation,
52
+ decision: result.decision,
53
+ reason: result.reason,
54
+ policyId: policyId ?? null,
55
+ policyResult,
56
+ legacyResult,
57
+ };
58
+ }
@@ -0,0 +1,24 @@
1
+ import type { ApplicationGraph } from '@cynodia/axiom-core';
2
+ /**
3
+ * Required-runtime-capability analysis (spec16 §30-31, §50-51). A graph declares no host or
4
+ * provider; it requires *capability domains* — the closed vocabulary a conforming
5
+ * `AxiomServer` already reasons about (persistence, coordination, durable workflow storage,
6
+ * a scheduler, mutation observation for live queries, …). This module answers "what would
7
+ * a runtime need to execute this graph", with the provenance for each requirement (spec16
8
+ * §31), never a specific provider brand (spec16 §51).
9
+ */
10
+ export declare const REQUIRED_CAPABILITIES: readonly ["persistence", "coordination", "mutation-observation", "live-queries", "workflow-store", "event-journal", "scheduler", "effect-execution", "provider-transaction", "blob-storage", "subscription-adapter"];
11
+ export type RequiredCapability = (typeof REQUIRED_CAPABILITIES)[number];
12
+ export interface CapabilityRequirement {
13
+ capability: RequiredCapability;
14
+ required: boolean;
15
+ /** Why this capability is or is not required — structural, not a guess (spec16 §31). */
16
+ reasons: string[];
17
+ }
18
+ export interface CapabilityAnalysis {
19
+ requirements: CapabilityRequirement[];
20
+ /** Only the capabilities this graph actually requires. */
21
+ requiredCapabilities: RequiredCapability[];
22
+ }
23
+ export declare function analyzeCapabilities(graph: ApplicationGraph): CapabilityAnalysis;
24
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1,90 @@
1
+ import { actionOperations } from '@cynodia/axiom-core';
2
+ import { analyzeLiveQuery } from './live-query.js';
3
+ /**
4
+ * Required-runtime-capability analysis (spec16 §30-31, §50-51). A graph declares no host or
5
+ * provider; it requires *capability domains* — the closed vocabulary a conforming
6
+ * `AxiomServer` already reasons about (persistence, coordination, durable workflow storage,
7
+ * a scheduler, mutation observation for live queries, …). This module answers "what would
8
+ * a runtime need to execute this graph", with the provenance for each requirement (spec16
9
+ * §31), never a specific provider brand (spec16 §51).
10
+ */
11
+ export const REQUIRED_CAPABILITIES = [
12
+ 'persistence',
13
+ 'coordination',
14
+ 'mutation-observation',
15
+ 'live-queries',
16
+ 'workflow-store',
17
+ 'event-journal',
18
+ 'scheduler',
19
+ 'effect-execution',
20
+ 'provider-transaction',
21
+ 'blob-storage',
22
+ 'subscription-adapter',
23
+ ];
24
+ export function analyzeCapabilities(graph) {
25
+ const reasons = new Map();
26
+ const require = (capability, reason) => {
27
+ const list = reasons.get(capability) ?? [];
28
+ list.push(reason);
29
+ reasons.set(capability, list);
30
+ };
31
+ const actions = graph.getNodesByKind('action');
32
+ const workflows = graph.getNodesByKind('workflow');
33
+ const queries = graph.getNodesByKind('query');
34
+ const triggers = graph.getNodesByKind('trigger');
35
+ const subscriptions = graph.getNodesByKind('subscription');
36
+ const storages = graph.getNodesByKind('storage');
37
+ const serverStates = graph.getNodesByKind('state').filter((s) => (s.authority ?? 'client') === 'server');
38
+ if (serverStates.length > 0) {
39
+ require('persistence', `server-authoritative StateDef: ${serverStates.map((s) => s.id).sort().join(', ')}`);
40
+ }
41
+ if (queries.length > 0) {
42
+ require('persistence', `QueryDef reads authoritative data through a DataProvider: ${queries.map((q) => q.id).sort().join(', ')}`);
43
+ require('provider-transaction', `QueryDef/provider-record mutation requires a transactional provider: ${queries.map((q) => q.id).sort().join(', ')}`);
44
+ }
45
+ if (workflows.length > 0) {
46
+ require('persistence', `WorkflowDef instances are durable state: ${workflows.map((w) => w.id).sort().join(', ')}`);
47
+ require('coordination', `WorkflowDef instance ownership is a fenced, leased claim: ${workflows.map((w) => w.id).sort().join(', ')}`);
48
+ require('workflow-store', `WorkflowDef requires a durable WorkflowStore: ${workflows.map((w) => w.id).sort().join(', ')}`);
49
+ for (const workflow of workflows) {
50
+ const events = graph.getOutgoingEdges(workflow.id, { kinds: ['references'] }).filter((e) => graph.getNode(e.to)?.kind === 'event');
51
+ if (events.length > 0) {
52
+ require('event-journal', `WorkflowDef ${workflow.id} waits on an event, requiring the durable event journal`);
53
+ }
54
+ }
55
+ }
56
+ const timedTriggers = triggers.filter((t) => t.when.kind === 'interval' || t.when.kind === 'delay');
57
+ if (timedTriggers.length > 0) {
58
+ require('scheduler', `timed TriggerDef: ${timedTriggers.map((t) => t.id).sort().join(', ')}`);
59
+ require('coordination', `a distributed scheduler must fence duplicate firings: ${timedTriggers.map((t) => t.id).sort().join(', ')}`);
60
+ }
61
+ for (const query of queries) {
62
+ const capability = analyzeLiveQuery(graph, query.id).capability;
63
+ if (capability.capability !== 'not-live-capable') {
64
+ require('live-queries', `QueryDef ${query.id} is ${capability.capability}`);
65
+ require('mutation-observation', `QueryDef ${query.id} is live-capable and needs commit revision observation`);
66
+ }
67
+ }
68
+ for (const action of actions) {
69
+ const effectOps = actionOperations(action).filter((op) => op.kind === 'integration-effect' || op.kind === 'blob-commit' || op.kind === 'blob-delete');
70
+ if (effectOps.length > 0) {
71
+ require('effect-execution', `ActionDef ${action.id} creates a logical effect (${effectOps.map((op) => op.kind).join(', ')})`);
72
+ }
73
+ }
74
+ if (storages.length > 0) {
75
+ require('blob-storage', `StorageDef: ${storages.map((s) => s.id).sort().join(', ')}`);
76
+ }
77
+ if (subscriptions.length > 0) {
78
+ require('subscription-adapter', `SubscriptionDef: ${subscriptions.map((s) => s.id).sort().join(', ')}`);
79
+ require('coordination', `SubscriptionDef cursor ownership is a fenced claim: ${subscriptions.map((s) => s.id).sort().join(', ')}`);
80
+ }
81
+ const requirements = REQUIRED_CAPABILITIES.map((capability) => ({
82
+ capability,
83
+ required: reasons.has(capability),
84
+ reasons: reasons.get(capability) ?? [],
85
+ }));
86
+ return {
87
+ requirements,
88
+ requiredCapabilities: requirements.filter((r) => r.required).map((r) => r.capability),
89
+ };
90
+ }
@@ -0,0 +1,25 @@
1
+ import type { ApplicationGraph, EdgeKind, GraphEdge, NodeId } from '@cynodia/axiom-core';
2
+ /**
3
+ * Transitive dependency/dependent analysis and edge provenance (spec16 §12-14). Cycle-safe
4
+ * — a graph edge can form a cycle (a workflow branch, a mutual `invoke`) and this must still
5
+ * terminate with a deterministic, canonically-ordered answer (spec16 §13).
6
+ */
7
+ export interface TransitiveDependencyResult {
8
+ root: string;
9
+ /** Ids reachable from the root, canonically sorted, root excluded. */
10
+ ids: string[];
11
+ }
12
+ /** Every node transitively reachable by following outgoing edges from `root` (spec16 §13). */
13
+ export declare function transitiveDependencies(graph: ApplicationGraph, root: NodeId, kinds?: readonly EdgeKind[]): TransitiveDependencyResult;
14
+ /** Every node that transitively depends on `root`, by following incoming edges (spec16 §13). */
15
+ export declare function transitiveDependents(graph: ApplicationGraph, root: NodeId, kinds?: readonly EdgeKind[]): TransitiveDependencyResult;
16
+ export interface DependencyProvenance {
17
+ from: string;
18
+ to: string;
19
+ edges: GraphEdge[];
20
+ /** One rendered reason per edge, structural rather than free prose (spec16 §14). */
21
+ reasons: string[];
22
+ }
23
+ /** Why a dependency edge exists between two nodes, in structural (not fabricated) terms (spec16 §14). */
24
+ export declare function explainDependency(graph: ApplicationGraph, from: NodeId, to: NodeId): DependencyProvenance | undefined;
25
+ //# sourceMappingURL=dependencies.d.ts.map
@@ -0,0 +1,61 @@
1
+ function walk(graph, root, kinds, direction) {
2
+ const seen = new Set([root]);
3
+ const found = [];
4
+ let frontier = [root];
5
+ while (frontier.length > 0) {
6
+ const next = [];
7
+ for (const id of frontier) {
8
+ const edges = direction === 'out'
9
+ ? graph.getOutgoingEdges(id, kinds ? { kinds } : {})
10
+ : graph.getIncomingEdges(id, kinds ? { kinds } : {});
11
+ for (const edge of edges) {
12
+ const neighbour = direction === 'out' ? edge.to : edge.from;
13
+ if (!seen.has(neighbour)) {
14
+ seen.add(neighbour);
15
+ found.push(neighbour);
16
+ next.push(neighbour);
17
+ }
18
+ }
19
+ }
20
+ frontier = next;
21
+ }
22
+ return found.sort();
23
+ }
24
+ /** Every node transitively reachable by following outgoing edges from `root` (spec16 §13). */
25
+ export function transitiveDependencies(graph, root, kinds) {
26
+ return { root: String(root), ids: walk(graph, root, kinds, 'out') };
27
+ }
28
+ /** Every node that transitively depends on `root`, by following incoming edges (spec16 §13). */
29
+ export function transitiveDependents(graph, root, kinds) {
30
+ return { root: String(root), ids: walk(graph, root, kinds, 'in') };
31
+ }
32
+ function renderReason(graph, edge) {
33
+ const fromNode = graph.getNode(edge.from);
34
+ const toNode = graph.getNode(edge.to);
35
+ const fieldIds = edge.metadata?.fieldIds ?? [];
36
+ const fields = fieldIds.length > 0 ? ` (fields: ${[...fieldIds].sort().join(', ')})` : '';
37
+ const fromDesc = fromNode ? `${fromNode.kind} ${edge.from}` : String(edge.from);
38
+ const toDesc = toNode ? `${toNode.kind} ${edge.to}` : String(edge.to);
39
+ const verbs = {
40
+ reads: 'reads',
41
+ writes: 'writes',
42
+ invokes: 'invokes',
43
+ renders: 'renders',
44
+ binds: 'binds into',
45
+ 'depends-on': 'depends on',
46
+ 'derives-from': 'derives from',
47
+ constrains: 'constrains',
48
+ 'routes-to': 'routes to',
49
+ references: 'references',
50
+ contains: 'contains',
51
+ };
52
+ return `${fromDesc} ${verbs[edge.kind] ?? edge.kind} ${toDesc}${fields}`;
53
+ }
54
+ /** Why a dependency edge exists between two nodes, in structural (not fabricated) terms (spec16 §14). */
55
+ export function explainDependency(graph, from, to) {
56
+ const edges = graph.getOutgoingEdges(from, {}).filter((edge) => edge.to === to);
57
+ if (edges.length === 0) {
58
+ return undefined;
59
+ }
60
+ return { from: String(from), to: String(to), edges, reasons: edges.map((edge) => renderReason(graph, edge)) };
61
+ }
@@ -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.operations ?? []).some((op) => EFFECT_OP_KINDS.has(op.kind)));
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');
@@ -0,0 +1,125 @@
1
+ import { type ApplicationGraph, type NativeEffect, type NodeId, type StatePersistence, type TypeRef } from '@cynodia/axiom-core';
2
+ import type { QueryExplanation } from './queries.js';
3
+ import type { OperationProtection } from './authorization.js';
4
+ import type { WorkflowAnalysis } from './workflow.js';
5
+ import type { LiveQueryAnalysis } from './live-query.js';
6
+ /**
7
+ * Structured, machine-readable explanations of the semantic nodes an agent most often needs
8
+ * to reason about before proposing a change (spec16 §17-21, §28-29). Every field here is
9
+ * derived from the graph's own edges and node data — never from re-reading application
10
+ * source — and an incomplete answer says so explicitly rather than presenting itself as
11
+ * exhaustive (spec16 §16, §29, §102, §103).
12
+ */
13
+ export interface ActionExplanation {
14
+ actionId: string;
15
+ name?: string;
16
+ parameters: Array<{
17
+ id: string;
18
+ required: boolean;
19
+ valueType?: TypeRef;
20
+ }>;
21
+ /** States this action may read, and the specific fields where known. */
22
+ reads: {
23
+ stateIds: string[];
24
+ fieldIds: string[];
25
+ };
26
+ /** States this action may write, and the specific fields where known. */
27
+ writes: {
28
+ stateIds: string[];
29
+ fieldIds: string[];
30
+ };
31
+ /** Other actions this action invokes directly (an `invoke` operation). */
32
+ invokesActions: string[];
33
+ /** Integration operations this action calls, split by query/effect mode. */
34
+ integrationQueries: string[];
35
+ integrationEffects: string[];
36
+ /** Registered `QueryDef`s this action runs via a `query` operation. */
37
+ runsQueries: string[];
38
+ /** Object stores this action reads, commits into, or deletes from. */
39
+ storages: string[];
40
+ /** Native operations embedded in this action, with their declared effects. */
41
+ nativeOperations: Array<{
42
+ implementationId: string;
43
+ declaredEffects: NativeEffect[];
44
+ }>;
45
+ /** How `action.invoke` is protected. */
46
+ authorization: OperationProtection;
47
+ /** Entity constraints and transition constraints that can refuse this action's write. */
48
+ constraintsThatMayBlock: {
49
+ constraints: string[];
50
+ transitionConstraints: string[];
51
+ };
52
+ /** What can cause this action to run other than a direct client invocation. */
53
+ invokedBy: {
54
+ triggers: string[];
55
+ workflowSteps: string[];
56
+ };
57
+ clientInvocable: boolean;
58
+ systemOnly: boolean;
59
+ destructive: boolean;
60
+ /** False when a native operation without declared effects prevents a complete answer. */
61
+ analysisComplete: boolean;
62
+ analysisGaps: string[];
63
+ }
64
+ export declare function explainAction(graph: ApplicationGraph, actionId: NodeId): ActionExplanation | undefined;
65
+ export interface StateExplanation {
66
+ stateId: string;
67
+ name?: string;
68
+ valueType: TypeRef;
69
+ derived: boolean;
70
+ draft: boolean;
71
+ ephemeral: boolean;
72
+ authority: 'client' | 'server';
73
+ serverOnly: boolean;
74
+ persistence: StatePersistence;
75
+ hasInitialValue: boolean;
76
+ /** Nodes that read this state: views, derived state, action conditions and reads. */
77
+ readers: string[];
78
+ /** Actions that mutate this state. */
79
+ writers: string[];
80
+ /** Entities this state's type holds instances of. */
81
+ entities: string[];
82
+ constraints: string[];
83
+ transitionConstraints: string[];
84
+ }
85
+ export declare function explainState(graph: ApplicationGraph, stateId: NodeId): StateExplanation | undefined;
86
+ export interface FullQueryExplanation extends QueryExplanation {
87
+ authorization: OperationProtection;
88
+ liveCapability: LiveQueryAnalysis['capability']['capability'];
89
+ }
90
+ /** `explainQuery` plus the authorization surface and live-query capability (spec16 §18). */
91
+ export declare function explainQueryFull(graph: ApplicationGraph, queryId: NodeId): FullQueryExplanation | undefined;
92
+ export interface FullWorkflowExplanation extends WorkflowAnalysis {
93
+ startPolicyId: string | null;
94
+ instanceAccessPolicyId: string | null;
95
+ actionAuthorization: Array<{
96
+ actionId: string;
97
+ protection: OperationProtection;
98
+ }>;
99
+ privilegeReviewActions: string[];
100
+ }
101
+ /** `analyzeWorkflow` plus its authorization surface — start policy, instance access, and each step action's protection (spec16 §19). */
102
+ export declare function explainWorkflowFull(graph: ApplicationGraph, workflowId: NodeId): FullWorkflowExplanation;
103
+ export interface GraphSummary {
104
+ nodeCountsByKind: Record<string, number>;
105
+ executableRoots: {
106
+ actions: string[];
107
+ workflows: string[];
108
+ queries: string[];
109
+ };
110
+ securityBoundaries: {
111
+ protectedActions: number;
112
+ publicActions: number;
113
+ protectedQueries: number;
114
+ publicQueries: number;
115
+ };
116
+ externalCapabilities: {
117
+ integrations: number;
118
+ subscriptions: number;
119
+ storages: number;
120
+ };
121
+ opaqueBoundaries: number;
122
+ }
123
+ /** A structural, domain-neutral graph summary (spec16 §161-162): counts and roots, never invented business prose. */
124
+ export declare function explainGraph(graph: ApplicationGraph): GraphSummary;
125
+ //# sourceMappingURL=explain.d.ts.map