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.
@@ -26,6 +26,9 @@ jobs:
26
26
  - name: Verify deterministic context traversal
27
27
  run: node packages/cli/validate-context-traversal.js
28
28
 
29
+ - name: Verify portable project continuity
30
+ run: node packages/cli/validate-project-continuity.js
31
+
29
32
  release-check:
30
33
  runs-on: ubuntu-latest
31
34
 
package/CHANGELOG.md CHANGED
@@ -4,6 +4,54 @@ All notable changes to Mirai Graph will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.2.0] - 2026-08-27
8
+
9
+ ### Added
10
+
11
+ - Portable project continuity in the existing Project Technology `sync`,
12
+ `context` and `verify` operations.
13
+ - Task-boundary evidence compaction into accepted facts, evidence, reusable
14
+ regression cases and bounded proposals in `graph/specs`.
15
+ - Host-local receipts keyed by graph identity, with lease, compare-and-swap,
16
+ backup, atomic write, readback and rollback support for shared non-Git folders.
17
+ - Automatic discovery of relevant previous cases through the existing context
18
+ traversal protocol.
19
+
20
+ ### Safety Boundary
21
+
22
+ - Chat transcripts, secrets, private source, user paths and unverified claims
23
+ are not portable project memory.
24
+ - New goals, architecture changes and ownership changes remain proposals.
25
+ - Generated human context is a projection only and never authorizes writes.
26
+ - The public `graph.json` schema and Project Technology activation contract
27
+ remain `2.0.0` and `1.0.0` respectively.
28
+
29
+ ### Verification
30
+
31
+ - New fixtures cover Git and non-Git projects, two-installation portability,
32
+ idempotence, stale receipts, concurrent-write protection, secret rejection,
33
+ proposal boundaries and two-case method promotion.
34
+
35
+ ## [1.1.1] - 2026-08-26
36
+
37
+ ### Fixed
38
+
39
+ - Sequential context traversal now reads the accepted legacy aliases used by
40
+ existing `graph.json` 2.0.0 repositories (`type`, `from`, `to`,
41
+ `relation_type` and `status`) without requiring a graph rewrite.
42
+ - Established structural and required relation names are projected onto the
43
+ public traversal vocabulary while preserving their direction and mandatory
44
+ closure semantics.
45
+ - Existing accepted Mirai readiness levels and revision-bound objects without
46
+ an explicit historical readiness field remain usable; draft, seed, gap,
47
+ blocked, stale and deprecated states still fail closed.
48
+
49
+ ### Compatibility
50
+
51
+ - This patch makes the 1.1.0 compatibility promise executable. It does not
52
+ change schema `2.0.0`, the Project Technology activation contract, provider
53
+ transport or write boundaries.
54
+
7
55
  ## [1.1.0] - 2026-08-26
8
56
 
9
57
  ### Added
package/CITATION.cff CHANGED
@@ -17,5 +17,5 @@ keywords:
17
17
  - complex systems
18
18
  - context engineering
19
19
  - systems engineering
20
- version: "1.1.0"
21
- date-released: "2026-08-26"
20
+ version: "1.2.0"
21
+ date-released: "2026-08-27"
package/README.md CHANGED
@@ -10,7 +10,7 @@ Use it when a software project has useful knowledge spread across README files,
10
10
  docs, issues, chats, code comments and AI prompts, and you need a small
11
11
  machine-readable map that developers and AI assistants can validate and reuse.
12
12
 
13
- Status: stable public `1.1.0` release.
13
+ Status: stable public `1.2.0` release.
14
14
 
15
15
  ## What Problem Does It Solve?
16
16
 
@@ -42,7 +42,7 @@ Start with a normal repository and use Mirai Graph to:
42
42
 
43
43
  ## Current Installation Status
44
44
 
45
- The repository is ready as the stable `mirai-graph@1.1.0` package. To verify a
45
+ The repository provides the stable `mirai-graph@1.2.0` package. To verify a
46
46
  repository checkout directly, use:
47
47
 
48
48
  ```bash
@@ -96,7 +96,8 @@ Enable it in the existing root manifest:
96
96
  "contract_version": "1.0.0",
97
97
  "enabled": true,
98
98
  "context_policy": "task_scoped",
99
- "source_boundary": "hybrid_sot"
99
+ "source_boundary": "hybrid_sot",
100
+ "continuity_policy": "task_boundary"
100
101
  }
101
102
  }
102
103
  }
@@ -130,6 +131,17 @@ The programmatic API exposes the same operations as `discoverContext`,
130
131
  `expandContext`, `compileContext` and `verifyContext`. Mirai Graph does not call
131
132
  or depend on a particular AI model.
132
133
 
134
+ Verified project experience can be saved without storing a chat transcript:
135
+
136
+ ```bash
137
+ mirai-graph technology sync . --boundary task_complete --evidence continuity-evidence.json
138
+ mirai-graph technology sync . --boundary task_complete --evidence continuity-evidence.json --apply
139
+ ```
140
+
141
+ Accepted facts and reusable cases live in `graph/specs`; mutable receipts and
142
+ rollback data stay host-local. The same contract works in Git repositories and
143
+ ordinary shared folders.
144
+
133
145
  Changing operations return a preview unless `--apply` is explicit. See
134
146
  [Project Technology](standard/project-technology.md) for provider/consumer
135
147
  binding, local state and safety boundaries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirai-graph",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "description": "An evolutionary graph operating model for managing the growth of complex systems.",
6
6
  "license": "MIT",
@@ -13,9 +13,10 @@
13
13
  "./project-technology": "./packages/project-technology/index.js"
14
14
  },
15
15
  "scripts": {
16
- "pretest": "npm run test:project-technology && npm run test:context-traversal && npm run validate:project-technology-schemas",
16
+ "pretest": "npm run test:project-technology && npm run test:context-traversal && npm run test:project-continuity && npm run validate:project-technology-schemas",
17
17
  "test:project-technology": "node packages/cli/validate-project-technology.js",
18
18
  "test:context-traversal": "node packages/cli/validate-context-traversal.js",
19
+ "test:project-continuity": "node packages/cli/validate-project-continuity.js",
19
20
  "validate:project-technology-schemas": "node -e \"const fs=require('fs'); for (const file of ['project-technology-extension.schema.json','project-technology-target-export.schema.json','context-traversal-receipt.schema.json','context-pack.schema.json']) JSON.parse(fs.readFileSync('schemas/'+file))\"",
20
21
  "validate:graph-manifest": "node packages/cli/validate-graph-manifest-migration.js",
21
22
  "validate:minimal": "node packages/cli/validate-mirai-graph.js examples/minimal-graph",
@@ -130,7 +130,8 @@ function validateManifest(manifest, repo = null) {
130
130
  };
131
131
  for (const field of Object.keys(expected)) if (technology[field] !== expected[field]) errors.push(`extensions.mirai.project_technology.${field} is invalid`);
132
132
  if (typeof technology.enabled !== "boolean") errors.push("extensions.mirai.project_technology.enabled is invalid");
133
- for (const field of Object.keys(technology)) if (!(field in expected) && field !== "enabled") errors.push(`extensions.mirai.project_technology has unknown field ${field}`);
133
+ if (technology.continuity_policy !== undefined && technology.continuity_policy !== "task_boundary") errors.push("extensions.mirai.project_technology.continuity_policy is invalid");
134
+ for (const field of Object.keys(technology)) if (!(field in expected) && !["enabled", "continuity_policy"].includes(field)) errors.push(`extensions.mirai.project_technology has unknown field ${field}`);
134
135
  }
135
136
  }
136
137
  if (manifest.graph !== null) {
@@ -13,6 +13,8 @@ function usage() {
13
13
  console.error("Options: --apply --task <text> --source <file> --target-id <id>");
14
14
  console.error(" --semantic-digest <sha256:...> --provider-revision <sha>");
15
15
  console.error(" --refresh-binding --significant-work --max-objects <count>");
16
+ console.error("Continuity: sync --boundary task_start|stage_complete|task_complete [--evidence <json>] [--expected-graph-digest <sha256:...>]");
17
+ console.error(" verify --significant-work [--receipt-digest <sha256:...>]");
16
18
  console.error("Context: --phase discover --task <text>");
17
19
  console.error(" --phase expand --input <receipt.json> --select <id> [--select <id>]");
18
20
  console.error(" --phase compile --input <receipt.json> --selection <selection.json>");
@@ -25,7 +27,7 @@ function parse(argv) {
25
27
  let repository = ".";
26
28
  if (argv[0] && !argv[0].startsWith("--")) repository = argv.shift();
27
29
  const options = {};
28
- const values = new Set(["--task", "--source", "--target-id", "--semantic-digest", "--provider-revision", "--max-objects", "--phase", "--input", "--select", "--selection", "--packet", "--evidence", "--selector", "--reason", "--confidence", "--context-budget"]);
30
+ const values = new Set(["--task", "--source", "--target-id", "--semantic-digest", "--provider-revision", "--max-objects", "--phase", "--input", "--select", "--selection", "--packet", "--evidence", "--selector", "--reason", "--confidence", "--context-budget", "--boundary", "--expected-graph-digest", "--receipt-digest", "--state-root"]);
29
31
  const flags = new Set(["--apply", "--refresh-binding", "--significant-work"]);
30
32
  const names = {
31
33
  "--task": "task", "--source": "source", "--target-id": "targetId",
@@ -36,6 +38,8 @@ function parse(argv) {
36
38
  "--selection": "selectionFile", "--packet": "packetFile", "--evidence": "evidenceFile",
37
39
  "--selector": "selector", "--reason": "reason", "--confidence": "confidence",
38
40
  "--context-budget": "contextBudget",
41
+ "--boundary": "boundary", "--expected-graph-digest": "expectedGraphDigest",
42
+ "--receipt-digest": "receiptDigest", "--state-root": "stateRoot",
39
43
  };
40
44
  while (argv.length) {
41
45
  const token = argv.shift();
@@ -57,6 +61,11 @@ function readJsonFile(file) {
57
61
  }
58
62
 
59
63
  function hydrateContextOptions(request) {
64
+ if (request.operation === "sync" && request.options.boundary) {
65
+ if (request.options.boundary !== "task_start" && !request.options.evidenceFile) throw new Error(`${request.options.boundary} requires --evidence`);
66
+ if (request.options.evidenceFile) request.options.continuityEvidence = readJsonFile(request.options.evidenceFile);
67
+ return;
68
+ }
60
69
  if (request.operation !== "context" || !request.options.phase) return;
61
70
  const phase = request.options.phase;
62
71
  if (!new Set(["discover", "expand", "compile", "verify"]).has(phase)) throw new Error(`unsupported context phase ${phase}`);
@@ -73,6 +73,44 @@ function createFixture(root, name, scope = "repository", mutation = null) {
73
73
  return repo;
74
74
  }
75
75
 
76
+ function createLegacyFixture(root) {
77
+ const repo = createFixture(root, "legacy-skill-graph", "skill");
78
+ const objectFile = path.join(repo, "graph/specs/objects.json");
79
+ const relationFile = path.join(repo, "graph/specs/relations.json");
80
+ const objects = JSON.parse(fs.readFileSync(objectFile, "utf8")).map((item) => {
81
+ const legacy = { ...item, type: item.kind };
82
+ delete legacy.kind;
83
+ delete legacy.readiness;
84
+ return legacy;
85
+ });
86
+ const aliases = {
87
+ contains: "implements",
88
+ governed_by: "governed_by",
89
+ validated_by: "validates",
90
+ documented_by: "evidenced_by",
91
+ };
92
+ const relations = JSON.parse(fs.readFileSync(relationFile, "utf8")).map((item) => {
93
+ const legacyType = aliases[item.type] || item.type;
94
+ const reverse = item.type === "validated_by";
95
+ const legacy = {
96
+ ...item,
97
+ relation_type: legacyType,
98
+ from: reverse ? item.target : item.source,
99
+ to: reverse ? item.source : item.target,
100
+ status: item.readiness,
101
+ };
102
+ delete legacy.type;
103
+ delete legacy.source;
104
+ delete legacy.target;
105
+ delete legacy.readiness;
106
+ return legacy;
107
+ });
108
+ writeJson(objectFile, objects);
109
+ writeJson(relationFile, relations);
110
+ git(repo, "add", "."); git(repo, "commit", "-qm", "legacy graph aliases");
111
+ return repo;
112
+ }
113
+
76
114
  function selection(receipt) {
77
115
  return {
78
116
  selector: "ai", task_digest: receipt.task.digest, graph_digest: receipt.graph.digest,
@@ -110,6 +148,28 @@ try {
110
148
  check(`universal_scope_${scope}`, discovered.status === "success" && discovered.traversal_receipt.repository_id === name, discovered.blockers);
111
149
  }
112
150
 
151
+ const legacyRepo = createLegacyFixture(root);
152
+ let legacyReceipt = technology.discoverContext(legacyRepo, "build application delivery safely").traversal_receipt;
153
+ for (const ids of [["capability.delivery"], ["process.delivery"], ["resource.delivery_guide", "constraint.safety", "check.delivery"], ["resource.shared"]]) {
154
+ legacyReceipt = technology.expandContext(legacyRepo, legacyReceipt, ids, { selector: "ai", reason: "legacy graph compatibility" }).traversal_receipt;
155
+ }
156
+ const legacyCompiled = technology.compileContext(legacyRepo, legacyReceipt, selection(legacyReceipt));
157
+ check("legacy_graph_2_aliases_compile", legacyCompiled.status === "ready", legacyCompiled.blockers);
158
+ check("legacy_relation_semantics_preserved", legacyCompiled.context_pack.required_closure.object_ids.includes("constraint.safety") && legacyCompiled.context_pack.validators.includes("check.delivery"), legacyCompiled.context_pack);
159
+ const legacyReadinessRepo = createFixture(root, "legacy-readiness", "skill", (objects) => {
160
+ objects.find((item) => item.id === "capability.delivery").readiness = "R3_structured";
161
+ });
162
+ let legacyReadinessReceipt = technology.discoverContext(legacyReadinessRepo, "build application delivery safely").traversal_receipt;
163
+ for (const ids of [["capability.delivery"], ["process.delivery"], ["resource.delivery_guide", "constraint.safety", "check.delivery"], ["resource.shared"]]) {
164
+ legacyReadinessReceipt = technology.expandContext(legacyReadinessRepo, legacyReadinessReceipt, ids).traversal_receipt;
165
+ }
166
+ check("legacy_ready_level_compiles", technology.compileContext(legacyReadinessRepo, legacyReadinessReceipt, selection(legacyReadinessReceipt)).status === "ready");
167
+ const legacyDraftRepo = createFixture(root, "legacy-draft-readiness", "skill", (objects) => {
168
+ objects.find((item) => item.id === "capability.delivery").readiness = "R2_seed";
169
+ });
170
+ const legacyDraftReceipt = technology.discoverContext(legacyDraftRepo, "build application delivery safely").traversal_receipt;
171
+ check("legacy_draft_level_remains_blocked", technology.compileContext(legacyDraftRepo, legacyDraftReceipt, selection(legacyDraftReceipt)).status !== "ready");
172
+
113
173
  const before = git(repo, "status", "--porcelain=v1");
114
174
  const discovered = technology.discoverContext(repo, "build application delivery safely", { maxCandidates: 2 });
115
175
  check("discover_is_bounded", discovered.status === "success" && discovered.traversal_receipt.candidates.length <= 2, discovered);
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const assert = require("assert");
6
+ const fs = require("fs");
7
+ const os = require("os");
8
+ const path = require("path");
9
+ const { spawnSync } = require("child_process");
10
+ const technology = require("../project-technology");
11
+
12
+ function writeJson(file, value) {
13
+ fs.mkdirSync(path.dirname(file), { recursive: true });
14
+ fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
15
+ }
16
+
17
+ function git(repo, ...args) {
18
+ const run = spawnSync("git", args, { cwd: repo, encoding: "utf8" });
19
+ if (run.status !== 0) throw new Error(run.stderr || `git ${args.join(" ")} failed`);
20
+ return run.stdout.trim();
21
+ }
22
+
23
+ function manifest(id) {
24
+ return {
25
+ $schema: "https://mirai-graph.dev/schemas/graph-manifest.schema.json",
26
+ format: "mirai-graph", schema_version: "2.0.0", id, aliases: [], title: id,
27
+ scope: "repository", kind: "repository_graph", owner: "fixture-owner",
28
+ profiles: ["project_management", "implementation_control"],
29
+ graph: {
30
+ root: "graph", source_of_truth: ["graph/specs"], objects: ["graph/specs/objects.json"],
31
+ relations: ["graph/specs/relations.json"], schemas: [], generated: ["graph/generated"], raw_sources: ["README.md"],
32
+ },
33
+ imports: [],
34
+ extensions: { "mirai.project_technology": technology.extensionContract() },
35
+ };
36
+ }
37
+
38
+ function createRepo(root, name, withGit = true) {
39
+ const repo = path.join(root, name); fs.mkdirSync(repo, { recursive: true });
40
+ writeJson(path.join(repo, "graph.json"), manifest(name));
41
+ writeJson(path.join(repo, "graph/specs/objects.json"), [{
42
+ id: "goal.fixture.delivery", kind: "goal", title: "Deliver fixture", summary: "Complete a safe fixture task",
43
+ readiness: "accepted", profile: "project_management", source_refs: ["README.md"],
44
+ }]);
45
+ writeJson(path.join(repo, "graph/specs/relations.json"), []);
46
+ fs.writeFileSync(path.join(repo, "README.md"), "# Fixture\nPortable safe context.\n");
47
+ if (withGit) {
48
+ git(repo, "init", "-q"); git(repo, "config", "user.name", "Mirai Fixture"); git(repo, "config", "user.email", "fixture@example.invalid");
49
+ git(repo, "add", "."); git(repo, "commit", "-qm", "initial graph");
50
+ }
51
+ return repo;
52
+ }
53
+
54
+ function evidence(task, options = {}) {
55
+ return {
56
+ task_digest: technology.sha256(task, true),
57
+ outcome: options.outcome || `Verified result for ${task}`,
58
+ requirement_refs: options.requirement_refs || ["requirement.fixture.portability"],
59
+ evidence_refs: ["repo://fixture/checks/continuity"],
60
+ checks: [{ id: "check.fixture.continuity", verdict: "pass", evidence_ref: "repo://fixture/checks/continuity" }],
61
+ changed_surfaces: ["src/fixture.js"],
62
+ case_signature: options.case_signature || "case.fixture.portable-work",
63
+ method_candidate: options.method_candidate || null,
64
+ decisions: options.decisions || [],
65
+ source_revision: options.source_revision || null,
66
+ };
67
+ }
68
+
69
+ const checks = [];
70
+ function check(id, condition, details = null) {
71
+ checks.push({ id, passed: Boolean(condition), details });
72
+ assert.ok(condition, `${id}: ${JSON.stringify(details)}`);
73
+ }
74
+
75
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "mirai-continuity-"));
76
+ const stateRoot = path.join(root, "host-state");
77
+ try {
78
+ const repo = createRepo(root, "git-project");
79
+ const before = git(repo, "status", "--porcelain=v1");
80
+ const taskStart = technology.execute("sync", repo, { apply: true, boundary: "task_start", stateRoot });
81
+ check("task_start_is_read_only", taskStart.status === "success" && taskStart.changed === false && git(repo, "status", "--porcelain=v1") === before, taskStart);
82
+ const preview = technology.execute("sync", repo, { apply: false, boundary: "task_complete", continuityEvidence: evidence("preview"), stateRoot });
83
+ check("preview_is_zero_write", preview.status === "preview" && git(repo, "status", "--porcelain=v1") === before, preview);
84
+ const missingReceiptVerify = technology.verify(repo, { stateRoot });
85
+ check("configured_continuity_without_receipt_blocks_verify", missingReceiptVerify.status === "blocked" && missingReceiptVerify.blockers.includes("continuity_missing"), missingReceiptVerify);
86
+
87
+ const first = technology.execute("sync", repo, { apply: true, boundary: "stage_complete", continuityEvidence: evidence("first", { method_candidate: "Use the verified bounded fixture path." }), stateRoot });
88
+ check("verified_result_is_saved", first.status === "success" && first.changed && first.continuity.saved_refs.length === 2, first);
89
+ check("portable_authority_is_graph_specs", fs.existsSync(path.join(repo, "graph/specs/project-continuity.json")) && first.continuity.authority === "graph/specs");
90
+ check("host_state_is_outside_project", !fs.existsSync(path.join(repo, ".mirai-graph/project-technology/receipts")) && fs.existsSync(stateRoot));
91
+ check("human_projection_is_generated", fs.readFileSync(path.join(repo, "graph/docs/project-context.md"), "utf8").includes("not a source of truth"));
92
+ const objectsAfterFirst = JSON.parse(fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"), "utf8")).objects;
93
+ check("first_method_is_proposal", objectsAfterFirst.some((item) => item.kind === "lesson" && item.readiness === "proposal"));
94
+ check("architecture_decision_is_not_accepted", technology.execute("sync", repo, {
95
+ apply: true, boundary: "task_complete", stateRoot,
96
+ continuityEvidence: evidence("decision", { decisions: [{ summary: "Replace the project architecture", changes_architecture: true }] }),
97
+ }).status === "success");
98
+ const decisionObjects = JSON.parse(fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"), "utf8")).objects;
99
+ check("architecture_change_remains_proposal", decisionObjects.some((item) => item.kind === "decision" && item.summary === "Replace the project architecture" && item.readiness === "proposal"));
100
+
101
+ const second = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: evidence("second", { method_candidate: "Use the verified bounded fixture path." }), stateRoot });
102
+ check("second_independent_case_is_saved", second.status === "success", second);
103
+ const objectsAfterSecond = JSON.parse(fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"), "utf8")).objects;
104
+ check("method_promotes_after_two_independent_cases", objectsAfterSecond.some((item) => item.kind === "lesson" && item.readiness === "accepted" && item.supporting_case_ids.length >= 2));
105
+
106
+ const beforeRepeat = fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"));
107
+ const repeated = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: evidence("second", { method_candidate: "Use the verified bounded fixture path." }), stateRoot });
108
+ check("repeat_is_byte_identical", repeated.status === "success" && repeated.changed === false && fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json")).equals(beforeRepeat), repeated);
109
+ check("fresh_continuity_verifies_without_target_for_light_work", technology.verify(repo, { stateRoot }).status === "success");
110
+ check("fresh_receipt_allows_significant_work", technology.continuity.verify(repo, JSON.parse(fs.readFileSync(path.join(repo, "graph.json"), "utf8")), { stateRoot }).status === "success");
111
+
112
+ const staleDigest = technology.continuity.graphDigest(repo);
113
+ fs.appendFileSync(path.join(repo, "graph/specs/objects.json"), " ");
114
+ check("stale_continuity_blocks_significant_work", technology.continuity.verify(repo, JSON.parse(fs.readFileSync(path.join(repo, "graph.json"), "utf8")), { stateRoot }).blockers.includes("continuity_stale"));
115
+ const baseObjects = JSON.parse(fs.readFileSync(path.join(repo, "graph/specs/objects.json"), "utf8"));
116
+ baseObjects[0].summary = "Changed concurrently";
117
+ fs.writeFileSync(path.join(repo, "graph/specs/objects.json"), JSON.stringify(baseObjects, null, 2) + "\n");
118
+ const cas = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: evidence("cas"), expectedGraphDigest: staleDigest, stateRoot });
119
+ check("compare_and_swap_conflict_is_safe", cas.status === "blocked" && cas.blockers.includes("continuity_compare_and_swap_conflict"), cas);
120
+ baseObjects[0].summary = "Complete a safe fixture task";
121
+ fs.writeFileSync(path.join(repo, "graph/specs/objects.json"), JSON.stringify(baseObjects, null, 2) + "\n");
122
+
123
+ const secret = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: evidence("secret", { outcome: "token=not-a-real-secret-value-123456" }), stateRoot });
124
+ check("secret_is_rejected", secret.status === "blocked" && secret.blockers.includes("continuity_sensitive_or_host_data_forbidden"), secret);
125
+ const privatePath = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: { ...evidence("private"), changed_surfaces: ["source/memory/private.md"] }, stateRoot });
126
+ check("private_projection_is_rejected", privatePath.status === "blocked" && privatePath.blockers.includes("continuity_sensitive_or_host_data_forbidden"), privatePath);
127
+ const failedCheck = technology.execute("sync", repo, { apply: true, boundary: "task_complete", continuityEvidence: { ...evidence("fail"), checks: [{ id: "check.fixture", verdict: "fail", evidence_ref: "repo://fixture/fail" }] }, stateRoot });
128
+ check("unverified_result_is_rejected", failedCheck.status === "blocked" && failedCheck.blockers.includes("continuity_result_not_verified"), failedCheck);
129
+
130
+ git(repo, "add", "graph.json", "graph/specs", "graph/docs"); git(repo, "commit", "-qm", "portable continuity records");
131
+ const discovered = technology.context(repo, { phase: "discover", task: "use the verified portable work case" });
132
+ check("new_task_discovers_past_case", discovered.status === "success" && discovered.traversal_receipt.candidates.some((item) => item.id.startsWith("regression_case.continuity.")), discovered.blockers);
133
+ const beforeReadOnly = git(repo, "status", "--porcelain=v1");
134
+ technology.status(repo); technology.context(repo, { phase: "discover", task: "read only project context" });
135
+ check("read_only_operations_write_nothing", git(repo, "status", "--porcelain=v1") === beforeReadOnly);
136
+
137
+ const folder = createRepo(root, "shared-folder", false);
138
+ const folderFirst = technology.execute("sync", folder, { apply: true, boundary: "task_complete", continuityEvidence: evidence("folder"), stateRoot });
139
+ check("non_git_folder_supports_continuity", folderFirst.status === "success" && folderFirst.changed, folderFirst);
140
+ const folderRepeat = technology.execute("sync", folder, { apply: true, boundary: "task_complete", continuityEvidence: evidence("folder"), stateRoot });
141
+ check("non_git_repeat_is_idempotent", folderRepeat.status === "success" && folderRepeat.changed === false, folderRepeat);
142
+ const sharedLease = path.join(folder, "graph/.project-technology-continuity.lock");
143
+ fs.writeFileSync(sharedLease, "occupied\n");
144
+ const leaseConflict = technology.execute("sync", folder, { apply: true, boundary: "task_complete", continuityEvidence: evidence("parallel"), stateRoot: path.join(root, "third-installation-state") });
145
+ check("shared_folder_lease_blocks_parallel_writer", leaseConflict.status === "blocked" && leaseConflict.blockers.includes("continuity_lease_conflict"), leaseConflict);
146
+ fs.unlinkSync(sharedLease);
147
+ const folderDiscovery = technology.context(folder, { phase: "discover", task: "portable fixture case" });
148
+ check("non_git_context_is_available", folderDiscovery.status === "success", folderDiscovery.blockers);
149
+ check("same_content_has_stable_digest", technology.continuity.graphDigest(folder) === technology.continuity.graphDigest(folder));
150
+
151
+ const otherStateRoot = path.join(root, "other-installation-state");
152
+ const firstInstallContext = technology.context(repo, { phase: "discover", task: "use verified portable work" });
153
+ const secondInstallContext = technology.context(repo, { phase: "discover", task: "use verified portable work", stateRoot: otherStateRoot });
154
+ check("two_installations_get_same_context", firstInstallContext.traversal_receipt.traversal_digest === secondInstallContext.traversal_receipt.traversal_digest);
155
+ check("portable_specs_contain_no_host_paths", !/(?:\/Users\/|\/home\/|[A-Za-z]:\\Users\\)/.test(fs.readFileSync(path.join(repo, "graph/specs/project-continuity.json"), "utf8")));
156
+ check("continuity_policy_keeps_contract_version", JSON.parse(fs.readFileSync(path.join(repo, "graph.json"), "utf8")).extensions["mirai.project_technology"].contract_version === "1.0.0");
157
+
158
+ const bootstrapRepo = path.join(root, "existing-code-new-graph");
159
+ fs.mkdirSync(bootstrapRepo, { recursive: true });
160
+ fs.writeFileSync(path.join(bootstrapRepo, "README.md"), "# Existing code\n");
161
+ git(bootstrapRepo, "init", "-q"); git(bootstrapRepo, "config", "user.name", "Mirai Fixture"); git(bootstrapRepo, "config", "user.email", "fixture@example.invalid");
162
+ git(bootstrapRepo, "add", "README.md"); git(bootstrapRepo, "commit", "-qm", "existing code");
163
+ writeJson(path.join(bootstrapRepo, "graph.json"), manifest("bootstrap-project"));
164
+ writeJson(path.join(bootstrapRepo, "graph/specs/objects.json"), [{ id: "goal.bootstrap", kind: "goal", title: "Bootstrap", summary: "Use content-bound bootstrap context", readiness: "accepted", profile: "project_management" }]);
165
+ writeJson(path.join(bootstrapRepo, "graph/specs/relations.json"), []);
166
+ const bootstrapContext = technology.context(bootstrapRepo, { phase: "discover", task: "bootstrap context" });
167
+ check("new_git_graph_is_content_bound_before_first_commit", bootstrapContext.status === "success" && String(bootstrapContext.traversal_receipt.graph.revision).startsWith("content:"), bootstrapContext.blockers);
168
+
169
+ process.stdout.write(`${JSON.stringify({ status: "success", checks_passed: checks.length, checks_failed: 0, checks }, null, 2)}\n`);
170
+ } catch (error) {
171
+ process.stdout.write(`${JSON.stringify({ status: "fail", checks_passed: checks.filter((item) => item.passed).length, checks_failed: 1, checks, error: error.message }, null, 2)}\n`);
172
+ process.exitCode = 1;
173
+ }
@@ -72,6 +72,18 @@ function executionContract() {
72
72
  };
73
73
  }
74
74
 
75
+ function continuityEvidence(repositoryId) {
76
+ return {
77
+ task_digest: technology.sha256(`task:${repositoryId}`, true),
78
+ outcome: `Exact target binding verified for ${repositoryId}`,
79
+ requirement_refs: ["requirement.fixture.runtime"],
80
+ evidence_refs: ["repo://fixture/evidence/project-technology"],
81
+ checks: [{ id: "check.fixture.binding", verdict: "pass", evidence_ref: "repo://fixture/evidence/project-technology" }],
82
+ changed_surfaces: ["graph/specs/project-continuity.json"],
83
+ case_signature: "case.fixture.target-binding",
84
+ };
85
+ }
86
+
75
87
  function initRepo(root, name, scope = "repository", profiles) {
76
88
  const repo = path.join(root, name); fs.mkdirSync(repo, { recursive: true });
77
89
  git(repo, "init", "-q"); git(repo, "config", "user.name", "Mirai Fixture");
@@ -104,6 +116,7 @@ function check(id, condition, details = null) {
104
116
  }
105
117
 
106
118
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "mirai-project-technology-test-"));
119
+ process.env.MIRAI_GRAPH_STATE_ROOT = path.join(root, "host-state");
107
120
  try {
108
121
  const provider = initRepo(root, "fixture-provider", "repository");
109
122
  const consumer = initRepo(root, "fixture-consumer", "repository");
@@ -112,6 +125,11 @@ try {
112
125
  const federation = initRepo(root, "fixture-federation", "federation", ["project_management", "implementation_control"]);
113
126
  const providerRevision = addTarget(provider);
114
127
 
128
+ const legacyRuntimeRepo = initRepo(root, "legacy-runtime-repository");
129
+ writeJson(path.join(legacyRuntimeRepo, ".mirai-graph/project-technology/inventory.json"), { schema_version: "1.0.0", repository_id: "legacy-runtime-repository", inventory_digest: `sha256:${"0".repeat(64)}`, files: [] });
130
+ const legacyRuntimeEnabled = technology.execute("enable", legacyRuntimeRepo, { apply: true });
131
+ check("project_local_runtime_moves_to_host_state", legacyRuntimeEnabled.status === "success" && legacyRuntimeEnabled.migration_ref.startsWith("host-local://") && fs.existsSync(path.join(legacyRuntimeRepo, ".mirai-graph/project-technology/inventory.json")), legacyRuntimeEnabled);
132
+
115
133
  const before = fs.readFileSync(path.join(consumer, "graph.json"));
116
134
  const preview = technology.execute("enable", consumer, { apply: false });
117
135
  check("preview_is_zero_write", preview.status === "preview" && fs.readFileSync(path.join(consumer, "graph.json")).equals(before));
@@ -147,12 +165,18 @@ try {
147
165
  apply: true, source: exportPath, targetId: TARGET_ID,
148
166
  semanticDigest: SEMANTIC_DIGEST, providerRevision,
149
167
  });
150
- check("consumer_binding_ready", connected.status === "success" && technology.verify(consumer, { significantWork: true }).status === "success", connected.blockers);
168
+ const consumerContinuity = technology.execute("sync", consumer, {
169
+ apply: true, boundary: "task_complete", continuityEvidence: continuityEvidence("fixture-consumer"),
170
+ });
171
+ check("consumer_binding_ready", connected.status === "success" && consumerContinuity.status === "success" && technology.verify(consumer, { significantWork: true }).status === "success", { connect: connected.blockers, continuity: consumerContinuity.blockers });
151
172
  const connectedTwo = technology.execute("connect", consumerTwo, {
152
173
  apply: true, source: exportPath, targetId: TARGET_ID,
153
174
  semanticDigest: SEMANTIC_DIGEST, providerRevision,
154
175
  });
155
- check("second_consumer_binding_ready", connectedTwo.status === "success" && technology.verify(consumerTwo, { significantWork: true }).status === "success", connectedTwo.blockers);
176
+ const consumerTwoContinuity = technology.execute("sync", consumerTwo, {
177
+ apply: true, boundary: "task_complete", continuityEvidence: continuityEvidence("fixture-consumer-two"),
178
+ });
179
+ check("second_consumer_binding_ready", connectedTwo.status === "success" && consumerTwoContinuity.status === "success" && technology.verify(consumerTwo, { significantWork: true }).status === "success", { connect: connectedTwo.blockers, continuity: consumerTwoContinuity.blockers });
156
180
  const repeated = technology.execute("connect", consumer, {
157
181
  apply: true, source: exportPath, targetId: TARGET_ID,
158
182
  semanticDigest: SEMANTIC_DIGEST, providerRevision,