@cynodia/axiom-agent-api 0.11.0-alpha.1 → 0.11.2-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.
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_SCHEMA_VERSION } from '@cynodia/axiom-core';
2
- import type { ApplicationGraph, SchemaChangeClass, SchemaDiff, SchemaDiffEntry } from '@cynodia/axiom-core';
2
+ import type { ApplicationGraph, MigrationOperation, SchemaChangeClass, SchemaDiff, SchemaDiffEntry } from '@cynodia/axiom-core';
3
3
  /**
4
4
  * Authoring-time schema-evolution inspection for an agent (spec11 §56-58, §93).
5
5
  *
@@ -57,14 +57,44 @@ export declare function inspectSchema(graph: ApplicationGraph): SchemaInspection
57
57
  * never a JSON text diff.
58
58
  */
59
59
  export declare function explainSchemaDiff(diff: SchemaDiff): string;
60
+ /**
61
+ * How `covered` was decided (spec11.1 §23-24).
62
+ *
63
+ * - `step` — `previous` and `next` are one schema version apart, so coverage is evaluated
64
+ * against the operations of the single `N → N+1` migration. This is the authoritative
65
+ * check and agrees with `migrationCoversDiff` and `validateGraph`.
66
+ * - `chain` — `previous` and `next` are more than one version apart. A single endpoint diff
67
+ * has no ordinary per-step coverage, so `covered` reports only whether a complete
68
+ * migration chain connects the two versions; `steps` lists the migrations it would run.
69
+ * - `none` — `previous` and `next` declare the same schema version, or a downgrade.
70
+ */
71
+ export type CoverageMode = 'step' | 'chain' | 'none';
72
+ export interface MigrationCoverageStep {
73
+ migrationId: string;
74
+ fromSchema: number;
75
+ toSchema: number;
76
+ operationCount: number;
77
+ }
60
78
  export interface MigrationImpact {
61
79
  fromVersion: number;
62
80
  toVersion: number;
63
81
  diff: SchemaDiff;
64
82
  verdict: SchemaChangeClass;
65
- /** Whether the migration chain in `next` accounts for every data-affecting diff entry. */
83
+ /**
84
+ * Whether the migration accounts for the data-affecting part of this diff. For a
85
+ * single-step diff (`coverageMode: 'step'`) this is the authoritative answer and matches
86
+ * `migrationCoversDiff(diff, thatStep.operations).covered`. For a multi-step diff
87
+ * (`coverageMode: 'chain'`) it reports only whether a complete chain exists. Always
88
+ * accompanied by `uncovered` / `unmatched` / `steps` explaining the value (spec11.1 §25).
89
+ */
66
90
  covered: boolean;
91
+ coverageMode: CoverageMode;
92
+ /** Data-affecting diff entries with no matching migration operation. */
67
93
  uncovered: SchemaDiffEntry[];
94
+ /** Migration operations in the evaluated step that correspond to no diff entry. */
95
+ unmatched: MigrationOperation[];
96
+ /** The migration steps between `fromVersion` and `toVersion`, in order. */
97
+ steps: MigrationCoverageStep[];
68
98
  dataLossPossible: boolean;
69
99
  affectedEntities: string[];
70
100
  affectedFields: string[];
package/dist/migration.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DEFAULT_SCHEMA_VERSION, diffSchema, migrationCoversDiff, schemaFingerprint, schemaProjection, } from '@cynodia/axiom-core';
1
+ import { DEFAULT_SCHEMA_VERSION, diffSchema, migrationCoversDiff, migrationPath, schemaFingerprint, schemaProjection, sortMigrations, } from '@cynodia/axiom-core';
2
2
  function destructiveOps(migration) {
3
3
  return migration.operations.filter((operation) => operation.destructive === true ||
4
4
  operation.kind === 'remove-field' ||
@@ -87,9 +87,46 @@ export function migrationImpact(previous, next) {
87
87
  const diff = diffSchema(previous, next);
88
88
  const changedFields = new Set(diff.entries.map((entry) => entry.fieldId).filter(Boolean));
89
89
  const changedEntities = new Set(diff.entries.map((entry) => entry.entityId).filter(Boolean));
90
- const migrations = next.getNodesByKind('migration');
91
- const operations = migrations.flatMap((migration) => migration.operations);
92
- const coverage = migrationCoversDiff(diff, operations);
90
+ // Coverage is scoped to the semantic transition being evaluated, NOT the whole chain in
91
+ // `next` (spec11.1 §22-24). Feeding every historical operation into an endpoint diff
92
+ // produces false negatives.
93
+ const migrations = sortMigrations(next.getNodesByKind('migration'));
94
+ const fromV = diff.fromVersion;
95
+ const toV = diff.toVersion;
96
+ const chain = toV > fromV ? migrationPath(migrations, fromV, toV) : [];
97
+ const steps = (chain ?? []).map((migration) => ({
98
+ migrationId: String(migration.id),
99
+ fromSchema: migration.fromSchema,
100
+ toSchema: migration.toSchema,
101
+ operationCount: migration.operations.length,
102
+ }));
103
+ let coverageMode;
104
+ let covered;
105
+ let uncovered = [];
106
+ let unmatched = [];
107
+ if (toV <= fromV) {
108
+ // Same version (nothing to cover) or a downgrade (no reverse path is evaluated here).
109
+ coverageMode = 'none';
110
+ covered = toV === fromV;
111
+ }
112
+ else if (toV === fromV + 1) {
113
+ // Single step: evaluate the diff against exactly the `fromV → fromV+1` migration.
114
+ coverageMode = 'step';
115
+ const step = migrations.find((migration) => migration.fromSchema === fromV);
116
+ const result = migrationCoversDiff(diff, step?.operations ?? []);
117
+ covered = result.covered;
118
+ uncovered = result.uncovered;
119
+ unmatched = result.unmatched;
120
+ }
121
+ else {
122
+ // Multi-step: a single endpoint diff has no ordinary step coverage. Report only whether
123
+ // a complete chain connects the two versions (spec11.1 §24, option B).
124
+ coverageMode = 'chain';
125
+ covered = chain !== null;
126
+ if (chain === null) {
127
+ uncovered = diff.needsMigration;
128
+ }
129
+ }
93
130
  const touchesChange = (nodeId) => {
94
131
  for (const edge of next.getEdges(nodeId, { kinds: ['reads', 'writes', 'references'] })) {
95
132
  const fieldIds = edge.metadata?.fieldIds ?? [];
@@ -136,8 +173,11 @@ export function migrationImpact(previous, next) {
136
173
  toVersion: diff.toVersion,
137
174
  diff,
138
175
  verdict: diff.verdict,
139
- covered: coverage.covered,
140
- uncovered: coverage.uncovered,
176
+ covered,
177
+ coverageMode,
178
+ uncovered,
179
+ unmatched,
180
+ steps,
141
181
  dataLossPossible: diff.destructive.length > 0,
142
182
  affectedEntities: [...changedEntities].sort(),
143
183
  affectedFields: [...changedFields].sort(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom-agent-api",
3
- "version": "0.11.0-alpha.1",
3
+ "version": "0.11.2-alpha.1",
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.11.0-alpha.1"
34
+ "@cynodia/axiom-core": "0.11.2-alpha.1"
35
35
  },
36
36
  "scripts": {
37
37
  "build": "tsc -b tsconfig.json tsconfig.test.json",