mirai-graph 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,44 @@ All notable changes to Mirai Graph will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.5.0] - 2026-09-03
8
+
9
+ Compatible 1.x maintenance line, published under `legacy-1`; the npm `latest`
10
+ channel and the separate 2.x development line are unchanged.
11
+
12
+ - Project Technology can connect and refresh a bounded provider export without
13
+ Git using explicit authenticated-release trust supplied by the consumer.
14
+ Exact export bytes, graph identity, revision and forward ancestry are checked;
15
+ target/architecture/permission gates are unchanged. No automatic trust discovery.
16
+ - Read-only source export verification proves the archive anchor against the
17
+ accepted target and Git revision before a distributor seals it in release
18
+ metadata. CLI trust can be supplied on stdin; no temporary trust file is needed.
19
+ - No-Git inventory covers explicitly declared graph and raw-source paths, detects
20
+ stale content and blocks unsafe or missing sources before sync writes. Existing
21
+ Git inventories remain revision-bound; this does not invent source history.
22
+
23
+ ## [1.4.0] - 2026-08-29
24
+
25
+ ### Added
26
+
27
+ - A domain-neutral executable-technology contract for large, ordered methods
28
+ whose scenarios reuse independently executable operations.
29
+ - Deterministic Course Pack compilation with required dependency closure,
30
+ exact source binding and partial courses for one scenario.
31
+ - Read-only `technology course compile`, `verify` and `reconcile` operations in
32
+ the public CLI and JavaScript API.
33
+ - Universal positive and negative fixtures for course projection.
34
+
35
+ ### Safety Boundary
36
+
37
+ - A course is a projection, not another source of truth. Reconciliation never
38
+ writes to the executable technology and semantic changes require an owner
39
+ decision.
40
+ - Missing operations, dependency cycles, stale digests and secret fields fail
41
+ closed.
42
+ - The public `graph.json` schema remains `2.0.0`; the Project Technology
43
+ activation contract remains `1.0.0` and no new profile is introduced.
44
+
7
45
  ## [1.3.0] - 2026-08-28
8
46
 
9
47
  ### 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.2.0"
21
- date-released: "2026-08-27"
20
+ version: "1.5.0"
21
+ date-released: "2026-09-03"
package/README.md CHANGED
@@ -156,6 +156,23 @@ The protected files remain outside graph data. The graph keeps only safe
156
156
  identity, lineage, state, references and checksums. Mirai Graph performs
157
157
  technical integrity checks; a domain owner decides what a version means.
158
158
 
159
+ Large operational methods can be described once as executable technologies and
160
+ compiled into staff courses without maintaining a second semantic copy:
161
+
162
+ ```bash
163
+ mirai-graph technology course compile . --technology graph/specs/technology.json
164
+ mirai-graph technology course compile . --technology graph/specs/technology.json --scenario scenario.recovery
165
+ mirai-graph technology course verify . --course-pack course-pack.json
166
+ mirai-graph technology course reconcile . --course-pack course-pack.json --projection edited-course.json
167
+ ```
168
+
169
+ An executable technology contains reusable operations, their prerequisites,
170
+ inputs, outputs, stop conditions, rollback paths, checks and raw source refs.
171
+ Scenarios select the operations needed for a complete or partial outcome.
172
+ Compilation restores every required dependency. Reconciliation is read-only:
173
+ editorial differences go to the documentation owner, while changed execution
174
+ or safety rules become proposals to the technology owner.
175
+
159
176
  Changing operations return a preview unless `--apply` is explicit. See
160
177
  [Project Technology](standard/project-technology.md) for provider/consumer
161
178
  binding, local state and safety boundaries.
@@ -0,0 +1,16 @@
1
+ # Executable Technology Course
2
+
3
+ This domain-neutral fixture demonstrates one accepted executable technology,
4
+ two independently usable scenarios and a deterministic instructional course
5
+ projection. The recovery scenario automatically includes its required
6
+ inventory operation.
7
+
8
+ ```bash
9
+ mirai-graph technology course compile . \
10
+ --technology examples/executable-technology-course/technology.json \
11
+ --scenario scenario.recovery_preparation
12
+ ```
13
+
14
+ Course packs are read-only projections. Their reconciliation result can return
15
+ editorial changes, but semantic changes remain owner proposals and never write
16
+ back into the executable technology.
@@ -0,0 +1,99 @@
1
+ {
2
+ "schema_version": "1.0.0",
3
+ "id": "technology.service_release",
4
+ "title": "Safe service release",
5
+ "owner": "role.release_owner",
6
+ "outcome": "A verified service version is promoted with a tested recovery path.",
7
+ "lifecycle": "accepted",
8
+ "version": "2026.08",
9
+ "source_refs": ["repo://example/operations/release.md"],
10
+ "projection_refs": ["docs://staff-course/service-release"],
11
+ "operations": [
12
+ {
13
+ "id": "operation.inventory",
14
+ "title": "Inventory the current state",
15
+ "summary": "Record exact versions, dependencies and available environments before change.",
16
+ "owner": "role.release_owner",
17
+ "capability_ref": "capability.release.inventory",
18
+ "prerequisites": [],
19
+ "input_refs": ["input.release_request"],
20
+ "output_refs": ["evidence.current_state"],
21
+ "check_refs": ["check.inventory_complete"],
22
+ "stop_condition_refs": ["stop.unknown_current_state"],
23
+ "rollback_refs": [],
24
+ "source_refs": ["repo://example/operations/inventory.md"],
25
+ "instructional_refs": ["lesson.inventory"],
26
+ "applicability": ["release", "recovery"],
27
+ "negative_boundaries": ["Do not mutate the environment during inventory."]
28
+ },
29
+ {
30
+ "id": "operation.backup",
31
+ "title": "Prepare recovery",
32
+ "summary": "Create and verify a bounded backup and rollback procedure.",
33
+ "owner": "role.operations",
34
+ "capability_ref": "capability.release.recovery",
35
+ "prerequisites": ["operation.inventory"],
36
+ "input_refs": ["evidence.current_state"],
37
+ "output_refs": ["evidence.backup", "procedure.rollback"],
38
+ "check_refs": ["check.restore_smoke"],
39
+ "stop_condition_refs": ["stop.restore_not_proven"],
40
+ "rollback_refs": ["procedure.rollback"],
41
+ "source_refs": ["repo://example/operations/recovery.md"],
42
+ "instructional_refs": ["lesson.recovery"],
43
+ "applicability": ["release", "recovery"],
44
+ "negative_boundaries": ["A backup without a restore check is not sufficient."]
45
+ },
46
+ {
47
+ "id": "operation.validate",
48
+ "title": "Validate the candidate",
49
+ "summary": "Run the required functional, compatibility and safety checks.",
50
+ "owner": "role.tester",
51
+ "capability_ref": "capability.release.validation",
52
+ "prerequisites": ["operation.inventory"],
53
+ "input_refs": ["input.release_candidate"],
54
+ "output_refs": ["evidence.validation"],
55
+ "check_refs": ["check.acceptance"],
56
+ "stop_condition_refs": ["stop.blocking_regression"],
57
+ "rollback_refs": [],
58
+ "source_refs": ["repo://example/operations/validation.md"],
59
+ "instructional_refs": ["lesson.validation"],
60
+ "applicability": ["release"],
61
+ "negative_boundaries": ["A green syntax check is not an outcome verdict."]
62
+ },
63
+ {
64
+ "id": "operation.promote",
65
+ "title": "Promote the accepted version",
66
+ "summary": "Apply the exact accepted candidate and verify the resulting identity.",
67
+ "owner": "role.release_owner",
68
+ "capability_ref": "capability.release.promotion",
69
+ "prerequisites": ["operation.backup", "operation.validate"],
70
+ "input_refs": ["evidence.backup", "evidence.validation"],
71
+ "output_refs": ["evidence.release_readback"],
72
+ "check_refs": ["check.release_identity", "check.runtime_smoke"],
73
+ "stop_condition_refs": ["stop.identity_mismatch"],
74
+ "rollback_refs": ["procedure.rollback"],
75
+ "source_refs": ["repo://example/operations/promotion.md"],
76
+ "instructional_refs": ["lesson.promotion"],
77
+ "applicability": ["release"],
78
+ "negative_boundaries": ["Do not promote a candidate that differs from the accepted identity."]
79
+ }
80
+ ],
81
+ "scenarios": [
82
+ {
83
+ "id": "scenario.full_release",
84
+ "title": "Full release",
85
+ "outcome": "The accepted service version is released and verified.",
86
+ "operation_ids": ["operation.inventory", "operation.backup", "operation.validate", "operation.promote"],
87
+ "required_inputs": ["input.release_request", "input.release_candidate"],
88
+ "audience": ["release_manager", "operations_engineer"]
89
+ },
90
+ {
91
+ "id": "scenario.recovery_preparation",
92
+ "title": "Recovery preparation only",
93
+ "outcome": "A verified rollback path exists without performing a release.",
94
+ "operation_ids": ["operation.backup"],
95
+ "required_inputs": ["input.release_request"],
96
+ "audience": ["operations_engineer"]
97
+ }
98
+ ]
99
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirai-graph",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "private": false,
5
5
  "description": "An evolutionary graph operating model for managing the growth of complex systems.",
6
6
  "license": "MIT",
@@ -12,47 +12,14 @@
12
12
  "exports": {
13
13
  "./project-technology": "./packages/project-technology/index.js"
14
14
  },
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/zabarov/mirai-graph.git"
18
- },
19
- "keywords": [
20
- "graph",
21
- "knowledge-graph",
22
- "governance",
23
- "complex-systems",
24
- "context-engineering",
25
- "ai-assisted-work"
26
- ],
27
- "author": "Rim Zabarov",
28
- "files": [
29
- "README.md",
30
- "CHANGELOG.md",
31
- "ROADMAP.md",
32
- "LICENSE",
33
- "LICENSE-DOCS",
34
- "CITATION.cff",
35
- "docs",
36
- "standard",
37
- "schemas",
38
- "benchmarks",
39
- "examples",
40
- "packages",
41
- "profiles",
42
- "pilots",
43
- "playground",
44
- "publications",
45
- "releases",
46
- "templates",
47
- ".github"
48
- ],
49
15
  "scripts": {
50
- "pretest": "npm run test:project-technology && npm run test:context-traversal && npm run test:project-continuity && npm run test:artifact-releases && npm run validate:project-technology-schemas",
51
- "test:project-technology": "node packages/cli/validate-project-technology.js",
16
+ "pretest": "npm run test:project-technology && npm run test:context-traversal && npm run test:project-continuity && npm run test:artifact-releases && npm run test:technology-courses && npm run validate:project-technology-schemas",
17
+ "test:project-technology": "node packages/cli/validate-project-technology.js && node packages/cli/validate-provider-archive.js",
52
18
  "test:context-traversal": "node packages/cli/validate-context-traversal.js",
53
19
  "test:project-continuity": "node packages/cli/validate-project-continuity.js",
54
20
  "test:artifact-releases": "node packages/cli/validate-artifact-releases.js",
55
- "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))\"",
21
+ "test:technology-courses": "node packages/cli/validate-technology-course.js",
22
+ "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','executable-technology.schema.json','technology-course-pack.schema.json']) JSON.parse(fs.readFileSync('schemas/'+file))\"",
56
23
  "validate:graph-manifest": "node packages/cli/validate-graph-manifest-migration.js",
57
24
  "validate:minimal": "node packages/cli/validate-mirai-graph.js examples/minimal-graph",
58
25
  "validate:benchmark": "node packages/cli/validate-mirai-graph.js benchmarks/synthetic-context-reduction-v0",
@@ -123,5 +90,39 @@
123
90
  "validate:playground-report": "npm run --silent playground:report >/tmp/mirai-graph-playground-report.md && diff -u playground/demo-report.md /tmp/mirai-graph-playground-report.md && rg 'transition explanation|Instrumentation|Multi-source feedback|Baseline Comparison|baseline comparison' playground/demo-report.md",
124
91
  "test": "npm run validate:graph-manifest && npm run validate:minimal && npm run validate:benchmark && npm run validate:seed && npm run validate:profiles && npm run validate:ai-employee && npm run validate:skill-runtime && npm run validate:managed-project && npm run validate:implementation-control && npm run validate:implementation-control-cycles && npm run validate:launch-record && npm run validate:process-transition && npm run validate:process-transition-report && npm run validate:process-control && npm run validate:technology-quality-feedback && npm run validate:instrumentation-layer && npm run validate:instrumentation-report && npm run validate:dynamic-episodes && npm run validate:dynamic-episode-report && npm run validate:semantic-intent-resolution && npm run validate:goal-vector-quality-control && npm run validate:dna-alignment && npm run validate:work-state && npm run validate:risk-control && npm run validate:source-boundary && npm run validate:organization-governance && npm run validate:societal-governance && npm run validate:character-layer && npm run validate:character-layer-integration && npm run validate:model-portability-evidence && npm run validate:character-layer-readiness && npm run validate:templates && npm run validate:adopter-workflow && npm run validate:self-service-onboarding && npm run validate:init-fixtures && npm run validate:bootstrap-fixtures && npm run validate:release-state && npm run validate:mirai-graph-compat && npm run validate:cli-unified && npm run validate:pilots && npm run validate:context-packs && npm run validate:profile-results && npm run validate:routing-control && npm run seed:preview && npm run context:generate && npm run playground:demo && npm run playground:report && npm run readiness:benchmark && npm run benchmark:synthetic && npm run validate:baseline-comparison && npm run validate:markdown-report && npm run validate:playground-demo && npm run validate:playground-report && npm run test:negative && npm run test:character-layer-semantic-negative && npm run test:societal-governance-negative && npm run test:process-transition-negative && npm run test:technology-quality-feedback-negative && npm run test:instrumentation-layer-negative && npm run test:dynamic-episodes-negative && npm run test:semantic-intent-resolution-negative && npm run test:goal-vector-quality-control-negative",
125
92
  "release:check": "npm test && git diff --check"
126
- }
127
- }
93
+ },
94
+ "repository": {
95
+ "type": "git",
96
+ "url": "git+https://github.com/zabarov/mirai-graph.git"
97
+ },
98
+ "keywords": [
99
+ "graph",
100
+ "knowledge-graph",
101
+ "governance",
102
+ "complex-systems",
103
+ "context-engineering",
104
+ "ai-assisted-work"
105
+ ],
106
+ "author": "Rim Zabarov",
107
+ "files": [
108
+ "README.md",
109
+ "CHANGELOG.md",
110
+ "ROADMAP.md",
111
+ "LICENSE",
112
+ "LICENSE-DOCS",
113
+ "CITATION.cff",
114
+ "docs",
115
+ "standard",
116
+ "schemas",
117
+ "benchmarks",
118
+ "examples",
119
+ "packages",
120
+ "profiles",
121
+ "pilots",
122
+ "playground",
123
+ "publications",
124
+ "releases",
125
+ "templates",
126
+ ".github"
127
+ ]
128
+ }
File without changes
@@ -25,7 +25,7 @@ function usage() {
25
25
  console.error(" mirai-graph report playground");
26
26
  console.error(" mirai-graph release state [--markdown] [--require-git-tag] [--require-github-release] [--require-npm-published]");
27
27
  console.error(" mirai-graph technology <explain|status|plan|verify|context> [repository] [options]");
28
- console.error(" mirai-graph technology <enable|sync|connect|disconnect|provide|disable|repair|artifact> [repository] [options] [--apply]");
28
+ console.error(" mirai-graph technology <enable|sync|connect|disconnect|provide|disable|repair|artifact|course> [repository] [options] [--apply]");
29
29
  console.error("");
30
30
  console.error("Boundary:");
31
31
  console.error(" Reports explain validation decisions. They do not authorize canonical updates.");
File without changes
@@ -13,6 +13,7 @@ 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("Archive connect: --provider-archive-trust <verified-release-anchor.json>");
16
17
  console.error("Continuity: sync --boundary task_start|stage_complete|task_complete [--evidence <json>] [--expected-graph-digest <sha256:...>]");
17
18
  console.error(" verify --significant-work [--receipt-digest <sha256:...>]");
18
19
  console.error("Context: --phase discover --task <text>");
@@ -23,22 +24,32 @@ function usage() {
23
24
  console.error(" artifact release <repo> --input <path> --matter-id <id> --direction inbound|internal|outbound [--parent-release <id>] [--client-note <text>] [--create-export] [--apply]");
24
25
  console.error(" artifact compare <repo> --matter-id <id> --base-release <id> --target-release <id>");
25
26
  console.error(" artifact verify <repo> --matter-id <id> --release-id <id>");
27
+ console.error("Courses: course compile <repo> --technology <file> [--scenario <id>] [--audience <id>]");
28
+ console.error(" course verify <repo> --course-pack <file>");
29
+ console.error(" course reconcile <repo> --course-pack <file> --projection <file>");
26
30
  }
27
31
 
28
32
  function parse(argv) {
29
33
  if (!argv.length) return null;
30
34
  const operation = argv.shift();
31
35
  let artifactAction = null;
36
+ let courseAction = null;
32
37
  if (operation === "artifact") {
33
38
  artifactAction = argv.shift() || null;
34
39
  if (!new Set(["inspect", "release", "compare", "verify"]).has(artifactAction)) throw new Error("artifact requires inspect, release, compare or verify");
35
40
  }
41
+ if (operation === "course") {
42
+ courseAction = argv.shift() || null;
43
+ if (!new Set(["compile", "verify", "reconcile"]).has(courseAction)) throw new Error("course requires compile, verify or reconcile");
44
+ }
36
45
  let repository = ".";
37
46
  if (argv[0] && !argv[0].startsWith("--")) repository = argv.shift();
38
47
  const options = {};
39
- 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", "--matter-id", "--direction", "--artifact-root", "--release-date", "--release-id", "--parent-release", "--base-release", "--target-release", "--client-note"]);
48
+ 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", "--matter-id", "--direction", "--artifact-root", "--release-date", "--release-id", "--parent-release", "--base-release", "--target-release", "--client-note", "--technology", "--scenario", "--audience", "--course-pack", "--projection"]);
40
49
  const flags = new Set(["--apply", "--refresh-binding", "--significant-work", "--create-export"]);
50
+ values.add("--provider-archive-trust");
41
51
  const names = {
52
+ "--provider-archive-trust": "providerArchiveTrustFile",
42
53
  "--task": "task", "--source": "source", "--target-id": "targetId",
43
54
  "--semantic-digest": "semanticDigest", "--provider-revision": "providerRevision",
44
55
  "--max-objects": "maxObjects", "--apply": "apply",
@@ -54,6 +65,8 @@ function parse(argv) {
54
65
  "--parent-release": "parentReleaseIds", "--base-release": "baseReleaseId",
55
66
  "--target-release": "targetReleaseId", "--client-note": "clientNote",
56
67
  "--create-export": "createExport",
68
+ "--technology": "technologyFile", "--scenario": "scenarioIds",
69
+ "--audience": "audience", "--course-pack": "coursePackFile", "--projection": "projectionFile",
57
70
  };
58
71
  while (argv.length) {
59
72
  const token = argv.shift();
@@ -63,10 +76,12 @@ function parse(argv) {
63
76
  const value = argv.shift();
64
77
  if (token === "--select") options.selectedIds = [...(options.selectedIds || []), value];
65
78
  else if (token === "--parent-release") options.parentReleaseIds = [...(options.parentReleaseIds || []), value];
79
+ else if (token === "--scenario") options.scenarioIds = [...(options.scenarioIds || []), value];
66
80
  else options[names[token]] = value;
67
81
  } else throw new Error(`unsupported argument ${token}`);
68
82
  }
69
83
  if (artifactAction) options.artifactAction = artifactAction;
84
+ if (courseAction) options.courseAction = courseAction;
70
85
  return { operation, repository: path.resolve(repository), options };
71
86
  }
72
87
 
@@ -77,6 +92,11 @@ function readJsonFile(file) {
77
92
  }
78
93
 
79
94
  function hydrateContextOptions(request) {
95
+ if (request.options.providerArchiveTrustFile) {
96
+ if (request.operation !== "connect") throw new Error("archive trust is only supported by connect");
97
+ request.options.providerArchive = request.options.providerArchiveTrustFile === "-"
98
+ ? JSON.parse(fs.readFileSync(0, "utf8")) : readJsonFile(request.options.providerArchiveTrustFile);
99
+ }
80
100
  if (request.operation === "sync" && request.options.boundary) {
81
101
  if (request.options.boundary !== "task_start" && !request.options.evidenceFile) throw new Error(`${request.options.boundary} requires --evidence`);
82
102
  if (request.options.evidenceFile) request.options.continuityEvidence = readJsonFile(request.options.evidenceFile);
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const assert = require("assert");
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const vm = require("vm");
8
+ const { createRequire } = require("module");
9
+ const { spawnSync } = require("child_process");
10
+ const technology = require("../project-technology");
11
+ const fixtureFile = path.join(__dirname, "validate-project-technology.js");
12
+ const fixtureText = fs.readFileSync(fixtureFile, "utf8");
13
+ const sandbox = { require: createRequire(fixtureFile), module: { exports: {} } };
14
+ vm.runInNewContext(fixtureText.slice(0, fixtureText.indexOf("const checks = [];")) +
15
+ "\nmodule.exports={initRepo,addTarget,git,TARGET_ID,SEMANTIC_DIGEST};", sandbox);
16
+ const fixture = sandbox.module.exports;
17
+ const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "mirai-archive-test-")));
18
+ const originalPath = process.env.PATH;
19
+ const checks = [];
20
+ function check(id, condition, details) { checks.push({ id, passed: Boolean(condition) }); assert.ok(condition, `${id}: ${JSON.stringify(details)}`); }
21
+ function digestTree(dir) {
22
+ const files = [];
23
+ function walk(current) {
24
+ if (!fs.existsSync(current)) return;
25
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
26
+ const file = path.join(current, entry.name);
27
+ if (entry.isDirectory()) walk(file);
28
+ else files.push([path.relative(dir, file), technology.sha256(fs.readFileSync(file))]);
29
+ }
30
+ }
31
+ walk(dir); return technology.sha256(JSON.stringify(files.sort()));
32
+ }
33
+ try {
34
+ const sourceRoot = path.join(root, "source"); fs.mkdirSync(sourceRoot);
35
+ const provider = fixture.initRepo(sourceRoot, "fixture-provider");
36
+ const consumer = fixture.initRepo(sourceRoot, "fixture-consumer");
37
+ let revision = fixture.addTarget(provider);
38
+ const stateRoot = path.join(root, "state");
39
+ const base = { targetId: fixture.TARGET_ID, semanticDigest: fixture.SEMANTIC_DIGEST, stateRoot, apply: true };
40
+ for (const repo of [provider, consumer]) assert.equal(technology.execute("enable", repo, base).status, "success");
41
+ fixture.git(provider, "add", "graph.json"); fixture.git(provider, "commit", "-qm", "accepted activation");
42
+ revision = fixture.git(provider, "rev-parse", "HEAD");
43
+ const releases = [];
44
+ for (let n = 0; n < 2; n++) {
45
+ if (n) { fixture.git(provider, "commit", "--allow-empty", "-qm", "compatible revision"); revision = fixture.git(provider, "rev-parse", "HEAD"); }
46
+ const provided = technology.execute("provide", provider, { ...base, providerRevision: revision });
47
+ assert.equal(provided.status, "success");
48
+ const beforeSourceProof = digestTree(root);
49
+ const sourceProof = technology.execute("verify", provider, { source: path.join(provider, provided.export_ref) });
50
+ check(`source_export_proven_${n}`, sourceProof.status === "success" && sourceProof.provider_archive.providerRevision === revision, sourceProof);
51
+ check(`source_proof_zero_write_${n}`, digestTree(root) === beforeSourceProof);
52
+ check(`source_proof_not_authority_${n}`, technology.execute("verify", provider, { source: path.join(provider, provided.export_ref), significantWork: true }).status === "blocked");
53
+ const providerManifestPath = path.join(provider, "graph.json");
54
+ const providerManifestBytes = fs.readFileSync(providerManifestPath);
55
+ fs.appendFileSync(providerManifestPath, " ");
56
+ check(`uncommitted_manifest_blocks_source_proof_${n}`, technology.execute("verify", provider, { source: path.join(provider, provided.export_ref) }).blockers.includes("provider_manifest_not_revision_bound"));
57
+ fs.writeFileSync(providerManifestPath, providerManifestBytes);
58
+ const bytes = fs.readFileSync(path.join(provider, provided.export_ref));
59
+ const source = path.join(root, `export-${n}.json`); fs.writeFileSync(source, bytes);
60
+ check(`external_export_blocks_source_proof_${n}`, technology.execute("verify", provider, { source }).blockers.includes("provider_export_outside_repository"));
61
+ // Build anchor on the verified SOURCE side, before Git is removed.
62
+ const ancestors = n ? [releases[0].providerRevision] : [];
63
+ for (const ancestor of ancestors) fixture.git(provider, "merge-base", "--is-ancestor", ancestor, revision);
64
+ releases.push({ ...base, source, providerRevision: revision, providerArchive: {
65
+ exportSha256: technology.sha256(bytes), graphId: "fixture-provider", providerRevision: revision, ancestorRevisions: ancestors,
66
+ } });
67
+ }
68
+ const packedConsumer = path.join(root, "consumer");
69
+ fs.cpSync(consumer, packedConsumer, { recursive: true, filter: file => path.basename(file) !== ".git" });
70
+ const emptyPath = path.join(root, "no-tools"); fs.mkdirSync(emptyPath); process.env.PATH = emptyPath;
71
+ check("git_is_absent", spawnSync("git", ["--version"]).error?.code === "ENOENT");
72
+ const invoke = options => technology.execute("connect", packedConsumer, options);
73
+ const noTrust = { ...releases[0] }; delete noTrust.providerArchive;
74
+ check("untrusted_archive_blocked", invoke(noTrust).blockers.includes("provider_revision_order_unverifiable"));
75
+ const before = digestTree(root);
76
+ check("preview", invoke({ ...releases[0], apply: false }).status === "preview");
77
+ check("preview_zero_write", digestTree(root) === before);
78
+ const connected = invoke(releases[0]);
79
+ check("archive_connect", connected.status === "success", connected);
80
+ check("complete_contract_imported", connected.target_binding.status === "ready", connected);
81
+ const after = digestTree(root);
82
+ check("repeat_changed_false", invoke(releases[0]).changed === false);
83
+ check("repeat_byte_identical", digestTree(root) === after);
84
+ const status = technology.execute("status", packedConsumer, base);
85
+ check("read_only_ready", status.target_binding.status === "ready", status);
86
+ technology.execute("verify", packedConsumer, base);
87
+ check("diagnostics_zero_write", digestTree(root) === after);
88
+ function denied(id, options, blocker) {
89
+ const prior = digestTree(root); const result = invoke(options);
90
+ check(id, result.status === "fail" && (!blocker || result.blockers.includes(blocker)), result);
91
+ check(`${id}_zero_write`, prior === digestTree(root));
92
+ }
93
+ denied("null_trust", { ...releases[0], providerArchive: null }, "provider_archive_trust_invalid");
94
+ denied("bad_digest", { ...releases[0], providerArchive: { ...releases[0].providerArchive, exportSha256: "0".repeat(64) } }, "provider_archive_export_digest_mismatch");
95
+ denied("bad_graph", { ...releases[0], providerArchive: { ...releases[0].providerArchive, graphId: "other.provider" } }, "provider_archive_graph_mismatch");
96
+ denied("bad_revision", { ...releases[0], providerArchive: { ...releases[0].providerArchive, providerRevision: "f".repeat(40) } }, "provider_archive_revision_mismatch");
97
+ denied("extra_trust_fields", { ...releases[0], providerArchive: { ...releases[0].providerArchive, approved: true } }, "provider_archive_trust_invalid");
98
+ denied("explicit_refresh_required", releases[1], "target_provider_refresh_required");
99
+ denied("missing_ancestry", { ...releases[1], refreshBinding: true, providerArchive: { ...releases[1].providerArchive, ancestorRevisions: [] } }, "provider_revision_not_forward");
100
+ denied("target_mismatch", { ...releases[1], targetId: "other.target", refreshBinding: true }, "target_provider_target_mismatch");
101
+ denied("semantic_mismatch", { ...releases[1], semanticDigest: `sha256:${"3".repeat(64)}`, refreshBinding: true }, "target_provider_semantic_mismatch");
102
+ const refreshed = invoke({ ...releases[1], refreshBinding: true });
103
+ check("forward_refresh", refreshed.status === "success" && refreshed.changed, refreshed);
104
+ const refreshedTree = digestTree(root);
105
+ check("refresh_idempotent", invoke({ ...releases[1], refreshBinding: true }).changed === false);
106
+ check("refresh_byte_identical", digestTree(root) === refreshedTree);
107
+ denied("downgrade", { ...releases[0], refreshBinding: true }, "provider_revision_not_forward");
108
+ const original = fs.readFileSync(releases[1].source);
109
+ fs.appendFileSync(releases[1].source, " ");
110
+ denied("tampered_bytes", { ...releases[1], refreshBinding: true }, "provider_archive_export_digest_mismatch");
111
+ fs.writeFileSync(releases[1].source, original);
112
+ function alteredExport(id, alter, expected) {
113
+ const payload = JSON.parse(original); alter(payload);
114
+ fs.writeFileSync(releases[1].source, technology.canonicalBytes(payload));
115
+ const options = { ...releases[1], refreshBinding: true, providerArchive: { ...releases[1].providerArchive,
116
+ exportSha256: technology.sha256(fs.readFileSync(releases[1].source)), graphId: payload.provider_graph_id } };
117
+ denied(id, options, expected); fs.writeFileSync(releases[1].source, original);
118
+ }
119
+ alteredExport("provider_conflict", p => { p.provider_graph_id = "another.provider"; }, "target_provider_graph_conflict");
120
+ alteredExport("incomplete_contract", p => { p.requirement_bindings = []; });
121
+ alteredExport("contract_tamper", p => { p.goal_binding.done_when_ids = ["done.other"]; });
122
+ alteredExport("valid_but_different_contract", p => {
123
+ p.allowed_change_scope[0].file_patterns = ["packages/other/**"];
124
+ p.execution_contract_digest = technology.sha256(technology.canonicalBytes(technology.normalizeExecutionContract(p).contract));
125
+ }, "provider_execution_contract_refresh_mismatch");
126
+ alteredExport("reviewed_not_accepted", p => { p.architecture_contract.lifecycle = "reviewed"; });
127
+ alteredExport("private_extra_field", p => { p.private_source = "not allowed"; }, "provider_export_not_bounded");
128
+ const graphFile = path.join(packedConsumer, "graph.json"); const graphBefore = fs.readFileSync(graphFile);
129
+ const graph = JSON.parse(graphBefore); graph.extensions["mirai.project_technology"].enabled = false;
130
+ fs.writeFileSync(graphFile, JSON.stringify(graph));
131
+ denied("disabled", releases[1], "project_technology_not_enabled");
132
+ check("disabled_diagnostics_available", technology.execute("status", packedConsumer, base).operation_mode === "read_only");
133
+ fs.writeFileSync(graphFile, graphBefore);
134
+ const trustFile = path.join(root, "trust.json"); fs.writeFileSync(trustFile, JSON.stringify(releases[1].providerArchive));
135
+ const cli = spawnSync(process.execPath, [path.join(__dirname, "project-technology.js"), "connect", packedConsumer,
136
+ "--source", releases[1].source, "--target-id", base.targetId, "--semantic-digest", base.semanticDigest,
137
+ "--provider-revision", releases[1].providerRevision, "--state-root", stateRoot,
138
+ "--provider-archive-trust", trustFile, "--apply"], { encoding: "utf8" });
139
+ check("cli_no_git_idempotent", cli.status === 0 && JSON.parse(cli.stdout).changed === false, cli.stdout);
140
+ // A new identity field is optional for historical Git exports, but required
141
+ // for authenticated archive imports. Existing stored contracts remain valid.
142
+ const legacy = JSON.parse(original); delete legacy.provider_graph_id;
143
+ fs.writeFileSync(releases[1].source, technology.canonicalBytes(legacy));
144
+ check("historical_export_readable", technology.readExport(releases[1].source).blockers.length === 0);
145
+ denied("legacy_archive_needs_graph_identity", { ...releases[1], providerArchive: { ...releases[1].providerArchive,
146
+ exportSha256: technology.sha256(fs.readFileSync(releases[1].source)) } }, "provider_archive_graph_mismatch");
147
+ fs.writeFileSync(releases[1].source, original);
148
+ const folderInventory = technology.inventory(packedConsumer);
149
+ check("folder_inventory_nonempty", folderInventory.files.some(file => file.path === "graph.json") && folderInventory.files.some(file => file.path === "README.md"), folderInventory);
150
+ check("folder_inventory_sync", technology.execute("sync", packedConsumer, base).status === "success");
151
+ check("folder_inventory_sync_noop", technology.execute("sync", packedConsumer, base).changed === false);
152
+ const readme = path.join(packedConsumer, "README.md"); const readmeBefore = fs.readFileSync(readme);
153
+ fs.appendFileSync(readme, "\nChanged declared raw source\n");
154
+ const staleTree = digestTree(root);
155
+ check("folder_changed_source_is_stale", technology.execute("status", packedConsumer, base).blockers.includes("project_technology_inventory_stale"));
156
+ check("folder_stale_status_zero_write", digestTree(root) === staleTree);
157
+ fs.writeFileSync(readme, readmeBefore);
158
+ const graphForMissing = JSON.parse(fs.readFileSync(graphFile)); graphForMissing.graph.raw_sources.push("missing-required-source.md");
159
+ fs.writeFileSync(graphFile, JSON.stringify(graphForMissing));
160
+ const missingBefore = digestTree(root);
161
+ check("missing_declared_source_blocks_sync", technology.execute("sync", packedConsumer, base).blockers.includes("inventory_declared_source_missing"));
162
+ check("missing_source_sync_zero_write", digestTree(root) === missingBefore);
163
+ fs.writeFileSync(graphFile, graphBefore);
164
+ console.log(JSON.stringify({ status: "success", check_count: checks.length, checks }, null, 2));
165
+ } finally { process.env.PATH = originalPath; fs.rmSync(root, { recursive: true, force: true }); }