mirai-graph 1.1.0 → 1.2.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 +48 -0
- package/CITATION.cff +2 -2
- package/README.md +15 -3
- package/package.json +3 -2
- package/packages/cli/graph-manifest.js +2 -1
- package/packages/cli/project-technology.js +10 -1
- package/packages/cli/validate-context-traversal.js +60 -0
- package/packages/cli/validate-project-continuity.js +173 -0
- package/packages/cli/validate-project-technology.js +26 -2
- package/packages/project-technology/context-traversal.js +108 -13
- package/packages/project-technology/continuity.js +414 -0
- package/packages/project-technology/index.js +140 -36
- package/profiles/implementation-control/profile.json +3 -1
- package/profiles/project-management/profile.json +8 -1
- package/releases/1.1.1.md +25 -0
- package/releases/1.2.0.md +19 -0
- package/releases/README.md +2 -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 +35 -4
- package/standard/test-suite.md +4 -1
|
@@ -14,15 +14,16 @@ const {
|
|
|
14
14
|
validateManifest,
|
|
15
15
|
} = require("../cli/graph-manifest");
|
|
16
16
|
const traversal = require("./context-traversal");
|
|
17
|
+
const continuity = require("./continuity");
|
|
17
18
|
|
|
18
19
|
const CONTRACT_VERSION = "1.0.0";
|
|
19
20
|
const EXTENSION_KEY = "mirai.project_technology";
|
|
20
21
|
const LEGACY_EXTENSION_KEY = "simai.project_technology";
|
|
21
22
|
const LOCAL_DIR = path.join(".mirai-graph", "project-technology");
|
|
22
23
|
const LEGACY_LOCAL_DIR = path.join(".simai", "project-technology");
|
|
23
|
-
const INVENTORY_FILE =
|
|
24
|
-
const BINDING_FILE =
|
|
25
|
-
const IMPORT_DIR =
|
|
24
|
+
const INVENTORY_FILE = "inventory.json";
|
|
25
|
+
const BINDING_FILE = "target-provider-binding.json";
|
|
26
|
+
const IMPORT_DIR = "provider-exports";
|
|
26
27
|
const EXPORT_FILE = path.join("graph", "generated", "project-technology", "target-provider-export.json");
|
|
27
28
|
const TARGET_DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
|
|
28
29
|
const REVISION_RE = /^[0-9a-f]{40}$/;
|
|
@@ -90,6 +91,58 @@ function normalizeRepo(repo) {
|
|
|
90
91
|
return path.resolve(repo || ".");
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
function runtimeRoot(repo, manifest = null, options = {}) {
|
|
95
|
+
return continuity.stateRoot(repo, manifest || readManifest(repo).manifest, options);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function legacyRuntimeState(repo) {
|
|
99
|
+
const root = path.join(repo, LOCAL_DIR);
|
|
100
|
+
return {
|
|
101
|
+
root,
|
|
102
|
+
present: fs.existsSync(root),
|
|
103
|
+
inventory: path.join(root, INVENTORY_FILE),
|
|
104
|
+
binding: path.join(root, BINDING_FILE),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function migrateProjectLocalRuntime(repo, manifest, root) {
|
|
109
|
+
const legacy = legacyRuntimeState(repo);
|
|
110
|
+
if (!legacy.present) return { changed: false, blockers: [], migration_ref: null };
|
|
111
|
+
const blockers = [];
|
|
112
|
+
let changed = false;
|
|
113
|
+
if (fs.existsSync(legacy.binding)) {
|
|
114
|
+
const binding = readJson(legacy.binding);
|
|
115
|
+
const identity = bindingValues(binding); blockers.push(...identity.blockers);
|
|
116
|
+
const ref = String(binding.provider_export_ref || "");
|
|
117
|
+
const exportPath = ref && !path.isAbsolute(ref) ? path.resolve(repo, ref) : null;
|
|
118
|
+
const exported = exportPath ? readExport(exportPath) : { export: {}, blockers: ["legacy_provider_export_missing"] };
|
|
119
|
+
blockers.push(...exported.blockers);
|
|
120
|
+
for (const key of Object.keys(identity.values)) if (exported.export[key] !== identity.values[key]) blockers.push(`legacy_${key}_mismatch`);
|
|
121
|
+
if (!blockers.length) {
|
|
122
|
+
const exportBytes = canonicalBytes(exported.export);
|
|
123
|
+
const exportSha = sha256(exportBytes);
|
|
124
|
+
changed = atomicWrite(path.join(root, IMPORT_DIR, `${exportSha}.json`), exportBytes) || changed;
|
|
125
|
+
changed = atomicWrite(path.join(root, BINDING_FILE), canonicalBytes({
|
|
126
|
+
schema_version: "1.0.0", ...identity.values,
|
|
127
|
+
provider_export_ref: `host-local://${IMPORT_DIR}/${exportSha}.json`,
|
|
128
|
+
provider_export_sha256: exportSha,
|
|
129
|
+
})) || changed;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (blockers.length) return { changed: false, blockers: [...new Set(blockers)].sort(), migration_ref: null };
|
|
133
|
+
const receipt = {
|
|
134
|
+
schema_version: "1.0.0",
|
|
135
|
+
status: "migrated",
|
|
136
|
+
repository_id: manifest.id,
|
|
137
|
+
source: LOCAL_DIR,
|
|
138
|
+
source_preserved: true,
|
|
139
|
+
active_runtime: "host-local",
|
|
140
|
+
};
|
|
141
|
+
const receiptName = `project-local-runtime-${sha256(canonicalBytes(receipt)).slice(0, 16)}.json`;
|
|
142
|
+
changed = atomicWrite(path.join(root, "migrations", receiptName), canonicalBytes(receipt)) || changed;
|
|
143
|
+
return { changed, blockers: [], migration_ref: `host-local://migrations/${receiptName}` };
|
|
144
|
+
}
|
|
145
|
+
|
|
93
146
|
function readManifest(repo) {
|
|
94
147
|
const manifestPath = path.join(repo, "graph.json");
|
|
95
148
|
if (!fs.existsSync(manifestPath)) return { manifest: null, blockers: ["graph_manifest_missing"] };
|
|
@@ -106,6 +159,7 @@ function extensionContract() {
|
|
|
106
159
|
enabled: true,
|
|
107
160
|
context_policy: "task_scoped",
|
|
108
161
|
source_boundary: "hybrid_sot",
|
|
162
|
+
continuity_policy: "task_boundary",
|
|
109
163
|
};
|
|
110
164
|
}
|
|
111
165
|
|
|
@@ -432,19 +486,23 @@ function context(repoArg, options = {}) {
|
|
|
432
486
|
source_refs: (nodes.get(candidate.id)?.source_refs || []).map((source) => source.ref),
|
|
433
487
|
})),
|
|
434
488
|
runtime_contract: { graph_first_scope: "routing/capability/policy orientation", raw_source_authoritative: true, canonical_write_allowed: false },
|
|
489
|
+
continuity: continuity.status(normalizeRepo(repoArg), readManifest(normalizeRepo(repoArg)).manifest, options),
|
|
435
490
|
};
|
|
436
491
|
}
|
|
437
492
|
|
|
438
|
-
function targetBindingStatus(repo) {
|
|
439
|
-
const
|
|
493
|
+
function targetBindingStatus(repo, options = {}) {
|
|
494
|
+
const manifest = readManifest(repo).manifest;
|
|
495
|
+
const root = runtimeRoot(repo, manifest, options);
|
|
496
|
+
const bindingPath = path.join(root, BINDING_FILE);
|
|
440
497
|
if (!fs.existsSync(bindingPath)) return { status: "not_configured", enabled: false, blockers: [], next_action: "connect an exact target provider binding" };
|
|
441
498
|
let binding;
|
|
442
499
|
try { binding = readJson(bindingPath); } catch (_) { return { status: "blocked", enabled: true, blockers: ["target_binding_invalid"] }; }
|
|
443
500
|
const identity = bindingValues(binding);
|
|
444
501
|
const exportRef = String(binding.provider_export_ref || "");
|
|
445
|
-
const
|
|
502
|
+
const hostPrefix = "host-local://";
|
|
503
|
+
const exportPath = exportRef.startsWith(hostPrefix) ? path.join(root, exportRef.slice(hostPrefix.length)) : null;
|
|
446
504
|
const blockers = [...identity.blockers];
|
|
447
|
-
if (!exportPath || !exportPath.startsWith(`${
|
|
505
|
+
if (!exportPath || !exportPath.startsWith(`${root}${path.sep}`)) blockers.push("provider_export_outside_host_state");
|
|
448
506
|
const exported = exportPath ? readExport(exportPath) : { export: {}, blockers: ["provider_export_missing"] };
|
|
449
507
|
blockers.push(...exported.blockers);
|
|
450
508
|
for (const key of Object.keys(identity.values)) if (exported.export[key] !== identity.values[key]) blockers.push(`${key}_mismatch`);
|
|
@@ -466,18 +524,20 @@ function targetBindingStatus(repo) {
|
|
|
466
524
|
};
|
|
467
525
|
}
|
|
468
526
|
|
|
469
|
-
function status(repoArg) {
|
|
527
|
+
function status(repoArg, options = {}) {
|
|
470
528
|
const repo = normalizeRepo(repoArg);
|
|
471
529
|
const manifestState = readManifest(repo);
|
|
472
530
|
const ext = extensionState(manifestState.manifest);
|
|
473
531
|
let stored = null;
|
|
474
|
-
|
|
532
|
+
const root = runtimeRoot(repo, manifestState.manifest, options);
|
|
533
|
+
try { if (fs.existsSync(path.join(root, INVENTORY_FILE))) stored = readJson(path.join(root, INVENTORY_FILE)); } catch (_) { /* stale */ }
|
|
475
534
|
const current = inventory(repo);
|
|
476
535
|
const freshness = stored && stored.inventory_digest === current.inventory_digest ? "current" : stored ? "stale" : "missing";
|
|
477
536
|
const blockers = [...manifestState.blockers, ...ext.blockers];
|
|
537
|
+
if (!stored && legacyRuntimeState(repo).present) blockers.push("project_technology_host_state_migration_required");
|
|
478
538
|
if (ext.contract && ext.contract.enabled === false) blockers.push("project_technology_disabled");
|
|
479
539
|
if (freshness !== "current") blockers.push(`project_technology_inventory_${freshness}`);
|
|
480
|
-
const binding = targetBindingStatus(repo);
|
|
540
|
+
const binding = targetBindingStatus(repo, options);
|
|
481
541
|
if (binding.status === "blocked") blockers.push(...binding.blockers);
|
|
482
542
|
return result("status", "read_only", blockers.length ? "blocked" : "success", {
|
|
483
543
|
repository_id: manifestState.manifest?.id || path.basename(repo),
|
|
@@ -486,6 +546,7 @@ function status(repoArg) {
|
|
|
486
546
|
technology_freshness: freshness,
|
|
487
547
|
technology_digest: current.inventory_digest,
|
|
488
548
|
target_binding: binding,
|
|
549
|
+
continuity: continuity.status(repo, manifestState.manifest, {}),
|
|
489
550
|
blockers: [...new Set(blockers)].sort(),
|
|
490
551
|
next_action: ext.legacy ? "run enable --apply to migrate the legacy contract" : blockers.length ? "run plan, then the required transactional operation" : "none",
|
|
491
552
|
});
|
|
@@ -502,18 +563,18 @@ function plan(repoArg) {
|
|
|
502
563
|
const state = status(repoArg);
|
|
503
564
|
return result("plan", "read_only", "success", {
|
|
504
565
|
current_status: state.status,
|
|
505
|
-
planned_changes: ["validate_graph_manifest", "migrate_legacy_extension_if_present", "enable_public_contract", "build_safe_inventory", "verify_target_binding"],
|
|
566
|
+
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
567
|
blockers: state.blockers.filter((item) => !["project_technology_not_configured", "project_technology_migration_required", "project_technology_inventory_missing", "project_technology_inventory_stale"].includes(item)),
|
|
507
568
|
next_action: "rerun the selected operation with --apply",
|
|
508
569
|
});
|
|
509
570
|
}
|
|
510
571
|
|
|
511
|
-
function preview(operation, repoArg) {
|
|
512
|
-
const state = status(repoArg);
|
|
572
|
+
function preview(operation, repoArg, options = {}) {
|
|
573
|
+
const state = status(repoArg, options);
|
|
513
574
|
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
575
|
}
|
|
515
576
|
|
|
516
|
-
function enable(repoArg) {
|
|
577
|
+
function enable(repoArg, options = {}) {
|
|
517
578
|
const repo = normalizeRepo(repoArg);
|
|
518
579
|
const manifestState = readManifest(repo);
|
|
519
580
|
if (!manifestState.manifest || manifestState.blockers.length) return result("enable", "transactional", "fail", { blockers: manifestState.blockers, next_action: "repair graph.json" });
|
|
@@ -523,7 +584,13 @@ function enable(repoArg) {
|
|
|
523
584
|
delete extensions[LEGACY_EXTENSION_KEY];
|
|
524
585
|
extensions[EXTENSION_KEY] = extensionContract();
|
|
525
586
|
manifest.extensions = extensions;
|
|
526
|
-
const
|
|
587
|
+
const root = runtimeRoot(repo, manifest, options);
|
|
588
|
+
const runtimeMigration = migrateProjectLocalRuntime(repo, manifest, root);
|
|
589
|
+
if (runtimeMigration.blockers.length) return result("enable", "transactional", "fail", {
|
|
590
|
+
blockers: runtimeMigration.blockers,
|
|
591
|
+
next_action: "repair the project-local runtime before host-state migration",
|
|
592
|
+
});
|
|
593
|
+
const migrationRoot = path.join(root, "migrations", sha256(canonicalBytes({ legacy: legacy || null, head: git(repo, "rev-parse", "HEAD") })).slice(0, 16));
|
|
527
594
|
fs.mkdirSync(migrationRoot, { recursive: true });
|
|
528
595
|
atomicWrite(path.join(migrationRoot, "graph.json.before"), fs.readFileSync(path.join(repo, "graph.json")));
|
|
529
596
|
if (legacy) atomicWrite(path.join(migrationRoot, "legacy-extension.json"), canonicalBytes(legacy));
|
|
@@ -531,25 +598,52 @@ function enable(repoArg) {
|
|
|
531
598
|
if (fs.existsSync(legacyLocal)) atomicWrite(path.join(migrationRoot, "legacy-local-state.json"), canonicalBytes({ status: "preserved_for_rollback", path: LEGACY_LOCAL_DIR }));
|
|
532
599
|
const manifestChanged = atomicWrite(path.join(repo, "graph.json"), canonicalBytes(manifest));
|
|
533
600
|
const inv = inventory(repo);
|
|
534
|
-
const inventoryChanged = atomicWrite(path.join(
|
|
601
|
+
const inventoryChanged = atomicWrite(path.join(root, INVENTORY_FILE), canonicalBytes(inv));
|
|
535
602
|
const verified = readManifest(repo);
|
|
536
603
|
if (verified.blockers.length || extensionState(verified.manifest).blockers.length) {
|
|
537
604
|
fs.copyFileSync(path.join(migrationRoot, "graph.json.before"), path.join(repo, "graph.json"));
|
|
538
605
|
return result("enable", "transactional", "fail", { blockers: ["project_technology_enable_rolled_back", ...verified.blockers], next_action: "inspect the rollback receipt" });
|
|
539
606
|
}
|
|
540
|
-
return result("enable", "transactional", "success", {
|
|
607
|
+
return result("enable", "transactional", "success", {
|
|
608
|
+
changed: manifestChanged || inventoryChanged || runtimeMigration.changed,
|
|
609
|
+
migration_ref: runtimeMigration.migration_ref || `host-local://migrations/${path.basename(migrationRoot)}`,
|
|
610
|
+
technology_digest: inv.inventory_digest,
|
|
611
|
+
});
|
|
541
612
|
}
|
|
542
613
|
|
|
543
|
-
function sync(repoArg) {
|
|
614
|
+
function sync(repoArg, options = {}) {
|
|
544
615
|
const repo = normalizeRepo(repoArg);
|
|
545
616
|
const manifestState = readManifest(repo);
|
|
546
617
|
const ext = extensionState(manifestState.manifest);
|
|
547
618
|
const blockers = [...manifestState.blockers, ...ext.blockers];
|
|
619
|
+
const hostRoot = runtimeRoot(repo, manifestState.manifest, options);
|
|
620
|
+
if (legacyRuntimeState(repo).present && !fs.existsSync(path.join(hostRoot, INVENTORY_FILE))) blockers.push("project_technology_host_state_migration_required");
|
|
548
621
|
if (!ext.contract || ext.legacy || ext.contract.enabled !== true) blockers.push("project_technology_not_enabled");
|
|
549
622
|
if (blockers.length) return result("sync", "transactional", "fail", { blockers: [...new Set(blockers)].sort(), next_action: "enable or repair Project Technology" });
|
|
623
|
+
let continuityResult = null;
|
|
624
|
+
if (options.boundary) {
|
|
625
|
+
continuityResult = continuity.sync(repo, manifestState.manifest, options.boundary, options.continuityEvidence, options);
|
|
626
|
+
if (continuityResult.status !== "success") return result("sync", "transactional", continuityResult.status, {
|
|
627
|
+
blockers: continuityResult.blockers,
|
|
628
|
+
continuity: continuityResult.continuity || null,
|
|
629
|
+
next_action: "repair the continuity evidence or reconcile the graph state",
|
|
630
|
+
});
|
|
631
|
+
if (options.boundary === "task_start") return result("sync", "read_only", "success", {
|
|
632
|
+
changed: false,
|
|
633
|
+
technology_digest: inventory(repo).inventory_digest,
|
|
634
|
+
target_binding: targetBindingStatus(repo, options),
|
|
635
|
+
continuity: continuityResult.continuity,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
550
638
|
const inv = inventory(repo);
|
|
551
|
-
const
|
|
552
|
-
|
|
639
|
+
const currentManifest = readManifest(repo).manifest;
|
|
640
|
+
const changed = atomicWrite(path.join(runtimeRoot(repo, currentManifest || manifestState.manifest, options), INVENTORY_FILE), canonicalBytes(inv));
|
|
641
|
+
return result("sync", "transactional", "success", {
|
|
642
|
+
changed: changed || Boolean(continuityResult?.changed),
|
|
643
|
+
technology_digest: inv.inventory_digest,
|
|
644
|
+
target_binding: targetBindingStatus(repo, options),
|
|
645
|
+
continuity: continuityResult?.continuity || continuity.status(repo, currentManifest, options),
|
|
646
|
+
});
|
|
553
647
|
}
|
|
554
648
|
|
|
555
649
|
function provide(repoArg, options = {}) {
|
|
@@ -588,7 +682,8 @@ function connect(repoArg, options = {}) {
|
|
|
588
682
|
const providerRoot = providerRootFor(source);
|
|
589
683
|
if (!providerRoot) blockers.push("provider_revision_order_unverifiable");
|
|
590
684
|
else if (git(providerRoot, "rev-parse", "HEAD").toLowerCase() !== identity.values.provider_revision) blockers.push("provider_revision_does_not_match_head");
|
|
591
|
-
const
|
|
685
|
+
const root = runtimeRoot(repo, manifestState.manifest, options);
|
|
686
|
+
const currentPath = path.join(root, BINDING_FILE);
|
|
592
687
|
let current = null;
|
|
593
688
|
try { if (fs.existsSync(currentPath)) current = readJson(currentPath); } catch (_) { blockers.push("current_target_binding_invalid"); }
|
|
594
689
|
if (current) {
|
|
@@ -596,14 +691,14 @@ function connect(repoArg, options = {}) {
|
|
|
596
691
|
const same = Object.keys(identity.values).every((key) => current[key] === identity.values[key]);
|
|
597
692
|
if (!same) blockers.push("target_provider_conflict", "target_provider_refresh_required");
|
|
598
693
|
else {
|
|
599
|
-
const currentState = targetBindingStatus(repo);
|
|
694
|
+
const currentState = targetBindingStatus(repo, options);
|
|
600
695
|
if (currentState.status !== "ready") blockers.push(...currentState.blockers);
|
|
601
696
|
if (currentState.execution_contract_digest !== read.export.execution_contract_digest) blockers.push("target_provider_conflict", "provider_execution_contract_mismatch");
|
|
602
697
|
}
|
|
603
698
|
} else {
|
|
604
699
|
if (current.target_id !== identity.values.target_id) blockers.push("target_provider_target_mismatch");
|
|
605
700
|
if (current.semantic_digest !== identity.values.semantic_digest) blockers.push("target_provider_semantic_mismatch");
|
|
606
|
-
const currentState = targetBindingStatus(repo);
|
|
701
|
+
const currentState = targetBindingStatus(repo, options);
|
|
607
702
|
if (currentState.status !== "ready") blockers.push(...currentState.blockers);
|
|
608
703
|
if (currentState.execution_contract_digest !== read.export.execution_contract_digest) blockers.push("provider_execution_contract_refresh_mismatch");
|
|
609
704
|
if (!providerRoot) blockers.push("provider_revision_order_unverifiable");
|
|
@@ -618,8 +713,8 @@ function connect(repoArg, options = {}) {
|
|
|
618
713
|
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
714
|
const exportBytes = canonicalBytes(read.export);
|
|
620
715
|
const exportSha = sha256(exportBytes);
|
|
621
|
-
const imported = path.join(
|
|
622
|
-
const binding = { schema_version: "1.0.0", ...identity.values, provider_export_ref:
|
|
716
|
+
const imported = path.join(root, IMPORT_DIR, `${exportSha}.json`);
|
|
717
|
+
const binding = { schema_version: "1.0.0", ...identity.values, provider_export_ref: `host-local://${IMPORT_DIR}/${exportSha}.json`, provider_export_sha256: exportSha };
|
|
623
718
|
const transaction = fs.mkdtempSync(path.join(os.tmpdir(), "mirai-project-technology-"));
|
|
624
719
|
const bindingBefore = fs.existsSync(currentPath) ? fs.readFileSync(currentPath) : null;
|
|
625
720
|
const importBefore = fs.existsSync(imported) ? fs.readFileSync(imported) : null;
|
|
@@ -627,7 +722,7 @@ function connect(repoArg, options = {}) {
|
|
|
627
722
|
const exportChanged = atomicWrite(imported, exportBytes);
|
|
628
723
|
const bindingChanged = atomicWrite(currentPath, canonicalBytes(binding));
|
|
629
724
|
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) });
|
|
725
|
+
return result("connect", "transactional", "success", { changed: exportChanged || bindingChanged, refresh_binding: Boolean(options.refreshBinding), target_binding: targetBindingStatus(repo, options) });
|
|
631
726
|
} catch (_) {
|
|
632
727
|
if (bindingBefore) atomicWrite(currentPath, bindingBefore); else if (fs.existsSync(currentPath)) fs.unlinkSync(currentPath);
|
|
633
728
|
if (importBefore) atomicWrite(imported, importBefore); else if (fs.existsSync(imported)) fs.unlinkSync(imported);
|
|
@@ -636,14 +731,15 @@ function connect(repoArg, options = {}) {
|
|
|
636
731
|
}
|
|
637
732
|
}
|
|
638
733
|
|
|
639
|
-
function disconnect(repoArg) {
|
|
734
|
+
function disconnect(repoArg, options = {}) {
|
|
640
735
|
const repo = normalizeRepo(repoArg);
|
|
641
|
-
const
|
|
736
|
+
const root = runtimeRoot(repo, readManifest(repo).manifest, options);
|
|
737
|
+
const bindingPath = path.join(root, BINDING_FILE);
|
|
642
738
|
if (!fs.existsSync(bindingPath)) return result("disconnect", "transactional", "success");
|
|
643
|
-
const backup = path.join(
|
|
739
|
+
const backup = path.join(root, "rollback", `binding-${sha256(fs.readFileSync(bindingPath)).slice(0, 16)}.json`);
|
|
644
740
|
atomicWrite(backup, fs.readFileSync(bindingPath));
|
|
645
741
|
fs.unlinkSync(bindingPath);
|
|
646
|
-
return result("disconnect", "transactional", "success", { changed: true, rollback_ref: path.
|
|
742
|
+
return result("disconnect", "transactional", "success", { changed: true, rollback_ref: `host-local://rollback/${path.basename(backup)}` });
|
|
647
743
|
}
|
|
648
744
|
|
|
649
745
|
function disable(repoArg) {
|
|
@@ -658,19 +754,25 @@ function disable(repoArg) {
|
|
|
658
754
|
return result("disable", "transactional", "success", { changed });
|
|
659
755
|
}
|
|
660
756
|
|
|
661
|
-
function repair(repoArg) {
|
|
757
|
+
function repair(repoArg, options = {}) {
|
|
662
758
|
const repo = normalizeRepo(repoArg);
|
|
663
759
|
const manifestState = readManifest(repo);
|
|
664
760
|
if (!manifestState.manifest || manifestState.blockers.length) return result("repair", "transactional", "fail", { blockers: manifestState.blockers, next_action: "repair graph.json before Project Technology" });
|
|
665
761
|
const ext = extensionState(manifestState.manifest);
|
|
666
|
-
if (!ext.contract || ext.legacy) return enable(repo);
|
|
667
|
-
if (ext.blockers.length) return enable(repo);
|
|
668
|
-
|
|
762
|
+
if (!ext.contract || ext.legacy) return enable(repo, options);
|
|
763
|
+
if (ext.blockers.length) return enable(repo, options);
|
|
764
|
+
const root = runtimeRoot(repo, manifestState.manifest, options);
|
|
765
|
+
if (legacyRuntimeState(repo).present && !fs.existsSync(path.join(root, INVENTORY_FILE))) return enable(repo, options);
|
|
766
|
+
return sync(repo, options);
|
|
669
767
|
}
|
|
670
768
|
|
|
671
769
|
function verify(repoArg, options = {}) {
|
|
672
|
-
const state = status(repoArg);
|
|
770
|
+
const state = status(repoArg, options);
|
|
673
771
|
const blockers = [...state.blockers];
|
|
772
|
+
const repo = normalizeRepo(repoArg);
|
|
773
|
+
const manifest = readManifest(repo).manifest;
|
|
774
|
+
const continuityResult = continuity.verify(repo, manifest, options);
|
|
775
|
+
if (continuity.continuityPolicy(manifest) === "task_boundary" || options.receiptDigest || options.significantWork) blockers.push(...continuityResult.blockers);
|
|
674
776
|
if (options.significantWork && state.target_binding.status !== "ready") blockers.push("accepted_target_binding_required_for_significant_work");
|
|
675
777
|
return result("verify", "read_only", blockers.length ? "blocked" : "success", {
|
|
676
778
|
repository_id: state.repository_id,
|
|
@@ -679,6 +781,7 @@ function verify(repoArg, options = {}) {
|
|
|
679
781
|
technology_freshness: state.technology_freshness,
|
|
680
782
|
technology_digest: state.technology_digest,
|
|
681
783
|
target_binding: state.target_binding,
|
|
784
|
+
continuity: continuityResult.continuity,
|
|
682
785
|
blockers: [...new Set(blockers)].sort(),
|
|
683
786
|
next_action: blockers.length ? "repair the listed Project Technology blockers" : "none",
|
|
684
787
|
});
|
|
@@ -689,7 +792,7 @@ function execute(operation, repoArg, options = {}) {
|
|
|
689
792
|
if (readOnly[operation]) return readOnly[operation](repoArg, options);
|
|
690
793
|
const transactional = { enable, sync, connect, disconnect, provide, disable, repair };
|
|
691
794
|
if (!transactional[operation]) return result(operation, "read_only", "fail", { blockers: ["unsupported_project_technology_operation"] });
|
|
692
|
-
if (!options.apply) return preview(operation, repoArg);
|
|
795
|
+
if (!options.apply) return preview(operation, repoArg, options);
|
|
693
796
|
return transactional[operation](repoArg, options);
|
|
694
797
|
}
|
|
695
798
|
|
|
@@ -712,6 +815,7 @@ module.exports = {
|
|
|
712
815
|
extensionContract,
|
|
713
816
|
inventory,
|
|
714
817
|
normalizeExecutionContract,
|
|
818
|
+
continuity,
|
|
715
819
|
plan,
|
|
716
820
|
provide,
|
|
717
821
|
readExport,
|
|
@@ -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,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.
|
|
@@ -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`.
|
package/releases/README.md
CHANGED
|
@@ -15,6 +15,8 @@ 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.
|
|
@@ -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.2 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,31 @@ 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
|
+
## Portable Project Continuity
|
|
23
|
+
|
|
24
|
+
Project Technology preserves verified project experience at task boundaries:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
task context -> verified outcome -> semantic candidate -> graph/specs
|
|
28
|
+
-> next task context -> usage verification
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`graph/specs` is the portable authority. Chat history and local workflow files
|
|
32
|
+
are not required by another person or installation. Mutable receipts and
|
|
33
|
+
rollback data are stored outside the project in a host-local directory keyed by
|
|
34
|
+
`graph.id`.
|
|
35
|
+
|
|
36
|
+
The existing `sync` operation accepts `task_start`, `stage_complete` and
|
|
37
|
+
`task_complete`. Task start is read-only. A completed stage or task requires a
|
|
38
|
+
verified, requirement-bound input and records accepted evidence plus one
|
|
39
|
+
reusable regression case. New goals, architectural decisions and ownership
|
|
40
|
+
changes remain proposals.
|
|
41
|
+
|
|
42
|
+
Two independent verified cases with the same signature may promote a method
|
|
43
|
+
lesson when they do not change architecture. Repeated input is byte-identical
|
|
44
|
+
and returns `changed=false`. Shared folders without Git additionally use a
|
|
45
|
+
lease, compare-and-swap graph digest, backup, atomic write and readback.
|
|
46
|
+
|
|
22
47
|
## Sequential Context Traversal
|
|
23
48
|
|
|
24
49
|
Project Technology exposes one model-independent navigation protocol:
|
|
@@ -59,13 +84,15 @@ The only manifest extension key is `mirai.project_technology`:
|
|
|
59
84
|
"contract_version": "1.0.0",
|
|
60
85
|
"enabled": true,
|
|
61
86
|
"context_policy": "task_scoped",
|
|
62
|
-
"source_boundary": "hybrid_sot"
|
|
87
|
+
"source_boundary": "hybrid_sot",
|
|
88
|
+
"continuity_policy": "task_boundary"
|
|
63
89
|
}
|
|
64
90
|
```
|
|
65
91
|
|
|
66
92
|
Canonical graph objects remain in `graph/specs`. Exact external revisions
|
|
67
|
-
remain in `graph.lock.json`. Mutable
|
|
68
|
-
|
|
93
|
+
remain in `graph.lock.json`. Mutable continuity state is host-local and keyed
|
|
94
|
+
by graph identity; project-local runtime directories are not continuity
|
|
95
|
+
authority.
|
|
69
96
|
|
|
70
97
|
The only generated provider artifact is:
|
|
71
98
|
|
|
@@ -106,6 +133,10 @@ source still answers "how should the domain work be done?".
|
|
|
106
133
|
diagnostic `verify` calls.
|
|
107
134
|
- Context traversal is read-only in every phase and never grants write
|
|
108
135
|
authority.
|
|
136
|
+
- Chat text, secrets, private source, user paths and unverified assumptions are
|
|
137
|
+
rejected from portable continuity.
|
|
138
|
+
- Significant work with an enabled continuity policy fails closed when its
|
|
139
|
+
terminal receipt is missing or stale.
|
|
109
140
|
- A context pack is not ready while a required branch, source, access boundary
|
|
110
141
|
or validator is missing, stale, blocked, deprecated, conflicting or
|
|
111
142
|
digest-mismatched.
|
package/standard/test-suite.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mirai Graph Test Suite
|
|
2
2
|
|
|
3
|
-
Status: 1.
|
|
3
|
+
Status: 1.2 stable standard section
|
|
4
4
|
|
|
5
5
|
## Purpose
|
|
6
6
|
|
|
@@ -36,6 +36,9 @@ Current checks:
|
|
|
36
36
|
research, organization, platform, skill-like and multi-repository scopes;
|
|
37
37
|
- reject incomplete mandatory closure, stale nodes, conflicts, cycles,
|
|
38
38
|
tampered digests, secret-like evidence and context-budget shortcuts.
|
|
39
|
+
- verify portable continuity for Git and non-Git projects, two independent
|
|
40
|
+
installations, idempotent repeats, stale receipts, CAS conflicts, secret
|
|
41
|
+
rejection, bounded proposals and two-case method promotion.
|
|
39
42
|
|
|
40
43
|
## Structural Validation
|
|
41
44
|
|