amalgm 0.1.238 → 0.1.240

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 (86) hide show
  1. package/lib/shared-realtime-tunnel.js +22 -14
  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/state/attachments.js +40 -12
  6. package/runtime/scripts/amalgm-mcp/state/db.js +216 -17
  7. package/runtime/scripts/amalgm-mcp/state/doc-disk.js +550 -0
  8. package/runtime/scripts/amalgm-mcp/state/docs.js +1101 -301
  9. package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +99 -0
  10. package/runtime/scripts/amalgm-mcp/state/mutations.js +507 -130
  11. package/runtime/scripts/amalgm-mcp/state/promotions.js +21 -1
  12. package/runtime/scripts/amalgm-mcp/state/replicas.js +134 -13
  13. package/runtime/scripts/amalgm-mcp/state/shared-rest.js +14 -8
  14. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +193 -0
  15. package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +313 -94
  16. package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +1 -0
  17. package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +32 -17
  18. package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +84 -0
  19. package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +376 -13
  20. package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +309 -0
  21. package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +758 -30
  22. package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +72 -3
  23. package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +33 -6
  24. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +27 -0
  25. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +334 -191
  26. package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +24 -0
  27. package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +1999 -0
  28. package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +364 -0
  29. package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +81 -3
  30. package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +114 -8
  31. package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +185 -0
  32. package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +174 -269
  33. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +26 -0
  34. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +2060 -407
  35. package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +119 -0
  36. package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +1446 -0
  37. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +5319 -3
  38. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +191 -1
  39. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +3140 -17
  40. package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +135 -637
  41. package/runtime/scripts/amalgm-mcp/workspace/access-store.js +5 -1
  42. package/runtime/scripts/amalgm-mcp/workspace/card.js +7 -7
  43. package/runtime/scripts/amalgm-mcp/workspace/cards.js +36 -12
  44. package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +436 -173
  45. package/runtime/scripts/amalgm-mcp/workspace/content.js +32 -0
  46. package/runtime/scripts/amalgm-mcp/workspace/durable.js +45 -0
  47. package/runtime/scripts/amalgm-mcp/workspace/git.js +391 -37
  48. package/runtime/scripts/amalgm-mcp/workspace/intent.js +535 -0
  49. package/runtime/scripts/amalgm-mcp/workspace/merge.js +89 -6
  50. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +31 -0
  51. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
  52. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
  53. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +72 -0
  54. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +165 -0
  55. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +5 -0
  56. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +2 -0
  57. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +2201 -0
  58. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +19 -0
  59. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +18 -0
  60. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +18 -0
  61. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +33 -0
  62. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +47 -0
  63. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +43 -0
  64. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +114 -0
  65. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +84 -0
  66. package/runtime/scripts/amalgm-mcp/workspace/objects.js +119 -54
  67. package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +616 -168
  68. package/runtime/scripts/amalgm-mcp/workspace/provenance.js +240 -0
  69. package/runtime/scripts/amalgm-mcp/workspace/reducer.js +1979 -570
  70. package/runtime/scripts/amalgm-mcp/workspace/rest.js +49 -28
  71. package/runtime/scripts/amalgm-mcp/workspace/store.js +77 -4
  72. package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +240 -0
  73. package/runtime/scripts/amalgm-mcp/workspace/transition.js +1729 -192
  74. package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +100 -0
  75. package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +7 -1
  76. package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +73 -61
  77. package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +127 -0
  78. package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +12 -0
  79. package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +51 -6
  80. package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +125 -0
  81. package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +79 -8
  82. package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +76 -0
  83. package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +3480 -165
  84. package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +3766 -0
  85. package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +899 -71
  86. package/runtime/scripts/amalgm-mcp/workspace/wall.js +352 -497
@@ -6,688 +6,186 @@ const crypto = require('crypto');
6
6
  const fs = require('fs');
7
7
  const os = require('os');
8
8
  const path = require('path');
9
- const { execFile } = require('child_process');
10
- const { promisify } = require('util');
11
-
12
- const execFileAsync = promisify(execFile);
13
9
 
14
10
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-wall-test-'));
15
11
  process.env.AMALGM_DIR = path.join(tempRoot, '.amalgm');
16
12
  process.env.AMALGM_WORKSPACES_DIR = path.join(process.env.AMALGM_DIR, 'workspaces');
17
13
  process.env.AMALGM_COMPUTER_ID = 'machine-a';
18
- process.env.AMALGM_DEVICE_ID = 'device-machine-a';
19
-
14
+ process.env.AMALGM_DEVICE_ID = 'machine-a';
20
15
  fs.mkdirSync(process.env.AMALGM_DIR, { recursive: true });
21
16
  fs.writeFileSync(
22
17
  path.join(process.env.AMALGM_DIR, 'computer.json'),
23
- JSON.stringify({ computer_id: 'machine-a', user_id: '11111111-1111-4111-8111-111111111111' }),
18
+ JSON.stringify({
19
+ computer_id: 'machine-a',
20
+ user_id: '11111111-1111-4111-8111-111111111111',
21
+ }),
24
22
  );
25
23
 
26
24
  const { closeLocalDb } = require('../state/db');
27
- const { appendStateEvent } = require('../state/events');
28
- const {
29
- acknowledgeCloudMutation,
30
- listPendingCloudMutations,
31
- pruneMutationJournal,
32
- } = require('../state/mutations');
33
- const { registerReplica } = require('../state/replicas');
34
- const { listWorkspaces, upsertWorkspace } = require('../workspace/store');
35
- const { refreshRepoCards } = require('../workspace/cards');
36
- const { checkpointFile, repoKeyOfCard } = require('../workspace/checkpoint');
25
+ const mutations = require('../state/mutations');
26
+ const replicas = require('../state/replicas');
27
+ const checkpoint = require('../workspace/checkpoint');
28
+ const intent = require('../workspace/intent');
37
29
  const projectStream = require('../workspace/project-stream');
38
- const {
39
- _test: wallTest,
40
- bindProject,
41
- hasLocalContribution,
42
- isCommittedCard,
43
- projectForRepo,
44
- projectResourceId,
45
- publishedCardFor,
46
- publishWall,
47
- readWall,
48
- startWallService,
49
- stampCards,
50
- stopWallService,
51
- wallStats,
52
- } = require('../workspace/wall');
30
+ const wall = require('../workspace/wall');
53
31
 
54
- let repoKey;
55
- let project;
56
- let nextCloudVersion = 1;
57
-
58
- function markReducerApplied(version) {
59
- const digest = crypto.createHash('sha256').update(repoKey).digest('hex').slice(0, 24);
60
- const file = path.join(process.env.AMALGM_DIR, 'state', 'repo-reducer', `${digest}.json`);
61
- fs.mkdirSync(path.dirname(file), { recursive: true });
62
- fs.writeFileSync(file, `${JSON.stringify({
63
- repoKey,
64
- resourceId: project.resourceId,
65
- appliedThrough: version,
66
- })}\n`);
67
- }
68
-
69
- function settleProject() {
70
- const versions = [];
71
- for (const mutation of listPendingCloudMutations(project.resourceId)) {
72
- const version = nextCloudVersion;
73
- nextCloudVersion += 1;
74
- acknowledgeCloudMutation(project.resourceId, mutation.mutationId, {
75
- version,
76
- committedAt: `2026-07-23T00:00:${String(version).padStart(2, '0')}.000Z`,
77
- });
78
- versions.push(version);
79
- }
80
- if (versions.length > 0) markReducerApplied(versions.at(-1));
81
- return versions;
82
- }
32
+ const PROJECT = '018f0000-0000-7000-8000-00000000babe';
33
+ const HEAD = 'a'.repeat(40);
34
+ const EMPTY_TREE = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
35
+ const REPO_KEY = `code-folder:${PROJECT}`;
83
36
 
84
37
  test.after(() => {
85
38
  closeLocalDb();
86
39
  fs.rmSync(tempRoot, { recursive: true, force: true });
87
40
  });
88
41
 
89
- async function run(cwd, ...args) {
90
- await execFileAsync(
91
- 'git',
92
- ['-c', 'user.email=test@amalgm.test', '-c', 'user.name=Amalgm Test', '-c', 'commit.gpgsign=false', ...args],
93
- { cwd, env: { ...process.env, GIT_TERMINAL_PROMPT: '0', LC_ALL: 'C' } },
94
- );
95
- }
96
-
97
- test('a project id is stable for one owner and repo, and distinct for another owner or repo', () => {
98
- const userA = '11111111-1111-4111-8111-111111111111';
99
- const userB = '22222222-2222-4222-8222-222222222222';
100
- assert.equal(projectResourceId('repo-a', userA), projectResourceId('repo-a', userA));
101
- assert.notEqual(projectResourceId('repo-a', userA), projectResourceId('repo-a', userB));
102
- assert.notEqual(projectResourceId('repo-a', userA), projectResourceId('repo-b', userA));
103
- assert.match(projectResourceId('repo-a', userA), /^shr_[A-Za-z0-9_-]{16,128}$/);
104
- assert.equal(projectResourceId(null, userA), null);
105
- });
106
-
107
- test('no commit means no shared card', () => {
108
- assert.equal(isCommittedCard({ head: { branch: 'main', oid: null } }), false);
109
- assert.equal(isCommittedCard({ head: { branch: 'main', oid: 'a'.repeat(40) } }), true);
110
- });
111
-
112
- test('transport metadata alone does not make a new code record', () => {
113
- const base = {
114
- wall: 'code-project@6',
115
- project: 'root:ordered',
116
- machine: 'machine-a',
117
- mutationId: 'mut_older_transport',
118
- version: 10,
119
- publishedAt: '2026-07-26T00:00:00.000Z',
120
- cards: [{ marker: 'same-content' }],
121
- };
122
- assert.equal(wallTest.samePublishedRecord(base, {
123
- ...base,
124
- mutationId: 'mut_newer_transport',
125
- version: 11,
126
- publishedAt: '2026-07-26T00:01:00.000Z',
127
- }), true);
128
- });
129
-
130
- test('only a value different from both the materialized baseline and the rail is new work', () => {
131
- const oidA = 'a'.repeat(40);
132
- const oidB = 'b'.repeat(40);
133
- const oidC = 'c'.repeat(40);
134
- const local = {
135
- head: { branch: 'main', oid: oidA, detached: false },
136
- refs: [{ name: 'main', oid: oidA }],
137
- };
138
- const plan = {
139
- card: { head: { branch: 'main', oid: oidA, detached: false } },
140
- branches: [
141
- { name: 'main', oid: oidA },
142
- { name: 'feature', oid: oidB },
143
- ],
144
- transitions: [],
145
- };
146
- const baseline = {
147
- branch: 'main',
148
- baseOid: oidA,
149
- branches: [{ name: 'main', oid: oidA }],
42
+ function bundle(oid = HEAD, byte = 'b') {
43
+ const chunks = [{ sha256: byte.repeat(64), bytes: 1 }];
44
+ return {
45
+ kind: 'bundle',
46
+ contract: projectStream.BUNDLE_CONTRACT,
47
+ resourceId: PROJECT,
48
+ oid,
49
+ ref: `refs/amalgm/exports/${oid}`,
50
+ checksum: crypto.createHash('sha256').update(Buffer.from(JSON.stringify(chunks))).digest('hex'),
51
+ bytes: 1,
52
+ chunks,
150
53
  };
54
+ }
151
55
 
152
- assert.equal(hasLocalContribution(local, plan, baseline), false);
153
- assert.equal(hasLocalContribution({
154
- ...local,
155
- refs: [...local.refs, { name: 'feature', oid: oidB }],
156
- }, plan, baseline), false);
157
- assert.equal(hasLocalContribution({
158
- ...local,
159
- head: { branch: 'main', oid: oidC, detached: false },
160
- refs: [{ name: 'main', oid: oidC }],
161
- }, plan, baseline), true);
162
- assert.equal(hasLocalContribution(local, plan, {
163
- ...baseline,
164
- branches: [...baseline.branches, { name: 'feature', oid: oidB }],
165
- }), true);
166
-
167
- const remoteAdvance = {
168
- ...plan,
169
- card: { head: { branch: 'main', oid: oidC, detached: false } },
170
- branches: [{ name: 'main', oid: oidC }],
171
- };
172
- assert.equal(hasLocalContribution(local, remoteAdvance, baseline), false);
173
- assert.equal(hasLocalContribution({
174
- ...local,
175
- head: { branch: 'main', oid: oidB, detached: false },
176
- refs: [{ name: 'main', oid: oidB }],
177
- }, remoteAdvance, {
178
- ...baseline,
179
- baseOid: oidB,
180
- pendingCard: true,
181
- }), true);
182
- assert.equal(hasLocalContribution(local, remoteAdvance, baseline, {
183
- checkpoint: 'repo-checkpoint@9',
184
- machine: 'machine-a',
185
- branch: 'main',
186
- baseOid: oidA,
187
- from: { revision: null, indexDiff: '', worktreeDiff: '' },
188
- to: { revision: 'old-work', indexDiff: '', worktreeDiff: 'patch' },
189
- parked: [],
190
- }), false);
191
- assert.equal(hasLocalContribution(
192
- {
193
- head: { branch: 'main', oid: oidB, detached: false },
194
- refs: [{ name: 'main', oid: oidB }],
195
- },
196
- remoteAdvance,
197
- {
198
- branch: 'main',
199
- baseOid: oidB,
200
- branches: [{ name: 'main', oid: oidB }],
201
- },
202
- null,
203
- {
204
- head: { branch: 'main', oid: oidA, detached: false },
205
- branches: [{ name: 'main', oid: oidA }],
206
- },
207
- ), false);
208
- assert.equal(hasLocalContribution(
209
- {
210
- head: { branch: 'main', oid: oidB, detached: false },
211
- refs: [{ name: 'main', oid: oidB }],
212
- },
213
- remoteAdvance,
214
- baseline,
215
- null,
216
- {
217
- positionVersion: 10,
218
- head: { branch: 'main', oid: oidB, detached: false },
219
- branches: [{ name: 'main', oid: oidB, version: 10 }],
220
- },
221
- ), false);
222
- assert.equal(hasLocalContribution(
223
- {
224
- head: { branch: 'main', oid: oidB, detached: false },
225
- refs: [{ name: 'main', oid: oidB }],
226
- },
227
- remoteAdvance,
228
- baseline,
229
- null,
230
- {
231
- positionVersion: null,
232
- head: { branch: 'main', oid: oidB, detached: false },
233
- branches: [{ name: 'main', oid: oidB, version: null }],
234
- },
235
- ), true);
236
- assert.equal(hasLocalContribution(
237
- local,
238
- plan,
239
- {
240
- ...baseline,
241
- branches: [...baseline.branches, { name: 'feature', oid: null }],
242
- },
243
- null,
244
- {
245
- head: { branch: 'main', oid: oidA, detached: false },
246
- branches: [
247
- { name: 'main', oid: oidA, version: 10 },
248
- { name: 'feature', oid: oidB, version: 10 },
249
- ],
250
- },
251
- ), true);
252
- assert.equal(hasLocalContribution(local, plan, baseline, {
253
- checkpoint: 'repo-checkpoint@9',
254
- machine: 'machine-a',
255
- branch: 'main',
256
- baseOid: oidA,
257
- from: { revision: null, indexDiff: '', worktreeDiff: '' },
258
- to: { revision: 'local-work', indexDiff: '', worktreeDiff: 'patch' },
259
- parked: [],
260
- }), true);
261
- });
262
-
263
- test('checkpoint work on the materialized baseline keeps the baseline card and branch numbers', () => {
264
- const baselineOid = 'a'.repeat(40);
265
- const railOid = 'b'.repeat(40);
266
- const card = {
267
- card: 'repo-card@2',
56
+ function localCard() {
57
+ return {
58
+ card: projectStream.CARD_CONTRACT,
268
59
  machine: 'machine-a',
269
- replica: 'rpl_baseline',
60
+ replica: 'replica-a',
61
+ path: path.join(tempRoot, 'repo'),
62
+ repo: { remote: null, rootCommit: 'explicit-binding' },
270
63
  kind: 'primary',
271
- head: { branch: 'main', oid: baselineOid, detached: false },
272
- refs: [{ name: 'main', oid: baselineOid }],
273
64
  health: 'healthy',
65
+ operation: 'none',
66
+ head: { branch: 'main', oid: HEAD, detached: false },
67
+ objects: bundle(),
68
+ refs: [{ name: 'main', oid: HEAD, objects: bundle() }],
69
+ dirty: { staged: 0, unstaged: 0, untracked: 0, conflicted: 0, submodulesChanged: 0 },
274
70
  };
275
- const stamped = stampCards(
276
- [card],
277
- [{
278
- ...card,
279
- head: { branch: 'old', oid: 'c'.repeat(40), detached: false },
280
- positionMutationId: 'mut_stale_card_01',
281
- positionVersion: 8,
282
- branches: [],
283
- }],
284
- {
285
- card: {
286
- ...card,
287
- head: { branch: 'main', oid: railOid, detached: false },
288
- positionMutationId: 'mut_winning_card1',
289
- positionVersion: 12,
290
- },
291
- branches: [{
292
- name: 'main',
293
- oid: railOid,
294
- mutationId: 'mut_winning_ref01',
295
- version: 12,
296
- }],
297
- },
298
- {
299
- branch: 'main',
300
- baseOid: baselineOid,
301
- cardMutationId: 'mut_baseline_card',
302
- cardVersion: 9,
303
- branches: [{
304
- name: 'main',
305
- oid: baselineOid,
306
- mutationId: 'mut_baseline_ref1',
307
- version: 9,
308
- }],
309
- },
310
- 'mut_checkpoint_only',
311
- { repoKey: 'root:baseline-stamp', resourceId: 'shr_baseline_stamp_test' },
312
- )[0];
313
-
314
- assert.equal(stamped.positionMutationId, 'mut_baseline_card');
315
- assert.equal(stamped.positionVersion, 9);
316
- assert.equal(stamped.branches[0].mutationId, 'mut_baseline_ref1');
317
- assert.equal(stamped.branches[0].version, 9);
318
- });
319
-
320
- test('a collaborator binds the same repo to the owner project, not a parallel project', () => {
321
- const shared = projectResourceId('repo-shared', '22222222-2222-4222-8222-222222222222');
322
- const localPath = path.join(tempRoot, 'projects/shared');
323
- const bound = bindProject(
324
- 'repo-shared',
325
- shared,
326
- '22222222-2222-4222-8222-222222222222',
327
- localPath,
328
- );
329
- assert.equal(bound.resourceId, shared);
330
- assert.equal(projectForRepo('repo-shared').resourceId, shared);
331
- assert.equal(projectForRepo('repo-shared').ownerUserId, '22222222-2222-4222-8222-222222222222');
332
- assert.equal(projectForRepo('repo-shared').path, localPath);
333
- assert.equal(listWorkspaces().find((workspace) => workspace.path === localPath)?.source, 'computer');
334
- });
335
-
336
- test('a repo project appends immutable machine records', async () => {
337
- const repo = path.join(tempRoot, 'projects/app');
338
- fs.mkdirSync(repo, { recursive: true });
339
- await run(repo, 'init', '-b', 'main');
340
- fs.writeFileSync(path.join(repo, 'README.md'), '# app\n');
341
- await run(repo, 'add', '.');
342
- await run(repo, 'commit', '-m', 'initial');
343
- upsertWorkspace({ path: path.join(tempRoot, 'projects'), name: 'Projects', source: 'computer' });
344
- const refreshed = await refreshRepoCards();
345
- repoKey = repoKeyOfCard(refreshed.cards[0]);
346
- project = projectForRepo(repoKey);
347
-
348
- projectStream.writeSnapshot(project.file, projectStream._test.emptySnapshot(repoKey));
349
- registerReplica({
350
- resourceId: project.resourceId,
351
- localResource: `${projectStream.LOCAL_RESOURCE_PREFIX}${project.resourceId}`,
352
- localPath: project.file,
353
- contract: projectStream.STREAM_CONTRACT,
354
- schemaVersion: 1,
355
- authorityEpoch: 1,
356
- });
71
+ }
357
72
 
358
- const first = publishWall(repoKey);
359
- assert.equal(first.published, true);
360
- const entries = readWall(repoKey);
361
- assert.equal(entries.length, 1);
362
- assert.equal(entries[0].wall, 'code-project@6');
363
- assert.equal(entries[0].project, repoKey);
364
- assert.equal(entries[0].machine, 'machine-a');
365
- assert.equal(entries[0].cards.length, 1);
366
- assert.equal(entries[0].cards[0].head.branch, 'main');
367
- assert.equal(entries[0].cards[0].positionMutationId, first.mutationId);
368
- assert.equal(entries[0].cards[0].positionVersion, null);
369
- assert.equal(publishedCardFor(refreshed.cards[0]), null);
370
- const awaitingNumber = publishWall(repoKey);
371
- assert.equal(awaitingNumber.published, false);
372
- assert.equal(awaitingNumber.reason, 'unchanged');
373
- assert.equal(
374
- require('../workspace/reducer').localCardMayMaterialize(null, awaitingNumber),
375
- false,
73
+ test('only an explicit folder UUID creates a code project', () => {
74
+ assert.equal(wall.projectForRepo(REPO_KEY), null);
75
+ assert.throws(
76
+ () => wall.bindProject('shr_legacy_or_derived_identity', null, localCard().path),
77
+ /folder UUID/,
376
78
  );
377
-
378
- assert.deepEqual(settleProject(), [1]);
379
- const sealed = publishWall(repoKey);
380
- assert.equal(sealed.published, false);
381
- assert.equal(sealed.reason, 'shared-state-covered');
382
- const sealedCard = readWall(repoKey)[0].cards[0];
383
- assert.equal(sealedCard.positionMutationId, first.mutationId);
384
- assert.equal(sealedCard.positionVersion, 1);
385
- const sealedSnapshot = projectStream.readSnapshot(project.file, repoKey);
386
- assert.equal(sealedSnapshot.records.length, 1);
387
- assert.equal(sealedSnapshot.records[0].version, 1);
388
- assert.equal(sealedSnapshot.records[0].cards[0].positionVersion ?? null, null);
389
- assert.equal(publishedCardFor(refreshed.cards[0]).positionVersion, 1);
390
- const readyObjects = {
391
- ...refreshed.cards[0],
392
- objects: { kind: 'bundle' },
393
- refs: refreshed.cards[0].refs.map((ref) => ({
394
- ...ref,
395
- objects: { kind: 'bundle' },
396
- })),
397
- };
398
- assert.equal(
399
- publishedCardFor(readyObjects).positionVersion,
400
- 1,
401
- 'transport readiness does not turn an accepted card into new Git work',
79
+ const bound = wall.bindProject(
80
+ PROJECT,
81
+ '11111111-1111-4111-8111-111111111111',
82
+ localCard().path,
402
83
  );
403
-
404
- const again = publishWall(repoKey);
405
- assert.equal(again.published, false);
406
- assert.equal(again.reason, 'shared-state-covered');
407
-
408
- fs.writeFileSync(path.join(repo, 'new.txt'), 'x\n');
409
- await refreshRepoCards();
410
- const republished = publishWall(repoKey);
411
- assert.equal(republished.published, false);
412
- const after = readWall(repoKey);
413
- assert.equal(after.length, 1);
414
- assert.equal(Object.hasOwn(after[0].cards[0], 'dirty'), false);
415
- assert.equal(after[0].cards[0].positionMutationId, first.mutationId);
416
- assert.equal(after[0].cards[0].positionVersion, 1);
417
- assert.equal(projectStream.readSnapshot(project.file, repoKey).records.length, 1);
84
+ assert.equal(bound.resourceId, PROJECT);
85
+ assert.equal(wall.projectForRepo(REPO_KEY).resourceId, PROJECT);
86
+ assert.equal(wall.projectForRepo('root:identical-unregistered-clone'), null);
418
87
  });
419
88
 
420
- test('a checkpoint base replaces history already covered by the applied rail', async () => {
421
- const card = readWall(repoKey)[0].cards[0];
422
- const file = checkpointFile('machine-a', repoKey);
423
- fs.mkdirSync(path.dirname(file), { recursive: true });
424
- const firstCheckpoint = {
425
- checkpoint: 'repo-checkpoint@9',
426
- machine: 'machine-a',
427
- repoKey,
428
- branch: card.head.branch,
429
- baseOid: card.head.oid,
430
- from: { revision: null, indexDiff: '', worktreeDiff: '' },
431
- to: { revision: 'revision-a', indexDiff: '', worktreeDiff: 'patch-a' },
432
- parked: [],
433
- capturedAt: '2026-07-23T00:00:01.000Z',
89
+ test('card identity is Git state, not bundle transport metadata', () => {
90
+ const first = localCard();
91
+ const second = {
92
+ ...first,
93
+ objects: bundle(HEAD, 'c'),
94
+ refs: [{ ...first.refs[0], objects: bundle(HEAD, 'd') }],
434
95
  };
435
- fs.writeFileSync(file, `${JSON.stringify(firstCheckpoint)}\n`);
436
- const first = publishWall(repoKey);
437
- assert.equal(first.published, true);
438
- assert.equal(readWall(repoKey)[0].cards[0].checkpoints.length, 1);
439
- assert.equal(readWall(repoKey)[0].cards[0].checkpoints[0].mutationId, first.mutationId);
440
-
441
- assert.deepEqual(settleProject(), [2]);
442
- assert.equal(readWall(repoKey)[0].cards[0].checkpoints[0].version, 2);
443
- assert.equal(publishWall(repoKey).published, false);
444
- const checkpointSnapshot = projectStream.readSnapshot(project.file, repoKey);
445
- assert.equal(checkpointSnapshot.records[0].version, 2);
446
- assert.equal(
447
- checkpointSnapshot.records[0].cards[0].checkpoints[0].version ?? null,
448
- null,
449
- );
450
- const reducerFile = path.join(
451
- process.env.AMALGM_DIR,
452
- 'state',
453
- 'repo-reducer',
454
- `${crypto.createHash('sha256').update(repoKey).digest('hex').slice(0, 24)}.json`,
455
- );
456
- fs.writeFileSync(reducerFile, `${JSON.stringify({
457
- repoKey,
458
- resourceId: project.resourceId,
459
- baseOid: card.head.oid,
460
- branch: card.head.branch,
461
- appliedThrough: 2,
462
- indexDiff: '',
463
- worktreeDiff: 'patch-a',
464
- })}\n`);
465
-
466
- fs.writeFileSync(file, `${JSON.stringify({
467
- ...firstCheckpoint,
468
- from: firstCheckpoint.to,
469
- to: { revision: 'revision-b', indexDiff: '', worktreeDiff: 'patch-b' },
470
- capturedAt: '2026-07-23T00:00:03.000Z',
471
- })}\n`);
472
- const second = publishWall(repoKey);
473
- assert.equal(second.published, true);
474
- const latestCard = readWall(repoKey)[0].cards[0];
475
- assert.equal(latestCard.checkpointBase.through, 2);
476
- assert.equal(latestCard.checkpoints.length, 1);
477
- assert.deepEqual(latestCard.checkpoints.map((entry) => entry.mutationId), [second.mutationId]);
478
- assert.deepEqual(settleProject(), [3]);
479
- });
480
-
481
- test('an uploading checkpoint base is not announced as complete', () => {
482
- const card = readWall(repoKey)[0].cards[0];
483
- const [stamped] = stampCards([card], [], { card, branches: [], transitions: [] }, {
484
- repoKey,
485
- baseOid: card.head.oid,
486
- branch: card.head.branch,
487
- appliedThrough: 3,
488
- indexDiff: 'x'.repeat(600 * 1024),
489
- worktreeDiff: '',
490
- }, 'mut_project_pending_base', project);
491
-
492
- assert.equal(stamped.checkpointBase, undefined);
96
+ assert.equal(wall._test.sameCardState(first, second), true);
97
+ assert.equal(wall._test.sameCardState(first, {
98
+ ...second,
99
+ refs: [{ ...second.refs[0], oid: 'e'.repeat(40) }],
100
+ }), false);
493
101
  });
494
102
 
495
- test('one failed project publisher cannot block the next repo', () => {
496
- const selected = [{ repoKey: 'root:first' }, { repoKey: 'root:second' }];
497
- const calls = [];
498
- const results = wallTest.publishSelected(selected, [], (selectedProject) => {
499
- calls.push(selectedProject.repoKey);
500
- if (selectedProject.repoKey === 'root:first') throw new Error('too large');
501
- return { published: true, repoKey: selectedProject.repoKey };
103
+ test('a physical card publishes only after an explicit local intent capture', () => {
104
+ const project = wall.projectForRepo(REPO_KEY);
105
+ projectStream.writeSnapshot(project.file, projectStream.emptySnapshot(PROJECT));
106
+ replicas.registerReplica({
107
+ resourceId: PROJECT,
108
+ localResource: `${projectStream.LOCAL_RESOURCE_PREFIX}${PROJECT}`,
109
+ localPath: project.file,
110
+ contract: projectStream.STREAM_CONTRACT,
111
+ schemaVersion: 2,
112
+ authorityEpoch: 1,
113
+ appliedVersion: 0,
114
+ snapshotVersion: 0,
502
115
  });
503
- assert.deepEqual(calls, ['root:first', 'root:second']);
504
- assert.equal(results[0].reason, 'publish-failed');
505
- assert.equal(results[1].published, true);
506
- });
507
-
508
- test('side branches keep one rail operation while the checked-out card advances', async () => {
509
- const repo = path.join(tempRoot, 'projects/app');
510
- const before = readWall(repoKey)[0].cards[0];
511
- const branch = 'storm/persistent-ref';
512
-
513
- await run(repo, 'branch', branch);
514
- await refreshRepoCards();
515
- const beforeMaterialization = fs.readFileSync(project.file, 'utf8');
516
- const blocked = await require('../workspace/reducer').withMaterialization(
517
- repoKey,
518
- async () => publishWall(repoKey),
519
- );
520
- assert.equal(blocked.published, false);
521
- assert.equal(blocked.reason, 'repo-materializing');
522
- assert.equal(fs.readFileSync(project.file, 'utf8'), beforeMaterialization);
523
116
 
524
- const created = publishWall(repoKey);
525
- assert.equal(created.published, true);
526
- let card = readWall(repoKey)[0].cards[0];
527
- const ref = card.branches.find((entry) => entry.name === branch);
528
- assert.equal(card.positionMutationId, before.positionMutationId);
529
- assert.equal(ref.mutationId, created.mutationId);
530
-
531
- assert.deepEqual(settleProject(), [4]);
532
- await run(repo, 'add', 'new.txt');
533
- await run(repo, 'commit', '-m', 'advance without moving side branch');
534
- await refreshRepoCards();
535
- const advanced = publishWall(repoKey);
536
- assert.equal(advanced.published, true);
537
- card = readWall(repoKey)[0].cards[0];
538
- assert.equal(card.positionMutationId, advanced.mutationId);
539
- assert.equal(card.branches.find((entry) => entry.name === branch).mutationId, created.mutationId);
540
-
541
- assert.deepEqual(settleProject(), [5]);
542
- await run(repo, 'branch', '-D', branch);
543
- await refreshRepoCards();
544
- const deleted = publishWall(repoKey);
545
- assert.equal(deleted.published, true);
546
- card = readWall(repoKey)[0].cards[0];
547
- assert.equal(card.branches.find((entry) => entry.name === branch).oid, null);
548
- assert.equal(card.branches.find((entry) => entry.name === branch).mutationId, deleted.mutationId);
549
- assert.deepEqual(settleProject(), [6]);
550
- });
551
-
552
- test('an inbound numbered record and a later local publish keep one record per machine', async () => {
553
- const peerMutationId = 'mut_project_peer_0001';
554
- projectStream.applyCloudMutation({
555
- version: 7,
556
- committedAt: '2026-07-26T12:00:07.000Z',
557
- envelope: {
558
- resourceId: project.resourceId,
559
- authorityEpoch: 1,
560
- mutationId: peerMutationId,
561
- deviceId: 'machine-b',
562
- baseVersion: 6,
563
- contract: projectStream.STREAM_CONTRACT,
564
- schemaVersion: 1,
565
- operationKind: 'code.publish',
566
- operation: {
567
- kind: 'code.publish',
568
- record: {
569
- wall: 'code-project@6',
570
- project: repoKey,
571
- machine: 'machine-b',
572
- mutationId: peerMutationId,
573
- publishedAt: '2026-07-26T12:00:07.000Z',
574
- cards: [],
575
- },
117
+ const card = localCard();
118
+ const withoutIntent = wall._test.publishProject(project, [card]);
119
+ assert.equal(withoutIntent.published, false);
120
+ assert.equal(withoutIntent.reason, 'shared-state-covered');
121
+ assert.equal(mutations.listPendingCloudMutations(PROJECT).length, 0);
122
+
123
+ intent.writeIntent(project, {
124
+ base: {
125
+ through: 0,
126
+ card: null,
127
+ indexTree: EMPTY_TREE,
128
+ worktreeTree: EMPTY_TREE,
129
+ },
130
+ local: {
131
+ card: {
132
+ head: card.head,
133
+ objects: card.objects,
134
+ branches: card.refs,
576
135
  },
136
+ checkpoint: {
137
+ checkpoint: checkpoint.CHECKPOINT_CONTRACT,
138
+ baseOid: card.head.oid,
139
+ branch: card.head.branch,
140
+ value: { revision: null, indexDiff: '', worktreeDiff: '' },
141
+ },
142
+ indexTree: HEAD,
143
+ worktreeTree: HEAD,
144
+ physicalDigest: 'captured-physical-state',
577
145
  },
578
146
  });
579
- nextCloudVersion = 8;
580
- markReducerApplied(7);
581
- assert.deepEqual(
582
- readWall(repoKey).map((entry) => [entry.machine, entry.version]),
583
- [['machine-a', 6], ['machine-b', 7]],
584
- );
585
147
 
586
- const repo = path.join(tempRoot, 'projects/app');
587
- fs.writeFileSync(path.join(repo, 'another.txt'), 'y\n');
588
- await run(repo, 'add', '.');
589
- await run(repo, 'commit', '-m', 'advance card');
590
- await refreshRepoCards();
591
- const result = publishWall(repoKey);
148
+ const result = wall._test.publishProject(project, [card]);
592
149
  assert.equal(result.published, true);
593
- assert.deepEqual(readWall(repoKey).map((entry) => entry.machine).sort(), ['machine-a', 'machine-b']);
594
- assert.deepEqual(settleProject(), [8]);
595
- assert.equal(projectStream.readSnapshot(project.file, repoKey).records.length, 2);
596
- });
597
-
598
- test('official code order survives mutation-journal pruning', () => {
599
- const recordBeforePrune = readWall(repoKey).find((entry) => entry.machine === 'machine-a');
600
- assert.equal(recordBeforePrune.version, 8);
601
- assert.equal(recordBeforePrune.cards[0].positionVersion, 8);
602
-
603
- const pruned = pruneMutationJournal({
604
- cutoffIso: '2100-01-01T00:00:00.000Z',
605
- batchSize: 10_000,
606
- });
607
- assert.ok(pruned.deleted >= 8);
608
- assert.equal(
609
- require('../state/mutations').getMutation(
610
- `resource:${project.resourceId}`,
611
- recordBeforePrune.mutationId,
612
- ),
613
- null,
614
- );
615
-
616
- const durable = readWall(repoKey).find((entry) => entry.machine === 'machine-a');
617
- assert.equal(durable.version, 8);
618
- assert.equal(durable.cards[0].positionVersion, 8);
619
- assert.equal(
620
- require('../workspace/wall').mutationVersion(
621
- project.resourceId,
622
- durable.mutationId,
623
- repoKey,
624
- ),
625
- 8,
626
- );
627
- assert.equal(
628
- require('../workspace/reducer').reduceWall(readWall(repoKey)).get(repoKey).card.positionVersion,
629
- 8,
150
+ assert.equal(result.delta, 'card.set');
151
+ assert.equal(projectStream.readSnapshot(project.file, PROJECT).version, 0);
152
+ const pending = mutations.listPendingCloudMutations(PROJECT);
153
+ assert.equal(pending.length, 1);
154
+ assert.equal(pending[0].operation.delta.kind, 'card.set');
155
+ assert.deepEqual(
156
+ Object.keys(pending[0].operation.delta).sort(),
157
+ ['branches', 'card', 'checkpoint', 'head', 'kind', 'objects'].sort(),
630
158
  );
631
159
  });
632
160
 
633
- test('project stats name every project resource, binding, and publish counter', () => {
634
- const stats = wallStats();
635
- const row = stats.projects.find((entry) => entry.repoKey === repoKey);
636
- assert.equal(stats.contract, 'code-project@6');
637
- assert.equal(row.resourceId, project.resourceId);
638
- assert.equal(row.replica.appliedVersion >= 0, true);
639
- assert.equal(row.machines, 2);
640
- assert.ok(stats.publishes >= 2);
641
- assert.ok(stats.publishSkips >= 1);
642
- assert.ok(stats.paths.includes(project.file));
643
- });
644
-
645
- test('ready repo objects wake the card publisher', async () => {
646
- startWallService();
647
- await new Promise((resolve) => setTimeout(resolve, 100));
648
- const before = wallStats();
649
- appendStateEvent({ resource: 'repo-objects', op: 'update', id: 'ready-bundle' });
650
- await new Promise((resolve) => setTimeout(resolve, 100));
651
- stopWallService();
652
- const after = wallStats();
653
- assert.ok(
654
- after.publishes + after.publishSkips
655
- > before.publishes + before.publishSkips,
161
+ test('catch-up coverage creates no second outbox event', () => {
162
+ const project = wall.projectForRepo(REPO_KEY);
163
+ const snapshot = projectStream._test.fold(
164
+ projectStream.emptySnapshot(PROJECT),
165
+ mutations.listPendingCloudMutations(PROJECT)[0].operation,
166
+ { machine: 'machine-a', mutationId: 'mut_official_card_0001', version: 1 },
656
167
  );
168
+ projectStream.writeSnapshot(project.file, snapshot);
169
+ const covered = wall._test.sameCardState(localCard(), snapshot.card);
170
+ assert.equal(covered, true);
657
171
  });
658
172
 
659
- test('a restarted wall publishes once even when the project is already attached', async () => {
660
- const before = wallStats();
661
- startWallService();
662
- await new Promise((resolve) => setTimeout(resolve, 500));
663
- stopWallService();
664
- const after = wallStats();
665
- assert.ok(
666
- after.publishes + after.publishSkips
667
- > before.publishes + before.publishSkips,
173
+ test('one failed project publisher cannot block another project', () => {
174
+ const first = wall.projectForRepo(REPO_KEY);
175
+ const second = wall.bindProject(
176
+ '018f0000-0000-7000-8000-00000000baaf',
177
+ '11111111-1111-4111-8111-111111111111',
178
+ path.join(tempRoot, 'repo-second'),
668
179
  );
669
- });
670
-
671
- test('the periodic project check retries an unsent wall', async () => {
672
- const originalSetInterval = global.setInterval;
673
- let periodicCheck = null;
674
- global.setInterval = (callback) => {
675
- periodicCheck = callback;
676
- return { unref() {} };
677
- };
678
- try {
679
- startWallService();
680
- await new Promise((resolve) => setTimeout(resolve, 100));
681
- const before = wallStats();
682
- periodicCheck();
683
- await new Promise((resolve) => setTimeout(resolve, 100));
684
- const after = wallStats();
685
- assert.ok(
686
- after.publishes + after.publishSkips
687
- > before.publishes + before.publishSkips,
688
- );
689
- } finally {
690
- stopWallService();
691
- global.setInterval = originalSetInterval;
692
- }
180
+ const cards = [first, second].map((project, index) => ({
181
+ ...localCard(),
182
+ path: project.path,
183
+ replica: `replica-${index}`,
184
+ }));
185
+ const results = wall._test.publishSelected([first, second], cards, (project) => {
186
+ if (project.resourceId === first.resourceId) throw new Error('too large');
187
+ return { published: true, repoKey: project.repoKey };
188
+ });
189
+ assert.equal(results[0].published, false);
190
+ assert.equal(results[1].published, true);
693
191
  });