mirai-graph 1.2.0 → 1.4.0
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 +52 -0
- package/README.md +33 -2
- package/docs/adoption/cli.md +13 -0
- package/examples/executable-technology-course/README.md +16 -0
- package/examples/executable-technology-course/technology.json +99 -0
- package/package.json +5 -3
- package/packages/cli/mirai-graph.js +1 -1
- package/packages/cli/project-technology.js +30 -2
- package/packages/cli/validate-artifact-releases.js +282 -0
- package/packages/cli/validate-technology-course.js +81 -0
- package/packages/project-technology/artifact-release.js +738 -0
- package/packages/project-technology/index.js +12 -0
- package/packages/project-technology/technology-course.js +282 -0
- package/releases/1.3.0.md +16 -0
- package/releases/README.md +1 -0
- package/schemas/executable-technology.schema.json +70 -0
- package/schemas/technology-course-pack.schema.json +57 -0
- package/standard/project-technology.md +74 -1
|
@@ -15,6 +15,8 @@ const {
|
|
|
15
15
|
} = require("../cli/graph-manifest");
|
|
16
16
|
const traversal = require("./context-traversal");
|
|
17
17
|
const continuity = require("./continuity");
|
|
18
|
+
const artifacts = require("./artifact-release");
|
|
19
|
+
const technologyCourse = require("./technology-course");
|
|
18
20
|
|
|
19
21
|
const CONTRACT_VERSION = "1.0.0";
|
|
20
22
|
const EXTENSION_KEY = "mirai.project_technology";
|
|
@@ -788,6 +790,8 @@ function verify(repoArg, options = {}) {
|
|
|
788
790
|
}
|
|
789
791
|
|
|
790
792
|
function execute(operation, repoArg, options = {}) {
|
|
793
|
+
if (operation === "artifact") return artifacts.executeArtifact(repoArg, options);
|
|
794
|
+
if (operation === "course") return technologyCourse.executeCourse(repoArg, options);
|
|
791
795
|
const readOnly = { explain, status, plan, verify, context };
|
|
792
796
|
if (readOnly[operation]) return readOnly[operation](repoArg, options);
|
|
793
797
|
const transactional = { enable, sync, connect, disconnect, provide, disable, repair };
|
|
@@ -802,9 +806,12 @@ module.exports = {
|
|
|
802
806
|
LOCAL_DIR,
|
|
803
807
|
bindingValues,
|
|
804
808
|
canonicalBytes,
|
|
809
|
+
compareArtifactReleases: artifacts.compareArtifactReleases,
|
|
805
810
|
connect,
|
|
806
811
|
compileContext: traversal.compileContext,
|
|
812
|
+
compileTechnologyCourse: technologyCourse.compileTechnologyCourse,
|
|
807
813
|
context,
|
|
814
|
+
createArtifactRelease: artifacts.createArtifactRelease,
|
|
808
815
|
discoverContext: traversal.discoverContext,
|
|
809
816
|
disable,
|
|
810
817
|
disconnect,
|
|
@@ -814,16 +821,21 @@ module.exports = {
|
|
|
814
821
|
explain,
|
|
815
822
|
extensionContract,
|
|
816
823
|
inventory,
|
|
824
|
+
inspectArtifactBundle: artifacts.inspectArtifactBundle,
|
|
817
825
|
normalizeExecutionContract,
|
|
826
|
+
normalizeTechnology: technologyCourse.normalizeTechnology,
|
|
818
827
|
continuity,
|
|
819
828
|
plan,
|
|
820
829
|
provide,
|
|
821
830
|
readExport,
|
|
831
|
+
reconcileTechnologyCourse: technologyCourse.reconcileTechnologyCourse,
|
|
822
832
|
repair,
|
|
823
833
|
sha256,
|
|
824
834
|
status,
|
|
825
835
|
sync,
|
|
826
836
|
targetBindingStatus,
|
|
827
837
|
verify,
|
|
838
|
+
verifyArtifactRelease: artifacts.verifyArtifactRelease,
|
|
839
|
+
verifyTechnologyCourse: technologyCourse.verifyTechnologyCourse,
|
|
828
840
|
verifyContext: traversal.verifyContext,
|
|
829
841
|
};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:/@?=+*-]{1,255}$/;
|
|
8
|
+
const ACTIVE_LIFECYCLES = new Set(["reviewed", "accepted", "active"]);
|
|
9
|
+
const SECRET_MARKERS = ["password", "secret", "token", "cookie", "private_key", "totp", ".env"];
|
|
10
|
+
|
|
11
|
+
function sortValue(value) {
|
|
12
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
13
|
+
if (!value || typeof value !== "object") return value;
|
|
14
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortValue(value[key])]));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function canonicalBytes(value) {
|
|
18
|
+
return `${JSON.stringify(sortValue(value), null, 2)}\n`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sha256(value, prefix = true) {
|
|
22
|
+
const digest = crypto.createHash("sha256").update(Buffer.from(String(value))).digest("hex");
|
|
23
|
+
return prefix ? `sha256:${digest}` : digest;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function result(action, status, extra = {}) {
|
|
27
|
+
return {
|
|
28
|
+
schema_version: "1.0.0",
|
|
29
|
+
operation_id: `mirai.project_technology.course.${action}`,
|
|
30
|
+
operation_mode: "read_only",
|
|
31
|
+
status,
|
|
32
|
+
changed: false,
|
|
33
|
+
blockers: [],
|
|
34
|
+
warnings: [],
|
|
35
|
+
next_action: "none",
|
|
36
|
+
...extra,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readJson(file) {
|
|
41
|
+
const absolute = path.resolve(file);
|
|
42
|
+
if (!fs.existsSync(absolute) || fs.lstatSync(absolute).isSymbolicLink()) throw new Error("unsafe_or_missing_json_input");
|
|
43
|
+
return JSON.parse(fs.readFileSync(absolute, "utf8").replace(/^\uFEFF/, ""));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function unwrapCoursePack(value) {
|
|
47
|
+
return value && typeof value === "object" && !Array.isArray(value) && value.course_pack
|
|
48
|
+
? value.course_pack
|
|
49
|
+
: value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function uniqueStrings(value, field, blockers, required = true) {
|
|
53
|
+
if (!Array.isArray(value) || (required && value.length === 0)) {
|
|
54
|
+
blockers.push(`technology_${field}_empty`);
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const output = [];
|
|
58
|
+
for (const item of value) {
|
|
59
|
+
if (typeof item !== "string" || !SAFE_ID.test(item)) blockers.push(`technology_${field}_unsafe`);
|
|
60
|
+
else output.push(item);
|
|
61
|
+
}
|
|
62
|
+
return [...new Set(output)];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeTechnology(input) {
|
|
66
|
+
const blockers = [];
|
|
67
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return { technology: {}, blockers: ["technology_contract_missing"] };
|
|
68
|
+
const technology = {
|
|
69
|
+
schema_version: String(input.schema_version || "1.0.0"),
|
|
70
|
+
id: String(input.id || ""),
|
|
71
|
+
title: String(input.title || ""),
|
|
72
|
+
owner: String(input.owner || ""),
|
|
73
|
+
outcome: String(input.outcome || ""),
|
|
74
|
+
lifecycle: String(input.lifecycle || ""),
|
|
75
|
+
version: String(input.version || ""),
|
|
76
|
+
source_refs: uniqueStrings(input.source_refs || [], "source_refs", blockers),
|
|
77
|
+
projection_refs: uniqueStrings(input.projection_refs || [], "projection_refs", blockers, false),
|
|
78
|
+
};
|
|
79
|
+
for (const field of ["id", "owner"]) if (!SAFE_ID.test(technology[field])) blockers.push(`technology_${field}_missing_or_unsafe`);
|
|
80
|
+
for (const field of ["title", "outcome", "version"]) if (!technology[field].trim()) blockers.push(`technology_${field}_missing`);
|
|
81
|
+
if (!ACTIVE_LIFECYCLES.has(technology.lifecycle)) blockers.push("technology_lifecycle_not_executable");
|
|
82
|
+
|
|
83
|
+
const operationIds = new Set();
|
|
84
|
+
technology.operations = [];
|
|
85
|
+
if (!Array.isArray(input.operations) || input.operations.length === 0) blockers.push("technology_operations_empty");
|
|
86
|
+
else for (const raw of input.operations) {
|
|
87
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) { blockers.push("technology_operation_invalid"); continue; }
|
|
88
|
+
const operation = {
|
|
89
|
+
id: String(raw.id || ""), title: String(raw.title || ""), summary: String(raw.summary || ""),
|
|
90
|
+
owner: String(raw.owner || ""), capability_ref: String(raw.capability_ref || ""),
|
|
91
|
+
prerequisites: uniqueStrings(raw.prerequisites || [], "operation_prerequisites", blockers, false),
|
|
92
|
+
input_refs: uniqueStrings(raw.input_refs || [], "operation_input_refs", blockers, false),
|
|
93
|
+
output_refs: uniqueStrings(raw.output_refs || [], "operation_output_refs", blockers),
|
|
94
|
+
check_refs: uniqueStrings(raw.check_refs || [], "operation_check_refs", blockers),
|
|
95
|
+
stop_condition_refs: uniqueStrings(raw.stop_condition_refs || [], "operation_stop_condition_refs", blockers, false),
|
|
96
|
+
rollback_refs: uniqueStrings(raw.rollback_refs || [], "operation_rollback_refs", blockers, false),
|
|
97
|
+
source_refs: uniqueStrings(raw.source_refs || [], "operation_source_refs", blockers),
|
|
98
|
+
instructional_refs: uniqueStrings(raw.instructional_refs || [], "operation_instructional_refs", blockers, false),
|
|
99
|
+
applicability: Array.isArray(raw.applicability) ? [...new Set(raw.applicability.map(String))].sort() : [],
|
|
100
|
+
negative_boundaries: Array.isArray(raw.negative_boundaries) ? [...new Set(raw.negative_boundaries.map(String))].sort() : [],
|
|
101
|
+
};
|
|
102
|
+
for (const field of ["id", "owner", "capability_ref"]) if (!SAFE_ID.test(operation[field])) blockers.push(`technology_operation_${field}_missing_or_unsafe`);
|
|
103
|
+
if (!operation.title || !operation.summary) blockers.push("technology_operation_explanation_missing");
|
|
104
|
+
if (operationIds.has(operation.id)) blockers.push("technology_operation_duplicate");
|
|
105
|
+
operationIds.add(operation.id); technology.operations.push(operation);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
technology.scenarios = [];
|
|
109
|
+
if (!Array.isArray(input.scenarios) || input.scenarios.length === 0) blockers.push("technology_scenarios_empty");
|
|
110
|
+
else {
|
|
111
|
+
const scenarioIds = new Set();
|
|
112
|
+
for (const raw of input.scenarios) {
|
|
113
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) { blockers.push("technology_scenario_invalid"); continue; }
|
|
114
|
+
const scenario = {
|
|
115
|
+
id: String(raw.id || ""), title: String(raw.title || ""), outcome: String(raw.outcome || ""),
|
|
116
|
+
operation_ids: uniqueStrings(raw.operation_ids || [], "scenario_operation_ids", blockers),
|
|
117
|
+
required_inputs: uniqueStrings(raw.required_inputs || [], "scenario_required_inputs", blockers, false),
|
|
118
|
+
audience: Array.isArray(raw.audience) ? [...new Set(raw.audience.map(String))].sort() : [],
|
|
119
|
+
};
|
|
120
|
+
if (!SAFE_ID.test(scenario.id)) blockers.push("technology_scenario_id_missing_or_unsafe");
|
|
121
|
+
if (!scenario.title || !scenario.outcome) blockers.push("technology_scenario_explanation_missing");
|
|
122
|
+
if (scenarioIds.has(scenario.id)) blockers.push("technology_scenario_duplicate");
|
|
123
|
+
scenarioIds.add(scenario.id);
|
|
124
|
+
for (const id of scenario.operation_ids) if (!operationIds.has(id)) blockers.push("technology_scenario_operation_unknown");
|
|
125
|
+
technology.scenarios.push(scenario);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const operation of technology.operations) for (const dependency of operation.prerequisites) if (!operationIds.has(dependency)) blockers.push("technology_operation_prerequisite_unknown");
|
|
129
|
+
|
|
130
|
+
const visiting = new Set(); const visited = new Set(); const byId = new Map(technology.operations.map((item) => [item.id, item]));
|
|
131
|
+
function visit(id) {
|
|
132
|
+
if (visiting.has(id)) return true;
|
|
133
|
+
if (visited.has(id)) return false;
|
|
134
|
+
visiting.add(id);
|
|
135
|
+
for (const dependency of (byId.get(id) || {}).prerequisites || []) if (visit(dependency)) return true;
|
|
136
|
+
visiting.delete(id); visited.add(id); return false;
|
|
137
|
+
}
|
|
138
|
+
if ([...byId.keys()].some(visit)) blockers.push("technology_required_dependency_cycle");
|
|
139
|
+
return { technology, blockers: [...new Set(blockers)].sort() };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function technologyDigest(technology) {
|
|
143
|
+
return sha256(canonicalBytes(technology));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resolveClosure(operations, selectedIds, blockers) {
|
|
147
|
+
const byId = new Map(operations.map((item) => [item.id, item]));
|
|
148
|
+
const output = []; const seen = new Set();
|
|
149
|
+
function add(id) {
|
|
150
|
+
if (seen.has(id)) return;
|
|
151
|
+
const operation = byId.get(id);
|
|
152
|
+
if (!operation) { blockers.push("course_required_operation_missing"); return; }
|
|
153
|
+
for (const dependency of operation.prerequisites) add(dependency);
|
|
154
|
+
seen.add(id); output.push(operation);
|
|
155
|
+
}
|
|
156
|
+
for (const id of selectedIds) add(id);
|
|
157
|
+
return output;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function compileTechnologyCourse(repository, options = {}) {
|
|
161
|
+
let raw;
|
|
162
|
+
try { raw = options.technology || readJson(options.technologyFile); }
|
|
163
|
+
catch (error) { return result("compile", "blocked", { blockers: [String(error.message || error)], next_action: "provide a valid executable technology contract" }); }
|
|
164
|
+
const normalized = normalizeTechnology(raw);
|
|
165
|
+
const blockers = [...normalized.blockers];
|
|
166
|
+
if (blockers.length) return result("compile", "blocked", { blockers, next_action: "repair the executable technology contract" });
|
|
167
|
+
const scenarioIds = options.scenarioIds && options.scenarioIds.length ? options.scenarioIds : normalized.technology.scenarios.map((item) => item.id);
|
|
168
|
+
const scenarios = normalized.technology.scenarios.filter((item) => scenarioIds.includes(item.id));
|
|
169
|
+
if (scenarios.length !== new Set(scenarioIds).size) blockers.push("course_scenario_unknown");
|
|
170
|
+
const selectedOperationIds = scenarios.flatMap((item) => item.operation_ids);
|
|
171
|
+
const operations = resolveClosure(normalized.technology.operations, selectedOperationIds, blockers);
|
|
172
|
+
const audience = String(options.audience || "learner");
|
|
173
|
+
if (!SAFE_ID.test(audience)) blockers.push("course_audience_unsafe");
|
|
174
|
+
if (blockers.length) return result("compile", "blocked", { blockers: [...new Set(blockers)].sort(), next_action: "repair the technology or course selection" });
|
|
175
|
+
const technology_digest = technologyDigest(normalized.technology);
|
|
176
|
+
const packBase = {
|
|
177
|
+
schema_version: "1.0.0",
|
|
178
|
+
technology_id: normalized.technology.id,
|
|
179
|
+
technology_title: normalized.technology.title,
|
|
180
|
+
technology_outcome: normalized.technology.outcome,
|
|
181
|
+
technology_version: normalized.technology.version,
|
|
182
|
+
technology_digest,
|
|
183
|
+
audience,
|
|
184
|
+
scenario_ids: scenarios.map((item) => item.id),
|
|
185
|
+
source_refs: normalized.technology.source_refs,
|
|
186
|
+
source_revisions: options.sourceRevisions || {},
|
|
187
|
+
scenarios,
|
|
188
|
+
sections: operations.map((item, index) => ({
|
|
189
|
+
order: index + 1,
|
|
190
|
+
technology_node_id: item.id,
|
|
191
|
+
title: item.title,
|
|
192
|
+
summary: item.summary,
|
|
193
|
+
owner: item.owner,
|
|
194
|
+
capability_ref: item.capability_ref,
|
|
195
|
+
prerequisite_ids: item.prerequisites,
|
|
196
|
+
input_refs: item.input_refs,
|
|
197
|
+
output_refs: item.output_refs,
|
|
198
|
+
check_refs: item.check_refs,
|
|
199
|
+
stop_condition_refs: item.stop_condition_refs,
|
|
200
|
+
rollback_refs: item.rollback_refs,
|
|
201
|
+
source_refs: item.source_refs,
|
|
202
|
+
instructional_refs: item.instructional_refs,
|
|
203
|
+
})),
|
|
204
|
+
exercises: options.exercises || [],
|
|
205
|
+
checks: [...new Set(operations.flatMap((item) => item.check_refs))].sort(),
|
|
206
|
+
omissions: [],
|
|
207
|
+
limitations: [],
|
|
208
|
+
};
|
|
209
|
+
const context = { ...packBase, course_pack_digest: sha256(canonicalBytes(packBase)) };
|
|
210
|
+
return result("compile", "success", { repository: path.resolve(repository || "."), course_pack: context });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function verifyTechnologyCourse(repository, options = {}) {
|
|
214
|
+
let pack;
|
|
215
|
+
try { pack = unwrapCoursePack(options.coursePack || readJson(options.coursePackFile)); }
|
|
216
|
+
catch (error) { return result("verify", "blocked", { blockers: [String(error.message || error)] }); }
|
|
217
|
+
const blockers = [];
|
|
218
|
+
const digest = pack.course_pack_digest;
|
|
219
|
+
const base = { ...pack }; delete base.course_pack_digest;
|
|
220
|
+
if (digest !== sha256(canonicalBytes(base))) blockers.push("course_pack_digest_mismatch");
|
|
221
|
+
if (!Array.isArray(pack.sections) || pack.sections.length === 0) blockers.push("course_sections_empty");
|
|
222
|
+
const ids = new Set();
|
|
223
|
+
for (const section of pack.sections || []) {
|
|
224
|
+
if (!SAFE_ID.test(String(section.technology_node_id || ""))) blockers.push("course_section_identity_missing");
|
|
225
|
+
if (ids.has(section.technology_node_id)) blockers.push("course_section_duplicate");
|
|
226
|
+
ids.add(section.technology_node_id);
|
|
227
|
+
if (!section.title || !section.summary || !section.owner || !section.capability_ref) blockers.push("course_section_incomplete");
|
|
228
|
+
}
|
|
229
|
+
const serialized = JSON.stringify(pack).toLowerCase();
|
|
230
|
+
for (const marker of SECRET_MARKERS) if (serialized.includes(`\"${marker}\":`)) blockers.push("course_pack_secret_field_forbidden");
|
|
231
|
+
return result("verify", blockers.length ? "blocked" : "success", { blockers: [...new Set(blockers)].sort(), course_pack_digest: digest, next_action: blockers.length ? "recompile the course from current accepted technology" : "none" });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function reconcileTechnologyCourse(repository, options = {}) {
|
|
235
|
+
let pack; let projection;
|
|
236
|
+
try {
|
|
237
|
+
pack = unwrapCoursePack(options.coursePack || readJson(options.coursePackFile));
|
|
238
|
+
projection = options.projection || readJson(options.projectionFile);
|
|
239
|
+
} catch (error) { return result("reconcile", "blocked", { blockers: [String(error.message || error)] }); }
|
|
240
|
+
const verified = verifyTechnologyCourse(repository, { coursePack: pack });
|
|
241
|
+
if (verified.status !== "success") return result("reconcile", "blocked", { blockers: verified.blockers, next_action: verified.next_action });
|
|
242
|
+
if (projection.course_pack_digest !== pack.course_pack_digest) return result("reconcile", "blocked", { blockers: ["course_projection_source_stale"], next_action: "re-export the projection or explicitly reconcile against its source pack" });
|
|
243
|
+
const base = new Map(pack.sections.map((item) => [item.technology_node_id, item]));
|
|
244
|
+
const seen = new Set(); const changes = [];
|
|
245
|
+
for (const current of projection.sections || []) {
|
|
246
|
+
const id = current.technology_node_id;
|
|
247
|
+
if (!base.has(id)) { changes.push({ type: "semantic_proposal", technology_node_id: id || null, reason: "course_section_added" }); continue; }
|
|
248
|
+
seen.add(id); const previous = base.get(id);
|
|
249
|
+
const semanticFields = ["owner", "capability_ref", "prerequisite_ids", "input_refs", "output_refs", "check_refs", "stop_condition_refs", "rollback_refs", "source_refs"];
|
|
250
|
+
const semantic = semanticFields.some((field) => canonicalBytes(previous[field] || null) !== canonicalBytes(current[field] || null));
|
|
251
|
+
const editorial = previous.title !== current.title || previous.summary !== current.summary || canonicalBytes(previous.instructional_refs || []) !== canonicalBytes(current.instructional_refs || []);
|
|
252
|
+
if (semantic) changes.push({ type: "semantic_proposal", technology_node_id: id, reason: "executable_contract_changed" });
|
|
253
|
+
else if (editorial) changes.push({ type: "editorial", technology_node_id: id, reason: "instructional_projection_changed" });
|
|
254
|
+
}
|
|
255
|
+
for (const id of base.keys()) if (!seen.has(id)) changes.push({ type: "semantic_proposal", technology_node_id: id, reason: "required_course_section_removed" });
|
|
256
|
+
const semantic = changes.filter((item) => item.type === "semantic_proposal");
|
|
257
|
+
return result("reconcile", semantic.length ? "needs_decision" : "success", {
|
|
258
|
+
changes,
|
|
259
|
+
semantic_proposals: semantic,
|
|
260
|
+
editorial_changes: changes.filter((item) => item.type === "editorial"),
|
|
261
|
+
canonical_write_allowed: false,
|
|
262
|
+
next_action: semantic.length ? "send semantic proposals to the technology owners" : changes.length ? "apply editorial changes through the documentation owner" : "none",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function executeCourse(repository, options = {}) {
|
|
267
|
+
const action = options.courseAction;
|
|
268
|
+
if (action === "compile") return compileTechnologyCourse(repository, options);
|
|
269
|
+
if (action === "verify") return verifyTechnologyCourse(repository, options);
|
|
270
|
+
if (action === "reconcile") return reconcileTechnologyCourse(repository, options);
|
|
271
|
+
return result(String(action || "unknown"), "fail", { blockers: ["unsupported_technology_course_action"] });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = {
|
|
275
|
+
canonicalBytes,
|
|
276
|
+
compileTechnologyCourse,
|
|
277
|
+
executeCourse,
|
|
278
|
+
normalizeTechnology,
|
|
279
|
+
reconcileTechnologyCourse,
|
|
280
|
+
technologyDigest,
|
|
281
|
+
verifyTechnologyCourse,
|
|
282
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Mirai Graph 1.3.0
|
|
2
|
+
|
|
3
|
+
Mirai Graph 1.3.0 adds generic immutable releases for file bundles to the
|
|
4
|
+
existing Project Technology engine.
|
|
5
|
+
|
|
6
|
+
Projects can inspect a bundle, preview or create a release, compare two
|
|
7
|
+
releases and verify stored files against their manifest. The graph stores only
|
|
8
|
+
safe lineage, state, references and checksums; protected artifact content
|
|
9
|
+
remains outside graph data.
|
|
10
|
+
|
|
11
|
+
The release supports direct files, directories, ZIP, TAR and TAR.GZ, with
|
|
12
|
+
fail-closed archive checks, transactional activation, rollback, concurrency
|
|
13
|
+
protection and idempotent repeats.
|
|
14
|
+
|
|
15
|
+
The public `graph.json` schema remains `2.0.0`. The Project Technology
|
|
16
|
+
activation contract remains `1.0.0`.
|
package/releases/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Release notes must separate:
|
|
|
20
20
|
- [v1.1.0](1.1.0.md) - universal sequential context traversal and usage
|
|
21
21
|
verification in Project Technology.
|
|
22
22
|
- [v1.0.0](1.0.0.md) - stable Project Technology, CLI and public contract.
|
|
23
|
+
- [v1.3.0](1.3.0.md) - immutable artifact releases in Project Technology.
|
|
23
24
|
- [v1.0.0-rc.6](1.0.0-rc.6.md) - anti-drift / quality-control release
|
|
24
25
|
consolidation across Semantic Intent, Dynamic Episode, Goal Vector and
|
|
25
26
|
Technology Quality Feedback.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://mirai-graph.org/schemas/executable-technology.schema.json",
|
|
4
|
+
"title": "Mirai Graph Executable Technology",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "id", "title", "owner", "outcome", "lifecycle", "version", "source_refs", "operations", "scenarios"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": "1.0.0" },
|
|
10
|
+
"id": { "$ref": "#/$defs/id" },
|
|
11
|
+
"title": { "$ref": "#/$defs/text" },
|
|
12
|
+
"owner": { "$ref": "#/$defs/id" },
|
|
13
|
+
"outcome": { "$ref": "#/$defs/text" },
|
|
14
|
+
"lifecycle": { "enum": ["reviewed", "accepted", "active"] },
|
|
15
|
+
"version": { "$ref": "#/$defs/text" },
|
|
16
|
+
"source_refs": { "$ref": "#/$defs/nonEmptyIds" },
|
|
17
|
+
"projection_refs": { "$ref": "#/$defs/ids" },
|
|
18
|
+
"operations": {
|
|
19
|
+
"type": "array",
|
|
20
|
+
"minItems": 1,
|
|
21
|
+
"items": { "$ref": "#/$defs/operation" }
|
|
22
|
+
},
|
|
23
|
+
"scenarios": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"minItems": 1,
|
|
26
|
+
"items": { "$ref": "#/$defs/scenario" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"$defs": {
|
|
30
|
+
"id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@?=+*\\-]{1,255}$" },
|
|
31
|
+
"text": { "type": "string", "minLength": 1 },
|
|
32
|
+
"ids": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/id" } },
|
|
33
|
+
"nonEmptyIds": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/$defs/id" } },
|
|
34
|
+
"operation": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": false,
|
|
37
|
+
"required": ["id", "title", "summary", "owner", "capability_ref", "output_refs", "check_refs", "source_refs"],
|
|
38
|
+
"properties": {
|
|
39
|
+
"id": { "$ref": "#/$defs/id" },
|
|
40
|
+
"title": { "$ref": "#/$defs/text" },
|
|
41
|
+
"summary": { "$ref": "#/$defs/text" },
|
|
42
|
+
"owner": { "$ref": "#/$defs/id" },
|
|
43
|
+
"capability_ref": { "$ref": "#/$defs/id" },
|
|
44
|
+
"prerequisites": { "$ref": "#/$defs/ids" },
|
|
45
|
+
"input_refs": { "$ref": "#/$defs/ids" },
|
|
46
|
+
"output_refs": { "$ref": "#/$defs/nonEmptyIds" },
|
|
47
|
+
"check_refs": { "$ref": "#/$defs/nonEmptyIds" },
|
|
48
|
+
"stop_condition_refs": { "$ref": "#/$defs/ids" },
|
|
49
|
+
"rollback_refs": { "$ref": "#/$defs/ids" },
|
|
50
|
+
"source_refs": { "$ref": "#/$defs/nonEmptyIds" },
|
|
51
|
+
"instructional_refs": { "$ref": "#/$defs/ids" },
|
|
52
|
+
"applicability": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/text" } },
|
|
53
|
+
"negative_boundaries": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/text" } }
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"scenario": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"additionalProperties": false,
|
|
59
|
+
"required": ["id", "title", "outcome", "operation_ids"],
|
|
60
|
+
"properties": {
|
|
61
|
+
"id": { "$ref": "#/$defs/id" },
|
|
62
|
+
"title": { "$ref": "#/$defs/text" },
|
|
63
|
+
"outcome": { "$ref": "#/$defs/text" },
|
|
64
|
+
"operation_ids": { "$ref": "#/$defs/nonEmptyIds" },
|
|
65
|
+
"required_inputs": { "$ref": "#/$defs/ids" },
|
|
66
|
+
"audience": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/text" } }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://mirai-graph.org/schemas/technology-course-pack.schema.json",
|
|
4
|
+
"title": "Mirai Graph Technology Course Pack",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "technology_id", "technology_title", "technology_outcome", "technology_version", "technology_digest", "audience", "scenario_ids", "source_refs", "source_revisions", "scenarios", "sections", "exercises", "checks", "omissions", "limitations", "course_pack_digest"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": "1.0.0" },
|
|
10
|
+
"technology_id": { "$ref": "#/$defs/id" },
|
|
11
|
+
"technology_title": { "type": "string", "minLength": 1 },
|
|
12
|
+
"technology_outcome": { "type": "string", "minLength": 1 },
|
|
13
|
+
"technology_version": { "type": "string", "minLength": 1 },
|
|
14
|
+
"technology_digest": { "$ref": "#/$defs/digest" },
|
|
15
|
+
"audience": { "$ref": "#/$defs/id" },
|
|
16
|
+
"scenario_ids": { "$ref": "#/$defs/nonEmptyIds" },
|
|
17
|
+
"source_refs": { "$ref": "#/$defs/nonEmptyIds" },
|
|
18
|
+
"source_revisions": { "type": "object", "additionalProperties": { "type": "string", "minLength": 1 } },
|
|
19
|
+
"scenarios": { "type": "array", "minItems": 1, "items": { "type": "object" } },
|
|
20
|
+
"sections": {
|
|
21
|
+
"type": "array",
|
|
22
|
+
"minItems": 1,
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"required": ["order", "technology_node_id", "title", "summary", "owner", "capability_ref", "prerequisite_ids", "input_refs", "output_refs", "check_refs", "stop_condition_refs", "rollback_refs", "source_refs", "instructional_refs"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"order": { "type": "integer", "minimum": 1 },
|
|
29
|
+
"technology_node_id": { "$ref": "#/$defs/id" },
|
|
30
|
+
"title": { "type": "string", "minLength": 1 },
|
|
31
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
32
|
+
"owner": { "$ref": "#/$defs/id" },
|
|
33
|
+
"capability_ref": { "$ref": "#/$defs/id" },
|
|
34
|
+
"prerequisite_ids": { "$ref": "#/$defs/ids" },
|
|
35
|
+
"input_refs": { "$ref": "#/$defs/ids" },
|
|
36
|
+
"output_refs": { "$ref": "#/$defs/nonEmptyIds" },
|
|
37
|
+
"check_refs": { "$ref": "#/$defs/nonEmptyIds" },
|
|
38
|
+
"stop_condition_refs": { "$ref": "#/$defs/ids" },
|
|
39
|
+
"rollback_refs": { "$ref": "#/$defs/ids" },
|
|
40
|
+
"source_refs": { "$ref": "#/$defs/nonEmptyIds" },
|
|
41
|
+
"instructional_refs": { "$ref": "#/$defs/ids" }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"exercises": { "type": "array" },
|
|
46
|
+
"checks": { "$ref": "#/$defs/nonEmptyIds" },
|
|
47
|
+
"omissions": { "type": "array" },
|
|
48
|
+
"limitations": { "type": "array" },
|
|
49
|
+
"course_pack_digest": { "$ref": "#/$defs/digest" }
|
|
50
|
+
},
|
|
51
|
+
"$defs": {
|
|
52
|
+
"id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@?=+*\\-]{1,255}$" },
|
|
53
|
+
"digest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
|
|
54
|
+
"ids": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/id" } },
|
|
55
|
+
"nonEmptyIds": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/$defs/id" } }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Project Technology
|
|
2
2
|
|
|
3
|
-
Status: 1.
|
|
3
|
+
Status: 1.4 stable standard; activation contract remains 1.0.0
|
|
4
4
|
|
|
5
5
|
Project Technology is the shared executable mechanism of Mirai Graph. It is
|
|
6
6
|
not a profile, a second graph or a source of domain methodology.
|
|
@@ -19,6 +19,75 @@ Each graph keeps its own objects and relations. Project Technology only
|
|
|
19
19
|
standardizes safe inventory, task context, accepted-target binding, freshness
|
|
20
20
|
and verification.
|
|
21
21
|
|
|
22
|
+
## Executable Technologies And Course Projections
|
|
23
|
+
|
|
24
|
+
An executable technology is an accepted graph of reusable operations and
|
|
25
|
+
user-facing scenarios. It is suitable for a long method that has both an
|
|
26
|
+
end-to-end path and independently useful parts. Each operation binds its owner,
|
|
27
|
+
capability, prerequisites, inputs, outputs, checks, stop conditions, rollback
|
|
28
|
+
and exact raw source references.
|
|
29
|
+
|
|
30
|
+
A scenario names an outcome and selects the operations needed to reach it.
|
|
31
|
+
Project Technology calculates prerequisite closure, so a course or executor
|
|
32
|
+
cannot silently omit a required safety step.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
mirai-graph technology course compile . --technology graph/specs/technology.json
|
|
36
|
+
mirai-graph technology course compile . --technology graph/specs/technology.json --scenario scenario.recovery
|
|
37
|
+
mirai-graph technology course verify . --course-pack course-pack.json
|
|
38
|
+
mirai-graph technology course reconcile . --course-pack course-pack.json --projection edited-course.json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The JavaScript API exposes `compileTechnologyCourse`,
|
|
42
|
+
`verifyTechnologyCourse` and `reconcileTechnologyCourse`.
|
|
43
|
+
|
|
44
|
+
A Course Pack is hash-bound to the normalized technology, chosen scenarios,
|
|
45
|
+
sources and revisions. It may be rendered into a document, learning system or
|
|
46
|
+
documentation site. It remains a projection: editorial changes may be routed
|
|
47
|
+
to the documentation owner, while changed prerequisites, owners, checks, stop
|
|
48
|
+
conditions, rollback or scope become semantic proposals. Reconciliation never
|
|
49
|
+
writes them into the accepted technology automatically.
|
|
50
|
+
|
|
51
|
+
## Immutable Artifact Releases
|
|
52
|
+
|
|
53
|
+
Project Technology can preserve versioned file bundles without turning their
|
|
54
|
+
contents into graph data:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
inspect -> release preview -> transactional release -> compare -> verify
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The generic CLI is:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
mirai-graph technology artifact inspect . --input incoming.zip
|
|
64
|
+
mirai-graph technology artifact release . --input incoming.zip --matter-id agreement-main --direction inbound
|
|
65
|
+
mirai-graph technology artifact release . --input incoming.zip --matter-id agreement-main --direction inbound --apply
|
|
66
|
+
mirai-graph technology artifact compare . --matter-id agreement-main --base-release 20260828-01 --target-release 20260828-02
|
|
67
|
+
mirai-graph technology artifact verify . --matter-id agreement-main --release-id 20260828-02
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The programmatic API exposes `inspectArtifactBundle`,
|
|
71
|
+
`createArtifactRelease`, `compareArtifactReleases` and
|
|
72
|
+
`verifyArtifactRelease`.
|
|
73
|
+
|
|
74
|
+
Each immutable release keeps the original input, a normalized package, a
|
|
75
|
+
hash-bound manifest and a technical comparison. The portable registry at
|
|
76
|
+
`graph/specs/artifact-releases.json` stores only opaque matter/release ids,
|
|
77
|
+
relative or provider refs, lineage, state and digests. Raw files, document
|
|
78
|
+
contents, party names and private discussion remain in the protected artifact
|
|
79
|
+
store.
|
|
80
|
+
|
|
81
|
+
Direct files, directories, ZIP, TAR and TAR.GZ are supported. Unsafe paths,
|
|
82
|
+
links, encrypted archives, executable or macro-enabled files, nested archives,
|
|
83
|
+
normalized path collisions and configured archive limits fail closed. RAR and
|
|
84
|
+
7z need an explicitly supplied safe provider.
|
|
85
|
+
|
|
86
|
+
Creation uses a lease, compare-and-swap registry digest, temporary assembly,
|
|
87
|
+
atomic activation and readback. Repeating the same release returns
|
|
88
|
+
`changed=false`. Domain consumers own the meaning of release states; Mirai
|
|
89
|
+
Graph owns only generic identity, lineage, integrity and technical comparison.
|
|
90
|
+
|
|
22
91
|
## Portable Project Continuity
|
|
23
92
|
|
|
24
93
|
Project Technology preserves verified project experience at task boundaries:
|
|
@@ -137,6 +206,10 @@ source still answers "how should the domain work be done?".
|
|
|
137
206
|
rejected from portable continuity.
|
|
138
207
|
- Significant work with an enabled continuity policy fails closed when its
|
|
139
208
|
terminal receipt is missing or stale.
|
|
209
|
+
- Artifact inspection, comparison and verification are read-only. Release
|
|
210
|
+
creation is preview-only without `--apply`.
|
|
211
|
+
- An artifact manifest or generated export never proves legal, business or
|
|
212
|
+
domain acceptance.
|
|
140
213
|
- A context pack is not ready while a required branch, source, access boundary
|
|
141
214
|
or validator is missing, stale, blocked, deprecated, conflicting or
|
|
142
215
|
digest-mismatched.
|