mirai-graph 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,64 @@ All notable changes to Mirai Graph will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.3.0] - 2026-08-28
8
+
9
+ ### Added
10
+
11
+ - Generic immutable artifact releases in the existing Project Technology
12
+ engine: inspect, release, compare and verify.
13
+ - Public JavaScript API and matching CLI for direct files, directories, ZIP,
14
+ TAR and TAR.GZ bundles.
15
+ - Hash-bound manifests, release lineage, technical comparisons, deterministic
16
+ client-safe exports and portable metadata in `graph/specs`.
17
+ - Transactional release creation with lease, compare-and-swap, atomic
18
+ activation, readback, rollback and idempotent repeat behavior.
19
+
20
+ ### Safety Boundary
21
+
22
+ - Unsafe paths, links, encrypted or nested archives, executable and
23
+ macro-enabled files, normalized duplicates and archive-limit violations fail
24
+ closed.
25
+ - Raw artifacts and private document content do not enter graph metadata.
26
+ - Artifact integrity is not domain approval; consumers retain semantic
27
+ ownership.
28
+ - The public graph schema remains `2.0.0` and the Project Technology
29
+ activation contract remains `1.0.0`.
30
+
31
+ ### Verification
32
+
33
+ - New fixtures cover immutable releases, multiple matters, branching,
34
+ comparisons, tamper detection, concurrency, CAS conflicts, safe archives,
35
+ unsupported formats, read-only zero-write and shared folders.
36
+
37
+ ## [1.2.0] - 2026-08-27
38
+
39
+ ### Added
40
+
41
+ - Portable project continuity in the existing Project Technology `sync`,
42
+ `context` and `verify` operations.
43
+ - Task-boundary evidence compaction into accepted facts, evidence, reusable
44
+ regression cases and bounded proposals in `graph/specs`.
45
+ - Host-local receipts keyed by graph identity, with lease, compare-and-swap,
46
+ backup, atomic write, readback and rollback support for shared non-Git folders.
47
+ - Automatic discovery of relevant previous cases through the existing context
48
+ traversal protocol.
49
+
50
+ ### Safety Boundary
51
+
52
+ - Chat transcripts, secrets, private source, user paths and unverified claims
53
+ are not portable project memory.
54
+ - New goals, architecture changes and ownership changes remain proposals.
55
+ - Generated human context is a projection only and never authorizes writes.
56
+ - The public `graph.json` schema and Project Technology activation contract
57
+ remain `2.0.0` and `1.0.0` respectively.
58
+
59
+ ### Verification
60
+
61
+ - New fixtures cover Git and non-Git projects, two-installation portability,
62
+ idempotence, stale receipts, concurrent-write protection, secret rejection,
63
+ proposal boundaries and two-case method promotion.
64
+
7
65
  ## [1.1.1] - 2026-08-26
8
66
 
9
67
  ### Fixed
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.3.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.3.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,31 @@ 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
+
145
+ Project Technology can also preserve immutable releases of file bundles:
146
+
147
+ ```bash
148
+ mirai-graph technology artifact inspect . --input incoming.zip
149
+ mirai-graph technology artifact release . --input incoming.zip --matter-id agreement-main --direction inbound
150
+ mirai-graph technology artifact release . --input incoming.zip --matter-id agreement-main --direction inbound --apply
151
+ mirai-graph technology artifact compare . --matter-id agreement-main --base-release 20260828-01 --target-release 20260828-02
152
+ mirai-graph technology artifact verify . --matter-id agreement-main --release-id 20260828-02
153
+ ```
154
+
155
+ The protected files remain outside graph data. The graph keeps only safe
156
+ identity, lineage, state, references and checksums. Mirai Graph performs
157
+ technical integrity checks; a domain owner decides what a version means.
158
+
133
159
  Changing operations return a preview unless `--apply` is explicit. See
134
160
  [Project Technology](standard/project-technology.md) for provider/consumer
135
161
  binding, local state and safety boundaries.
@@ -91,6 +91,19 @@ npx mirai-graph technology context . --phase compile --input receipt.json --sele
91
91
  npx mirai-graph technology context . --phase verify --packet context-pack.json --evidence usage-evidence.json
92
92
  ```
93
93
 
94
+ Versioned file bundles use the same Project Technology entrypoint:
95
+
96
+ ```bash
97
+ npx mirai-graph technology artifact inspect . --input incoming.zip
98
+ npx mirai-graph technology artifact release . --input incoming.zip --matter-id agreement-main --direction inbound
99
+ npx mirai-graph technology artifact release . --input incoming.zip --matter-id agreement-main --direction inbound --apply
100
+ npx mirai-graph technology artifact compare . --matter-id agreement-main --base-release 20260828-01 --target-release 20260828-02
101
+ npx mirai-graph technology artifact verify . --matter-id agreement-main --release-id 20260828-02
102
+ ```
103
+
104
+ Inspection, comparison and verification are read-only. Release creation is a
105
+ preview until `--apply` is present.
106
+
94
107
  Omitting `--phase` keeps the original bounded context-discovery behavior.
95
108
  Receipt, selection, context-pack and usage-evidence files are caller-owned
96
109
  inputs and outputs; the command never stores them itself.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirai-graph",
3
- "version": "1.1.1",
3
+ "version": "1.3.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,10 +12,46 @@
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
+ ],
15
49
  "scripts": {
16
- "pretest": "npm run test:project-technology && npm run test:context-traversal && npm run validate:project-technology-schemas",
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",
17
51
  "test:project-technology": "node packages/cli/validate-project-technology.js",
18
52
  "test:context-traversal": "node packages/cli/validate-context-traversal.js",
53
+ "test:project-continuity": "node packages/cli/validate-project-continuity.js",
54
+ "test:artifact-releases": "node packages/cli/validate-artifact-releases.js",
19
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))\"",
20
56
  "validate:graph-manifest": "node packages/cli/validate-graph-manifest-migration.js",
21
57
  "validate:minimal": "node packages/cli/validate-mirai-graph.js examples/minimal-graph",
@@ -87,39 +123,5 @@
87
123
  "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",
88
124
  "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",
89
125
  "release:check": "npm test && git diff --check"
90
- },
91
- "repository": {
92
- "type": "git",
93
- "url": "git+https://github.com/zabarov/mirai-graph.git"
94
- },
95
- "keywords": [
96
- "graph",
97
- "knowledge-graph",
98
- "governance",
99
- "complex-systems",
100
- "context-engineering",
101
- "ai-assisted-work"
102
- ],
103
- "author": "Rim Zabarov",
104
- "files": [
105
- "README.md",
106
- "CHANGELOG.md",
107
- "ROADMAP.md",
108
- "LICENSE",
109
- "LICENSE-DOCS",
110
- "CITATION.cff",
111
- "docs",
112
- "standard",
113
- "schemas",
114
- "benchmarks",
115
- "examples",
116
- "packages",
117
- "profiles",
118
- "pilots",
119
- "playground",
120
- "publications",
121
- "releases",
122
- "templates",
123
- ".github"
124
- ]
125
- }
126
+ }
127
+ }
File without changes
@@ -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) {
@@ -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> [repository] [options] [--apply]");
28
+ console.error(" mirai-graph technology <enable|sync|connect|disconnect|provide|disable|repair|artifact> [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,20 +13,31 @@ 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>");
19
21
  console.error(" --phase verify --packet <context-pack.json> --evidence <usage-evidence.json>");
22
+ console.error("Artifacts: artifact inspect <repo> --input <path>");
23
+ console.error(" artifact release <repo> --input <path> --matter-id <id> --direction inbound|internal|outbound [--parent-release <id>] [--client-note <text>] [--create-export] [--apply]");
24
+ console.error(" artifact compare <repo> --matter-id <id> --base-release <id> --target-release <id>");
25
+ console.error(" artifact verify <repo> --matter-id <id> --release-id <id>");
20
26
  }
21
27
 
22
28
  function parse(argv) {
23
29
  if (!argv.length) return null;
24
30
  const operation = argv.shift();
31
+ let artifactAction = null;
32
+ if (operation === "artifact") {
33
+ artifactAction = argv.shift() || null;
34
+ if (!new Set(["inspect", "release", "compare", "verify"]).has(artifactAction)) throw new Error("artifact requires inspect, release, compare or verify");
35
+ }
25
36
  let repository = ".";
26
37
  if (argv[0] && !argv[0].startsWith("--")) repository = argv.shift();
27
38
  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"]);
29
- const flags = new Set(["--apply", "--refresh-binding", "--significant-work"]);
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"]);
40
+ const flags = new Set(["--apply", "--refresh-binding", "--significant-work", "--create-export"]);
30
41
  const names = {
31
42
  "--task": "task", "--source": "source", "--target-id": "targetId",
32
43
  "--semantic-digest": "semanticDigest", "--provider-revision": "providerRevision",
@@ -36,6 +47,13 @@ function parse(argv) {
36
47
  "--selection": "selectionFile", "--packet": "packetFile", "--evidence": "evidenceFile",
37
48
  "--selector": "selector", "--reason": "reason", "--confidence": "confidence",
38
49
  "--context-budget": "contextBudget",
50
+ "--boundary": "boundary", "--expected-graph-digest": "expectedGraphDigest",
51
+ "--receipt-digest": "receiptDigest", "--state-root": "stateRoot",
52
+ "--matter-id": "matterId", "--direction": "direction", "--artifact-root": "artifactRoot",
53
+ "--release-date": "releaseDate", "--release-id": "releaseId",
54
+ "--parent-release": "parentReleaseIds", "--base-release": "baseReleaseId",
55
+ "--target-release": "targetReleaseId", "--client-note": "clientNote",
56
+ "--create-export": "createExport",
39
57
  };
40
58
  while (argv.length) {
41
59
  const token = argv.shift();
@@ -44,9 +62,11 @@ function parse(argv) {
44
62
  if (!argv.length) throw new Error(`missing value for ${token}`);
45
63
  const value = argv.shift();
46
64
  if (token === "--select") options.selectedIds = [...(options.selectedIds || []), value];
65
+ else if (token === "--parent-release") options.parentReleaseIds = [...(options.parentReleaseIds || []), value];
47
66
  else options[names[token]] = value;
48
67
  } else throw new Error(`unsupported argument ${token}`);
49
68
  }
69
+ if (artifactAction) options.artifactAction = artifactAction;
50
70
  return { operation, repository: path.resolve(repository), options };
51
71
  }
52
72
 
@@ -57,6 +77,11 @@ function readJsonFile(file) {
57
77
  }
58
78
 
59
79
  function hydrateContextOptions(request) {
80
+ if (request.operation === "sync" && request.options.boundary) {
81
+ if (request.options.boundary !== "task_start" && !request.options.evidenceFile) throw new Error(`${request.options.boundary} requires --evidence`);
82
+ if (request.options.evidenceFile) request.options.continuityEvidence = readJsonFile(request.options.evidenceFile);
83
+ return;
84
+ }
60
85
  if (request.operation !== "context" || !request.options.phase) return;
61
86
  const phase = request.options.phase;
62
87
  if (!new Set(["discover", "expand", "compile", "verify"]).has(phase)) throw new Error(`unsupported context phase ${phase}`);
File without changes
File without changes
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const crypto = require("crypto");
6
+ const fs = require("fs");
7
+ const os = require("os");
8
+ const path = require("path");
9
+ const zlib = require("zlib");
10
+ const technology = require("../project-technology");
11
+
12
+ let passed = 0;
13
+ const failures = [];
14
+ function check(id, condition, detail = null) {
15
+ if (condition) passed += 1;
16
+ else failures.push({ id, detail });
17
+ }
18
+
19
+ function writeJson(file, value) {
20
+ fs.mkdirSync(path.dirname(file), { recursive: true });
21
+ fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
22
+ }
23
+
24
+ function repository(root, id) {
25
+ const repo = path.join(root, id);
26
+ fs.mkdirSync(repo, { recursive: true });
27
+ writeJson(path.join(repo, "graph.json"), {
28
+ $schema: "https://mirai-graph.dev/schemas/graph-manifest.schema.json",
29
+ format: "mirai-graph",
30
+ schema_version: "2.0.0",
31
+ id,
32
+ aliases: [],
33
+ title: id,
34
+ scope: "repository",
35
+ kind: "project_graph",
36
+ owner: "fixture",
37
+ profiles: [],
38
+ graph: { root: "graph", source_of_truth: ["graph/specs"], objects: [], relations: [], schemas: [], generated: [], raw_sources: [] },
39
+ imports: [],
40
+ extensions: { "mirai.project_technology": { contract_version: "1.0.0", enabled: true, context_policy: "task_scoped", source_boundary: "hybrid_sot" } },
41
+ });
42
+ return repo;
43
+ }
44
+
45
+ function octal(value, width) {
46
+ return Buffer.from(value.toString(8).padStart(width - 1, "0") + "\0");
47
+ }
48
+
49
+ function tar(entries) {
50
+ const output = [];
51
+ for (const [name, content] of Object.entries(entries)) {
52
+ const bytes = Buffer.from(content);
53
+ const header = Buffer.alloc(512);
54
+ Buffer.from(name).copy(header, 0, 0, 100);
55
+ octal(0o600, 8).copy(header, 100);
56
+ octal(0, 8).copy(header, 108);
57
+ octal(0, 8).copy(header, 116);
58
+ octal(bytes.length, 12).copy(header, 124);
59
+ octal(0, 12).copy(header, 136);
60
+ Buffer.from(" ").copy(header, 148);
61
+ header[156] = "0".charCodeAt(0);
62
+ Buffer.from("ustar\0").copy(header, 257);
63
+ const checksum = header.reduce((sum, byte) => sum + byte, 0);
64
+ octal(checksum, 8).copy(header, 148);
65
+ output.push(header, bytes, Buffer.alloc((512 - (bytes.length % 512)) % 512));
66
+ }
67
+ output.push(Buffer.alloc(1024));
68
+ return Buffer.concat(output);
69
+ }
70
+
71
+ function rawZip(entries) {
72
+ const localParts = [];
73
+ const centralParts = [];
74
+ let offset = 0;
75
+ for (const entry of entries) {
76
+ const name = Buffer.from(entry.name);
77
+ const content = Buffer.from(entry.content || "");
78
+ const compressed = zlib.deflateRawSync(content);
79
+ const flags = entry.flags || 0x0800;
80
+ const local = Buffer.alloc(30);
81
+ local.writeUInt32LE(0x04034b50, 0);
82
+ local.writeUInt16LE(20, 4);
83
+ local.writeUInt16LE(flags, 6);
84
+ local.writeUInt16LE(8, 8);
85
+ local.writeUInt32LE(compressed.length, 18);
86
+ local.writeUInt32LE(entry.claimedSize || content.length, 22);
87
+ local.writeUInt16LE(name.length, 26);
88
+ localParts.push(local, name, compressed);
89
+ const central = Buffer.alloc(46);
90
+ central.writeUInt32LE(0x02014b50, 0);
91
+ central.writeUInt16LE(0x0314, 4);
92
+ central.writeUInt16LE(20, 6);
93
+ central.writeUInt16LE(flags, 8);
94
+ central.writeUInt16LE(8, 10);
95
+ central.writeUInt32LE(compressed.length, 20);
96
+ central.writeUInt32LE(entry.claimedSize || content.length, 24);
97
+ central.writeUInt16LE(name.length, 28);
98
+ central.writeUInt32LE(entry.externalAttributes || 0, 38);
99
+ central.writeUInt32LE(offset, 42);
100
+ centralParts.push(central, name);
101
+ offset += local.length + name.length + compressed.length;
102
+ }
103
+ const centralBytes = Buffer.concat(centralParts);
104
+ const end = Buffer.alloc(22);
105
+ end.writeUInt32LE(0x06054b50, 0);
106
+ end.writeUInt16LE(entries.length, 8);
107
+ end.writeUInt16LE(entries.length, 10);
108
+ end.writeUInt32LE(centralBytes.length, 12);
109
+ end.writeUInt32LE(offset, 16);
110
+ return Buffer.concat([...localParts, centralBytes, end]);
111
+ }
112
+
113
+ function repoSnapshot(repo) {
114
+ const files = [];
115
+ function walk(current) {
116
+ for (const name of fs.readdirSync(current).sort()) {
117
+ const absolute = path.join(current, name);
118
+ const stat = fs.lstatSync(absolute);
119
+ if (stat.isDirectory()) walk(absolute);
120
+ else if (stat.isFile()) files.push([path.relative(repo, absolute), crypto.createHash("sha256").update(fs.readFileSync(absolute)).digest("hex")]);
121
+ }
122
+ }
123
+ walk(repo);
124
+ return JSON.stringify(files);
125
+ }
126
+
127
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "mirai-artifact-release-"));
128
+ const repo = repository(root, "artifact-project");
129
+ const input = path.join(root, "incoming");
130
+ fs.mkdirSync(input);
131
+ fs.writeFileSync(path.join(input, "contract.txt"), "version one\n");
132
+
133
+ const inspectBefore = repoSnapshot(repo);
134
+ const inspected = technology.inspectArtifactBundle(repo, input);
135
+ check("inspect_success", inspected.status === "success" && inspected.bundle.file_count === 1, inspected);
136
+ check("inspect_zero_write", repoSnapshot(repo) === inspectBefore);
137
+
138
+ const preview = technology.createArtifactRelease(repo, input, {
139
+ matterId: "contract-main",
140
+ direction: "inbound",
141
+ artifactRoot: "documents/matters",
142
+ releaseDate: "20260828",
143
+ });
144
+ check("release_preview", preview.status === "preview" && preview.operation_mode === "preview" && preview.changed === false, preview);
145
+ check("preview_zero_write", repoSnapshot(repo) === inspectBefore);
146
+
147
+ const first = technology.createArtifactRelease(repo, input, {
148
+ matterId: "contract-main",
149
+ direction: "inbound",
150
+ artifactRoot: "documents/matters",
151
+ releaseDate: "20260828",
152
+ apply: true,
153
+ });
154
+ check("first_release_created", first.status === "success" && first.changed && first.release_id === "20260828-01", first);
155
+ check("first_release_verified", technology.verifyArtifactRelease(repo, first.release_id, { matterId: "contract-main" }).status === "success");
156
+
157
+ const repeated = technology.createArtifactRelease(repo, input, {
158
+ matterId: "contract-main",
159
+ direction: "inbound",
160
+ artifactRoot: "documents/matters",
161
+ releaseDate: "20260828",
162
+ apply: true,
163
+ });
164
+ check("same_release_idempotent", repeated.status === "success" && repeated.changed === false && repeated.release_id === first.release_id, repeated);
165
+
166
+ fs.writeFileSync(path.join(input, "contract.txt"), "version two\n");
167
+ fs.writeFileSync(path.join(input, "appendix.txt"), "appendix\n");
168
+ const second = technology.createArtifactRelease(repo, input, {
169
+ matterId: "contract-main",
170
+ direction: "outbound",
171
+ artifactRoot: "documents/matters",
172
+ releaseDate: "20260828",
173
+ parentReleaseIds: [first.release_id],
174
+ clientNote: "Обновлены условия и добавлено приложение.",
175
+ createExport: true,
176
+ apply: true,
177
+ });
178
+ check("second_release_numbered", second.status === "success" && second.release_id === "20260828-02", second);
179
+ check("client_export_created", Boolean(second.export_ref && fs.existsSync(path.join(repo, second.export_ref))), second);
180
+ const compared = technology.compareArtifactReleases(repo, first.release_id, second.release_id, { matterId: "contract-main" });
181
+ check("comparison_detects_changes", compared.status === "success" && compared.comparison.added.includes("appendix.txt") && compared.comparison.changed.includes("contract.txt"), compared);
182
+
183
+ const readOnlySnapshot = repoSnapshot(repo);
184
+ check("verify_success", technology.verifyArtifactRelease(repo, second.release_id, { matterId: "contract-main" }).status === "success");
185
+ check("compare_and_verify_zero_write", repoSnapshot(repo) === readOnlySnapshot);
186
+ const exportedInspect = technology.inspectArtifactBundle(repo, path.join(repo, second.export_ref));
187
+ check("generated_zip_is_readable", exportedInspect.status === "success" && exportedInspect.bundle.file_count === 3, exportedInspect);
188
+
189
+ const secondFile = path.join(repo, "documents/matters/contract-main/releases/20260828-02/package/contract.txt");
190
+ const secondBytes = fs.readFileSync(secondFile);
191
+ fs.writeFileSync(secondFile, "tampered\n");
192
+ check("tampered_file_blocked", technology.verifyArtifactRelease(repo, second.release_id, { matterId: "contract-main" }).blockers.includes("artifact_package_file_digest_mismatch"));
193
+ fs.writeFileSync(secondFile, secondBytes);
194
+
195
+ check("missing_parent_blocked", technology.createArtifactRelease(repo, input, {
196
+ matterId: "contract-main", direction: "internal", artifactRoot: "documents/matters",
197
+ releaseDate: "20260829", parentReleaseIds: ["20260101-01"], apply: true,
198
+ }).blockers.includes("artifact_parent_release_missing"));
199
+ check("stale_cas_blocked", technology.createArtifactRelease(repo, input, {
200
+ matterId: "contract-main", direction: "internal", artifactRoot: "documents/matters",
201
+ releaseDate: "20260829", expectedGraphDigest: "sha256:" + "0".repeat(64), apply: true,
202
+ }).blockers.includes("artifact_registry_compare_and_swap_conflict"));
203
+
204
+ const lease = path.join(repo, "documents/matters/contract-main/.artifact-release.lock");
205
+ fs.writeFileSync(lease, "busy");
206
+ check("concurrent_lease_blocked", technology.createArtifactRelease(repo, input, {
207
+ matterId: "contract-main", direction: "internal", artifactRoot: "documents/matters",
208
+ releaseDate: "20260829", apply: true,
209
+ }).blockers.includes("artifact_release_lease_conflict"));
210
+ fs.unlinkSync(lease);
211
+
212
+ const other = technology.createArtifactRelease(repo, input, {
213
+ matterId: "nda-secondary", direction: "inbound", artifactRoot: "documents/matters",
214
+ releaseDate: "20260828", apply: true,
215
+ });
216
+ check("multiple_matters_independent", other.status === "success" && other.release_id === "20260828-01", other);
217
+
218
+ const tarPath = path.join(root, "safe.tar");
219
+ fs.writeFileSync(tarPath, tar({ "folder/agreement.txt": "safe\n" }));
220
+ check("tar_supported", technology.inspectArtifactBundle(repo, tarPath).status === "success");
221
+ const tgzPath = path.join(root, "safe.tar.gz");
222
+ fs.writeFileSync(tgzPath, zlib.gzipSync(fs.readFileSync(tarPath)));
223
+ check("tar_gz_supported", technology.inspectArtifactBundle(repo, tgzPath).status === "success");
224
+
225
+ const unsafeTar = path.join(root, "unsafe.tar");
226
+ fs.writeFileSync(unsafeTar, tar({ "../escape.txt": "unsafe\n" }));
227
+ check("traversal_blocked", technology.inspectArtifactBundle(repo, unsafeTar).blockers.includes("artifact_path_unsafe"));
228
+ const unsafeZip = path.join(root, "unsafe.zip");
229
+ fs.writeFileSync(unsafeZip, rawZip([{ name: "../escape.txt", content: "unsafe" }]));
230
+ check("zip_traversal_blocked", technology.inspectArtifactBundle(repo, unsafeZip).blockers.includes("artifact_path_unsafe"));
231
+ const encryptedZip = path.join(root, "encrypted.zip");
232
+ fs.writeFileSync(encryptedZip, rawZip([{ name: "contract.txt", content: "secret", flags: 0x0801 }]));
233
+ check("encrypted_zip_blocked", technology.inspectArtifactBundle(repo, encryptedZip).blockers.includes("artifact_archive_encrypted"));
234
+ const duplicateZip = path.join(root, "duplicate.zip");
235
+ fs.writeFileSync(duplicateZip, rawZip([{ name: "Contract.txt", content: "a" }, { name: "contract.txt", content: "b" }]));
236
+ check("normalized_duplicate_blocked", technology.inspectArtifactBundle(repo, duplicateZip).blockers.includes("artifact_normalized_path_duplicate"));
237
+ const symlinkZip = path.join(root, "symlink.zip");
238
+ fs.writeFileSync(symlinkZip, rawZip([{ name: "contract-link", content: "target", externalAttributes: (0o120777 << 16) >>> 0 }]));
239
+ check("zip_symlink_blocked", technology.inspectArtifactBundle(repo, symlinkZip).blockers.includes("artifact_symlink_blocked"));
240
+ const bombZip = path.join(root, "bomb.zip");
241
+ fs.writeFileSync(bombZip, rawZip([{ name: "large.txt", content: "a", claimedSize: 50 * 1024 * 1024 }]));
242
+ check("compression_ratio_blocked", technology.inspectArtifactBundle(repo, bombZip).blockers.includes("artifact_compression_ratio_limit_exceeded"));
243
+ const executable = path.join(root, "payload.exe");
244
+ fs.writeFileSync(executable, "not executable");
245
+ check("executable_blocked", technology.inspectArtifactBundle(repo, executable).blockers.includes("artifact_executable_or_macro_blocked"));
246
+ const unsupported = path.join(root, "bundle.7z");
247
+ fs.writeFileSync(unsupported, "fixture");
248
+ check("unsupported_archive_blocked", technology.inspectArtifactBundle(repo, unsupported).blockers.includes("artifact_archive_provider_required"));
249
+
250
+ if (process.platform !== "win32") {
251
+ const symlink = path.join(root, "input-link");
252
+ fs.symlinkSync(input, symlink);
253
+ check("input_symlink_blocked", technology.inspectArtifactBundle(repo, symlink).blockers.includes("artifact_input_symlink_blocked"));
254
+ }
255
+
256
+ const registryText = fs.readFileSync(path.join(repo, "graph/specs/artifact-releases.json"), "utf8");
257
+ check("registry_has_no_host_path", !registryText.includes(root));
258
+ check("registry_has_no_document_content", !registryText.includes("version two") && !registryText.includes("Обновлены условия"));
259
+
260
+ const folder = repository(root, "shared-folder");
261
+ const folderInput = path.join(root, "folder-input.txt");
262
+ fs.writeFileSync(folderInput, "same bytes\n");
263
+ const folderRelease = technology.createArtifactRelease(folder, folderInput, {
264
+ matterId: "matter-one", direction: "inbound", releaseDate: "20260828", apply: true,
265
+ });
266
+ check("plain_folder_supported", folderRelease.status === "success", folderRelease);
267
+ const folderRepeat = technology.createArtifactRelease(folder, folderInput, {
268
+ matterId: "matter-one", direction: "inbound", releaseDate: "20260828", apply: true,
269
+ });
270
+ check("plain_folder_repeat_byte_identical", folderRepeat.changed === false);
271
+
272
+ const report = {
273
+ schema_version: "1.0.0",
274
+ operation_id: "mirai.project_technology.artifact_release_validation",
275
+ status: failures.length ? "fail" : "success",
276
+ passed,
277
+ failed: failures.length,
278
+ failures,
279
+ };
280
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
281
+ fs.rmSync(root, { recursive: true, force: true });
282
+ process.exitCode = failures.length ? 1 : 0;
File without changes
File without changes