amalgm 0.1.245 → 0.1.246

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 (157) hide show
  1. package/README.md +12 -0
  2. package/lib/cli.js +146 -8
  3. package/lib/layout.js +89 -4
  4. package/lib/shared-realtime-tunnel.js +384 -43
  5. package/lib/supervisor.js +0 -3
  6. package/package.json +2 -2
  7. package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
  8. package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
  9. package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
  10. package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
  11. package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
  12. package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
  13. package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
  14. package/runtime/scripts/amalgm-mcp/index.js +4 -0
  15. package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
  16. package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
  17. package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
  18. package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
  19. package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
  20. package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
  21. package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
  22. package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
  23. package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
  24. package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
  25. package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
  26. package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
  27. package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
  28. package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
  29. package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
  30. package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
  31. package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
  32. package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
  33. package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
  34. package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
  35. package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
  36. package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
  37. package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
  38. package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
  39. package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
  40. package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
  41. package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
  42. package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
  43. package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
  44. package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
  45. package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
  46. package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
  47. package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
  48. package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
  49. package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
  50. package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
  51. package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
  52. package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
  53. package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
  54. package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
  55. package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
  56. package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
  57. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
  58. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
  59. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
  60. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
  61. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
  62. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
  63. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
  64. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
  65. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
  66. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
  67. package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
  68. package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
  69. package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
  70. package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
  71. package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
  72. package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
  73. package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
  74. package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
  75. package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
  76. package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
  77. package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
  78. package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
  79. package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
  80. package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
  81. package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
  82. package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
  83. package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
  84. package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
  85. package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
  86. package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
  87. package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
  88. package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
  89. package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
  90. package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
  91. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
  92. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
  93. package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
  94. package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
  95. package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
  96. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
  97. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
  98. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
  99. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
  100. package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
  101. package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
  102. package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
  103. package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
  104. package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
  105. package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
  106. package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
  107. package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
  108. package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
  109. package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
  110. package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
  111. package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
  112. package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
  113. package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
  114. package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
  115. package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
  116. package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
  117. package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
  118. package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
  119. package/runtime/scripts/local-gateway.js +1 -0
  120. package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
  121. package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
  122. package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
  123. package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
  124. package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
  125. package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
  126. package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
  127. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
  128. package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
  129. package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
  130. package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
  131. package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
  132. package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
  133. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
  134. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
  135. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
  136. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
  137. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
  138. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
  139. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
  140. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
  141. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
  142. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
  143. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
  144. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
  145. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
  146. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
  147. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
  148. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
  149. package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
  150. package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
  151. package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
  152. package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
  153. package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
  154. package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
  155. package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
  156. package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
  157. package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
@@ -2,7 +2,6 @@
2
2
 
3
3
  const test = require('node:test');
4
4
  const assert = require('node:assert/strict');
5
- const crypto = require('crypto');
6
5
  const fs = require('fs');
7
6
  const os = require('os');
8
7
  const path = require('path');
@@ -22,22 +21,19 @@ fs.writeFileSync(
22
21
  );
23
22
 
24
23
  const { closeLocalDb } = require('../state/db');
25
- const replicas = require('../state/replicas');
26
24
  const {
25
+ _test: checkpointTest,
27
26
  CHECKPOINT_CONTRACT,
27
+ checkpointFile,
28
28
  sameTransition,
29
29
  } = require('../workspace/checkpoint');
30
- const { classifyBranchRefs } = require('../workspace/reducer');
31
- const projectStream = require('../workspace/project-stream');
32
- const wall = require('../workspace/wall');
33
-
34
- const PROJECT = '018f0000-0000-7000-8000-000000000040';
35
- const HEAD = 'a'.repeat(40);
36
- const project = wall.bindProject(
37
- PROJECT,
38
- '11111111-1111-4111-8111-111111111111',
39
- path.join(tempRoot, 'repo'),
40
- );
30
+ const { parkedForState } = require('../workspace/reducer');
31
+ const {
32
+ compactWallText,
33
+ hasLocalContribution,
34
+ publicationStatus,
35
+ stampCards,
36
+ } = require('../workspace/wall');
41
37
 
42
38
  test.after(() => {
43
39
  closeLocalDb();
@@ -46,210 +42,271 @@ test.after(() => {
46
42
 
47
43
  const rules = {
48
44
  card: {
49
- identity: 'HEAD, complete branch map, and checkpoint content',
50
- ownership: 'local until that exact value returns officially',
51
- liveness: 'one durable outbox proposal retries',
52
- gating: 'publish only while current',
45
+ identity: 'branch-and-commit-content',
46
+ ownership: 'originator-publishes',
47
+ liveness: 'shared-wall-outbox',
48
+ gating: 'defines-winning-card',
49
+ },
50
+ branch: {
51
+ identity: 'ref-name-and-commit-content',
52
+ ownership: 'originator-publishes',
53
+ liveness: 'shared-wall-outbox',
54
+ gating: 'per-ref-rail-order',
53
55
  },
54
56
  checkpoint: {
55
- identity: 'exact from and to working-tree content',
56
- ownership: 'local until that exact value returns officially',
57
- liveness: 'one durable outbox proposal retries',
58
- gating: 'compare-and-set from the current card',
57
+ identity: 'from-and-to-content',
58
+ ownership: 'originator-publishes',
59
+ liveness: 'shared-wall-outbox',
60
+ gating: 'winning-card-only',
59
61
  },
60
- snapshot: {
61
- identity: 'canonical state through one official version',
62
- ownership: 'server-derived only',
63
- liveness: 'hydrate then replay the contiguous tail',
64
- gating: 'never creates a content event',
62
+ parked: {
63
+ identity: 'commit-and-object-content',
64
+ ownership: 'holder-publishes-until-acknowledged',
65
+ liveness: 'shared-wall-outbox',
66
+ gating: 'inside-winning-card-checkpoint',
65
67
  },
66
- transport: {
67
- identity: 'content-addressed bytes',
68
- ownership: 'no repo meaning',
69
- liveness: 'repair or surface; never wait forever',
70
- gating: 'all bytes exist before numbering',
68
+ compaction: {
69
+ identity: 'derived-deletion',
70
+ ownership: 'any-machine-may-compact',
71
+ liveness: 'not-semantic-state',
72
+ gating: 'commutative-with-writers',
71
73
  },
72
74
  };
73
75
 
74
- test('the complete publication model has four properties and no hidden value type', () => {
75
- assert.deepEqual(Object.keys(rules), ['card', 'checkpoint', 'snapshot', 'transport']);
76
+ test('the publication model has one explicit rule for every value and property', () => {
77
+ assert.deepEqual(Object.keys(rules), ['card', 'branch', 'checkpoint', 'parked', 'compaction']);
76
78
  for (const rule of Object.values(rules)) {
77
79
  assert.deepEqual(Object.keys(rule), ['identity', 'ownership', 'liveness', 'gating']);
78
80
  assert.equal(Object.values(rule).every(Boolean), true);
79
81
  }
82
+ assert.deepEqual(
83
+ new Set(['card', 'branch', 'checkpoint', 'parked'].map((name) => rules[name].liveness)),
84
+ new Set(['shared-wall-outbox']),
85
+ );
80
86
  });
81
87
 
82
- function chunks(byte = 'a', bytes = 1) {
83
- const list = [{ sha256: byte.repeat(64), bytes }];
84
- return {
85
- checksum: crypto.createHash('sha256').update(JSON.stringify(list)).digest('hex'),
86
- bytes,
87
- chunks: list,
88
- };
89
- }
90
-
91
- function bundle(oid = HEAD, byte = 'a') {
92
- return {
93
- kind: 'bundle',
94
- contract: projectStream.BUNDLE_CONTRACT,
95
- resourceId: PROJECT,
96
- oid,
97
- ref: `refs/amalgm/exports/${oid}`,
98
- ...chunks(byte),
99
- };
100
- }
101
-
102
- function cardSet() {
103
- return {
104
- kind: 'card.set',
105
- card: projectStream.CARD_CONTRACT,
106
- head: { branch: 'main', oid: HEAD, detached: false },
107
- objects: bundle(),
108
- branches: [{ name: 'main', oid: HEAD, objects: bundle() }],
109
- checkpoint: {
110
- checkpoint: projectStream.CHECKPOINT_CONTRACT,
111
- baseOid: HEAD,
112
- branch: 'main',
113
- value: { revision: null, indexDiff: '', worktreeDiff: '' },
114
- },
115
- };
116
- }
117
-
118
- test('publication requires one current applied position and no unresolved send', () => {
119
- projectStream.writeSnapshot(project.file, projectStream.emptySnapshot(PROJECT));
120
- replicas.registerReplica({
121
- resourceId: PROJECT,
122
- localResource: `${projectStream.LOCAL_RESOURCE_PREFIX}${PROJECT}`,
123
- localPath: project.file,
124
- contract: projectStream.STREAM_CONTRACT,
125
- schemaVersion: 2,
88
+ test('publication requires one current applied rail position and no unresolved send', () => {
89
+ const repoKey = 'root:currentness';
90
+ const resourceId = require('../workspace/wall').projectResourceId(repoKey);
91
+ const file = path.join(tempRoot, 'currentness.jsonl');
92
+ fs.writeFileSync(file, '');
93
+ const opened = require('../state/docs').openDoc(file);
94
+ require('../state/replicas').registerReplica({
95
+ resourceId,
96
+ localResource: opened.resource,
97
+ localPath: file,
98
+ contract: 'text-yjs@1',
99
+ schemaVersion: 1,
126
100
  authorityEpoch: 1,
127
101
  appliedVersion: 12,
128
- snapshotVersion: 0,
129
102
  });
130
103
 
131
- assert.equal(wall.publicationStatus(project.repoKey, { appliedThrough: 11 }).current, false);
132
- assert.equal(wall.publicationStatus(project.repoKey, { appliedThrough: 12 }).current, true);
133
-
134
- projectStream.publish(project, cardSet(), 'mut_publication_gate_01');
135
- const waiting = wall.publicationStatus(project.repoKey, { appliedThrough: 12 });
136
- assert.equal(waiting.pending, 1);
137
- assert.equal(waiting.current, false);
104
+ assert.deepEqual(publicationStatus(repoKey, { appliedThrough: 11 }), {
105
+ receivedThrough: 12,
106
+ appliedThrough: 11,
107
+ pending: 0,
108
+ current: false,
109
+ });
110
+ assert.deepEqual(publicationStatus(repoKey, { appliedThrough: 12 }), {
111
+ receivedThrough: 12,
112
+ appliedThrough: 12,
113
+ pending: 0,
114
+ current: true,
115
+ });
116
+ require('../state/docs').writeDocText(file, 'pending\n');
117
+ assert.deepEqual(publicationStatus(repoKey, { appliedThrough: 12 }), {
118
+ receivedThrough: 12,
119
+ appliedThrough: 12,
120
+ pending: 1,
121
+ current: false,
122
+ });
138
123
  });
139
124
 
140
- test('server stamps and object locators do not invent new repo meaning', () => {
125
+ test('server rail numbers do not change checkpoint or parked identity', () => {
126
+ const oid = 'a'.repeat(40);
141
127
  const transition = {
142
128
  checkpoint: CHECKPOINT_CONTRACT,
143
129
  machine: 'machine-a',
144
- repoKey: project.repoKey,
130
+ repoKey: 'root:test',
145
131
  branch: 'main',
146
- baseOid: HEAD,
132
+ baseOid: oid,
147
133
  from: { revision: null, indexDiff: '', worktreeDiff: '' },
148
- to: { revision: 'b'.repeat(64), indexDiff: '', worktreeDiff: 'patch-a' },
134
+ to: { revision: 'work-a', indexDiff: '', worktreeDiff: 'patch-a' },
135
+ parked: [{
136
+ oid: 'b'.repeat(40),
137
+ ref: `refs/amalgm/parked/${'b'.repeat(40)}`,
138
+ objects: { kind: 'bundle', checksum: 'c'.repeat(64) },
139
+ }],
149
140
  };
150
141
  assert.equal(sameTransition(transition, {
151
142
  ...transition,
152
- mutationId: 'mut_server_metadata_0001',
143
+ mutationId: 'mut_server_metadata',
153
144
  version: 41,
145
+ parked: transition.parked.map((entry) => ({ ...entry, version: 41 })),
154
146
  }), true);
155
-
156
- const first = {
157
- head: { branch: 'main', oid: HEAD, detached: false },
158
- refs: [{ name: 'main', oid: HEAD, objects: bundle(HEAD, 'a') }],
159
- };
160
- const second = {
161
- ...first,
162
- objects: bundle(HEAD, 'b'),
163
- refs: [{ ...first.refs[0], objects: bundle(HEAD, 'c') }],
164
- };
165
- assert.equal(wall._test.sameCardState(first, second), true);
166
- assert.equal(wall._test.sameCardState(first, {
167
- ...second,
168
- refs: [{ ...second.refs[0], oid: 'd'.repeat(40) }],
147
+ assert.equal(sameTransition(transition, {
148
+ ...transition,
149
+ to: { ...transition.to, revision: 'different-work' },
150
+ }), false);
151
+ assert.equal(sameTransition(transition, {
152
+ ...transition,
153
+ parked: transition.parked.map((entry) => ({
154
+ ...entry,
155
+ objects: { ...entry.objects, checksum: 'd'.repeat(64) },
156
+ })),
169
157
  }), false);
170
158
  });
171
159
 
172
- test('only exact durable branch intent can hold a local value against the rail', () => {
173
- const a = 'a'.repeat(40);
174
- const b = 'b'.repeat(40);
175
- const c = 'c'.repeat(40);
176
- const d = 'd'.repeat(40);
177
- const card = { head: { branch: 'main', oid: HEAD } };
178
- const owned = {
179
- base: {
180
- card: {
181
- head: card.head,
182
- branches: [{ name: 'feature', oid: a }],
183
- },
184
- },
185
- local: {
186
- card: {
187
- head: card.head,
188
- branches: [{ name: 'feature', oid: c }],
189
- },
190
- },
160
+ test('receivers stay silent and only unacknowledged local parked work republishes', () => {
161
+ const head = 'a'.repeat(40);
162
+ const local = {
163
+ head: { branch: 'main', oid: head, detached: false },
164
+ refs: [{ name: 'main', oid: head }],
165
+ };
166
+ const transition = {
167
+ checkpoint: CHECKPOINT_CONTRACT,
168
+ machine: 'machine-a',
169
+ repoKey: 'root:test',
170
+ branch: 'main',
171
+ baseOid: head,
172
+ from: { revision: null, indexDiff: '', worktreeDiff: '' },
173
+ to: { revision: 'shared-work', indexDiff: '', worktreeDiff: 'patch' },
174
+ parked: [],
191
175
  };
176
+ const plan = {
177
+ card: { head: { branch: 'main', oid: head, detached: false } },
178
+ branches: [{ name: 'main', oid: head }],
179
+ transitions: [{ ...transition, version: 9 }],
180
+ };
181
+ const baseline = {
182
+ branch: 'main',
183
+ baseOid: head,
184
+ branches: [{ name: 'main', oid: head }],
185
+ };
186
+ assert.equal(hasLocalContribution(local, plan, baseline, transition), false);
192
187
 
193
- const first = classifyBranchRefs(
194
- {
195
- card,
196
- branches: [
197
- { name: 'main', oid: HEAD },
198
- { name: 'feature', oid: b },
199
- ],
200
- },
201
- [
202
- { name: 'main', oid: HEAD },
203
- { name: 'feature', oid: c },
204
- ],
205
- owned,
188
+ const localParked = { oid: 'b'.repeat(40), objects: { kind: 'bundle', checksum: 'local' } };
189
+ const unsentParked = { oid: 'c'.repeat(40), objects: { kind: 'bundle', checksum: 'unsent' } };
190
+ const peerParked = {
191
+ oid: 'd'.repeat(40),
192
+ objects: { kind: 'bundle', checksum: 'peer' },
193
+ version: 9,
194
+ };
195
+ assert.deepEqual(
196
+ checkpointTest.localParkedEntries(
197
+ { ...transition, parked: [localParked] },
198
+ { parked: [peerParked, unsentParked] },
199
+ local,
200
+ ),
201
+ [localParked, unsentParked],
206
202
  );
207
- assert.deepEqual(first.changes, []);
208
- assert.deepEqual(first.pending, [{ name: 'feature', oid: c }]);
209
-
210
- const acknowledged = classifyBranchRefs(
211
- {
212
- card,
213
- branches: [
214
- { name: 'main', oid: HEAD },
215
- { name: 'feature', oid: c },
216
- ],
217
- },
218
- [
219
- { name: 'main', oid: HEAD },
220
- { name: 'feature', oid: c },
221
- ],
222
- null,
203
+ assert.deepEqual(parkedForState([localParked], [], []), [localParked]);
204
+ assert.deepEqual(
205
+ parkedForState([localParked], [{ ...localParked, version: 10 }], []),
206
+ [{ ...localParked, version: 10 }],
223
207
  );
224
- assert.deepEqual(acknowledged.pending, []);
225
-
226
- const later = classifyBranchRefs(
227
- {
228
- card,
229
- branches: [
230
- { name: 'main', oid: HEAD },
231
- { name: 'feature', oid: d },
232
- ],
233
- },
234
- [
235
- { name: 'main', oid: HEAD },
236
- { name: 'feature', oid: c },
208
+ assert.deepEqual(parkedForState([{ ...localParked, version: 10 }], [], []), []);
209
+ });
210
+
211
+ test('card, branch, checkpoint, and parked work ride one wall mutation', () => {
212
+ const repoKey = 'https://github.com/amalgm-inc/publication-invariant-test';
213
+ const head = 'a'.repeat(40);
214
+ const parkedOid = 'b'.repeat(40);
215
+ const checkpoint = {
216
+ checkpoint: CHECKPOINT_CONTRACT,
217
+ machine: 'machine-a',
218
+ repoKey,
219
+ branch: 'main',
220
+ baseOid: head,
221
+ from: { revision: null, indexDiff: '', worktreeDiff: '' },
222
+ to: { revision: 'work-a', indexDiff: '', worktreeDiff: 'patch-a' },
223
+ parked: [{
224
+ oid: parkedOid,
225
+ ref: `refs/amalgm/parked/${parkedOid}`,
226
+ objects: { kind: 'bundle', checksum: 'c'.repeat(64) },
227
+ }],
228
+ };
229
+ const file = checkpointFile('machine-a', repoKey);
230
+ fs.mkdirSync(path.dirname(file), { recursive: true });
231
+ fs.writeFileSync(file, `${JSON.stringify(checkpoint)}\n`);
232
+
233
+ const mutationId = 'mut_one_shared_wall_outbox';
234
+ const stamped = stampCards([{
235
+ card: 'repo-card@2',
236
+ machine: 'machine-a',
237
+ replica: 'rpl_publication_test',
238
+ kind: 'primary',
239
+ repo: { remote: repoKey },
240
+ head: { branch: 'main', oid: head, detached: false },
241
+ refs: [
242
+ { name: 'main', oid: head },
243
+ { name: 'feature', oid: head },
237
244
  ],
238
- null,
239
- );
240
- assert.deepEqual(later.changes, [{ branch: 'feature', expectedOid: c, oid: d }]);
245
+ health: 'healthy',
246
+ }], [], null, null, mutationId, {
247
+ repoKey,
248
+ resourceId: 'shr_publication_invariant_test',
249
+ })[0];
250
+
251
+ assert.equal(stamped.positionMutationId, mutationId);
252
+ assert.equal(stamped.branches.every((ref) => ref.mutationId === mutationId), true);
253
+ assert.equal(stamped.checkpoints[0].mutationId, mutationId);
254
+ assert.equal(stamped.checkpoints[0].parked[0].oid, parkedOid);
241
255
  });
242
256
 
243
- test('a card waits for every referenced commit without creating transport events', () => {
244
- const pending = {
245
- head: { branch: 'main', oid: HEAD, detached: false },
246
- objects: { kind: 'pending' },
247
- refs: [{ name: 'main', oid: HEAD, objects: { kind: 'pending' } }],
257
+ test('two concurrent compacting publishers converge without losing either latest value', () => {
258
+ const Y = require('yjs');
259
+ const repoKey = 'root:compaction-collision';
260
+ const line = (machine, value) => JSON.stringify({
261
+ wall: 'code-project@4',
262
+ project: repoKey,
263
+ machine,
264
+ mutationId: `mut_${machine}_${value}`,
265
+ cards: [{ value }],
266
+ });
267
+ const baseText = [
268
+ line('machine-a', 'old-a'),
269
+ line('machine-b', 'old-b'),
270
+ line('machine-a', 'current-a'),
271
+ line('machine-b', 'current-b'),
272
+ '',
273
+ ].join('\n');
274
+ const compacted = compactWallText(baseText, repoKey);
275
+ const removedBytes = baseText.length - compacted.length;
276
+ assert.ok(removedBytes > 0);
277
+
278
+ const base = new Y.Doc();
279
+ base.getText('content').insert(0, baseText);
280
+ const baseUpdate = Y.encodeStateAsUpdate(base);
281
+ const baseVector = Y.encodeStateVector(base);
282
+ const publisherUpdate = (machine, value) => {
283
+ const writer = new Y.Doc();
284
+ Y.applyUpdate(writer, baseUpdate);
285
+ const text = writer.getText('content');
286
+ text.delete(0, removedBytes);
287
+ text.insert(text.length, `${line(machine, value)}\n`);
288
+ return Y.encodeStateAsUpdate(writer, baseVector);
248
289
  };
249
- assert.equal(wall._test.hasPendingContributionObjects(pending), true);
250
- assert.equal(wall._test.hasPendingContributionObjects({
251
- ...pending,
252
- objects: bundle(),
253
- refs: [{ ...pending.refs[0], objects: bundle() }],
254
- }), false);
290
+ const updateA = publisherUpdate('machine-a', 'latest-a');
291
+ const updateB = publisherUpdate('machine-b', 'latest-b');
292
+ const replicas = [
293
+ [updateA, updateB],
294
+ [updateB, updateA],
295
+ ].map((order) => {
296
+ const replica = new Y.Doc();
297
+ Y.applyUpdate(replica, baseUpdate);
298
+ for (const update of order) Y.applyUpdate(replica, update);
299
+ return replica.getText('content').toString();
300
+ });
301
+
302
+ assert.equal(replicas[0], replicas[1]);
303
+ const latest = new Map();
304
+ for (const raw of replicas[0].trim().split('\n')) {
305
+ const entry = JSON.parse(raw);
306
+ latest.set(entry.machine, entry.cards[0].value);
307
+ }
308
+ assert.deepEqual(Object.fromEntries(latest), {
309
+ 'machine-a': 'latest-a',
310
+ 'machine-b': 'latest-b',
311
+ });
255
312
  });