@apexops/kernel 0.10.0-next.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.
Files changed (110) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/benchmark.d.ts +14 -0
  3. package/dist/benchmark.d.ts.map +1 -0
  4. package/dist/benchmark.js +29 -0
  5. package/dist/benchmark.js.map +1 -0
  6. package/dist/canonical.d.ts +10 -0
  7. package/dist/canonical.d.ts.map +1 -0
  8. package/dist/canonical.js +52 -0
  9. package/dist/canonical.js.map +1 -0
  10. package/dist/content-cache.d.ts +18 -0
  11. package/dist/content-cache.d.ts.map +1 -0
  12. package/dist/content-cache.js +94 -0
  13. package/dist/content-cache.js.map +1 -0
  14. package/dist/event-journal.d.ts +20 -0
  15. package/dist/event-journal.d.ts.map +1 -0
  16. package/dist/event-journal.js +136 -0
  17. package/dist/event-journal.js.map +1 -0
  18. package/dist/evidence-store.d.ts +42 -0
  19. package/dist/evidence-store.d.ts.map +1 -0
  20. package/dist/evidence-store.js +151 -0
  21. package/dist/evidence-store.js.map +1 -0
  22. package/dist/files.d.ts +6 -0
  23. package/dist/files.d.ts.map +1 -0
  24. package/dist/files.js +33 -0
  25. package/dist/files.js.map +1 -0
  26. package/dist/gates.d.ts +6 -0
  27. package/dist/gates.d.ts.map +1 -0
  28. package/dist/gates.js +25 -0
  29. package/dist/gates.js.map +1 -0
  30. package/dist/improvement-store.d.ts +48 -0
  31. package/dist/improvement-store.d.ts.map +1 -0
  32. package/dist/improvement-store.js +263 -0
  33. package/dist/improvement-store.js.map +1 -0
  34. package/dist/index.d.ts +18 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +18 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/lease-store.d.ts +23 -0
  39. package/dist/lease-store.d.ts.map +1 -0
  40. package/dist/lease-store.js +108 -0
  41. package/dist/lease-store.js.map +1 -0
  42. package/dist/object-store.d.ts +10 -0
  43. package/dist/object-store.d.ts.map +1 -0
  44. package/dist/object-store.js +86 -0
  45. package/dist/object-store.js.map +1 -0
  46. package/dist/project-store.d.ts +30 -0
  47. package/dist/project-store.d.ts.map +1 -0
  48. package/dist/project-store.js +72 -0
  49. package/dist/project-store.js.map +1 -0
  50. package/dist/run-repository.d.ts +38 -0
  51. package/dist/run-repository.d.ts.map +1 -0
  52. package/dist/run-repository.js +151 -0
  53. package/dist/run-repository.js.map +1 -0
  54. package/dist/tasks.d.ts +14 -0
  55. package/dist/tasks.d.ts.map +1 -0
  56. package/dist/tasks.js +96 -0
  57. package/dist/tasks.js.map +1 -0
  58. package/dist/test/canonical.test.d.ts +2 -0
  59. package/dist/test/canonical.test.d.ts.map +1 -0
  60. package/dist/test/canonical.test.js +18 -0
  61. package/dist/test/canonical.test.js.map +1 -0
  62. package/dist/test/improvement-store.test.d.ts +2 -0
  63. package/dist/test/improvement-store.test.d.ts.map +1 -0
  64. package/dist/test/improvement-store.test.js +130 -0
  65. package/dist/test/improvement-store.test.js.map +1 -0
  66. package/dist/test/journal-lease.test.d.ts +2 -0
  67. package/dist/test/journal-lease.test.d.ts.map +1 -0
  68. package/dist/test/journal-lease.test.js +124 -0
  69. package/dist/test/journal-lease.test.js.map +1 -0
  70. package/dist/test/policies.test.d.ts +2 -0
  71. package/dist/test/policies.test.d.ts.map +1 -0
  72. package/dist/test/policies.test.js +115 -0
  73. package/dist/test/policies.test.js.map +1 -0
  74. package/dist/test/run-repository.test.d.ts +2 -0
  75. package/dist/test/run-repository.test.d.ts.map +1 -0
  76. package/dist/test/run-repository.test.js +100 -0
  77. package/dist/test/run-repository.test.js.map +1 -0
  78. package/dist/test/storage.test.d.ts +2 -0
  79. package/dist/test/storage.test.d.ts.map +1 -0
  80. package/dist/test/storage.test.js +41 -0
  81. package/dist/test/storage.test.js.map +1 -0
  82. package/dist/test/workflow-engine.test.d.ts +2 -0
  83. package/dist/test/workflow-engine.test.d.ts.map +1 -0
  84. package/dist/test/workflow-engine.test.js +108 -0
  85. package/dist/test/workflow-engine.test.js.map +1 -0
  86. package/dist/test/workflow.test.d.ts +2 -0
  87. package/dist/test/workflow.test.d.ts.map +1 -0
  88. package/dist/test/workflow.test.js +119 -0
  89. package/dist/test/workflow.test.js.map +1 -0
  90. package/dist/test/writer-transfer.test.d.ts +2 -0
  91. package/dist/test/writer-transfer.test.d.ts.map +1 -0
  92. package/dist/test/writer-transfer.test.js +355 -0
  93. package/dist/test/writer-transfer.test.js.map +1 -0
  94. package/dist/validator-registry.d.ts +22 -0
  95. package/dist/validator-registry.d.ts.map +1 -0
  96. package/dist/validator-registry.js +42 -0
  97. package/dist/validator-registry.js.map +1 -0
  98. package/dist/workflow-engine.d.ts +72 -0
  99. package/dist/workflow-engine.d.ts.map +1 -0
  100. package/dist/workflow-engine.js +269 -0
  101. package/dist/workflow-engine.js.map +1 -0
  102. package/dist/workflow-validator-ownership.d.ts +12 -0
  103. package/dist/workflow-validator-ownership.d.ts.map +1 -0
  104. package/dist/workflow-validator-ownership.js +163 -0
  105. package/dist/workflow-validator-ownership.js.map +1 -0
  106. package/dist/writer-transfer.d.ts +78 -0
  107. package/dist/writer-transfer.d.ts.map +1 -0
  108. package/dist/writer-transfer.js +313 -0
  109. package/dist/writer-transfer.js.map +1 -0
  110. package/package.json +39 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal-lease.test.d.ts","sourceRoot":"","sources":["../../src/test/journal-lease.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,124 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile, symlink, unlink, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import { EventJournal, LeaseStore } from "../index.js";
7
+ const projectId = "demo";
8
+ const runId = "run-1";
9
+ test("event journal enforces head CAS, epoch, and verifies replay", async () => {
10
+ const directory = await mkdtemp(join(tmpdir(), "apex-journal-"));
11
+ const journal = new EventJournal(directory);
12
+ const first = await journal.append({
13
+ eventId: "event-1",
14
+ projectId,
15
+ runId,
16
+ type: "started",
17
+ timestamp: "2026-01-01T00:00:00.000Z",
18
+ ownerEpoch: 2,
19
+ expectedHead: null,
20
+ payload: { value: 1 },
21
+ });
22
+ await assert.rejects(journal.append({
23
+ eventId: "event-2",
24
+ projectId,
25
+ runId,
26
+ type: "changed",
27
+ timestamp: "2026-01-01T00:01:00.000Z",
28
+ ownerEpoch: 2,
29
+ expectedHead: null,
30
+ payload: {},
31
+ }), /Stale journal head/);
32
+ await assert.rejects(journal.append({
33
+ eventId: "event-2",
34
+ projectId,
35
+ runId,
36
+ type: "changed",
37
+ timestamp: "2026-01-01T00:01:00.000Z",
38
+ ownerEpoch: 1,
39
+ expectedHead: first.hash,
40
+ payload: {},
41
+ }), /Stale owner epoch/);
42
+ const second = await journal.append({
43
+ eventId: "event-2",
44
+ projectId,
45
+ runId,
46
+ type: "changed",
47
+ timestamp: "2026-01-01T00:01:00.000Z",
48
+ ownerEpoch: 2,
49
+ expectedHead: first.hash,
50
+ payload: { value: 2 },
51
+ });
52
+ assert.equal(second.sequence, 2);
53
+ assert.equal((await journal.replay()).length, 2);
54
+ });
55
+ test("event journal detects corruption", async () => {
56
+ const directory = await mkdtemp(join(tmpdir(), "apex-journal-"));
57
+ const journal = new EventJournal(directory);
58
+ await journal.append({
59
+ eventId: "event-1",
60
+ projectId,
61
+ runId,
62
+ type: "started",
63
+ timestamp: "2026-01-01T00:00:00.000Z",
64
+ ownerEpoch: 1,
65
+ expectedHead: null,
66
+ payload: { value: 1 },
67
+ });
68
+ const path = join(directory, "0000000000000001.json");
69
+ const event = JSON.parse(await readFile(path, "utf8"));
70
+ event.payload = { value: 99 };
71
+ await writeFile(path, JSON.stringify(event));
72
+ await assert.rejects(journal.replay(), /Corrupt journal payload/);
73
+ });
74
+ test("event journal rejects symlinked and oversized event files", async () => {
75
+ const directory = await mkdtemp(join(tmpdir(), "apex-journal-safe-read-"));
76
+ const journal = new EventJournal(directory);
77
+ await journal.append({
78
+ eventId: "event-1",
79
+ projectId,
80
+ runId,
81
+ type: "started",
82
+ timestamp: "2026-01-01T00:00:00.000Z",
83
+ ownerEpoch: 1,
84
+ expectedHead: null,
85
+ payload: { value: 1 },
86
+ });
87
+ const eventPath = join(directory, "0000000000000001.json");
88
+ const outside = join(directory, "outside.json");
89
+ await writeFile(outside, await readFile(eventPath));
90
+ await unlink(eventPath);
91
+ await symlink(outside, eventPath);
92
+ await assert.rejects(journal.replay(), /unsafe file/);
93
+ await unlink(eventPath);
94
+ await writeFile(eventPath, Buffer.alloc(1024 * 1024 + 1, "x"));
95
+ await assert.rejects(journal.replay(), /unsafe file/);
96
+ });
97
+ test("lease expiry permits reacquisition with a new epoch", async () => {
98
+ const root = await mkdtemp(join(tmpdir(), "apex-lease-"));
99
+ let now = new Date("2026-01-01T00:00:00.000Z");
100
+ const leases = new LeaseStore(join(root, "lease.json"), () => now);
101
+ const first = await leases.acquire("owner-a", 1_000);
102
+ await assert.rejects(leases.acquire("owner-b", 1_000), /held by owner-a/);
103
+ now = new Date("2026-01-01T00:00:02.000Z");
104
+ await assert.rejects(leases.heartbeat("owner-a", first.ownerEpoch, 1_000), /expired/);
105
+ const second = await leases.acquire("owner-b", 1_000);
106
+ assert.equal(second.ownerEpoch, first.ownerEpoch + 1);
107
+ await assert.rejects(leases.release("owner-a", first.ownerEpoch), /Stale lease/);
108
+ await leases.release("owner-b", second.ownerEpoch);
109
+ const third = await leases.acquire("owner-c", 1_000);
110
+ assert.equal(third.ownerEpoch, second.ownerEpoch + 1);
111
+ });
112
+ test("lease acquisition can retry a run-authorized epoch without drifting", async () => {
113
+ const root = await mkdtemp(join(tmpdir(), "apex-exact-lease-"));
114
+ let now = new Date("2026-01-01T00:00:00.000Z");
115
+ const leases = new LeaseStore(join(root, "lease.json"), () => now);
116
+ const first = await leases.acquireAtEpoch("owner-a", 2, 1_000);
117
+ assert.equal(first.ownerEpoch, 2);
118
+ await leases.release("owner-a", 2);
119
+ const retried = await leases.acquireAtEpoch("owner-a", 2, 1_000);
120
+ assert.equal(retried.ownerEpoch, 2);
121
+ now = new Date("2026-01-01T00:00:02.000Z");
122
+ await assert.rejects(leases.acquireAtEpoch("owner-b", 1, 1_000), /cannot regress/);
123
+ });
124
+ //# sourceMappingURL=journal-lease.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal-lease.test.js","sourceRoot":"","sources":["../../src/test/journal-lease.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=policies.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policies.test.d.ts","sourceRoot":"","sources":["../../src/test/policies.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,115 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, mkdir, symlink } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import { benchmarkKernel, ContentCache, contentCacheKey, EvidencePolicy, EvidenceStore, ObjectStore, } from "../index.js";
7
+ test("evidence redacts structural secrets, deduplicates, enforces budgets, and quarantines uncertain content", async () => {
8
+ const root = await mkdtemp(join(tmpdir(), "apex-evidence-"));
9
+ const policy = new EvidencePolicy({
10
+ kinds: {
11
+ report: { contentTypes: ["application/json"], maxBytes: 200, retention: "immutable" },
12
+ log: { contentTypes: ["text/plain"], maxBytes: 200, retention: "optional" },
13
+ blob: { contentTypes: ["application/octet-stream"], maxBytes: 200, retention: "optional" },
14
+ },
15
+ });
16
+ const store = new EvidenceStore(root, policy);
17
+ const accepted = await store.accept({
18
+ kind: "report",
19
+ contentType: "application/json",
20
+ value: { nested: { apiKey: "secret-value" }, value: 1 },
21
+ required: true,
22
+ });
23
+ assert.equal(accepted.status, "accepted");
24
+ assert.equal(accepted.redacted, true);
25
+ assert.equal(accepted.retention, "immutable");
26
+ const object = await new ObjectStore(root).getJson(accepted.hash);
27
+ assert.equal(object.nested.apiKey, "[REDACTED]");
28
+ const duplicate = await store.accept({
29
+ kind: "report",
30
+ contentType: "application/json",
31
+ value: { nested: { apiKey: "different" }, value: 1 },
32
+ required: true,
33
+ });
34
+ assert.equal(duplicate.hash, accepted.hash);
35
+ const jsonFile = await store.accept({
36
+ kind: "report",
37
+ contentType: "application/json",
38
+ value: Buffer.from(JSON.stringify({ clientSecret: "file-secret", value: 1 })),
39
+ required: true,
40
+ });
41
+ assert.equal(jsonFile.status, "accepted");
42
+ assert.equal(jsonFile.redacted, true);
43
+ assert.equal((await new ObjectStore(root).getJson(jsonFile.hash)).clientSecret, "[REDACTED]");
44
+ const quarantined = await store.accept({
45
+ kind: "log",
46
+ contentType: "text/plain",
47
+ value: Buffer.from("github_pat_abcdefghijklmnopqrstuvwxyz123456"),
48
+ required: false,
49
+ });
50
+ assert.equal(quarantined.status, "quarantined");
51
+ assert.match(quarantined.quarantinePath, /\.apex\/local\/quarantine/);
52
+ const malformed = await store.accept({
53
+ kind: "log",
54
+ contentType: "text/plain",
55
+ value: Buffer.from("Authorization: Bearer abcdefghijklmnopqrstuvwxyz"),
56
+ required: false,
57
+ });
58
+ assert.equal(malformed.status, "quarantined");
59
+ const binary = await store.accept({
60
+ kind: "blob",
61
+ contentType: "application/octet-stream",
62
+ value: Buffer.from([0, 1, 2, 3]),
63
+ required: false,
64
+ });
65
+ assert.deepEqual(binary.reasons, ["unknown-binary"]);
66
+ await assert.rejects(store.accept({
67
+ kind: "report",
68
+ contentType: "application/json",
69
+ value: Buffer.from("{not-json"),
70
+ required: true,
71
+ }), /high-risk content/);
72
+ await assert.rejects(store.accept({
73
+ kind: "report",
74
+ contentType: "application/json",
75
+ value: { value: "x".repeat(300) },
76
+ required: true,
77
+ }), /byte budget/);
78
+ await assert.rejects(store.accept({ kind: "report", contentType: "text/plain", value: Buffer.from("x"), required: true }), /not allowed/);
79
+ });
80
+ test("telemetry consent is separate, exportable, and deletable", async () => {
81
+ const root = await mkdtemp(join(tmpdir(), "apex-telemetry-"));
82
+ const store = new EvidenceStore(root, new EvidencePolicy({ kinds: {} }));
83
+ await store.setTelemetryConsent(true);
84
+ assert.deepEqual(await store.exportTelemetry(), { consent: true });
85
+ await store.deleteTelemetry();
86
+ assert.equal(await store.exportTelemetry(), null);
87
+ });
88
+ test("content cache keys canonical inputs, invalidates dependencies, and rejects symlink roots", async () => {
89
+ const root = await mkdtemp(join(tmpdir(), "apex-cache-"));
90
+ const cache = new ContentCache(root);
91
+ const first = { dependencies: { source: "a" }, config: { z: 1, a: 2 }, toolchain: { version: "1" } };
92
+ const reordered = { dependencies: { source: "a" }, config: { a: 2, z: 1 }, toolchain: { version: "1" } };
93
+ assert.equal(contentCacheKey(first), contentCacheKey(reordered));
94
+ await cache.set(first, { result: 1 });
95
+ assert.deepEqual(await cache.get(first), { result: 1 });
96
+ assert.equal(await cache.invalidate("other"), 0);
97
+ assert.equal(await cache.invalidate("source"), 1);
98
+ assert.equal(await cache.get(first), null);
99
+ const linkedRoot = await mkdtemp(join(tmpdir(), "apex-cache-link-"));
100
+ const outside = await mkdtemp(join(tmpdir(), "apex-cache-outside-"));
101
+ await mkdir(join(linkedRoot, ".apex", "cache"), { recursive: true });
102
+ await symlink(outside, join(linkedRoot, ".apex", "cache", "content"));
103
+ await assert.rejects(new ContentCache(linkedRoot).set(first, { result: 1 }), /root must not be a symlink/);
104
+ });
105
+ test("benchmark report has deterministic shape and percentile samples", () => {
106
+ const report = benchmarkKernel(20);
107
+ assert.deepEqual(Object.keys(report), ["schemaVersion", "iterations", "append", "replay", "status"]);
108
+ assert.equal(report.iterations, 20);
109
+ for (const metric of [report.append, report.replay, report.status]) {
110
+ assert.equal(metric.samples, 20);
111
+ assert(metric.p50Ms >= 0);
112
+ assert(metric.p95Ms >= metric.p50Ms);
113
+ }
114
+ });
115
+ //# sourceMappingURL=policies.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policies.test.js","sourceRoot":"","sources":["../../src/test/policies.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=run-repository.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-repository.test.d.ts","sourceRoot":"","sources":["../../src/test/run-repository.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,100 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import { ProjectStore, RunRepository } from "../index.js";
7
+ test("run repository CAS permits one mutation and rejects a racing stale hash", async () => {
8
+ const root = await mkdtemp(join(tmpdir(), "apex-run-repository-"));
9
+ const store = new ProjectStore(root, () => new Date("2026-01-01T00:00:00.000Z"), () => "run-1");
10
+ await store.initializeProject({ projectId: "demo", displayName: "Demo", defaultIacTool: "bicep" });
11
+ await store.createRun("demo", { environment: "dev", targetScope: "scope", runtimeLockHash: "a".repeat(64) });
12
+ const repository = new RunRepository(store.runDirectory("demo", "run-1"));
13
+ const expectedRunHash = await repository.hash();
14
+ const mutation = (eventId) => repository.mutate({
15
+ expectedRunHash,
16
+ event: {
17
+ eventId,
18
+ projectId: "demo",
19
+ runId: "run-1",
20
+ type: "owner-changed",
21
+ timestamp: "2026-01-01T00:01:00.000Z",
22
+ ownerEpoch: 2,
23
+ payload: { eventId },
24
+ },
25
+ update: (run) => ({ ...run, ownerEpoch: 2 }),
26
+ });
27
+ const results = await Promise.allSettled([mutation("event-1"), mutation("event-2")]);
28
+ assert.equal(results.filter((result) => result.status === "fulfilled").length, 1);
29
+ assert.equal(results.filter((result) => result.status === "rejected").length, 1);
30
+ assert.equal((await repository.read()).ownerEpoch, 2);
31
+ assert.equal((await repository.journal.replay()).length, 1);
32
+ });
33
+ test("run repository rejects a mutation when the validated journal head changed", async () => {
34
+ const root = await mkdtemp(join(tmpdir(), "apex-run-journal-cas-"));
35
+ const store = new ProjectStore(root, () => new Date("2026-01-01T00:00:00.000Z"), () => "run-1");
36
+ await store.initializeProject({ projectId: "demo", displayName: "Demo", defaultIacTool: "bicep" });
37
+ await store.createRun("demo", { environment: "dev", targetScope: "scope", runtimeLockHash: "a".repeat(64) });
38
+ const repository = new RunRepository(store.runDirectory("demo", "run-1"));
39
+ const validatedHead = await repository.journal.head();
40
+ await repository.journal.append({
41
+ eventId: "transfer-requested",
42
+ projectId: "demo",
43
+ runId: "run-1",
44
+ type: "transfer-requested",
45
+ timestamp: "2026-01-01T00:00:30.000Z",
46
+ ownerEpoch: 1,
47
+ expectedHead: validatedHead,
48
+ payload: { claimHash: "b".repeat(64), recipient: "ci" },
49
+ });
50
+ await assert.rejects(repository.mutate({
51
+ expectedRunHash: await repository.hash(),
52
+ expectedJournalHead: validatedHead,
53
+ event: {
54
+ eventId: "gate-decided",
55
+ projectId: "demo",
56
+ runId: "run-1",
57
+ type: "gate.decided",
58
+ timestamp: "2026-01-01T00:01:00.000Z",
59
+ ownerEpoch: 1,
60
+ payload: { gate: 4 },
61
+ },
62
+ update: (run) => run,
63
+ }), /Stale journal head/);
64
+ assert.equal((await repository.journal.replay()).length, 1);
65
+ });
66
+ for (const stage of ["intent", "journal", "run", "cleanup"]) {
67
+ test(`run repository recovers a crash after ${stage}`, async () => {
68
+ const root = await mkdtemp(join(tmpdir(), "apex-run-recovery-"));
69
+ const store = new ProjectStore(root, () => new Date("2026-01-01T00:00:00.000Z"), () => "run-1");
70
+ await store.initializeProject({ projectId: "demo", displayName: "Demo", defaultIacTool: "bicep" });
71
+ await store.createRun("demo", { environment: "dev", targetScope: "scope", runtimeLockHash: "a".repeat(64) });
72
+ const directory = store.runDirectory("demo", "run-1");
73
+ const repository = new RunRepository(directory, {
74
+ faultInjector: (current) => {
75
+ if (current === stage)
76
+ throw new Error(`crash:${stage}`);
77
+ },
78
+ });
79
+ await assert.rejects(repository.mutate({
80
+ expectedRunHash: await repository.hash(),
81
+ event: {
82
+ eventId: `event-${stage}`,
83
+ projectId: "demo",
84
+ runId: "run-1",
85
+ type: "owner-changed",
86
+ timestamp: "2026-01-01T00:01:00.000Z",
87
+ ownerEpoch: 2,
88
+ payload: { stage },
89
+ },
90
+ update: (run) => ({ ...run, ownerEpoch: 2 }),
91
+ }), new RegExp(`crash:${stage}`));
92
+ const recovered = new RunRepository(directory);
93
+ const run = await recovered.read();
94
+ const events = await recovered.journal.replay();
95
+ const committed = stage !== "intent";
96
+ assert.equal(run.ownerEpoch, committed ? 2 : 1);
97
+ assert.equal(events.length, committed ? 1 : 0);
98
+ });
99
+ }
100
+ //# sourceMappingURL=run-repository.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-repository.test.js","sourceRoot":"","sources":["../../src/test/run-repository.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=storage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.test.d.ts","sourceRoot":"","sources":["../../src/test/storage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, mkdir, readFile, symlink, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test from "node:test";
6
+ import { atomicWriteJson, ObjectStore, sha256Text } from "../index.js";
7
+ test("atomic JSON writes canonical data and can refuse overwrite", async () => {
8
+ const root = await mkdtemp(join(tmpdir(), "apex-kernel-"));
9
+ const path = join(root, "value.json");
10
+ await atomicWriteJson(path, { z: 1, a: true });
11
+ assert.equal(await readFile(path, "utf8"), '{"a":true,"z":1}');
12
+ await assert.rejects(atomicWriteJson(path, { changed: true }, { refuseOverwrite: true }), { code: "EEXIST" });
13
+ });
14
+ test("object store deduplicates immutable content and rejects traversal", async () => {
15
+ const root = await mkdtemp(join(tmpdir(), "apex-kernel-"));
16
+ const store = new ObjectStore(root);
17
+ const hash = await store.putBytes(Buffer.from("immutable"));
18
+ assert.equal(await store.putBytes(Buffer.from("immutable")), hash);
19
+ assert.equal((await store.getBytes(hash)).toString(), "immutable");
20
+ await assert.rejects(store.getBytes("../outside"), /Invalid SHA-256/);
21
+ const objectPath = join(root, ".apex", "objects", "sha256", hash.slice(0, 2), hash.slice(2));
22
+ await writeFile(objectPath, "mutated");
23
+ await assert.rejects(store.getBytes(hash), /verification failed/);
24
+ });
25
+ test("object store rejects a symlinked hash bucket", async () => {
26
+ const root = await mkdtemp(join(tmpdir(), "apex-kernel-"));
27
+ const outside = await mkdtemp(join(tmpdir(), "apex-outside-"));
28
+ const hash = sha256Text("escape");
29
+ const storeRoot = join(root, ".apex", "objects", "sha256");
30
+ await mkdir(storeRoot, { recursive: true });
31
+ await symlink(outside, join(storeRoot, hash.slice(0, 2)));
32
+ await assert.rejects(new ObjectStore(root).putBytes(Buffer.from("escape")), /escapes the store/);
33
+ });
34
+ test("object store rejects a symlinked store root", async () => {
35
+ const root = await mkdtemp(join(tmpdir(), "apex-kernel-"));
36
+ const outside = await mkdtemp(join(tmpdir(), "apex-outside-"));
37
+ await mkdir(join(root, ".apex", "objects"), { recursive: true });
38
+ await symlink(outside, join(root, ".apex", "objects", "sha256"));
39
+ await assert.rejects(new ObjectStore(root).putBytes(Buffer.from("escape")), /root must not be a symlink/);
40
+ });
41
+ //# sourceMappingURL=storage.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.test.js","sourceRoot":"","sources":["../../src/test/storage.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workflow-engine.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-engine.test.d.ts","sourceRoot":"","sources":["../../src/test/workflow-engine.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,108 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import test from "node:test";
4
+ import { composeDependencyHash, WorkflowEngine, workflowValidatorOwnership } from "../index.js";
5
+ const manifest = JSON.parse(await readFile(new URL("../../../../config/workflow.v1.json", import.meta.url), "utf8"));
6
+ test("workflow manifest validates and routes both tracks deterministically", () => {
7
+ const engine = new WorkflowEngine(manifest);
8
+ assert.deepEqual(engine.manifest.nodes[0]?.validators, [
9
+ "schema:requirements-v1",
10
+ "business:requirements-completeness",
11
+ "review:requirements-comprehensive",
12
+ ]);
13
+ assert.ok(engine.manifest.nodes.flatMap(({ validators }) => validators).every((id) => workflowValidatorOwnership(id)));
14
+ const initial = engine.route({ run: { iacTool: "bicep" }, artifacts: {} });
15
+ assert.equal(initial.currentNode, "requirements");
16
+ assert.equal(initial.ownerRole, "requirements");
17
+ const bicep = engine.route({
18
+ run: { iacTool: "bicep" },
19
+ artifacts: {
20
+ "implementation-intent-v1": {},
21
+ "iac-binding-v1": {},
22
+ "environment-inputs-v1": {},
23
+ "policy-property-map-v1": {},
24
+ "runtime-bundle": {},
25
+ toolchain: {},
26
+ },
27
+ completedNodes: [
28
+ "requirements",
29
+ "gate-1",
30
+ "architecture",
31
+ "governance-discovery",
32
+ "governance-reconciliation",
33
+ "gate-2",
34
+ "plan",
35
+ "gate-3",
36
+ ],
37
+ gateStates: { "gate-1": "approved", "gate-2": "approved", "gate-3": "approved" },
38
+ });
39
+ assert.equal(bicep.nextTask, "codegen-bicep");
40
+ assert.equal(bicep.ownerRole, "bicep-codegen");
41
+ const discovery = engine.route({
42
+ run: { iacTool: "bicep", targetScope: "scope" },
43
+ artifacts: { "governance-capability-lock": {} },
44
+ completedNodes: ["requirements", "gate-1", "architecture"],
45
+ gateStates: { "gate-1": "approved" },
46
+ });
47
+ assert.equal(discovery.nextTask, "governance-discovery");
48
+ const bicepValidators = engine.activeValidatorIds({ run: { iacTool: "bicep" }, artifacts: {} });
49
+ assert.ok(bicepValidators.includes("bicep:build"));
50
+ assert.equal(bicepValidators.includes("terraform:validate"), false);
51
+ });
52
+ test("workflow rejects unknown operators, cycles, duplicate gates, and missing tracks", () => {
53
+ const base = structuredClone(manifest);
54
+ base.nodes[0].condition = { script: "true" };
55
+ assert.throws(() => new WorkflowEngine(base), /Unsupported/);
56
+ const cyclic = structuredClone(manifest);
57
+ cyclic.edges.push({ from: "gate-1", to: "requirements" });
58
+ assert.throws(() => new WorkflowEngine(cyclic), /cycle/);
59
+ const duplicate = structuredClone(manifest);
60
+ duplicate.nodes.find((node) => node.id === "gate-2").gateNumber = 1;
61
+ assert.throws(() => new WorkflowEngine(duplicate), /duplicate gate/);
62
+ const badInvalidation = structuredClone(manifest);
63
+ badInvalidation.nodes[0].invalidates.push("missing-node");
64
+ assert.throws(() => new WorkflowEngine(badInvalidation), /invalidates unknown node/);
65
+ const oneTrack = structuredClone(manifest);
66
+ oneTrack.nodes = oneTrack.nodes.filter((node) => !JSON.stringify(node.condition ?? {}).includes("terraform"));
67
+ const oneTrackIds = new Set(oneTrack.nodes.map((node) => node.id));
68
+ oneTrack.nodes.forEach((node) => {
69
+ node.invalidates = (node.invalidates ?? []).filter((target) => oneTrackIds.has(target));
70
+ });
71
+ oneTrack.edges = oneTrack.edges.filter((edge) => oneTrack.nodes.some((node) => node.id === edge.from) &&
72
+ oneTrack.nodes.some((node) => node.id === edge.to));
73
+ oneTrack.returnRoutes = oneTrack.returnRoutes.filter((edge) => oneTrack.nodes.some((node) => node.id === edge.from) &&
74
+ oneTrack.nodes.some((node) => node.id === edge.to));
75
+ assert.throws(() => new WorkflowEngine(oneTrack), /bicep and terraform/);
76
+ });
77
+ test("workflow rejects unowned and duplicate validator IDs", () => {
78
+ const unknown = structuredClone(manifest);
79
+ unknown.nodes[0].validators[0] = "schema:unknown-v1";
80
+ assert.throws(() => new WorkflowEngine(unknown), /Unknown workflow validator schema:unknown-v1/);
81
+ const duplicate = structuredClone(manifest);
82
+ duplicate.nodes[0].validators.push(duplicate.nodes[0].validators[0]);
83
+ assert.throws(() => new WorkflowEngine(duplicate), /Duplicate workflow validator/);
84
+ });
85
+ test("dependency hashes are canonical and invalidation cascades with semantic reasons", () => {
86
+ assert.equal(composeDependencyHash({ b: 2, a: 1 }), composeDependencyHash({ a: 1, b: 2 }));
87
+ const plan = new WorkflowEngine(manifest).invalidationPlan("requirements", "requirements changed");
88
+ assert(plan.some((entry) => entry.nodeId === "deploy-bicep"));
89
+ assert(plan.every((entry) => entry.reason === "requirements: requirements changed"));
90
+ assert.equal(new Set(plan.map((entry) => entry.nodeId)).size, plan.length);
91
+ });
92
+ test("manifest order and dependencies are runtime routing authority", () => {
93
+ const changed = structuredClone(manifest);
94
+ const architecture = changed.nodes.find((node) => node.id === "architecture");
95
+ const governance = changed.nodes.find((node) => node.id === "governance-discovery");
96
+ architecture.sourceDependencies.push("new-runtime-evidence");
97
+ changed.nodes.splice(changed.nodes.indexOf(governance), 1);
98
+ changed.nodes.splice(changed.nodes.indexOf(architecture), 0, governance);
99
+ const route = new WorkflowEngine(changed).route({
100
+ run: { iacTool: "bicep", targetScope: "scope" },
101
+ artifacts: { "governance-capability-lock": {} },
102
+ completedNodes: ["requirements", "gate-1"],
103
+ gateStates: { "gate-1": "approved" },
104
+ });
105
+ assert.equal(route.nextTask, "governance-discovery");
106
+ assert.notEqual(route.currentNode, "architecture");
107
+ });
108
+ //# sourceMappingURL=workflow-engine.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-engine.test.js","sourceRoot":"","sources":["../../src/test/workflow-engine.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workflow.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.test.d.ts","sourceRoot":"","sources":["../../src/test/workflow.test.ts"],"names":[],"mappings":""}