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
|
@@ -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
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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.
|