mirai-graph 1.1.0 → 1.1.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/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to Mirai Graph will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.1.1] - 2026-08-26
8
+
9
+ ### Fixed
10
+
11
+ - Sequential context traversal now reads the accepted legacy aliases used by
12
+ existing `graph.json` 2.0.0 repositories (`type`, `from`, `to`,
13
+ `relation_type` and `status`) without requiring a graph rewrite.
14
+ - Established structural and required relation names are projected onto the
15
+ public traversal vocabulary while preserving their direction and mandatory
16
+ closure semantics.
17
+ - Existing accepted Mirai readiness levels and revision-bound objects without
18
+ an explicit historical readiness field remain usable; draft, seed, gap,
19
+ blocked, stale and deprecated states still fail closed.
20
+
21
+ ### Compatibility
22
+
23
+ - This patch makes the 1.1.0 compatibility promise executable. It does not
24
+ change schema `2.0.0`, the Project Technology activation contract, provider
25
+ transport or write boundaries.
26
+
7
27
  ## [1.1.0] - 2026-08-26
8
28
 
9
29
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirai-graph",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "private": false,
5
5
  "description": "An evolutionary graph operating model for managing the growth of complex systems.",
6
6
  "license": "MIT",
@@ -73,6 +73,44 @@ function createFixture(root, name, scope = "repository", mutation = null) {
73
73
  return repo;
74
74
  }
75
75
 
76
+ function createLegacyFixture(root) {
77
+ const repo = createFixture(root, "legacy-skill-graph", "skill");
78
+ const objectFile = path.join(repo, "graph/specs/objects.json");
79
+ const relationFile = path.join(repo, "graph/specs/relations.json");
80
+ const objects = JSON.parse(fs.readFileSync(objectFile, "utf8")).map((item) => {
81
+ const legacy = { ...item, type: item.kind };
82
+ delete legacy.kind;
83
+ delete legacy.readiness;
84
+ return legacy;
85
+ });
86
+ const aliases = {
87
+ contains: "implements",
88
+ governed_by: "governed_by",
89
+ validated_by: "validates",
90
+ documented_by: "evidenced_by",
91
+ };
92
+ const relations = JSON.parse(fs.readFileSync(relationFile, "utf8")).map((item) => {
93
+ const legacyType = aliases[item.type] || item.type;
94
+ const reverse = item.type === "validated_by";
95
+ const legacy = {
96
+ ...item,
97
+ relation_type: legacyType,
98
+ from: reverse ? item.target : item.source,
99
+ to: reverse ? item.source : item.target,
100
+ status: item.readiness,
101
+ };
102
+ delete legacy.type;
103
+ delete legacy.source;
104
+ delete legacy.target;
105
+ delete legacy.readiness;
106
+ return legacy;
107
+ });
108
+ writeJson(objectFile, objects);
109
+ writeJson(relationFile, relations);
110
+ git(repo, "add", "."); git(repo, "commit", "-qm", "legacy graph aliases");
111
+ return repo;
112
+ }
113
+
76
114
  function selection(receipt) {
77
115
  return {
78
116
  selector: "ai", task_digest: receipt.task.digest, graph_digest: receipt.graph.digest,
@@ -110,6 +148,28 @@ try {
110
148
  check(`universal_scope_${scope}`, discovered.status === "success" && discovered.traversal_receipt.repository_id === name, discovered.blockers);
111
149
  }
112
150
 
151
+ const legacyRepo = createLegacyFixture(root);
152
+ let legacyReceipt = technology.discoverContext(legacyRepo, "build application delivery safely").traversal_receipt;
153
+ for (const ids of [["capability.delivery"], ["process.delivery"], ["resource.delivery_guide", "constraint.safety", "check.delivery"], ["resource.shared"]]) {
154
+ legacyReceipt = technology.expandContext(legacyRepo, legacyReceipt, ids, { selector: "ai", reason: "legacy graph compatibility" }).traversal_receipt;
155
+ }
156
+ const legacyCompiled = technology.compileContext(legacyRepo, legacyReceipt, selection(legacyReceipt));
157
+ check("legacy_graph_2_aliases_compile", legacyCompiled.status === "ready", legacyCompiled.blockers);
158
+ check("legacy_relation_semantics_preserved", legacyCompiled.context_pack.required_closure.object_ids.includes("constraint.safety") && legacyCompiled.context_pack.validators.includes("check.delivery"), legacyCompiled.context_pack);
159
+ const legacyReadinessRepo = createFixture(root, "legacy-readiness", "skill", (objects) => {
160
+ objects.find((item) => item.id === "capability.delivery").readiness = "R3_structured";
161
+ });
162
+ let legacyReadinessReceipt = technology.discoverContext(legacyReadinessRepo, "build application delivery safely").traversal_receipt;
163
+ for (const ids of [["capability.delivery"], ["process.delivery"], ["resource.delivery_guide", "constraint.safety", "check.delivery"], ["resource.shared"]]) {
164
+ legacyReadinessReceipt = technology.expandContext(legacyReadinessRepo, legacyReadinessReceipt, ids).traversal_receipt;
165
+ }
166
+ check("legacy_ready_level_compiles", technology.compileContext(legacyReadinessRepo, legacyReadinessReceipt, selection(legacyReadinessReceipt)).status === "ready");
167
+ const legacyDraftRepo = createFixture(root, "legacy-draft-readiness", "skill", (objects) => {
168
+ objects.find((item) => item.id === "capability.delivery").readiness = "R2_seed";
169
+ });
170
+ const legacyDraftReceipt = technology.discoverContext(legacyDraftRepo, "build application delivery safely").traversal_receipt;
171
+ check("legacy_draft_level_remains_blocked", technology.compileContext(legacyDraftRepo, legacyDraftReceipt, selection(legacyDraftReceipt)).status !== "ready");
172
+
113
173
  const before = git(repo, "status", "--porcelain=v1");
114
174
  const discovered = technology.discoverContext(repo, "build application delivery safely", { maxCandidates: 2 });
115
175
  check("discover_is_bounded", discovered.status === "success" && discovered.traversal_receipt.candidates.length <= 2, discovered);
@@ -19,7 +19,13 @@ const SUPPORTED_RELATIONS = new Set([
19
19
  "conflicts_with",
20
20
  ]);
21
21
  const TERMINAL_KINDS = new Set(["resource", "source", "check", "gate", "constraint"]);
22
- const ACTIVE_READINESS = new Set(["ready", "accepted", "implemented", "validated", "operating", "evolving"]);
22
+ const ACTIVE_READINESS = new Set(["ready", "accepted", "implemented", "validated", "operating", "evolving", "pilot"]);
23
+ const LEGACY_ACTIVE_READINESS = new Set([
24
+ "r3_structured", "r3_specified", "r4_integrated", "r4_validated",
25
+ "r4_executable", "r4_evidence_ready", "a4_validated_projection",
26
+ "a6_accepted_local_candidate", "t5_implementation_ready",
27
+ "active_controlled_paid_test",
28
+ ]);
23
29
  const BLOCKING_READINESS = new Set(["blocked", "deprecated", "stale", "retired", "superseded"]);
24
30
  const SECRET_PARTS = [".env", "credential", "secret", "token", "password", "cookie", "private-key", "id_rsa", ".pem", ".p12"];
25
31
 
@@ -53,6 +59,21 @@ function gitBlob(repo, relative) {
53
59
  return completed.status === 0 ? completed.stdout : null;
54
60
  }
55
61
 
62
+ function trackedState(repo) {
63
+ const trackedRun = spawnSync("git", ["ls-files", "-z"], { cwd: repo, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
64
+ const dirtyRun = spawnSync("git", ["diff", "--name-only", "-z", "HEAD", "--"], { cwd: repo, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
65
+ return {
66
+ tracked: new Set(trackedRun.status === 0 ? trackedRun.stdout.split("\0").filter(Boolean) : []),
67
+ dirty: new Set(dirtyRun.status === 0 ? dirtyRun.stdout.split("\0").filter(Boolean) : []),
68
+ };
69
+ }
70
+
71
+ function graphBlob(graph, relative) {
72
+ if (!graph.tracked?.has(relative) || graph.dirty?.has(relative)) return null;
73
+ if (!graph.blobCache.has(relative)) graph.blobCache.set(relative, gitBlob(graph.repo, relative));
74
+ return graph.blobCache.get(relative);
75
+ }
76
+
56
77
  function revisionBound(repo, relative) {
57
78
  if (!gitBlob(repo, relative)) return false;
58
79
  return spawnSync("git", ["diff", "--quiet", "HEAD", "--", relative], { cwd: repo }).status === 0;
@@ -72,6 +93,54 @@ function safeRef(value) {
72
93
  return safeRelative(value);
73
94
  }
74
95
 
96
+ const LEGACY_RELATION_TYPES = new Map([
97
+ ["implements", { type: "contains" }],
98
+ ["has_capability", { type: "contains" }],
99
+ ["uses_process", { type: "contains" }],
100
+ ["includes", { type: "contains" }],
101
+ ["member_of", { type: "contains", reverse: true }],
102
+ ["refines", { type: "specializes" }],
103
+ ["depends_on", { type: "requires" }],
104
+ ["requires_current_source_check", { type: "requires" }],
105
+ ["requires_gate", { type: "validated_by" }],
106
+ ["requires_quality_gate", { type: "validated_by" }],
107
+ ["tested_by_scenario", { type: "validated_by" }],
108
+ ["validates", { type: "validated_by", reverse: true }],
109
+ ["assessed_by", { type: "validated_by" }],
110
+ ["blocked_without_gate", { type: "validated_by" }],
111
+ ["conforms_to", { type: "governed_by" }],
112
+ ["constrained_by", { type: "governed_by" }],
113
+ ["evidenced_by", { type: "documented_by" }],
114
+ ]);
115
+
116
+ function normalizeObject(value) {
117
+ const normalized = { ...value };
118
+ if (!normalized.kind && normalized.type) normalized.kind = normalized.type;
119
+ if (!normalized.readiness) normalized.readiness = normalized.lifecycle || normalized.status || "accepted";
120
+ if (!value.kind) delete normalized.type;
121
+ return normalized;
122
+ }
123
+
124
+ function normalizeRelation(value) {
125
+ const legacyType = value.type || value.relation_type;
126
+ const mapping = LEGACY_RELATION_TYPES.get(legacyType) || { type: legacyType };
127
+ let source = value.source || value.from;
128
+ let target = value.target || value.to;
129
+ if (mapping.reverse) [source, target] = [target, source];
130
+ const normalized = {
131
+ ...value,
132
+ type: mapping.type,
133
+ source,
134
+ target,
135
+ readiness: value.readiness || value.status || value.lifecycle || "accepted",
136
+ };
137
+ delete normalized.from;
138
+ delete normalized.to;
139
+ delete normalized.relation_type;
140
+ delete normalized.status;
141
+ return normalized;
142
+ }
143
+
75
144
  function loadEntry(repo, relative, kind, output, visited = new Set()) {
76
145
  if (!safeRelative(relative) || visited.has(relative)) return;
77
146
  visited.add(relative);
@@ -112,14 +181,15 @@ function readGraph(repoArg) {
112
181
  for (const relative of manifest.graph?.relations || []) loadEntry(repo, relative, "relations", output);
113
182
  const objects = new Map();
114
183
  for (const record of output.objects) {
115
- const id = String(record.value.id || "").trim();
184
+ const normalized = normalizeObject(record.value);
185
+ const id = String(normalized.id || "").trim();
116
186
  if (!id || objects.has(id)) { output.blockers.push(id ? "duplicate_object_id" : "object_id_missing"); continue; }
117
- objects.set(id, record);
187
+ objects.set(id, { ...record, value: normalized });
118
188
  }
119
189
  const relations = [];
120
190
  const relationIds = new Set();
121
191
  for (const record of output.relations) {
122
- const relation = record.value;
192
+ const relation = normalizeRelation(record.value);
123
193
  const id = String(relation.id || "").trim();
124
194
  if (!id || relationIds.has(id)) { output.blockers.push(id ? "duplicate_relation_id" : "relation_id_missing"); continue; }
125
195
  relationIds.add(id);
@@ -128,11 +198,12 @@ function readGraph(repoArg) {
128
198
  relations.push({ ...relation, _record: record });
129
199
  }
130
200
  const revision = git(repo, "rev-parse", "HEAD") || null;
201
+ const state = trackedState(repo);
131
202
  for (const relative of unique([
132
203
  "graph.json",
133
204
  ...[...objects.values()].map((record) => record.relative),
134
205
  ...relations.map((relation) => relation._record.relative),
135
- ])) if (!revisionBound(repo, relative)) output.blockers.push("graph_source_not_revision_bound");
206
+ ])) if (!state.tracked.has(relative) || state.dirty.has(relative)) output.blockers.push("graph_source_not_revision_bound");
136
207
  const graphPayload = {
137
208
  manifest: { id: manifest.id, scope: manifest.scope, profiles: manifest.profiles, graph: manifest.graph },
138
209
  objects: [...objects.values()].map((record) => record.value).sort((a, b) => a.id.localeCompare(b.id)),
@@ -146,6 +217,9 @@ function readGraph(repoArg) {
146
217
  graphDigest: digest(graphPayload),
147
218
  objects,
148
219
  relations,
220
+ tracked: state.tracked,
221
+ dirty: state.dirty,
222
+ blobCache: new Map(),
149
223
  blockers: unique(output.blockers),
150
224
  };
151
225
  }
@@ -158,6 +232,11 @@ function readinessOf(object) {
158
232
  return String(object.readiness || object.lifecycle || object.status || "unknown").toLowerCase();
159
233
  }
160
234
 
235
+ function activeReadiness(value) {
236
+ const normalized = String(value || "").toLowerCase();
237
+ return ACTIVE_READINESS.has(normalized) || LEGACY_ACTIVE_READINESS.has(normalized);
238
+ }
239
+
161
240
  function outgoing(graph, id) {
162
241
  return graph.relations.filter((relation) => relation.source === id);
163
242
  }
@@ -181,7 +260,7 @@ function sourcePassport(graph, record, value) {
181
260
  if (!fs.existsSync(absolute) || fs.lstatSync(absolute).isSymbolicLink() || !fs.statSync(absolute).isFile()) {
182
261
  return { ref, availability: "unavailable", revision: graph.revision, sha256: null };
183
262
  }
184
- const blob = gitBlob(graph.repo, ref);
263
+ const blob = graphBlob(graph, ref);
185
264
  return { ref, availability: blob ? "available" : "unbound", revision: graph.revision, sha256: digest(blob || fs.readFileSync(absolute)) };
186
265
  }).concat([{ ref: record.relative, availability: "available", revision: graph.revision, sha256: record.sha256 }]);
187
266
  }
@@ -446,7 +525,7 @@ function compileContext(repository, traversalReceipt, selectionInput, options =
446
525
  for (const id of closure.ids) {
447
526
  const passport = nodePassport(graph, id);
448
527
  if (!passport) continue;
449
- if (!ACTIVE_READINESS.has(passport.readiness)) blockers.push(`context_node_${passport.readiness}`);
528
+ if (!activeReadiness(passport.readiness)) blockers.push(`context_node_${passport.readiness}`);
450
529
  if (passport.omitted_source_ref_count > 0) blockers.push("unsafe_source_reference_omitted");
451
530
  if (passport.sensitive_metadata_detected) blockers.push("context_node_sensitive_metadata");
452
531
  if (passport.expansion_policy === "terminal" && passport.expandable) blockers.push("terminal_node_has_children");
@@ -468,7 +547,7 @@ function compileContext(repository, traversalReceipt, selectionInput, options =
468
547
  const includedIds = [...included].sort();
469
548
  const includedNodes = includedIds.map((id) => nodePassport(graph, id)).filter(Boolean);
470
549
  const includedRelations = graph.relations.filter((relation) => included.has(relation.source) && included.has(relation.target)).map(relationPassport).sort((a, b) => a.id.localeCompare(b.id));
471
- for (const relation of includedRelations) if (!ACTIVE_READINESS.has(String(relation.readiness).toLowerCase())) blockers.push(`context_relation_${String(relation.readiness).toLowerCase()}`);
550
+ for (const relation of includedRelations) if (!activeReadiness(relation.readiness)) blockers.push(`context_relation_${String(relation.readiness).toLowerCase()}`);
472
551
  const terminalSourceMap = new Map();
473
552
  for (const node of includedNodes.filter((item) => !item.expandable || ["resource", "source"].includes(item.kind))) {
474
553
  for (const source of node.source_refs) {
@@ -0,0 +1,25 @@
1
+ # Mirai Graph 1.1.1
2
+
3
+ Mirai Graph 1.1.1 is a compatibility correction for sequential context
4
+ traversal.
5
+
6
+ ## What changed
7
+
8
+ - Existing graph 2.0.0 object and relation aliases are normalized in memory.
9
+ - Skill-style `implements`, `has_capability`, `validates` and related accepted
10
+ relations retain their structural or mandatory meaning.
11
+ - Accepted historical readiness levels remain executable, while incomplete and
12
+ unsafe states still block compilation.
13
+
14
+ ## Why this patch is needed
15
+
16
+ The 1.1.0 traversal fixtures used the newest field names, while long-lived
17
+ graphs used accepted older aliases. The result was a false loss of nested
18
+ context and two blocked repositories. This patch fixes the reader instead of
19
+ forcing every consumer to rewrite its graph.
20
+
21
+ ## Evidence boundary
22
+
23
+ The release candidate is accepted only when the full test suite and real
24
+ multi-repository consumer harness are green. Publication remains a separate
25
+ external action.