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.
- package/README.md +12 -0
- package/lib/cli.js +146 -8
- package/lib/layout.js +89 -4
- package/lib/shared-realtime-tunnel.js +384 -43
- package/lib/supervisor.js +0 -3
- package/package.json +2 -2
- package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
- package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
- package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
- package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
- package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
- package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
- package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
- package/runtime/scripts/amalgm-mcp/index.js +4 -0
- package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
- package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
- package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
- package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
- package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
- package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
- package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
- package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
- package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
- package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
- package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
- package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
- package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
- package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
- package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
- package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
- package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
- package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
- package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
- package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
- package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
- package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
- package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
- package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
- package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
- package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
- package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
- package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
- package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
- package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
- package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
- package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
- package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
- package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
- package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
- package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
- package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
- package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
- package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
- package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
- package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
- package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
- package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
- package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
- package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
- package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
- package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
- package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
- package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
- package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
- package/runtime/scripts/local-gateway.js +1 -0
- package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
- package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
- package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
- package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
- package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
- package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
- package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
- package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
- package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
- package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
- package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
- package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
- package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
- package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
- package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
- package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
|
@@ -123,7 +123,7 @@ test('repo object chunks travel through the shared tunnel one at a time', async
|
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
-
test('repo object chunks retry
|
|
126
|
+
test('repo object chunks retry when an acknowledgement is lost', async () => {
|
|
127
127
|
const uploadBytes = Buffer.from('retry this content-addressed chunk');
|
|
128
128
|
const uploadSha = crypto.createHash('sha256').update(uploadBytes).digest('hex');
|
|
129
129
|
const resourceId = 'shr_cp1abcdefghijklmnopqrstuvwx';
|
|
@@ -165,25 +165,14 @@ test('repo object chunks retry after the tunnel reconnects', async () => {
|
|
|
165
165
|
localPort: () => server.address().port,
|
|
166
166
|
localHeaders: (headers) => headers,
|
|
167
167
|
warn: () => {},
|
|
168
|
+
repoObjectRequestTimeoutMs: 25,
|
|
168
169
|
});
|
|
169
170
|
|
|
170
171
|
try {
|
|
171
|
-
tunnel.enable(['shared.mutation.v1']);
|
|
172
|
-
await until(
|
|
173
|
-
() => frames.filter((frame) => frame.type === 'repo.object.put').length === 1,
|
|
174
|
-
'the original upload',
|
|
175
|
-
);
|
|
176
|
-
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
177
|
-
assert.equal(
|
|
178
|
-
frames.filter((frame) => frame.type === 'repo.object.put').length,
|
|
179
|
-
1,
|
|
180
|
-
'a healthy ordered tunnel never duplicates an in-flight upload',
|
|
181
|
-
);
|
|
182
|
-
tunnel.stop();
|
|
183
172
|
tunnel.enable(['shared.mutation.v1']);
|
|
184
173
|
await until(
|
|
185
174
|
() => frames.filter((frame) => frame.type === 'repo.object.put').length >= 2,
|
|
186
|
-
'the upload
|
|
175
|
+
'the unacknowledged upload retry',
|
|
187
176
|
);
|
|
188
177
|
const puts = frames.filter((frame) => frame.type === 'repo.object.put');
|
|
189
178
|
assert.notEqual(puts[0].request_id, puts[1].request_id);
|
|
@@ -200,160 +189,3 @@ test('repo object chunks retry after the tunnel reconnects', async () => {
|
|
|
200
189
|
server.close();
|
|
201
190
|
}
|
|
202
191
|
});
|
|
203
|
-
|
|
204
|
-
test('a rejected stale object yields the tunnel to the next upload', async () => {
|
|
205
|
-
const staleBytes = Buffer.from('stale denied object');
|
|
206
|
-
const staleSha = crypto.createHash('sha256').update(staleBytes).digest('hex');
|
|
207
|
-
const currentBytes = Buffer.from('current allowed object');
|
|
208
|
-
const currentSha = crypto.createHash('sha256').update(currentBytes).digest('hex');
|
|
209
|
-
const resourceId = 'shr_cp1abcdefghijklmnopqrstuvwx';
|
|
210
|
-
let failed = false;
|
|
211
|
-
const calls = [];
|
|
212
|
-
const server = http.createServer((req, res) => {
|
|
213
|
-
let raw = '';
|
|
214
|
-
req.on('data', (chunk) => { raw += chunk; });
|
|
215
|
-
req.on('end', () => {
|
|
216
|
-
const body = raw ? JSON.parse(raw) : null;
|
|
217
|
-
calls.push({ path: req.url, body });
|
|
218
|
-
res.setHeader('content-type', 'application/json');
|
|
219
|
-
if (req.url === '/state/shared/replicas') {
|
|
220
|
-
res.end('{"replicas":[]}');
|
|
221
|
-
} else if (req.url.startsWith('/state/stream')) {
|
|
222
|
-
res.writeHead(200, { 'content-type': 'text/event-stream' });
|
|
223
|
-
res.write('\n');
|
|
224
|
-
} else if (req.url === '/state/repo-objects/uploads/claim') {
|
|
225
|
-
const bytes = failed ? currentBytes : staleBytes;
|
|
226
|
-
const sha256 = failed ? currentSha : staleSha;
|
|
227
|
-
res.end(JSON.stringify({
|
|
228
|
-
upload: {
|
|
229
|
-
resourceId,
|
|
230
|
-
sha256,
|
|
231
|
-
bytes: bytes.length,
|
|
232
|
-
dataBase64: bytes.toString('base64'),
|
|
233
|
-
},
|
|
234
|
-
}));
|
|
235
|
-
} else if (req.url === '/state/repo-objects/uploads/fail') {
|
|
236
|
-
failed = true;
|
|
237
|
-
res.end('{"changed":true,"terminal":true}');
|
|
238
|
-
} else if (req.url.endsWith('/claim')) {
|
|
239
|
-
res.end('{}');
|
|
240
|
-
} else {
|
|
241
|
-
res.end('{}');
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
await new Promise((resolve) => { server.listen(0, '127.0.0.1', resolve); });
|
|
246
|
-
|
|
247
|
-
const frames = [];
|
|
248
|
-
const tunnel = createSharedRealtimeTunnel({
|
|
249
|
-
send: (frame) => { frames.push(frame); return true; },
|
|
250
|
-
sendLarge: (frame) => { frames.push(frame); return true; },
|
|
251
|
-
localPort: () => server.address().port,
|
|
252
|
-
localHeaders: (headers) => headers,
|
|
253
|
-
warn: () => {},
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
tunnel.enable(['shared.mutation.v1']);
|
|
258
|
-
await until(
|
|
259
|
-
() => frames.some((frame) => frame.type === 'repo.object.put'),
|
|
260
|
-
'the stale upload',
|
|
261
|
-
);
|
|
262
|
-
const stale = frames.find((frame) => frame.type === 'repo.object.put');
|
|
263
|
-
assert.equal(stale.sha256, staleSha);
|
|
264
|
-
tunnel.handleFrame({
|
|
265
|
-
type: 'shared.error',
|
|
266
|
-
request_id: stale.request_id,
|
|
267
|
-
resource_id: resourceId,
|
|
268
|
-
code: 'shared_resource_read_forbidden',
|
|
269
|
-
message: 'stale rail is no longer readable',
|
|
270
|
-
retryable: false,
|
|
271
|
-
});
|
|
272
|
-
await until(
|
|
273
|
-
() => frames.some((frame) => (
|
|
274
|
-
frame.type === 'repo.object.put' && frame.sha256 === currentSha
|
|
275
|
-
)),
|
|
276
|
-
'the current upload behind the stale one',
|
|
277
|
-
);
|
|
278
|
-
assert.deepEqual(
|
|
279
|
-
calls.find((call) => call.path === '/state/repo-objects/uploads/fail').body,
|
|
280
|
-
{
|
|
281
|
-
resourceId,
|
|
282
|
-
sha256: staleSha,
|
|
283
|
-
retryable: false,
|
|
284
|
-
code: 'shared_resource_read_forbidden',
|
|
285
|
-
message: 'stale rail is no longer readable',
|
|
286
|
-
},
|
|
287
|
-
);
|
|
288
|
-
} finally {
|
|
289
|
-
tunnel.stop();
|
|
290
|
-
server.close();
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test('a healthy tunnel sends one request for one durable mutation', async () => {
|
|
295
|
-
const envelope = {
|
|
296
|
-
resourceId: '018f0000-0000-7000-8000-000000000050',
|
|
297
|
-
authorityEpoch: 1,
|
|
298
|
-
mutationId: 'mut_one_ordered_request',
|
|
299
|
-
deviceId: 'machine-a',
|
|
300
|
-
baseVersion: 0,
|
|
301
|
-
contract: 'code-project-stream@2',
|
|
302
|
-
schemaVersion: 2,
|
|
303
|
-
operationKind: 'code.publish',
|
|
304
|
-
operation: {
|
|
305
|
-
kind: 'code.publish',
|
|
306
|
-
project: '018f0000-0000-7000-8000-000000000050',
|
|
307
|
-
delta: { kind: 'checkpoint.set' },
|
|
308
|
-
},
|
|
309
|
-
};
|
|
310
|
-
let wakeResponse = null;
|
|
311
|
-
const server = http.createServer((req, res) => {
|
|
312
|
-
req.resume();
|
|
313
|
-
req.on('end', () => {
|
|
314
|
-
res.setHeader('content-type', 'application/json');
|
|
315
|
-
if (req.url === '/state/shared/replicas') {
|
|
316
|
-
res.end('{"replicas":[]}');
|
|
317
|
-
} else if (req.url.startsWith('/state/stream')) {
|
|
318
|
-
res.writeHead(200, { 'content-type': 'text/event-stream' });
|
|
319
|
-
res.write('\n');
|
|
320
|
-
wakeResponse = res;
|
|
321
|
-
} else if (req.url === '/state/mutations/claim') {
|
|
322
|
-
res.end(JSON.stringify({ mutations: [{ envelope }] }));
|
|
323
|
-
} else if (req.url.endsWith('/claim')) {
|
|
324
|
-
res.end('{}');
|
|
325
|
-
} else {
|
|
326
|
-
res.end('{}');
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
await new Promise((resolve) => { server.listen(0, '127.0.0.1', resolve); });
|
|
331
|
-
|
|
332
|
-
const frames = [];
|
|
333
|
-
const tunnel = createSharedRealtimeTunnel({
|
|
334
|
-
send: (frame) => { frames.push(frame); return true; },
|
|
335
|
-
sendLarge: (frame) => { frames.push(frame); return true; },
|
|
336
|
-
localPort: () => server.address().port,
|
|
337
|
-
localHeaders: (headers) => headers,
|
|
338
|
-
warn: () => {},
|
|
339
|
-
});
|
|
340
|
-
const submits = () => frames.filter((frame) => frame.type === 'shared.mutation.submit');
|
|
341
|
-
|
|
342
|
-
try {
|
|
343
|
-
tunnel.enable(['shared.mutation.v1']);
|
|
344
|
-
await until(() => submits().length === 1 && wakeResponse, 'the original mutation request');
|
|
345
|
-
for (let index = 0; index < 3; index += 1) {
|
|
346
|
-
wakeResponse.write('event: state\ndata: {"sharedResourceId":"shr_cp1abcdefghijklmnopqrstuvwx"}\n\n');
|
|
347
|
-
await new Promise((resolve) => { setTimeout(resolve, 20); });
|
|
348
|
-
}
|
|
349
|
-
assert.equal(submits().length, 1, 'wake signals never duplicate an in-flight mutation');
|
|
350
|
-
|
|
351
|
-
tunnel.stop();
|
|
352
|
-
tunnel.enable(['shared.mutation.v1']);
|
|
353
|
-
await until(() => submits().length === 2, 'the mutation after reconnect');
|
|
354
|
-
assert.notEqual(submits()[0].request_id, submits()[1].request_id);
|
|
355
|
-
} finally {
|
|
356
|
-
tunnel.stop();
|
|
357
|
-
server.close();
|
|
358
|
-
}
|
|
359
|
-
});
|
|
@@ -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');
|
|
@@ -26,38 +25,16 @@ fs.writeFileSync(
|
|
|
26
25
|
const { closeLocalDb } = require('../state/db');
|
|
27
26
|
const { hasCommit, headOid } = require('../workspace/git');
|
|
28
27
|
const {
|
|
29
|
-
_test: { locatorChecksum },
|
|
30
28
|
acknowledgeUpload,
|
|
31
29
|
claimDownload,
|
|
32
30
|
claimUpload,
|
|
33
|
-
failUpload,
|
|
34
31
|
importFromLocator,
|
|
35
32
|
objectSourceFor,
|
|
36
|
-
prepareBundleImport,
|
|
37
33
|
publishPayload,
|
|
38
34
|
readPayload,
|
|
39
35
|
receiveDownload,
|
|
40
36
|
} = require('../workspace/objects');
|
|
41
37
|
|
|
42
|
-
const PROJECT = '018f0000-0000-7000-8000-000000000030';
|
|
43
|
-
const PROJECT_BINDING = require('../workspace/wall').bindProject(
|
|
44
|
-
PROJECT,
|
|
45
|
-
'11111111-1111-4111-8111-111111111111',
|
|
46
|
-
tempRoot,
|
|
47
|
-
);
|
|
48
|
-
const REPO_KEY = PROJECT_BINDING.repoKey;
|
|
49
|
-
|
|
50
|
-
test('a locator checksum binds the exact ordered chunk list', () => {
|
|
51
|
-
const first = { sha256: 'a'.repeat(64), bytes: 3 };
|
|
52
|
-
const second = { sha256: 'b'.repeat(64), bytes: 4 };
|
|
53
|
-
assert.equal(locatorChecksum([first, second]), locatorChecksum([first, second]));
|
|
54
|
-
assert.notEqual(locatorChecksum([first, second]), locatorChecksum([second, first]));
|
|
55
|
-
assert.notEqual(
|
|
56
|
-
locatorChecksum([first, second]),
|
|
57
|
-
locatorChecksum([first, { ...second, bytes: 5 }]),
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
38
|
test.after(() => {
|
|
62
39
|
closeLocalDb();
|
|
63
40
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
@@ -72,7 +49,7 @@ async function git(cwd, ...args) {
|
|
|
72
49
|
return stdout;
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
test('
|
|
52
|
+
test('an unpushed commit travels as card-owned chunks and imports without moving a ref', async () => {
|
|
76
53
|
const origin = path.join(tempRoot, 'origin.git');
|
|
77
54
|
await git(tempRoot, 'init', '--bare', '-b', 'main', origin);
|
|
78
55
|
const source = path.join(tempRoot, 'source');
|
|
@@ -84,12 +61,12 @@ test('every committed card travels as project-owned chunks and imports without m
|
|
|
84
61
|
|
|
85
62
|
const destination = path.join(tempRoot, 'destination');
|
|
86
63
|
await git(tempRoot, 'clone', '-q', origin, destination);
|
|
87
|
-
assert.equal((await objectSourceFor(source,
|
|
64
|
+
assert.equal((await objectSourceFor(source, 'repo:test', await headOid(source))).kind, 'remote');
|
|
88
65
|
|
|
89
66
|
fs.appendFileSync(path.join(source, 'app.js'), 'two\n');
|
|
90
67
|
await git(source, 'commit', '-am', 'two');
|
|
91
68
|
const unpushedOid = await headOid(source);
|
|
92
|
-
assert.equal((await objectSourceFor(source,
|
|
69
|
+
assert.equal((await objectSourceFor(source, 'repo:test', unpushedOid)).kind, 'pending');
|
|
93
70
|
|
|
94
71
|
const uploaded = new Map();
|
|
95
72
|
const deadline = Date.now() + 10_000;
|
|
@@ -101,10 +78,10 @@ test('every committed card travels as project-owned chunks and imports without m
|
|
|
101
78
|
}
|
|
102
79
|
uploaded.set(upload.sha256, upload.dataBase64);
|
|
103
80
|
acknowledgeUpload(upload.resourceId, upload.sha256);
|
|
104
|
-
const sourceState = await objectSourceFor(source,
|
|
81
|
+
const sourceState = await objectSourceFor(source, 'repo:test', unpushedOid);
|
|
105
82
|
if (sourceState.kind === 'bundle') break;
|
|
106
83
|
}
|
|
107
|
-
const locator = await objectSourceFor(source,
|
|
84
|
+
const locator = await objectSourceFor(source, 'repo:test', unpushedOid);
|
|
108
85
|
assert.equal(locator.kind, 'bundle');
|
|
109
86
|
assert.ok(locator.chunks.length > 0);
|
|
110
87
|
|
|
@@ -126,7 +103,7 @@ test('every committed card travels as project-owned chunks and imports without m
|
|
|
126
103
|
|
|
127
104
|
test('a large checkpoint uses the same verified chunk lane', () => {
|
|
128
105
|
const content = Buffer.alloc(1_300_000, 0x61);
|
|
129
|
-
assert.equal(publishPayload('large-checkpoint', content,
|
|
106
|
+
assert.equal(publishPayload('large-checkpoint', content, 'repo:test').kind, 'pending');
|
|
130
107
|
|
|
131
108
|
const uploaded = new Map();
|
|
132
109
|
while (true) {
|
|
@@ -135,7 +112,7 @@ test('a large checkpoint uses the same verified chunk lane', () => {
|
|
|
135
112
|
uploaded.set(upload.sha256, upload.dataBase64);
|
|
136
113
|
acknowledgeUpload(upload.resourceId, upload.sha256);
|
|
137
114
|
}
|
|
138
|
-
const locator = publishPayload('large-checkpoint', content,
|
|
115
|
+
const locator = publishPayload('large-checkpoint', content, 'repo:test');
|
|
139
116
|
assert.equal(locator.kind, 'chunks');
|
|
140
117
|
assert.ok(locator.chunks.length >= 3);
|
|
141
118
|
|
|
@@ -152,194 +129,4 @@ test('a large checkpoint uses the same verified chunk lane', () => {
|
|
|
152
129
|
const received = readPayload(locator);
|
|
153
130
|
assert.equal(received.state, 'ready');
|
|
154
131
|
assert.deepEqual(received.bytes, content);
|
|
155
|
-
assert.equal(
|
|
156
|
-
publishPayload('same-content-after-download', content, REPO_KEY).kind,
|
|
157
|
-
'chunks',
|
|
158
|
-
'verified downloaded content is immediately reusable by a later checkpoint',
|
|
159
|
-
);
|
|
160
|
-
assert.equal(claimUpload(), null);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test('zero-byte chunks are not legal transport work', () => {
|
|
164
|
-
const chunks = [
|
|
165
|
-
{ sha256: '0'.repeat(64), bytes: 0 },
|
|
166
|
-
{ sha256: '1'.repeat(64), bytes: 1 },
|
|
167
|
-
];
|
|
168
|
-
const common = {
|
|
169
|
-
resourceId: PROJECT,
|
|
170
|
-
checksum: locatorChecksum(chunks),
|
|
171
|
-
bytes: 1,
|
|
172
|
-
chunks,
|
|
173
|
-
};
|
|
174
|
-
assert.throws(
|
|
175
|
-
() => readPayload({
|
|
176
|
-
kind: 'chunks',
|
|
177
|
-
contract: 'opaque-chunks@2',
|
|
178
|
-
...common,
|
|
179
|
-
}),
|
|
180
|
-
/invalid checkpoint payload locator/,
|
|
181
|
-
);
|
|
182
|
-
assert.throws(
|
|
183
|
-
() => prepareBundleImport({
|
|
184
|
-
kind: 'bundle',
|
|
185
|
-
contract: 'git-bundle-chunks@2',
|
|
186
|
-
oid: 'a'.repeat(40),
|
|
187
|
-
ref: `refs/amalgm/exports/${'a'.repeat(40)}`,
|
|
188
|
-
...common,
|
|
189
|
-
}),
|
|
190
|
-
/invalid git bundle locator/,
|
|
191
|
-
);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('a corrupt pending upload is retired and rebuilt from the writer', () => {
|
|
195
|
-
const content = Buffer.from('rebuild this upload');
|
|
196
|
-
assert.equal(publishPayload('corrupt-upload', content, REPO_KEY).kind, 'pending');
|
|
197
|
-
const first = claimUpload();
|
|
198
|
-
assert.ok(first);
|
|
199
|
-
const chunkFile = path.join(
|
|
200
|
-
process.env.AMALGM_DIR,
|
|
201
|
-
'state',
|
|
202
|
-
'repo-objects',
|
|
203
|
-
'chunks',
|
|
204
|
-
`${first.sha256}.bin`,
|
|
205
|
-
);
|
|
206
|
-
fs.writeFileSync(chunkFile, 'corrupt');
|
|
207
|
-
assert.equal(claimUpload(), null, 'the corrupt manifest is retired instead of retrying forever');
|
|
208
|
-
|
|
209
|
-
assert.equal(publishPayload('corrupt-upload', content, REPO_KEY).kind, 'pending');
|
|
210
|
-
const rebuilt = claimUpload();
|
|
211
|
-
assert.equal(rebuilt.sha256, first.sha256);
|
|
212
|
-
assert.equal(Buffer.from(rebuilt.dataBase64, 'base64').toString(), content.toString());
|
|
213
|
-
acknowledgeUpload(rebuilt.resourceId, rebuilt.sha256);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test('one malformed download request cannot block valid requests behind it', () => {
|
|
217
|
-
const content = Buffer.from('download me');
|
|
218
|
-
const chunks = [{
|
|
219
|
-
sha256: require('crypto').createHash('sha256').update(content).digest('hex'),
|
|
220
|
-
bytes: content.length,
|
|
221
|
-
}];
|
|
222
|
-
const locator = {
|
|
223
|
-
kind: 'chunks',
|
|
224
|
-
contract: 'opaque-chunks@2',
|
|
225
|
-
resourceId: PROJECT,
|
|
226
|
-
checksum: locatorChecksum(chunks),
|
|
227
|
-
bytes: content.length,
|
|
228
|
-
chunks,
|
|
229
|
-
};
|
|
230
|
-
fs.rmSync(path.join(
|
|
231
|
-
process.env.AMALGM_DIR,
|
|
232
|
-
'state',
|
|
233
|
-
'repo-objects',
|
|
234
|
-
'chunks',
|
|
235
|
-
`${chunks[0].sha256}.bin`,
|
|
236
|
-
), { force: true });
|
|
237
|
-
assert.equal(readPayload(locator).state, 'downloading');
|
|
238
|
-
const requests = path.join(process.env.AMALGM_DIR, 'state', 'repo-objects', 'requests');
|
|
239
|
-
fs.writeFileSync(path.join(requests, '000-malformed.json'), '{');
|
|
240
|
-
|
|
241
|
-
const claimed = claimDownload();
|
|
242
|
-
assert.equal(claimed.resourceId, PROJECT);
|
|
243
|
-
assert.equal(claimed.sha256, chunks[0].sha256);
|
|
244
|
-
assert.equal(fs.existsSync(path.join(requests, '000-malformed.json')), false);
|
|
245
|
-
receiveDownload(PROJECT, chunks[0].sha256, content.toString('base64'));
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test('failed uploads yield the bulk lane to later durable objects', () => {
|
|
249
|
-
for (const content of ['first queued object', 'second queued object', 'third queued object']) {
|
|
250
|
-
assert.equal(
|
|
251
|
-
publishPayload(`fair-${content}`, Buffer.from(content), REPO_KEY).kind,
|
|
252
|
-
'pending',
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const first = claimUpload();
|
|
257
|
-
assert.ok(first);
|
|
258
|
-
const retry = failUpload(first.resourceId, first.sha256, {
|
|
259
|
-
retryable: true,
|
|
260
|
-
code: 'temporary_object_failure',
|
|
261
|
-
message: 'try this object later',
|
|
262
|
-
});
|
|
263
|
-
assert.equal(retry.changed, true);
|
|
264
|
-
assert.equal(retry.terminal, false);
|
|
265
|
-
assert.ok(Date.parse(retry.retryAfter) > Date.now());
|
|
266
|
-
|
|
267
|
-
const second = claimUpload();
|
|
268
|
-
assert.ok(second);
|
|
269
|
-
assert.notEqual(second.sha256, first.sha256);
|
|
270
|
-
const terminal = failUpload(second.resourceId, second.sha256, {
|
|
271
|
-
retryable: false,
|
|
272
|
-
code: 'invalid_repo_object',
|
|
273
|
-
message: 'this exact object is invalid',
|
|
274
|
-
});
|
|
275
|
-
assert.equal(terminal.changed, true);
|
|
276
|
-
assert.equal(terminal.terminal, true);
|
|
277
|
-
|
|
278
|
-
const third = claimUpload();
|
|
279
|
-
assert.ok(third);
|
|
280
|
-
assert.notEqual(third.sha256, first.sha256);
|
|
281
|
-
assert.notEqual(third.sha256, second.sha256);
|
|
282
|
-
|
|
283
|
-
acknowledgeUpload(first.resourceId, first.sha256);
|
|
284
|
-
acknowledgeUpload(second.resourceId, second.sha256);
|
|
285
|
-
acknowledgeUpload(third.resourceId, third.sha256);
|
|
286
|
-
assert.equal(claimUpload(), null);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
test('legacy shared-resource uploads can terminate without starving current projects', () => {
|
|
290
|
-
const legacyResourceId = 'shr_legacyobjectqueue0001';
|
|
291
|
-
const legacyChunks = [
|
|
292
|
-
Buffer.from('first stale object chunk from a legacy rail'),
|
|
293
|
-
Buffer.from('second stale object chunk from a legacy rail'),
|
|
294
|
-
].map((bytes) => ({
|
|
295
|
-
bytes,
|
|
296
|
-
sha256: crypto.createHash('sha256').update(bytes).digest('hex'),
|
|
297
|
-
}));
|
|
298
|
-
const objectRoot = path.join(process.env.AMALGM_DIR, 'state', 'repo-objects');
|
|
299
|
-
const manifests = path.join(objectRoot, 'manifests');
|
|
300
|
-
const chunks = path.join(objectRoot, 'chunks');
|
|
301
|
-
for (const chunk of legacyChunks) {
|
|
302
|
-
fs.writeFileSync(path.join(chunks, `${chunk.sha256}.bin`), chunk.bytes);
|
|
303
|
-
}
|
|
304
|
-
fs.writeFileSync(path.join(manifests, '000-legacy-shared-resource.json'), JSON.stringify({
|
|
305
|
-
contract: 'git-bundle-chunks@2',
|
|
306
|
-
resourceId: legacyResourceId,
|
|
307
|
-
repoKey: 'legacy-repo',
|
|
308
|
-
oid: '1'.repeat(40),
|
|
309
|
-
ref: `refs/amalgm/exports/${'1'.repeat(40)}`,
|
|
310
|
-
checksum: locatorChecksum(legacyChunks.map((chunk) => ({
|
|
311
|
-
sha256: chunk.sha256,
|
|
312
|
-
bytes: chunk.bytes.length,
|
|
313
|
-
}))),
|
|
314
|
-
bytes: legacyChunks.reduce((total, chunk) => total + chunk.bytes.length, 0),
|
|
315
|
-
chunks: legacyChunks.map((chunk) => ({
|
|
316
|
-
sha256: chunk.sha256,
|
|
317
|
-
bytes: chunk.bytes.length,
|
|
318
|
-
uploaded: false,
|
|
319
|
-
})),
|
|
320
|
-
createdAt: new Date().toISOString(),
|
|
321
|
-
}));
|
|
322
|
-
|
|
323
|
-
assert.equal(
|
|
324
|
-
publishPayload('current-after-legacy', Buffer.from('current project object'), REPO_KEY).kind,
|
|
325
|
-
'pending',
|
|
326
|
-
);
|
|
327
|
-
const legacy = claimUpload();
|
|
328
|
-
assert.equal(legacy.resourceId, legacyResourceId);
|
|
329
|
-
assert.equal(legacy.sha256, legacyChunks[0].sha256);
|
|
330
|
-
const terminal = failUpload(legacy.resourceId, legacy.sha256, {
|
|
331
|
-
retryable: false,
|
|
332
|
-
code: 'shared_resource_read_forbidden',
|
|
333
|
-
message: 'the legacy rail is no longer readable',
|
|
334
|
-
});
|
|
335
|
-
assert.equal(terminal.changed, true);
|
|
336
|
-
assert.equal(terminal.terminal, true);
|
|
337
|
-
|
|
338
|
-
const current = claimUpload();
|
|
339
|
-
assert.equal(current.resourceId, PROJECT);
|
|
340
|
-
assert.ok(
|
|
341
|
-
legacyChunks.every((chunk) => current.sha256 !== chunk.sha256),
|
|
342
|
-
'a resource-wide rejection skips every remaining chunk from the stale rail',
|
|
343
|
-
);
|
|
344
|
-
acknowledgeUpload(current.resourceId, current.sha256);
|
|
345
132
|
});
|