mirai-graph 1.1.1 → 1.3.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/.github/workflows/ci.yml +3 -0
- package/CHANGELOG.md +58 -0
- package/CITATION.cff +2 -2
- package/README.md +29 -3
- package/docs/adoption/cli.md +13 -0
- package/package.json +40 -38
- package/packages/cli/context-pack.js +0 -0
- package/packages/cli/graph-manifest.js +2 -1
- package/packages/cli/mirai-graph.js +1 -1
- package/packages/cli/mirai_graph.js +0 -0
- package/packages/cli/project-technology.js +27 -2
- package/packages/cli/readiness-score.js +0 -0
- package/packages/cli/seed-preview.js +0 -0
- package/packages/cli/validate-artifact-releases.js +282 -0
- package/packages/cli/validate-mirai-graph.js +0 -0
- package/packages/cli/validate-profile-results.js +0 -0
- package/packages/cli/validate-project-continuity.js +173 -0
- package/packages/cli/validate-project-technology.js +26 -2
- package/packages/project-technology/artifact-release.js +738 -0
- package/packages/project-technology/context-traversal.js +22 -6
- package/packages/project-technology/continuity.js +414 -0
- package/packages/project-technology/index.js +146 -36
- package/profiles/implementation-control/profile.json +3 -1
- package/profiles/project-management/profile.json +8 -1
- package/releases/1.2.0.md +19 -0
- package/releases/1.3.0.md +16 -0
- package/releases/README.md +3 -0
- package/schemas/project-technology-extension.schema.json +2 -1
- package/standard/context-pack-generation.md +8 -2
- package/standard/object-model.md +6 -0
- package/standard/project-technology.md +79 -4
- package/standard/test-suite.md +4 -1
|
@@ -14,15 +14,17 @@ const {
|
|
|
14
14
|
validateManifest,
|
|
15
15
|
} = require("../cli/graph-manifest");
|
|
16
16
|
const traversal = require("./context-traversal");
|
|
17
|
+
const continuity = require("./continuity");
|
|
18
|
+
const artifacts = require("./artifact-release");
|
|
17
19
|
|
|
18
20
|
const CONTRACT_VERSION = "1.0.0";
|
|
19
21
|
const EXTENSION_KEY = "mirai.project_technology";
|
|
20
22
|
const LEGACY_EXTENSION_KEY = "simai.project_technology";
|
|
21
23
|
const LOCAL_DIR = path.join(".mirai-graph", "project-technology");
|
|
22
24
|
const LEGACY_LOCAL_DIR = path.join(".simai", "project-technology");
|
|
23
|
-
const INVENTORY_FILE =
|
|
24
|
-
const BINDING_FILE =
|
|
25
|
-
const IMPORT_DIR =
|
|
25
|
+
const INVENTORY_FILE = "inventory.json";
|
|
26
|
+
const BINDING_FILE = "target-provider-binding.json";
|
|
27
|
+
const IMPORT_DIR = "provider-exports";
|
|
26
28
|
const EXPORT_FILE = path.join("graph", "generated", "project-technology", "target-provider-export.json");
|
|
27
29
|
const TARGET_DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
|
|
28
30
|
const REVISION_RE = /^[0-9a-f]{40}$/;
|
|
@@ -90,6 +92,58 @@ function normalizeRepo(repo) {
|
|
|
90
92
|
return path.resolve(repo || ".");
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
function runtimeRoot(repo, manifest = null, options = {}) {
|
|
96
|
+
return continuity.stateRoot(repo, manifest || readManifest(repo).manifest, options);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function legacyRuntimeState(repo) {
|
|
100
|
+
const root = path.join(repo, LOCAL_DIR);
|
|
101
|
+
return {
|
|
102
|
+
root,
|
|
103
|
+
present: fs.existsSync(root),
|
|
104
|
+
inventory: path.join(root, INVENTORY_FILE),
|
|
105
|
+
binding: path.join(root, BINDING_FILE),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function migrateProjectLocalRuntime(repo, manifest, root) {
|
|
110
|
+
const legacy = legacyRuntimeState(repo);
|
|
111
|
+
if (!legacy.present) return { changed: false, blockers: [], migration_ref: null };
|
|
112
|
+
const blockers = [];
|
|
113
|
+
let changed = false;
|
|
114
|
+
if (fs.existsSync(legacy.binding)) {
|
|
115
|
+
const binding = readJson(legacy.binding);
|
|
116
|
+
const identity = bindingValues(binding); blockers.push(...identity.blockers);
|
|
117
|
+
const ref = String(binding.provider_export_ref || "");
|
|
118
|
+
const exportPath = ref && !path.isAbsolute(ref) ? path.resolve(repo, ref) : null;
|
|
119
|
+
const exported = exportPath ? readExport(exportPath) : { export: {}, blockers: ["legacy_provider_export_missing"] };
|
|
120
|
+
blockers.push(...exported.blockers);
|
|
121
|
+
for (const key of Object.keys(identity.values)) if (exported.export[key] !== identity.values[key]) blockers.push(`legacy_${key}_mismatch`);
|
|
122
|
+
if (!blockers.length) {
|
|
123
|
+
const exportBytes = canonicalBytes(exported.export);
|
|
124
|
+
const exportSha = sha256(exportBytes);
|
|
125
|
+
changed = atomicWrite(path.join(root, IMPORT_DIR, `${exportSha}.json`), exportBytes) || changed;
|
|
126
|
+
changed = atomicWrite(path.join(root, BINDING_FILE), canonicalBytes({
|
|
127
|
+
schema_version: "1.0.0", ...identity.values,
|
|
128
|
+
provider_export_ref: `host-local://${IMPORT_DIR}/${exportSha}.json`,
|
|
129
|
+
provider_export_sha256: exportSha,
|
|
130
|
+
})) || changed;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (blockers.length) return { changed: false, blockers: [...new Set(blockers)].sort(), migration_ref: null };
|
|
134
|
+
const receipt = {
|
|
135
|
+
schema_version: "1.0.0",
|
|
136
|
+
status: "migrated",
|
|
137
|
+
repository_id: manifest.id,
|
|
138
|
+
source: LOCAL_DIR,
|
|
139
|
+
source_preserved: true,
|
|
140
|
+
active_runtime: "host-local",
|
|
141
|
+
};
|
|
142
|
+
const receiptName = `project-local-runtime-${sha256(canonicalBytes(receipt)).slice(0, 16)}.json`;
|
|
143
|
+
changed = atomicWrite(path.join(root, "migrations", receiptName), canonicalBytes(receipt)) || changed;
|
|
144
|
+
return { changed, blockers: [], migration_ref: `host-local://migrations/${receiptName}` };
|
|
145
|
+
}
|
|
146
|
+
|
|
93
147
|
function readManifest(repo) {
|
|
94
148
|
const manifestPath = path.join(repo, "graph.json");
|
|
95
149
|
if (!fs.existsSync(manifestPath)) return { manifest: null, blockers: ["graph_manifest_missing"] };
|
|
@@ -106,6 +160,7 @@ function extensionContract() {
|
|
|
106
160
|
enabled: true,
|
|
107
161
|
context_policy: "task_scoped",
|
|
108
162
|
source_boundary: "hybrid_sot",
|
|
163
|
+
continuity_policy: "task_boundary",
|
|
109
164
|
};
|
|
110
165
|
}
|
|
111
166
|
|
|
@@ -432,19 +487,23 @@ function context(repoArg, options = {}) {
|
|
|
432
487
|
source_refs: (nodes.get(candidate.id)?.source_refs || []).map((source) => source.ref),
|
|
433
488
|
})),
|
|
434
489
|
runtime_contract: { graph_first_scope: "routing/capability/policy orientation", raw_source_authoritative: true, canonical_write_allowed: false },
|
|
490
|
+
continuity: continuity.status(normalizeRepo(repoArg), readManifest(normalizeRepo(repoArg)).manifest, options),
|
|
435
491
|
};
|
|
436
492
|
}
|
|
437
493
|
|
|
438
|
-
function targetBindingStatus(repo) {
|
|
439
|
-
const
|
|
494
|
+
function targetBindingStatus(repo, options = {}) {
|
|
495
|
+
const manifest = readManifest(repo).manifest;
|
|
496
|
+
const root = runtimeRoot(repo, manifest, options);
|
|
497
|
+
const bindingPath = path.join(root, BINDING_FILE);
|
|
440
498
|
if (!fs.existsSync(bindingPath)) return { status: "not_configured", enabled: false, blockers: [], next_action: "connect an exact target provider binding" };
|
|
441
499
|
let binding;
|
|
442
500
|
try { binding = readJson(bindingPath); } catch (_) { return { status: "blocked", enabled: true, blockers: ["target_binding_invalid"] }; }
|
|
443
501
|
const identity = bindingValues(binding);
|
|
444
502
|
const exportRef = String(binding.provider_export_ref || "");
|
|
445
|
-
const
|
|
503
|
+
const hostPrefix = "host-local://";
|
|
504
|
+
const exportPath = exportRef.startsWith(hostPrefix) ? path.join(root, exportRef.slice(hostPrefix.length)) : null;
|
|
446
505
|
const blockers = [...identity.blockers];
|
|
447
|
-
if (!exportPath || !exportPath.startsWith(`${
|
|
506
|
+
if (!exportPath || !exportPath.startsWith(`${root}${path.sep}`)) blockers.push("provider_export_outside_host_state");
|
|
448
507
|
const exported = exportPath ? readExport(exportPath) : { export: {}, blockers: ["provider_export_missing"] };
|
|
449
508
|
blockers.push(...exported.blockers);
|
|
450
509
|
for (const key of Object.keys(identity.values)) if (exported.export[key] !== identity.values[key]) blockers.push(`${key}_mismatch`);
|
|
@@ -466,18 +525,20 @@ function targetBindingStatus(repo) {
|
|
|
466
525
|
};
|
|
467
526
|
}
|
|
468
527
|
|
|
469
|
-
function status(repoArg) {
|
|
528
|
+
function status(repoArg, options = {}) {
|
|
470
529
|
const repo = normalizeRepo(repoArg);
|
|
471
530
|
const manifestState = readManifest(repo);
|
|
472
531
|
const ext = extensionState(manifestState.manifest);
|
|
473
532
|
let stored = null;
|
|
474
|
-
|
|
533
|
+
const root = runtimeRoot(repo, manifestState.manifest, options);
|
|
534
|
+
try { if (fs.existsSync(path.join(root, INVENTORY_FILE))) stored = readJson(path.join(root, INVENTORY_FILE)); } catch (_) { /* stale */ }
|
|
475
535
|
const current = inventory(repo);
|
|
476
536
|
const freshness = stored && stored.inventory_digest === current.inventory_digest ? "current" : stored ? "stale" : "missing";
|
|
477
537
|
const blockers = [...manifestState.blockers, ...ext.blockers];
|
|
538
|
+
if (!stored && legacyRuntimeState(repo).present) blockers.push("project_technology_host_state_migration_required");
|
|
478
539
|
if (ext.contract && ext.contract.enabled === false) blockers.push("project_technology_disabled");
|
|
479
540
|
if (freshness !== "current") blockers.push(`project_technology_inventory_${freshness}`);
|
|
480
|
-
const binding = targetBindingStatus(repo);
|
|
541
|
+
const binding = targetBindingStatus(repo, options);
|
|
481
542
|
if (binding.status === "blocked") blockers.push(...binding.blockers);
|
|
482
543
|
return result("status", "read_only", blockers.length ? "blocked" : "success", {
|
|
483
544
|
repository_id: manifestState.manifest?.id || path.basename(repo),
|
|
@@ -486,6 +547,7 @@ function status(repoArg) {
|
|
|
486
547
|
technology_freshness: freshness,
|
|
487
548
|
technology_digest: current.inventory_digest,
|
|
488
549
|
target_binding: binding,
|
|
550
|
+
continuity: continuity.status(repo, manifestState.manifest, {}),
|
|
489
551
|
blockers: [...new Set(blockers)].sort(),
|
|
490
552
|
next_action: ext.legacy ? "run enable --apply to migrate the legacy contract" : blockers.length ? "run plan, then the required transactional operation" : "none",
|
|
491
553
|
});
|
|
@@ -502,18 +564,18 @@ function plan(repoArg) {
|
|
|
502
564
|
const state = status(repoArg);
|
|
503
565
|
return result("plan", "read_only", "success", {
|
|
504
566
|
current_status: state.status,
|
|
505
|
-
planned_changes: ["validate_graph_manifest", "migrate_legacy_extension_if_present", "enable_public_contract", "build_safe_inventory", "verify_target_binding"],
|
|
567
|
+
planned_changes: ["validate_graph_manifest", "migrate_legacy_extension_if_present", "migrate_project_local_runtime_to_host_state", "enable_public_contract", "build_safe_inventory", "verify_target_binding"],
|
|
506
568
|
blockers: state.blockers.filter((item) => !["project_technology_not_configured", "project_technology_migration_required", "project_technology_inventory_missing", "project_technology_inventory_stale"].includes(item)),
|
|
507
569
|
next_action: "rerun the selected operation with --apply",
|
|
508
570
|
});
|
|
509
571
|
}
|
|
510
572
|
|
|
511
|
-
function preview(operation, repoArg) {
|
|
512
|
-
const state = status(repoArg);
|
|
573
|
+
function preview(operation, repoArg, options = {}) {
|
|
574
|
+
const state = status(repoArg, options);
|
|
513
575
|
return result(operation, "preview", "preview", { apply_required: true, current_status: state.status, blockers: state.blockers.filter((item) => item.startsWith("graph_manifest_")), next_action: `${operation} --apply` });
|
|
514
576
|
}
|
|
515
577
|
|
|
516
|
-
function enable(repoArg) {
|
|
578
|
+
function enable(repoArg, options = {}) {
|
|
517
579
|
const repo = normalizeRepo(repoArg);
|
|
518
580
|
const manifestState = readManifest(repo);
|
|
519
581
|
if (!manifestState.manifest || manifestState.blockers.length) return result("enable", "transactional", "fail", { blockers: manifestState.blockers, next_action: "repair graph.json" });
|
|
@@ -523,7 +585,13 @@ function enable(repoArg) {
|
|
|
523
585
|
delete extensions[LEGACY_EXTENSION_KEY];
|
|
524
586
|
extensions[EXTENSION_KEY] = extensionContract();
|
|
525
587
|
manifest.extensions = extensions;
|
|
526
|
-
const
|
|
588
|
+
const root = runtimeRoot(repo, manifest, options);
|
|
589
|
+
const runtimeMigration = migrateProjectLocalRuntime(repo, manifest, root);
|
|
590
|
+
if (runtimeMigration.blockers.length) return result("enable", "transactional", "fail", {
|
|
591
|
+
blockers: runtimeMigration.blockers,
|
|
592
|
+
next_action: "repair the project-local runtime before host-state migration",
|
|
593
|
+
});
|
|
594
|
+
const migrationRoot = path.join(root, "migrations", sha256(canonicalBytes({ legacy: legacy || null, head: git(repo, "rev-parse", "HEAD") })).slice(0, 16));
|
|
527
595
|
fs.mkdirSync(migrationRoot, { recursive: true });
|
|
528
596
|
atomicWrite(path.join(migrationRoot, "graph.json.before"), fs.readFileSync(path.join(repo, "graph.json")));
|
|
529
597
|
if (legacy) atomicWrite(path.join(migrationRoot, "legacy-extension.json"), canonicalBytes(legacy));
|
|
@@ -531,25 +599,52 @@ function enable(repoArg) {
|
|
|
531
599
|
if (fs.existsSync(legacyLocal)) atomicWrite(path.join(migrationRoot, "legacy-local-state.json"), canonicalBytes({ status: "preserved_for_rollback", path: LEGACY_LOCAL_DIR }));
|
|
532
600
|
const manifestChanged = atomicWrite(path.join(repo, "graph.json"), canonicalBytes(manifest));
|
|
533
601
|
const inv = inventory(repo);
|
|
534
|
-
const inventoryChanged = atomicWrite(path.join(
|
|
602
|
+
const inventoryChanged = atomicWrite(path.join(root, INVENTORY_FILE), canonicalBytes(inv));
|
|
535
603
|
const verified = readManifest(repo);
|
|
536
604
|
if (verified.blockers.length || extensionState(verified.manifest).blockers.length) {
|
|
537
605
|
fs.copyFileSync(path.join(migrationRoot, "graph.json.before"), path.join(repo, "graph.json"));
|
|
538
606
|
return result("enable", "transactional", "fail", { blockers: ["project_technology_enable_rolled_back", ...verified.blockers], next_action: "inspect the rollback receipt" });
|
|
539
607
|
}
|
|
540
|
-
return result("enable", "transactional", "success", {
|
|
608
|
+
return result("enable", "transactional", "success", {
|
|
609
|
+
changed: manifestChanged || inventoryChanged || runtimeMigration.changed,
|
|
610
|
+
migration_ref: runtimeMigration.migration_ref || `host-local://migrations/${path.basename(migrationRoot)}`,
|
|
611
|
+
technology_digest: inv.inventory_digest,
|
|
612
|
+
});
|
|
541
613
|
}
|
|
542
614
|
|
|
543
|
-
function sync(repoArg) {
|
|
615
|
+
function sync(repoArg, options = {}) {
|
|
544
616
|
const repo = normalizeRepo(repoArg);
|
|
545
617
|
const manifestState = readManifest(repo);
|
|
546
618
|
const ext = extensionState(manifestState.manifest);
|
|
547
619
|
const blockers = [...manifestState.blockers, ...ext.blockers];
|
|
620
|
+
const hostRoot = runtimeRoot(repo, manifestState.manifest, options);
|
|
621
|
+
if (legacyRuntimeState(repo).present && !fs.existsSync(path.join(hostRoot, INVENTORY_FILE))) blockers.push("project_technology_host_state_migration_required");
|
|
548
622
|
if (!ext.contract || ext.legacy || ext.contract.enabled !== true) blockers.push("project_technology_not_enabled");
|
|
549
623
|
if (blockers.length) return result("sync", "transactional", "fail", { blockers: [...new Set(blockers)].sort(), next_action: "enable or repair Project Technology" });
|
|
624
|
+
let continuityResult = null;
|
|
625
|
+
if (options.boundary) {
|
|
626
|
+
continuityResult = continuity.sync(repo, manifestState.manifest, options.boundary, options.continuityEvidence, options);
|
|
627
|
+
if (continuityResult.status !== "success") return result("sync", "transactional", continuityResult.status, {
|
|
628
|
+
blockers: continuityResult.blockers,
|
|
629
|
+
continuity: continuityResult.continuity || null,
|
|
630
|
+
next_action: "repair the continuity evidence or reconcile the graph state",
|
|
631
|
+
});
|
|
632
|
+
if (options.boundary === "task_start") return result("sync", "read_only", "success", {
|
|
633
|
+
changed: false,
|
|
634
|
+
technology_digest: inventory(repo).inventory_digest,
|
|
635
|
+
target_binding: targetBindingStatus(repo, options),
|
|
636
|
+
continuity: continuityResult.continuity,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
550
639
|
const inv = inventory(repo);
|
|
551
|
-
const
|
|
552
|
-
|
|
640
|
+
const currentManifest = readManifest(repo).manifest;
|
|
641
|
+
const changed = atomicWrite(path.join(runtimeRoot(repo, currentManifest || manifestState.manifest, options), INVENTORY_FILE), canonicalBytes(inv));
|
|
642
|
+
return result("sync", "transactional", "success", {
|
|
643
|
+
changed: changed || Boolean(continuityResult?.changed),
|
|
644
|
+
technology_digest: inv.inventory_digest,
|
|
645
|
+
target_binding: targetBindingStatus(repo, options),
|
|
646
|
+
continuity: continuityResult?.continuity || continuity.status(repo, currentManifest, options),
|
|
647
|
+
});
|
|
553
648
|
}
|
|
554
649
|
|
|
555
650
|
function provide(repoArg, options = {}) {
|
|
@@ -588,7 +683,8 @@ function connect(repoArg, options = {}) {
|
|
|
588
683
|
const providerRoot = providerRootFor(source);
|
|
589
684
|
if (!providerRoot) blockers.push("provider_revision_order_unverifiable");
|
|
590
685
|
else if (git(providerRoot, "rev-parse", "HEAD").toLowerCase() !== identity.values.provider_revision) blockers.push("provider_revision_does_not_match_head");
|
|
591
|
-
const
|
|
686
|
+
const root = runtimeRoot(repo, manifestState.manifest, options);
|
|
687
|
+
const currentPath = path.join(root, BINDING_FILE);
|
|
592
688
|
let current = null;
|
|
593
689
|
try { if (fs.existsSync(currentPath)) current = readJson(currentPath); } catch (_) { blockers.push("current_target_binding_invalid"); }
|
|
594
690
|
if (current) {
|
|
@@ -596,14 +692,14 @@ function connect(repoArg, options = {}) {
|
|
|
596
692
|
const same = Object.keys(identity.values).every((key) => current[key] === identity.values[key]);
|
|
597
693
|
if (!same) blockers.push("target_provider_conflict", "target_provider_refresh_required");
|
|
598
694
|
else {
|
|
599
|
-
const currentState = targetBindingStatus(repo);
|
|
695
|
+
const currentState = targetBindingStatus(repo, options);
|
|
600
696
|
if (currentState.status !== "ready") blockers.push(...currentState.blockers);
|
|
601
697
|
if (currentState.execution_contract_digest !== read.export.execution_contract_digest) blockers.push("target_provider_conflict", "provider_execution_contract_mismatch");
|
|
602
698
|
}
|
|
603
699
|
} else {
|
|
604
700
|
if (current.target_id !== identity.values.target_id) blockers.push("target_provider_target_mismatch");
|
|
605
701
|
if (current.semantic_digest !== identity.values.semantic_digest) blockers.push("target_provider_semantic_mismatch");
|
|
606
|
-
const currentState = targetBindingStatus(repo);
|
|
702
|
+
const currentState = targetBindingStatus(repo, options);
|
|
607
703
|
if (currentState.status !== "ready") blockers.push(...currentState.blockers);
|
|
608
704
|
if (currentState.execution_contract_digest !== read.export.execution_contract_digest) blockers.push("provider_execution_contract_refresh_mismatch");
|
|
609
705
|
if (!providerRoot) blockers.push("provider_revision_order_unverifiable");
|
|
@@ -618,8 +714,8 @@ function connect(repoArg, options = {}) {
|
|
|
618
714
|
if (blockers.length) return result("connect", "transactional", "fail", { blockers: [...new Set(blockers)].sort(), next_action: "repair the provider export or request an explicit accepted target change" });
|
|
619
715
|
const exportBytes = canonicalBytes(read.export);
|
|
620
716
|
const exportSha = sha256(exportBytes);
|
|
621
|
-
const imported = path.join(
|
|
622
|
-
const binding = { schema_version: "1.0.0", ...identity.values, provider_export_ref:
|
|
717
|
+
const imported = path.join(root, IMPORT_DIR, `${exportSha}.json`);
|
|
718
|
+
const binding = { schema_version: "1.0.0", ...identity.values, provider_export_ref: `host-local://${IMPORT_DIR}/${exportSha}.json`, provider_export_sha256: exportSha };
|
|
623
719
|
const transaction = fs.mkdtempSync(path.join(os.tmpdir(), "mirai-project-technology-"));
|
|
624
720
|
const bindingBefore = fs.existsSync(currentPath) ? fs.readFileSync(currentPath) : null;
|
|
625
721
|
const importBefore = fs.existsSync(imported) ? fs.readFileSync(imported) : null;
|
|
@@ -627,7 +723,7 @@ function connect(repoArg, options = {}) {
|
|
|
627
723
|
const exportChanged = atomicWrite(imported, exportBytes);
|
|
628
724
|
const bindingChanged = atomicWrite(currentPath, canonicalBytes(binding));
|
|
629
725
|
fs.rmSync(transaction, { recursive: true, force: true });
|
|
630
|
-
return result("connect", "transactional", "success", { changed: exportChanged || bindingChanged, refresh_binding: Boolean(options.refreshBinding), target_binding: targetBindingStatus(repo) });
|
|
726
|
+
return result("connect", "transactional", "success", { changed: exportChanged || bindingChanged, refresh_binding: Boolean(options.refreshBinding), target_binding: targetBindingStatus(repo, options) });
|
|
631
727
|
} catch (_) {
|
|
632
728
|
if (bindingBefore) atomicWrite(currentPath, bindingBefore); else if (fs.existsSync(currentPath)) fs.unlinkSync(currentPath);
|
|
633
729
|
if (importBefore) atomicWrite(imported, importBefore); else if (fs.existsSync(imported)) fs.unlinkSync(imported);
|
|
@@ -636,14 +732,15 @@ function connect(repoArg, options = {}) {
|
|
|
636
732
|
}
|
|
637
733
|
}
|
|
638
734
|
|
|
639
|
-
function disconnect(repoArg) {
|
|
735
|
+
function disconnect(repoArg, options = {}) {
|
|
640
736
|
const repo = normalizeRepo(repoArg);
|
|
641
|
-
const
|
|
737
|
+
const root = runtimeRoot(repo, readManifest(repo).manifest, options);
|
|
738
|
+
const bindingPath = path.join(root, BINDING_FILE);
|
|
642
739
|
if (!fs.existsSync(bindingPath)) return result("disconnect", "transactional", "success");
|
|
643
|
-
const backup = path.join(
|
|
740
|
+
const backup = path.join(root, "rollback", `binding-${sha256(fs.readFileSync(bindingPath)).slice(0, 16)}.json`);
|
|
644
741
|
atomicWrite(backup, fs.readFileSync(bindingPath));
|
|
645
742
|
fs.unlinkSync(bindingPath);
|
|
646
|
-
return result("disconnect", "transactional", "success", { changed: true, rollback_ref: path.
|
|
743
|
+
return result("disconnect", "transactional", "success", { changed: true, rollback_ref: `host-local://rollback/${path.basename(backup)}` });
|
|
647
744
|
}
|
|
648
745
|
|
|
649
746
|
function disable(repoArg) {
|
|
@@ -658,19 +755,25 @@ function disable(repoArg) {
|
|
|
658
755
|
return result("disable", "transactional", "success", { changed });
|
|
659
756
|
}
|
|
660
757
|
|
|
661
|
-
function repair(repoArg) {
|
|
758
|
+
function repair(repoArg, options = {}) {
|
|
662
759
|
const repo = normalizeRepo(repoArg);
|
|
663
760
|
const manifestState = readManifest(repo);
|
|
664
761
|
if (!manifestState.manifest || manifestState.blockers.length) return result("repair", "transactional", "fail", { blockers: manifestState.blockers, next_action: "repair graph.json before Project Technology" });
|
|
665
762
|
const ext = extensionState(manifestState.manifest);
|
|
666
|
-
if (!ext.contract || ext.legacy) return enable(repo);
|
|
667
|
-
if (ext.blockers.length) return enable(repo);
|
|
668
|
-
|
|
763
|
+
if (!ext.contract || ext.legacy) return enable(repo, options);
|
|
764
|
+
if (ext.blockers.length) return enable(repo, options);
|
|
765
|
+
const root = runtimeRoot(repo, manifestState.manifest, options);
|
|
766
|
+
if (legacyRuntimeState(repo).present && !fs.existsSync(path.join(root, INVENTORY_FILE))) return enable(repo, options);
|
|
767
|
+
return sync(repo, options);
|
|
669
768
|
}
|
|
670
769
|
|
|
671
770
|
function verify(repoArg, options = {}) {
|
|
672
|
-
const state = status(repoArg);
|
|
771
|
+
const state = status(repoArg, options);
|
|
673
772
|
const blockers = [...state.blockers];
|
|
773
|
+
const repo = normalizeRepo(repoArg);
|
|
774
|
+
const manifest = readManifest(repo).manifest;
|
|
775
|
+
const continuityResult = continuity.verify(repo, manifest, options);
|
|
776
|
+
if (continuity.continuityPolicy(manifest) === "task_boundary" || options.receiptDigest || options.significantWork) blockers.push(...continuityResult.blockers);
|
|
674
777
|
if (options.significantWork && state.target_binding.status !== "ready") blockers.push("accepted_target_binding_required_for_significant_work");
|
|
675
778
|
return result("verify", "read_only", blockers.length ? "blocked" : "success", {
|
|
676
779
|
repository_id: state.repository_id,
|
|
@@ -679,17 +782,19 @@ function verify(repoArg, options = {}) {
|
|
|
679
782
|
technology_freshness: state.technology_freshness,
|
|
680
783
|
technology_digest: state.technology_digest,
|
|
681
784
|
target_binding: state.target_binding,
|
|
785
|
+
continuity: continuityResult.continuity,
|
|
682
786
|
blockers: [...new Set(blockers)].sort(),
|
|
683
787
|
next_action: blockers.length ? "repair the listed Project Technology blockers" : "none",
|
|
684
788
|
});
|
|
685
789
|
}
|
|
686
790
|
|
|
687
791
|
function execute(operation, repoArg, options = {}) {
|
|
792
|
+
if (operation === "artifact") return artifacts.executeArtifact(repoArg, options);
|
|
688
793
|
const readOnly = { explain, status, plan, verify, context };
|
|
689
794
|
if (readOnly[operation]) return readOnly[operation](repoArg, options);
|
|
690
795
|
const transactional = { enable, sync, connect, disconnect, provide, disable, repair };
|
|
691
796
|
if (!transactional[operation]) return result(operation, "read_only", "fail", { blockers: ["unsupported_project_technology_operation"] });
|
|
692
|
-
if (!options.apply) return preview(operation, repoArg);
|
|
797
|
+
if (!options.apply) return preview(operation, repoArg, options);
|
|
693
798
|
return transactional[operation](repoArg, options);
|
|
694
799
|
}
|
|
695
800
|
|
|
@@ -699,9 +804,11 @@ module.exports = {
|
|
|
699
804
|
LOCAL_DIR,
|
|
700
805
|
bindingValues,
|
|
701
806
|
canonicalBytes,
|
|
807
|
+
compareArtifactReleases: artifacts.compareArtifactReleases,
|
|
702
808
|
connect,
|
|
703
809
|
compileContext: traversal.compileContext,
|
|
704
810
|
context,
|
|
811
|
+
createArtifactRelease: artifacts.createArtifactRelease,
|
|
705
812
|
discoverContext: traversal.discoverContext,
|
|
706
813
|
disable,
|
|
707
814
|
disconnect,
|
|
@@ -711,7 +818,9 @@ module.exports = {
|
|
|
711
818
|
explain,
|
|
712
819
|
extensionContract,
|
|
713
820
|
inventory,
|
|
821
|
+
inspectArtifactBundle: artifacts.inspectArtifactBundle,
|
|
714
822
|
normalizeExecutionContract,
|
|
823
|
+
continuity,
|
|
715
824
|
plan,
|
|
716
825
|
provide,
|
|
717
826
|
readExport,
|
|
@@ -721,5 +830,6 @@ module.exports = {
|
|
|
721
830
|
sync,
|
|
722
831
|
targetBindingStatus,
|
|
723
832
|
verify,
|
|
833
|
+
verifyArtifactRelease: artifacts.verifyArtifactRelease,
|
|
724
834
|
verifyContext: traversal.verifyContext,
|
|
725
835
|
};
|
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
"milestone",
|
|
13
13
|
"stakeholder",
|
|
14
14
|
"evidence",
|
|
15
|
-
"governance_gate"
|
|
15
|
+
"governance_gate",
|
|
16
|
+
"project_dna",
|
|
17
|
+
"stage",
|
|
18
|
+
"batch",
|
|
19
|
+
"requirement",
|
|
20
|
+
"method",
|
|
21
|
+
"lesson",
|
|
22
|
+
"regression_case"
|
|
16
23
|
],
|
|
17
24
|
"allowed_relation_types": [
|
|
18
25
|
"depends_on",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Mirai Graph 1.2.0
|
|
2
|
+
|
|
3
|
+
Mirai Graph 1.2.0 adds portable project continuity to the existing Project
|
|
4
|
+
Technology mechanism.
|
|
5
|
+
|
|
6
|
+
Verified outcomes, requirements, evidence and reusable cases can now be stored
|
|
7
|
+
in `graph/specs` and discovered automatically by a later task. The mechanism
|
|
8
|
+
does not store chat transcripts and does not introduce a second memory database.
|
|
9
|
+
|
|
10
|
+
Mutable receipts and rollback data remain on the current host. Git repositories
|
|
11
|
+
and ordinary shared folders use the same canonical graph contract; shared-folder
|
|
12
|
+
writes additionally use a lease and compare-and-swap digest.
|
|
13
|
+
|
|
14
|
+
Goals, architectural decisions and owner or source-of-truth changes remain
|
|
15
|
+
proposals until explicitly accepted. A generated context document remains a
|
|
16
|
+
readable projection rather than a source of truth or write authorization.
|
|
17
|
+
|
|
18
|
+
`graph.json` remains schema `2.0.0` and
|
|
19
|
+
`mirai.project_technology.contract_version` remains `1.0.0`.
|
|
@@ -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
|
@@ -15,9 +15,12 @@ Release notes must separate:
|
|
|
15
15
|
|
|
16
16
|
## Release Notes
|
|
17
17
|
|
|
18
|
+
- [v1.2.0](1.2.0.md) - portable task-boundary project continuity in Project
|
|
19
|
+
Technology.
|
|
18
20
|
- [v1.1.0](1.1.0.md) - universal sequential context traversal and usage
|
|
19
21
|
verification in Project Technology.
|
|
20
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.
|
|
21
24
|
- [v1.0.0-rc.6](1.0.0-rc.6.md) - anti-drift / quality-control release
|
|
22
25
|
consolidation across Semantic Intent, Dynamic Episode, Goal Vector and
|
|
23
26
|
Technology Quality Feedback.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"contract_version": { "const": "1.0.0" },
|
|
10
10
|
"enabled": { "type": "boolean" },
|
|
11
11
|
"context_policy": { "const": "task_scoped" },
|
|
12
|
-
"source_boundary": { "const": "hybrid_sot" }
|
|
12
|
+
"source_boundary": { "const": "hybrid_sot" },
|
|
13
|
+
"continuity_policy": { "enum": ["task_boundary"] }
|
|
13
14
|
}
|
|
14
15
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mirai Graph Context-Pack Generation
|
|
2
2
|
|
|
3
|
-
Status: 1.
|
|
3
|
+
Status: 1.2 stable standard section
|
|
4
4
|
|
|
5
5
|
## Purpose
|
|
6
6
|
|
|
@@ -56,7 +56,7 @@ task -> select relevant objects -> select relevant relations
|
|
|
56
56
|
-> generate context pack -> review/use for task
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Project Technology
|
|
59
|
+
Project Technology implements this as a deterministic four-phase protocol:
|
|
60
60
|
|
|
61
61
|
```text
|
|
62
62
|
discover -> expand -> compile -> verify
|
|
@@ -168,6 +168,12 @@ Generated context packs may be reviewed and then used as evidence or source
|
|
|
168
168
|
material for graph updates. They do not automatically mutate canonical graph
|
|
169
169
|
state.
|
|
170
170
|
|
|
171
|
+
With `continuity_policy: task_boundary`, accepted continuity objects in
|
|
172
|
+
`graph/specs` participate in the same traversal as every other graph object.
|
|
173
|
+
Relevant previous cases can therefore be found without reading chat history or
|
|
174
|
+
requiring a user to say "continue". Host-local receipts never enter a context
|
|
175
|
+
pack and never become portable authority.
|
|
176
|
+
|
|
171
177
|
## Anti-Patterns
|
|
172
178
|
|
|
173
179
|
- Treating a generated context pack as the graph.
|
package/standard/object-model.md
CHANGED
|
@@ -112,11 +112,17 @@ Initial core kinds:
|
|
|
112
112
|
- `resource`, `source`;
|
|
113
113
|
- `check`, `gate`, `constraint`;
|
|
114
114
|
- `access_requirement`.
|
|
115
|
+
- `project_dna`, `stage`, `batch`;
|
|
116
|
+
- `method`, `lesson`, `regression_case`.
|
|
115
117
|
|
|
116
118
|
The additional domain-neutral kinds support Project Technology traversal. A
|
|
117
119
|
consumer may display them using its own vocabulary without changing their
|
|
118
120
|
canonical meaning.
|
|
119
121
|
|
|
122
|
+
Portable continuity uses these existing domain-neutral kinds. A verified task
|
|
123
|
+
result is evidence plus a regression case; an inferred reusable method remains
|
|
124
|
+
a lesson proposal until its promotion rule is satisfied.
|
|
125
|
+
|
|
120
126
|
## Object Boundary Rules
|
|
121
127
|
|
|
122
128
|
An object should be created when the item needs at least one of:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Project Technology
|
|
2
2
|
|
|
3
|
-
Status: 1.
|
|
3
|
+
Status: 1.3 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,71 @@ 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
|
+
## Immutable Artifact Releases
|
|
23
|
+
|
|
24
|
+
Project Technology can preserve versioned file bundles without turning their
|
|
25
|
+
contents into graph data:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
inspect -> release preview -> transactional release -> compare -> verify
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The generic CLI is:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
mirai-graph technology artifact inspect . --input incoming.zip
|
|
35
|
+
mirai-graph technology artifact release . --input incoming.zip --matter-id agreement-main --direction inbound
|
|
36
|
+
mirai-graph technology artifact release . --input incoming.zip --matter-id agreement-main --direction inbound --apply
|
|
37
|
+
mirai-graph technology artifact compare . --matter-id agreement-main --base-release 20260828-01 --target-release 20260828-02
|
|
38
|
+
mirai-graph technology artifact verify . --matter-id agreement-main --release-id 20260828-02
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The programmatic API exposes `inspectArtifactBundle`,
|
|
42
|
+
`createArtifactRelease`, `compareArtifactReleases` and
|
|
43
|
+
`verifyArtifactRelease`.
|
|
44
|
+
|
|
45
|
+
Each immutable release keeps the original input, a normalized package, a
|
|
46
|
+
hash-bound manifest and a technical comparison. The portable registry at
|
|
47
|
+
`graph/specs/artifact-releases.json` stores only opaque matter/release ids,
|
|
48
|
+
relative or provider refs, lineage, state and digests. Raw files, document
|
|
49
|
+
contents, party names and private discussion remain in the protected artifact
|
|
50
|
+
store.
|
|
51
|
+
|
|
52
|
+
Direct files, directories, ZIP, TAR and TAR.GZ are supported. Unsafe paths,
|
|
53
|
+
links, encrypted archives, executable or macro-enabled files, nested archives,
|
|
54
|
+
normalized path collisions and configured archive limits fail closed. RAR and
|
|
55
|
+
7z need an explicitly supplied safe provider.
|
|
56
|
+
|
|
57
|
+
Creation uses a lease, compare-and-swap registry digest, temporary assembly,
|
|
58
|
+
atomic activation and readback. Repeating the same release returns
|
|
59
|
+
`changed=false`. Domain consumers own the meaning of release states; Mirai
|
|
60
|
+
Graph owns only generic identity, lineage, integrity and technical comparison.
|
|
61
|
+
|
|
62
|
+
## Portable Project Continuity
|
|
63
|
+
|
|
64
|
+
Project Technology preserves verified project experience at task boundaries:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
task context -> verified outcome -> semantic candidate -> graph/specs
|
|
68
|
+
-> next task context -> usage verification
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`graph/specs` is the portable authority. Chat history and local workflow files
|
|
72
|
+
are not required by another person or installation. Mutable receipts and
|
|
73
|
+
rollback data are stored outside the project in a host-local directory keyed by
|
|
74
|
+
`graph.id`.
|
|
75
|
+
|
|
76
|
+
The existing `sync` operation accepts `task_start`, `stage_complete` and
|
|
77
|
+
`task_complete`. Task start is read-only. A completed stage or task requires a
|
|
78
|
+
verified, requirement-bound input and records accepted evidence plus one
|
|
79
|
+
reusable regression case. New goals, architectural decisions and ownership
|
|
80
|
+
changes remain proposals.
|
|
81
|
+
|
|
82
|
+
Two independent verified cases with the same signature may promote a method
|
|
83
|
+
lesson when they do not change architecture. Repeated input is byte-identical
|
|
84
|
+
and returns `changed=false`. Shared folders without Git additionally use a
|
|
85
|
+
lease, compare-and-swap graph digest, backup, atomic write and readback.
|
|
86
|
+
|
|
22
87
|
## Sequential Context Traversal
|
|
23
88
|
|
|
24
89
|
Project Technology exposes one model-independent navigation protocol:
|
|
@@ -59,13 +124,15 @@ The only manifest extension key is `mirai.project_technology`:
|
|
|
59
124
|
"contract_version": "1.0.0",
|
|
60
125
|
"enabled": true,
|
|
61
126
|
"context_policy": "task_scoped",
|
|
62
|
-
"source_boundary": "hybrid_sot"
|
|
127
|
+
"source_boundary": "hybrid_sot",
|
|
128
|
+
"continuity_policy": "task_boundary"
|
|
63
129
|
}
|
|
64
130
|
```
|
|
65
131
|
|
|
66
132
|
Canonical graph objects remain in `graph/specs`. Exact external revisions
|
|
67
|
-
remain in `graph.lock.json`. Mutable
|
|
68
|
-
|
|
133
|
+
remain in `graph.lock.json`. Mutable continuity state is host-local and keyed
|
|
134
|
+
by graph identity; project-local runtime directories are not continuity
|
|
135
|
+
authority.
|
|
69
136
|
|
|
70
137
|
The only generated provider artifact is:
|
|
71
138
|
|
|
@@ -106,6 +173,14 @@ source still answers "how should the domain work be done?".
|
|
|
106
173
|
diagnostic `verify` calls.
|
|
107
174
|
- Context traversal is read-only in every phase and never grants write
|
|
108
175
|
authority.
|
|
176
|
+
- Chat text, secrets, private source, user paths and unverified assumptions are
|
|
177
|
+
rejected from portable continuity.
|
|
178
|
+
- Significant work with an enabled continuity policy fails closed when its
|
|
179
|
+
terminal receipt is missing or stale.
|
|
180
|
+
- Artifact inspection, comparison and verification are read-only. Release
|
|
181
|
+
creation is preview-only without `--apply`.
|
|
182
|
+
- An artifact manifest or generated export never proves legal, business or
|
|
183
|
+
domain acceptance.
|
|
109
184
|
- A context pack is not ready while a required branch, source, access boundary
|
|
110
185
|
or validator is missing, stale, blocked, deprecated, conflicting or
|
|
111
186
|
digest-mismatched.
|