amalgm 0.1.239 → 0.1.241

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 (87) hide show
  1. package/lib/shared-realtime-tunnel.js +18 -4
  2. package/package.json +1 -1
  3. package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +68 -20
  4. package/runtime/scripts/amalgm-mcp/project-context/store.js +87 -19
  5. package/runtime/scripts/amalgm-mcp/server/routes/state.js +13 -0
  6. package/runtime/scripts/amalgm-mcp/state/attachments.js +40 -12
  7. package/runtime/scripts/amalgm-mcp/state/db.js +216 -17
  8. package/runtime/scripts/amalgm-mcp/state/doc-disk.js +550 -0
  9. package/runtime/scripts/amalgm-mcp/state/docs.js +1101 -301
  10. package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +99 -0
  11. package/runtime/scripts/amalgm-mcp/state/mutations.js +507 -130
  12. package/runtime/scripts/amalgm-mcp/state/promotions.js +21 -1
  13. package/runtime/scripts/amalgm-mcp/state/replicas.js +134 -13
  14. package/runtime/scripts/amalgm-mcp/state/shared-rest.js +14 -8
  15. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +193 -0
  16. package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +313 -94
  17. package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +1 -0
  18. package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +32 -17
  19. package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +84 -0
  20. package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +376 -13
  21. package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +309 -0
  22. package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +758 -30
  23. package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +72 -3
  24. package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +33 -6
  25. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +27 -0
  26. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +334 -191
  27. package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +24 -0
  28. package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +1999 -0
  29. package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +364 -0
  30. package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +98 -4
  31. package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +156 -8
  32. package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +185 -0
  33. package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +174 -269
  34. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +26 -0
  35. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +2060 -407
  36. package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +119 -0
  37. package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +1446 -0
  38. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +5319 -3
  39. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +191 -1
  40. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +3140 -17
  41. package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +135 -637
  42. package/runtime/scripts/amalgm-mcp/workspace/access-store.js +5 -1
  43. package/runtime/scripts/amalgm-mcp/workspace/card.js +7 -7
  44. package/runtime/scripts/amalgm-mcp/workspace/cards.js +36 -12
  45. package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +436 -173
  46. package/runtime/scripts/amalgm-mcp/workspace/content.js +32 -0
  47. package/runtime/scripts/amalgm-mcp/workspace/durable.js +45 -0
  48. package/runtime/scripts/amalgm-mcp/workspace/git.js +391 -37
  49. package/runtime/scripts/amalgm-mcp/workspace/intent.js +535 -0
  50. package/runtime/scripts/amalgm-mcp/workspace/merge.js +89 -6
  51. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +31 -0
  52. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
  53. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
  54. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +72 -0
  55. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +165 -0
  56. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +5 -0
  57. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +2 -0
  58. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +2201 -0
  59. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +19 -0
  60. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +18 -0
  61. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +18 -0
  62. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +33 -0
  63. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +47 -0
  64. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +43 -0
  65. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +114 -0
  66. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +84 -0
  67. package/runtime/scripts/amalgm-mcp/workspace/objects.js +185 -55
  68. package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +616 -168
  69. package/runtime/scripts/amalgm-mcp/workspace/provenance.js +240 -0
  70. package/runtime/scripts/amalgm-mcp/workspace/reducer.js +1979 -570
  71. package/runtime/scripts/amalgm-mcp/workspace/rest.js +49 -28
  72. package/runtime/scripts/amalgm-mcp/workspace/store.js +77 -4
  73. package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +240 -0
  74. package/runtime/scripts/amalgm-mcp/workspace/transition.js +1729 -192
  75. package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +100 -0
  76. package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +7 -1
  77. package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +73 -61
  78. package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +127 -0
  79. package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +12 -0
  80. package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +51 -6
  81. package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +125 -0
  82. package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +79 -8
  83. package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +76 -0
  84. package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +3480 -165
  85. package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +3766 -0
  86. package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +899 -71
  87. package/runtime/scripts/amalgm-mcp/workspace/wall.js +352 -497
@@ -25,16 +25,38 @@ fs.writeFileSync(
25
25
  const { closeLocalDb } = require('../state/db');
26
26
  const { hasCommit, headOid } = require('../workspace/git');
27
27
  const {
28
+ _test: { locatorChecksum },
28
29
  acknowledgeUpload,
29
30
  claimDownload,
30
31
  claimUpload,
32
+ failUpload,
31
33
  importFromLocator,
32
34
  objectSourceFor,
35
+ prepareBundleImport,
33
36
  publishPayload,
34
37
  readPayload,
35
38
  receiveDownload,
36
39
  } = require('../workspace/objects');
37
40
 
41
+ const PROJECT = '018f0000-0000-7000-8000-000000000030';
42
+ const PROJECT_BINDING = require('../workspace/wall').bindProject(
43
+ PROJECT,
44
+ '11111111-1111-4111-8111-111111111111',
45
+ tempRoot,
46
+ );
47
+ const REPO_KEY = PROJECT_BINDING.repoKey;
48
+
49
+ test('a locator checksum binds the exact ordered chunk list', () => {
50
+ const first = { sha256: 'a'.repeat(64), bytes: 3 };
51
+ const second = { sha256: 'b'.repeat(64), bytes: 4 };
52
+ assert.equal(locatorChecksum([first, second]), locatorChecksum([first, second]));
53
+ assert.notEqual(locatorChecksum([first, second]), locatorChecksum([second, first]));
54
+ assert.notEqual(
55
+ locatorChecksum([first, second]),
56
+ locatorChecksum([first, { ...second, bytes: 5 }]),
57
+ );
58
+ });
59
+
38
60
  test.after(() => {
39
61
  closeLocalDb();
40
62
  fs.rmSync(tempRoot, { recursive: true, force: true });
@@ -49,7 +71,7 @@ async function git(cwd, ...args) {
49
71
  return stdout;
50
72
  }
51
73
 
52
- test('an unpushed commit travels as card-owned chunks and imports without moving a ref', async () => {
74
+ test('every committed card travels as project-owned chunks and imports without moving a ref', async () => {
53
75
  const origin = path.join(tempRoot, 'origin.git');
54
76
  await git(tempRoot, 'init', '--bare', '-b', 'main', origin);
55
77
  const source = path.join(tempRoot, 'source');
@@ -61,12 +83,12 @@ test('an unpushed commit travels as card-owned chunks and imports without moving
61
83
 
62
84
  const destination = path.join(tempRoot, 'destination');
63
85
  await git(tempRoot, 'clone', '-q', origin, destination);
64
- assert.equal((await objectSourceFor(source, 'repo:test', await headOid(source))).kind, 'remote');
86
+ assert.equal((await objectSourceFor(source, REPO_KEY, await headOid(source))).kind, 'pending');
65
87
 
66
88
  fs.appendFileSync(path.join(source, 'app.js'), 'two\n');
67
89
  await git(source, 'commit', '-am', 'two');
68
90
  const unpushedOid = await headOid(source);
69
- assert.equal((await objectSourceFor(source, 'repo:test', unpushedOid)).kind, 'pending');
91
+ assert.equal((await objectSourceFor(source, REPO_KEY, unpushedOid)).kind, 'pending');
70
92
 
71
93
  const uploaded = new Map();
72
94
  const deadline = Date.now() + 10_000;
@@ -78,10 +100,10 @@ test('an unpushed commit travels as card-owned chunks and imports without moving
78
100
  }
79
101
  uploaded.set(upload.sha256, upload.dataBase64);
80
102
  acknowledgeUpload(upload.resourceId, upload.sha256);
81
- const sourceState = await objectSourceFor(source, 'repo:test', unpushedOid);
103
+ const sourceState = await objectSourceFor(source, REPO_KEY, unpushedOid);
82
104
  if (sourceState.kind === 'bundle') break;
83
105
  }
84
- const locator = await objectSourceFor(source, 'repo:test', unpushedOid);
106
+ const locator = await objectSourceFor(source, REPO_KEY, unpushedOid);
85
107
  assert.equal(locator.kind, 'bundle');
86
108
  assert.ok(locator.chunks.length > 0);
87
109
 
@@ -103,7 +125,7 @@ test('an unpushed commit travels as card-owned chunks and imports without moving
103
125
 
104
126
  test('a large checkpoint uses the same verified chunk lane', () => {
105
127
  const content = Buffer.alloc(1_300_000, 0x61);
106
- assert.equal(publishPayload('large-checkpoint', content, 'repo:test').kind, 'pending');
128
+ assert.equal(publishPayload('large-checkpoint', content, REPO_KEY).kind, 'pending');
107
129
 
108
130
  const uploaded = new Map();
109
131
  while (true) {
@@ -112,7 +134,7 @@ test('a large checkpoint uses the same verified chunk lane', () => {
112
134
  uploaded.set(upload.sha256, upload.dataBase64);
113
135
  acknowledgeUpload(upload.resourceId, upload.sha256);
114
136
  }
115
- const locator = publishPayload('large-checkpoint', content, 'repo:test');
137
+ const locator = publishPayload('large-checkpoint', content, REPO_KEY);
116
138
  assert.equal(locator.kind, 'chunks');
117
139
  assert.ok(locator.chunks.length >= 3);
118
140
 
@@ -130,9 +152,135 @@ test('a large checkpoint uses the same verified chunk lane', () => {
130
152
  assert.equal(received.state, 'ready');
131
153
  assert.deepEqual(received.bytes, content);
132
154
  assert.equal(
133
- publishPayload('same-content-after-download', content, 'repo:test').kind,
155
+ publishPayload('same-content-after-download', content, REPO_KEY).kind,
134
156
  'chunks',
135
157
  'verified downloaded content is immediately reusable by a later checkpoint',
136
158
  );
137
159
  assert.equal(claimUpload(), null);
138
160
  });
161
+
162
+ test('zero-byte chunks are not legal transport work', () => {
163
+ const chunks = [
164
+ { sha256: '0'.repeat(64), bytes: 0 },
165
+ { sha256: '1'.repeat(64), bytes: 1 },
166
+ ];
167
+ const common = {
168
+ resourceId: PROJECT,
169
+ checksum: locatorChecksum(chunks),
170
+ bytes: 1,
171
+ chunks,
172
+ };
173
+ assert.throws(
174
+ () => readPayload({
175
+ kind: 'chunks',
176
+ contract: 'opaque-chunks@2',
177
+ ...common,
178
+ }),
179
+ /invalid checkpoint payload locator/,
180
+ );
181
+ assert.throws(
182
+ () => prepareBundleImport({
183
+ kind: 'bundle',
184
+ contract: 'git-bundle-chunks@2',
185
+ oid: 'a'.repeat(40),
186
+ ref: `refs/amalgm/exports/${'a'.repeat(40)}`,
187
+ ...common,
188
+ }),
189
+ /invalid git bundle locator/,
190
+ );
191
+ });
192
+
193
+ test('a corrupt pending upload is retired and rebuilt from the writer', () => {
194
+ const content = Buffer.from('rebuild this upload');
195
+ assert.equal(publishPayload('corrupt-upload', content, REPO_KEY).kind, 'pending');
196
+ const first = claimUpload();
197
+ assert.ok(first);
198
+ const chunkFile = path.join(
199
+ process.env.AMALGM_DIR,
200
+ 'state',
201
+ 'repo-objects',
202
+ 'chunks',
203
+ `${first.sha256}.bin`,
204
+ );
205
+ fs.writeFileSync(chunkFile, 'corrupt');
206
+ assert.equal(claimUpload(), null, 'the corrupt manifest is retired instead of retrying forever');
207
+
208
+ assert.equal(publishPayload('corrupt-upload', content, REPO_KEY).kind, 'pending');
209
+ const rebuilt = claimUpload();
210
+ assert.equal(rebuilt.sha256, first.sha256);
211
+ assert.equal(Buffer.from(rebuilt.dataBase64, 'base64').toString(), content.toString());
212
+ acknowledgeUpload(rebuilt.resourceId, rebuilt.sha256);
213
+ });
214
+
215
+ test('one malformed download request cannot block valid requests behind it', () => {
216
+ const content = Buffer.from('download me');
217
+ const chunks = [{
218
+ sha256: require('crypto').createHash('sha256').update(content).digest('hex'),
219
+ bytes: content.length,
220
+ }];
221
+ const locator = {
222
+ kind: 'chunks',
223
+ contract: 'opaque-chunks@2',
224
+ resourceId: PROJECT,
225
+ checksum: locatorChecksum(chunks),
226
+ bytes: content.length,
227
+ chunks,
228
+ };
229
+ fs.rmSync(path.join(
230
+ process.env.AMALGM_DIR,
231
+ 'state',
232
+ 'repo-objects',
233
+ 'chunks',
234
+ `${chunks[0].sha256}.bin`,
235
+ ), { force: true });
236
+ assert.equal(readPayload(locator).state, 'downloading');
237
+ const requests = path.join(process.env.AMALGM_DIR, 'state', 'repo-objects', 'requests');
238
+ fs.writeFileSync(path.join(requests, '000-malformed.json'), '{');
239
+
240
+ const claimed = claimDownload();
241
+ assert.equal(claimed.resourceId, PROJECT);
242
+ assert.equal(claimed.sha256, chunks[0].sha256);
243
+ assert.equal(fs.existsSync(path.join(requests, '000-malformed.json')), false);
244
+ receiveDownload(PROJECT, chunks[0].sha256, content.toString('base64'));
245
+ });
246
+
247
+ test('failed uploads yield the bulk lane to later durable objects', () => {
248
+ for (const content of ['first queued object', 'second queued object', 'third queued object']) {
249
+ assert.equal(
250
+ publishPayload(`fair-${content}`, Buffer.from(content), REPO_KEY).kind,
251
+ 'pending',
252
+ );
253
+ }
254
+
255
+ const first = claimUpload();
256
+ assert.ok(first);
257
+ const retry = failUpload(first.resourceId, first.sha256, {
258
+ retryable: true,
259
+ code: 'temporary_object_failure',
260
+ message: 'try this object later',
261
+ });
262
+ assert.equal(retry.changed, true);
263
+ assert.equal(retry.terminal, false);
264
+ assert.ok(Date.parse(retry.retryAfter) > Date.now());
265
+
266
+ const second = claimUpload();
267
+ assert.ok(second);
268
+ assert.notEqual(second.sha256, first.sha256);
269
+ const terminal = failUpload(second.resourceId, second.sha256, {
270
+ retryable: false,
271
+ code: 'shared_resource_read_forbidden',
272
+ message: 'the stale rail is no longer readable',
273
+ });
274
+ assert.equal(terminal.changed, true);
275
+ assert.equal(terminal.terminal, true);
276
+
277
+ const third = claimUpload();
278
+ assert.ok(third);
279
+ assert.notEqual(third.sha256, first.sha256);
280
+ assert.notEqual(third.sha256, second.sha256);
281
+
282
+ acknowledgeUpload(first.resourceId, first.sha256);
283
+ acknowledgeUpload(second.resourceId, second.sha256);
284
+ acknowledgeUpload(third.resourceId, third.sha256);
285
+ assert.equal(claimUpload(), null);
286
+ });
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert/strict');
4
+ const test = require('node:test');
5
+
6
+ const provenance = require('../workspace/provenance');
7
+
8
+ const {
9
+ ACTION,
10
+ CLASS,
11
+ STATE,
12
+ TRANSITIONS,
13
+ allStates,
14
+ provenanceClass,
15
+ publicationState,
16
+ requireTransition,
17
+ } = provenance;
18
+
19
+ const key = (from, action, to) => `${from} --${action}--> ${to}`;
20
+
21
+ test('managed bytes have exactly three provenance classes', () => {
22
+ assert.deepEqual(Object.values(CLASS), [
23
+ 'officially-applied',
24
+ 'journaled-local-intent',
25
+ 'unknown-provenance',
26
+ ]);
27
+ assert.deepEqual(
28
+ [...new Set(allStates.map(provenanceClass))].sort(),
29
+ Object.values(CLASS).sort(),
30
+ );
31
+ assert.equal(allStates.every((state) => provenanceClass(state) !== null), true);
32
+ });
33
+
34
+ test('the complete provenance transition relation has no implicit branch', () => {
35
+ const legal = new Set(TRANSITIONS.map((entry) => key(
36
+ entry.from,
37
+ entry.action,
38
+ entry.to,
39
+ )));
40
+ assert.equal(legal.size, TRANSITIONS.length, 'every transition is named once');
41
+
42
+ for (const from of allStates) {
43
+ for (const action of Object.values(ACTION)) {
44
+ for (const to of allStates) {
45
+ const transitionKey = key(from, action, to);
46
+ if (legal.has(transitionKey)) {
47
+ const entry = TRANSITIONS.find((candidate) => (
48
+ key(candidate.from, candidate.action, candidate.to) === transitionKey
49
+ ));
50
+ assert.ok(entry.facts.length > 0, `${transitionKey} names its durable facts`);
51
+ assert.equal(
52
+ requireTransition(from, action, to, entry.facts),
53
+ entry,
54
+ transitionKey,
55
+ );
56
+ } else {
57
+ assert.throws(
58
+ () => requireTransition(from, action, to),
59
+ /illegal provenance transition/,
60
+ transitionKey,
61
+ );
62
+ }
63
+ }
64
+ }
65
+ }
66
+ });
67
+
68
+ test('publication substates keep numbering, folding, and physical apply distinct', () => {
69
+ const publication = {
70
+ mutationId: 'mutation_provenance_state_01',
71
+ numberedVersion: null,
72
+ numberedAt: null,
73
+ foldedAt: null,
74
+ };
75
+ assert.equal(publicationState(null), STATE.CAPTURED);
76
+ assert.equal(publicationState(publication), STATE.RESERVED);
77
+ assert.equal(publicationState({
78
+ ...publication,
79
+ numberedVersion: 51,
80
+ numberedAt: '2026-07-27T00:00:00.000Z',
81
+ }), STATE.NUMBERED);
82
+ assert.equal(publicationState({
83
+ ...publication,
84
+ numberedVersion: 51,
85
+ numberedAt: '2026-07-27T00:00:00.000Z',
86
+ foldedAt: '2026-07-27T00:00:01.000Z',
87
+ }), STATE.FOLDED);
88
+
89
+ assert.throws(
90
+ () => requireTransition(
91
+ STATE.RESERVED,
92
+ ACTION.NUMBER,
93
+ STATE.NUMBERED,
94
+ ['publication-id', 'official-version'],
95
+ ),
96
+ /server-receipt/,
97
+ );
98
+ assert.throws(
99
+ () => requireTransition(
100
+ STATE.NUMBERED,
101
+ ACTION.APPLY,
102
+ STATE.OFFICIAL,
103
+ [],
104
+ ),
105
+ /illegal provenance transition/,
106
+ 'numbered work must fold before physical apply',
107
+ );
108
+ });
109
+
110
+ test('unknown bytes can only verify, capture, or remain unknown', () => {
111
+ const outgoing = TRANSITIONS
112
+ .filter((entry) => entry.from === STATE.UNKNOWN)
113
+ .map((entry) => [entry.action, entry.to])
114
+ .sort();
115
+ assert.deepEqual(outgoing, [
116
+ [ACTION.CAPTURE, STATE.CAPTURED],
117
+ [ACTION.OBSERVE_UNKNOWN, STATE.UNKNOWN],
118
+ [ACTION.RECOVER, STATE.UNKNOWN],
119
+ [ACTION.SNAPSHOT, STATE.OFFICIAL],
120
+ [ACTION.SNAPSHOT, STATE.UNKNOWN],
121
+ [ACTION.VERIFY, STATE.OFFICIAL],
122
+ ].sort());
123
+ assert.equal(
124
+ outgoing.some(([action]) => (
125
+ action === ACTION.RESERVE
126
+ || action === ACTION.RETRY
127
+ || action === ACTION.APPLY
128
+ )),
129
+ false,
130
+ );
131
+ });
132
+
133
+ test('server compaction, local journal prune, and intent retirement are distinct', () => {
134
+ const prunes = TRANSITIONS.filter((entry) => entry.action === ACTION.PRUNE);
135
+ assert.deepEqual(prunes[0], {
136
+ from: STATE.FOLDED,
137
+ action: ACTION.PRUNE,
138
+ to: STATE.FOLDED,
139
+ facts: [
140
+ 'official-fold',
141
+ 'durable-folded-intent',
142
+ ],
143
+ });
144
+ assert.deepEqual(prunes[1], {
145
+ from: STATE.OFFICIAL,
146
+ action: ACTION.PRUNE,
147
+ to: STATE.OFFICIAL,
148
+ facts: [
149
+ 'official-fold',
150
+ 'physical-verification',
151
+ 'applied-cursor',
152
+ ],
153
+ });
154
+ const compact = TRANSITIONS.find((entry) => entry.action === ACTION.COMPACT);
155
+ assert.deepEqual(compact.facts, [
156
+ 'retained-server-receipt',
157
+ 'verified-server-snapshot',
158
+ ]);
159
+ const physicalApply = TRANSITIONS.find((entry) => (
160
+ entry.from === STATE.FOLDED
161
+ && entry.action === ACTION.APPLY
162
+ && entry.to === STATE.OFFICIAL
163
+ ));
164
+ assert.deepEqual(physicalApply.facts.slice(-3), [
165
+ 'physical-verification',
166
+ 'applied-cursor',
167
+ 'intent-retired',
168
+ ]);
169
+ });
170
+
171
+ test('WAL recovery keeps source and target provenance but exposes any third value', () => {
172
+ for (const state of allStates.filter((candidate) => candidate !== STATE.UNKNOWN)) {
173
+ assert.ok(TRANSITIONS.some((entry) => (
174
+ entry.from === state
175
+ && entry.action === ACTION.RECOVER
176
+ && entry.to === state
177
+ )));
178
+ assert.ok(TRANSITIONS.some((entry) => (
179
+ entry.from === state
180
+ && entry.action === ACTION.RECOVER
181
+ && entry.to === STATE.UNKNOWN
182
+ && entry.facts.includes('exact-third-value-observation')
183
+ )));
184
+ }
185
+ });