@lapxo/bound 0.1.3 → 0.1.5
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/LICENSE +0 -1
- package/README.md +26 -5
- package/TARGET.bound +101 -22
- package/dist/cli/accept.js +21 -9
- package/dist/cli/owner.d.ts +2 -0
- package/dist/cli/owner.js +17 -2
- package/dist/cli/pass.js +15 -10
- package/dist/cli/place.js +8 -1
- package/dist/cli/verb.js +43 -3
- package/dist/fold/claims.js +5 -1
- package/dist/fold/closed.js +44 -13
- package/dist/fold/digests.js +5 -2
- package/dist/fold/observed.js +197 -174
- package/dist/fold/readers.d.ts +2 -0
- package/dist/fold/readers.js +3 -3
- package/dist/fold/resolved.js +6 -2
- package/dist/host/act-cost.d.ts +13 -0
- package/dist/host/act-cost.js +25 -0
- package/dist/host/adapters/modules/javascript.d.ts +1 -0
- package/dist/host/adapters/modules/javascript.js +48 -0
- package/dist/host/adapters/transport/http.d.ts +5 -0
- package/dist/host/adapters/transport/http.js +28 -0
- package/dist/host/capsule-process.d.ts +2 -1
- package/dist/host/capsule-process.js +24 -20
- package/dist/host/capsule.d.ts +1 -0
- package/dist/host/capsule.js +13 -16
- package/dist/host/cell-receipts.js +1 -1
- package/dist/host/content-store.d.ts +8 -0
- package/dist/host/content-store.js +31 -0
- package/dist/host/lifetime.d.ts +1 -0
- package/dist/host/lifetime.js +40 -0
- package/dist/host/objects.js +1 -1
- package/dist/host/ports/ingress.d.ts +24 -0
- package/dist/host/ports/ingress.js +81 -0
- package/dist/host/ports/transport.d.ts +1 -0
- package/dist/host/ports/transport.js +48 -0
- package/dist/host/process-lifetime.d.ts +11 -0
- package/dist/host/process-lifetime.js +12 -0
- package/dist/host/reading-closures.d.ts +28 -0
- package/dist/host/reading-closures.js +146 -0
- package/dist/host/release.js +24 -2
- package/dist/host/selected-capsules.js +38 -12
- package/dist/host/selected-topos.d.ts +4 -4
- package/dist/host/selected-topos.js +8 -30
- package/dist/host/source-content.d.ts +1 -1
- package/dist/host/source-content.js +15 -15
- package/dist/host/walk.d.ts +6 -0
- package/dist/host/walk.js +193 -0
- package/dist/observe/identity.d.ts +2 -0
- package/dist/observe/identity.js +3 -3
- package/dist/observe/regions.js +3 -5
- package/dist/observe/run.d.ts +2 -1
- package/dist/observe/run.js +5 -3
- package/dist/render/field.d.ts +1 -7
- package/dist/render/field.js +6 -12
- package/dist/render/names.d.ts +1 -1
- package/dist/render/names.js +1 -1
- package/dist/render/receipts.js +2 -1
- package/dist/render/view.js +16 -11
- package/docs/contracts/archive.md +11 -0
- package/docs/contracts/cli.md +11 -0
- package/docs/contracts/documents.md +35 -0
- package/docs/contracts/evidence-view.md +6 -0
- package/docs/contracts/publication.md +17 -0
- package/docs/contracts/typed-object.md +27 -0
- package/docs/contracts/view-walk.md +7 -0
- package/docs/contracts/views.md +29 -0
- package/docs/usage.md +6 -0
- package/package.json +6 -4
- package/receipts.bound +314 -94
package/dist/fold/observed.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { actCosts } from "../host/act-cost.js";
|
|
2
|
+
import { readingClosures } from "../host/reading-closures.js";
|
|
3
|
+
import { readerLifetime, readerAllowsEmpty, assertReaderInputs, parse as parseLifetime } from '@lapxo/topos/wire';
|
|
1
4
|
import { readOnly } from "../host/read-only.js";
|
|
2
5
|
import { basename, dirname, existsSync, mkdirSync, resolve, rmSync, spawn, statSync, writeFileSync } from "../host/io.js";
|
|
3
6
|
import { bundleOf, processOf } from "../observe/runner.js";
|
|
@@ -264,202 +267,222 @@ export function observedClaims(root, store, options = {}) {
|
|
|
264
267
|
}
|
|
265
268
|
function observeOne(reader, started) {
|
|
266
269
|
const module = resolve(root, reader.module);
|
|
267
|
-
const asked = reader.capsule === undefined ? undefined : capsuleAt(reader.capsule.digest, reader.capsule.entry);
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const stored = kept === undefined ? undefined : observeText(memoAt(store, `observed/${kept}`));
|
|
278
|
-
if (stored !== undefined) {
|
|
279
|
-
out.push(...stored.split('\n').filter(isWire));
|
|
280
|
-
read += offered.length;
|
|
281
|
-
whole_ += 1;
|
|
270
|
+
const asked = reader.capsule === undefined ? undefined : reader.capsule.selected ?? capsuleAt(reader.capsule.digest, reader.capsule.entry);
|
|
271
|
+
const projection = reader.capsule === undefined ? [] : (asked?.lines ?? []).filter(line => fieldOf(line, 'scope') === `region/${reader.capsule.region}` && fieldOf(line, 'measure') === 'input-projection' && fieldOf(line, 'value') !== 'withdraw');
|
|
272
|
+
if (projection.length) {
|
|
273
|
+
if (projection.length !== 1 || !asked)
|
|
274
|
+
throw Error('REFUSE·input projection is ambiguous ' + reader.module);
|
|
275
|
+
const files = [...new Set(reader.where.flatMap(where => handedAt(root, reader, roleOf, look, where)))].sort().map(place => ({ place, text: observeText(resolve(root, place)) ?? '' }));
|
|
276
|
+
const result = readingClosures({ root, store, reader: reader.module, speaker, digest: text => bytesDigest(store, Buffer.from(text)), capsule: asked, region: reader.capsule.region, projection: fieldOf(projection[0], 'value'), allowsEmpty: readerAllowsEmpty(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module), files, emit: line => process.stderr.write(line + '\n') });
|
|
277
|
+
out.push(...result.lines);
|
|
278
|
+
ran += result.executed;
|
|
279
|
+
read += result.read - result.opened;
|
|
282
280
|
return false;
|
|
283
281
|
}
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
282
|
+
const endAct = actCosts.begin();
|
|
283
|
+
try {
|
|
284
|
+
const source = reader.capsule === undefined ? observeFile(module) : asked === undefined ? undefined : new Uint8Array();
|
|
285
|
+
const bundle = source && reader.capsule === undefined ? bundleOf(module) ?? bytesDigest(store, source) : 'none';
|
|
286
|
+
const offered = offersOf(root, store, reader, roleOf, look, (where) => reached(trimmed(where)));
|
|
287
|
+
assertReaderInputs(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module, offered.length);
|
|
288
|
+
const vectors = reader.capsule === undefined ? observeText(resolve(root, vectorsOf(reader.module, standing))) : undefined;
|
|
289
|
+
const kept = whole && legacy === undefined && answered === undefined && source ? sha([
|
|
290
|
+
speaker, reader.module, reader.kind, reader.shape.join('|'), reader.where.join('|'), bundle, vectors === undefined ? 'no vectors' : sha(vectors),
|
|
291
|
+
reader.capsule === undefined ? '' : `${reader.capsule.selected?.selection ?? reader.capsule.digest} ${reader.capsule.region} ${reader.capsule.reads.join('|')} ${[...reader.capsule.globs].map(([place, globs]) => `${place}${globs.join('|')}`).join(' ')}`,
|
|
292
|
+
...offered.map((offer) => `${offer.place} ${offer.digest?.() ?? bytesDigest(store, offer.bytes())}`),
|
|
293
|
+
].join('\n')) : undefined;
|
|
294
|
+
const stored = kept === undefined ? undefined : observeText(memoAt(store, `observed/${kept}`));
|
|
295
|
+
if (stored !== undefined) {
|
|
296
|
+
out.push(...stored.split('\n').filter(isWire));
|
|
297
|
+
read += offered.length;
|
|
298
|
+
whole_ += 1;
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
const mark = out.length;
|
|
302
|
+
const home = regionIn(regions, reader.module);
|
|
303
|
+
const own = shardLines(store, speaker, home).filter((line) => fieldOf(line, 'scope') === `reader/${reader.module}`);
|
|
304
|
+
const lineage = own.filter((line) => fieldOf(line, 'measure') === 'identity').map((line) => fieldOf(line, 'value'));
|
|
305
|
+
const sum = source && reader.capsule === undefined ? bytesDigest(store, new TextEncoder().encode(`${reader.module} ${bundle}`)).replace(/^sha256:/, '').slice(0, 12) : 'none';
|
|
306
|
+
const measured = reader.capsule !== undefined ? { by: source ? sha(`${reader.capsule.selected?.selection ?? reader.capsule.digest}\n${reader.capsule.region}`).slice(0, 12) : 'none', lines: [] }
|
|
307
|
+
: source ? identityOf({ store, root, speaker, module: reader.module, kind: reader.kind, code: sum, lineage, lifetime: readerLifetime(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module), kept: own.filter((line) => /(?:^|\s)measure=(?:extension|emits)\s/.test(line)) })
|
|
308
|
+
: { by: 'none', lines: [] };
|
|
309
|
+
const by = measured.by;
|
|
310
|
+
const level = [...measured.lines];
|
|
311
|
+
const identified = Date.now();
|
|
312
|
+
const lines = [...lineage, by];
|
|
313
|
+
const before = [...lineage].reverse().find((one) => one !== by);
|
|
314
|
+
const regionsOf = new Map();
|
|
315
|
+
for (const offer of offered) {
|
|
316
|
+
const region = regionIn(regions, offer.place);
|
|
317
|
+
if (reached(region))
|
|
318
|
+
regionsOf.set(region, [...(regionsOf.get(region) ?? []), offer]);
|
|
319
|
+
}
|
|
320
|
+
const planned = [...regionsOf].map(([region, offers]) => {
|
|
321
|
+
const shard = open(region);
|
|
322
|
+
const places = offers.map((offer) => offer.place);
|
|
323
|
+
const pairs = [];
|
|
324
|
+
const kept = [];
|
|
325
|
+
const moved = [];
|
|
326
|
+
const all = [];
|
|
327
|
+
for (const offer of offers) {
|
|
328
|
+
const known = shard.places.get(`${by} ${offer.place}`);
|
|
329
|
+
if (known && fieldOf(known.line, 'value') === offer.seen) {
|
|
330
|
+
pairs.push(`${offer.place} ${known.at}`);
|
|
331
|
+
kept.push(known.line);
|
|
332
|
+
all.push({ ...offer, digest: known.at.slice('place:'.length) });
|
|
333
|
+
read += 1;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const bytes = offer.digest ? undefined : offer.bytes();
|
|
337
|
+
const digest = offer.digest?.() ?? bytesDigest(store, bytes);
|
|
338
|
+
pairs.push(`${offer.place} place:${digest}`);
|
|
339
|
+
kept.push(canonical({ scope: offer.place, role: 'writes', form: 'alphabet', measure: 'observed', value: offer.seen, by: `${speaker}:${by}`, at: `place:${digest}` }));
|
|
340
|
+
all.push({ ...offer, digest, bytes: () => bytes ?? offer.bytes() });
|
|
341
|
+
if (known?.at === `place:${digest}`)
|
|
342
|
+
read += 1;
|
|
343
|
+
else
|
|
344
|
+
moved.push({ place: offer.place, seen: offer.seen, digest, bytes: bytes ?? offer.bytes() });
|
|
345
|
+
}
|
|
346
|
+
return { region, shard, places, pairs, kept, moved, all, key: keyOf(by, region, pairs) };
|
|
347
|
+
});
|
|
348
|
+
if (answered !== undefined && planned.every((plan) => !plan.moved.length && observedAt(store, plan.key))) {
|
|
349
|
+
for (const plan of planned) {
|
|
350
|
+
for (const [at, digest] of digestsAt(plan.key))
|
|
351
|
+
answered.set(`${by} ${at}`, digest);
|
|
352
|
+
if (plan.shard.keys.get(by) !== plan.key || plan.kept.some((line) => plan.shard.places.get(`${by} ${fieldOf(line, 'scope')}`)?.line !== line)) {
|
|
353
|
+
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
354
|
+
}
|
|
355
|
+
out.push(...plan.kept);
|
|
318
356
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
kept.push(canonical({ scope: offer.place, role: 'writes', form: 'alphabet', measure: 'observed', value: offer.seen, by: `${speaker}:${by}`, at: `place:${digest}` }));
|
|
323
|
-
all.push({ ...offer, digest, bytes: () => bytes ?? offer.bytes() });
|
|
324
|
-
if (known?.at === `place:${digest}`)
|
|
325
|
-
read += 1;
|
|
326
|
-
else
|
|
327
|
-
moved.push({ place: offer.place, seen: offer.seen, digest, bytes: bytes ?? offer.bytes() });
|
|
357
|
+
if (level.length)
|
|
358
|
+
landShard(store, speaker, home, level);
|
|
359
|
+
return true;
|
|
328
360
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
361
|
+
const plans = planned.map(({ region, shard, places, pairs, kept, moved, all, key }) => {
|
|
362
|
+
const prior = lastOf(shard, by, places);
|
|
363
|
+
if (before !== undefined)
|
|
364
|
+
lastOf(shard, before, places);
|
|
365
|
+
const ats = [...new Set(pairs.map((pair) => pair.slice(pair.indexOf(' ') + 1)))];
|
|
366
|
+
const found = byAt(key);
|
|
367
|
+
const said = found !== undefined ? ats.flatMap((at) => found.get(at) ?? [])
|
|
368
|
+
: moved.length || prior === undefined ? undefined : ats.flatMap((at) => prior.get(at) ?? []);
|
|
369
|
+
const due = said === undefined && !moved.length ? all.map((one) => ({ place: one.place, seen: one.seen, digest: one.digest, bytes: one.bytes() })) : moved;
|
|
370
|
+
return { region, shard, places, pairs, kept, due, key, said, prior, found: found !== undefined };
|
|
371
|
+
});
|
|
372
|
+
const due = plans.flatMap((plan) => (plan.said === undefined ? plan.due.map((one) => ({ ...one, region: plan.region })) : []));
|
|
373
|
+
const heldOf = (place, region) => {
|
|
374
|
+
for (const one of [...lines].reverse()) {
|
|
375
|
+
const shard = open(region);
|
|
376
|
+
const mine = servedAt(shard, one, place, lastOf(shard, one, [place])).slice(1);
|
|
377
|
+
if (mine.length)
|
|
378
|
+
return mine.map((line) => [fieldOf(line, 'scope'), fieldOf(line, 'value')]);
|
|
337
379
|
}
|
|
338
|
-
|
|
380
|
+
return [];
|
|
381
|
+
};
|
|
382
|
+
if (due.length && !source) {
|
|
383
|
+
refused.push(`REFUSE·reader ${reader.module} unavailable · ${due.length} readings remain due`);
|
|
384
|
+
return false;
|
|
339
385
|
}
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
386
|
+
if (due.length && !wait) {
|
|
387
|
+
for (const plan of plans) {
|
|
388
|
+
if (plan.said !== undefined) {
|
|
389
|
+
out.push(...plan.kept, ...plan.said);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
for (const place of plan.places) {
|
|
393
|
+
const mine = servedAt(plan.shard, by, place, plan.prior);
|
|
394
|
+
out.push(...(mine.length || before === undefined ? mine : servedAt(plan.shard, before, place, lastOf(plan.shard, before, plan.places))));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const marker = storeAt(store, 'pending', by);
|
|
398
|
+
const pid = Number((observeText(marker) ?? '').split('\n')[1]);
|
|
399
|
+
const running = existsSync(marker) && Date.now() - statSync(marker).mtimeMs < 30 * 60 * 1000 && Number.isInteger(pid) && pid > 0 && alive(pid);
|
|
400
|
+
if (!running) {
|
|
401
|
+
mkdirSync(dirname(marker), { recursive: true });
|
|
402
|
+
const child = spawn(process.execPath, [...childFlags(), processOf('observe'), root, reader.module], { cwd: root, detached: true, stdio: 'ignore' });
|
|
403
|
+
child.unref();
|
|
404
|
+
writeFileSync(marker, `${reader.module}\n${child.pid ?? ''}\n`);
|
|
405
|
+
}
|
|
406
|
+
behind.push({ module: reader.module, places: due.length });
|
|
407
|
+
process.stderr.write(`GREY reading: ${reader.module} · ${due.length} places · runs behind${running ? ', already running' : ''}\n`);
|
|
408
|
+
if (level.length)
|
|
409
|
+
landShard(store, speaker, home, level);
|
|
410
|
+
if (Date.now() - started > 250)
|
|
411
|
+
spent.push(`${reader.module.split('/').pop()} behind ${Date.now() - started}`);
|
|
412
|
+
return false;
|
|
362
413
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
414
|
+
if (due.length) {
|
|
415
|
+
const at = Date.now();
|
|
416
|
+
let rows;
|
|
417
|
+
try {
|
|
418
|
+
rows = reader.capsule !== undefined && asked !== undefined
|
|
419
|
+
? asked.ask(due.map((d) => ({
|
|
420
|
+
protocol: PROTOCOL, verb: 'read', rootScope: d.place, region: reader.capsule.region, held: heldOf(d.place, d.region),
|
|
421
|
+
files: (reader.capsule.reads.some((read) => read.includes('@')) ? handedAt(root, reader, roleOf, look, d.place) : [d.place]).map((coordinate) => ({ place: coordinate, text: observeText(resolve(root, coordinate)) ?? '' })),
|
|
422
|
+
}))).map((answer) => {
|
|
423
|
+
if (answer?.kind !== 'fact')
|
|
424
|
+
throw new Error(`REFUSE·reader ${reader.module} · ${answer?.why ?? 'no capsule answer'}`);
|
|
425
|
+
return (answer.claims ?? []);
|
|
426
|
+
})
|
|
427
|
+
: runReader(module, root, due.map((d) => ({ place: d.place, ...(reader.kind === 'js' ? { text: new TextDecoder().decode(d.bytes) } : {}), held: heldOf(d.place, d.region) })), readerLifetime(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module));
|
|
374
428
|
}
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
429
|
+
catch (error) {
|
|
430
|
+
const why = `REFUSE·reader ${reader.module} · ${error instanceof Error ? error.message : String(error)}`;
|
|
431
|
+
refused.push(why);
|
|
432
|
+
process.stderr.write(`${why}\n`);
|
|
433
|
+
return false;
|
|
378
434
|
}
|
|
435
|
+
if (rows.every((mine) => !mine.length) && !readerAllowsEmpty(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module)) {
|
|
436
|
+
const why = `REFUSE·reader ${reader.module} · answered none of ${due.length} places · nothing landed, they stay due`;
|
|
437
|
+
refused.push(why);
|
|
438
|
+
process.stderr.write(`${why}\n`);
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
ran += due.length;
|
|
442
|
+
process.stderr.write(`READER ${reader.module} · ${due.length} run · ${Date.now() - at} ms\n`);
|
|
443
|
+
const fresh = new Map();
|
|
444
|
+
due.forEach((d, i) => fresh.set(d.place, (rows[i] ?? []).map((row) => lineOf(row, d.digest, by, speaker))));
|
|
445
|
+
for (const plan of plans.filter((one) => one.said === undefined)) {
|
|
446
|
+
const mine = new Set(plan.due.map((d) => d.place));
|
|
447
|
+
const stay = [...new Set(plan.pairs.filter((pair) => !mine.has(pair.slice(0, pair.indexOf(' ')))).map((pair) => pair.slice(pair.indexOf(' ') + 1)))];
|
|
448
|
+
const said = [...stay.flatMap((one) => plan.prior?.get(one) ?? []), ...[...mine].flatMap((place) => fresh.get(place) ?? [])];
|
|
449
|
+
keepObservation(store, plan.key, said);
|
|
450
|
+
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
451
|
+
out.push(...plan.kept, ...said);
|
|
452
|
+
}
|
|
453
|
+
const spent = Date.now() - at;
|
|
454
|
+
const over = bytesDigest(store, new TextEncoder().encode(due.map((d) => d.digest).sort().join('\n'))).replace(/^sha256:/, '');
|
|
455
|
+
level.push(canonical({ scope: `cost/reader/${reader.module}`, role: 'writes', form: 'interval', measure: 'ms', value: `${spent}..${spent}`, by: `${speaker}:${by}`, at: `place:${over}` }));
|
|
456
|
+
if (lineage.at(-1) !== by)
|
|
457
|
+
level.push(canonical({ scope: `reader/${reader.module}`, role: 'writes', form: 'alphabet', measure: 'identity', value: by, by: `${speaker}:${by}`, at: `place:${by}` }));
|
|
458
|
+
rmSync(storeAt(store, 'pending', by), { force: true });
|
|
379
459
|
}
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
writeFileSync(marker, `${reader.module}\n${child.pid ?? ''}\n`);
|
|
460
|
+
for (const plan of plans.filter((one) => one.said !== undefined)) {
|
|
461
|
+
if (!plan.found)
|
|
462
|
+
keepObservation(store, plan.key, plan.said);
|
|
463
|
+
if (plan.shard.keys.get(by) !== plan.key || plan.kept.some((line) => plan.shard.places.get(`${by} ${fieldOf(line, 'scope')}`)?.line !== line)) {
|
|
464
|
+
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
465
|
+
}
|
|
466
|
+
out.push(...plan.kept, ...plan.said);
|
|
388
467
|
}
|
|
389
|
-
behind.push({ module: reader.module, places: due.length });
|
|
390
|
-
process.stderr.write(`GREY reading: ${reader.module} · ${due.length} places · runs behind${running ? ', already running' : ''}\n`);
|
|
391
468
|
if (level.length)
|
|
392
469
|
landShard(store, speaker, home, level);
|
|
393
470
|
if (Date.now() - started > 250)
|
|
394
|
-
spent.push(`${reader.module.split('/').pop()}
|
|
471
|
+
spent.push(`${reader.module.split('/').pop()} ${identified - started}+${Date.now() - identified}`);
|
|
472
|
+
if (kept !== undefined)
|
|
473
|
+
landMemo(store, `observed/${kept}`, new TextEncoder().encode(out.slice(mark).join('\n')));
|
|
395
474
|
return false;
|
|
396
475
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
let rows;
|
|
400
|
-
try {
|
|
401
|
-
rows = reader.capsule !== undefined && asked !== undefined
|
|
402
|
-
? asked.ask(due.map((d) => ({
|
|
403
|
-
protocol: PROTOCOL, verb: 'read', rootScope: d.place, region: reader.capsule.region, held: heldOf(d.place, d.region),
|
|
404
|
-
files: (reader.capsule.reads.some((read) => read.includes('@')) ? handedAt(root, reader, roleOf, look, d.place) : [d.place]).map((coordinate) => ({ place: coordinate, text: observeText(resolve(root, coordinate)) ?? '' })),
|
|
405
|
-
}))).map((answer) => {
|
|
406
|
-
if (answer?.kind !== 'fact')
|
|
407
|
-
throw new Error(`REFUSE·reader ${reader.module} · ${answer?.why ?? 'no capsule answer'}`);
|
|
408
|
-
return (answer.claims ?? []);
|
|
409
|
-
})
|
|
410
|
-
: runReader(module, root, due.map((d) => ({ place: d.place, ...(reader.kind === 'js' ? { text: new TextDecoder().decode(d.bytes) } : {}), held: heldOf(d.place, d.region) })));
|
|
411
|
-
}
|
|
412
|
-
catch (error) {
|
|
413
|
-
const why = `REFUSE·reader ${reader.module} · ${error instanceof Error ? error.message : String(error)}`;
|
|
414
|
-
refused.push(why);
|
|
415
|
-
process.stderr.write(`${why}\n`);
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
if (rows.every((mine) => !mine.length)) {
|
|
419
|
-
const why = `REFUSE·reader ${reader.module} · answered none of ${due.length} places · nothing landed, they stay due`;
|
|
420
|
-
refused.push(why);
|
|
421
|
-
process.stderr.write(`${why}\n`);
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
ran += due.length;
|
|
425
|
-
process.stderr.write(`READER ${reader.module} · ${due.length} run · ${Date.now() - at} ms\n`);
|
|
426
|
-
const fresh = new Map();
|
|
427
|
-
due.forEach((d, i) => fresh.set(d.place, (rows[i] ?? []).map((row) => lineOf(row, d.digest, by, speaker))));
|
|
428
|
-
for (const plan of plans.filter((one) => one.said === undefined)) {
|
|
429
|
-
const mine = new Set(plan.due.map((d) => d.place));
|
|
430
|
-
const stay = [...new Set(plan.pairs.filter((pair) => !mine.has(pair.slice(0, pair.indexOf(' ')))).map((pair) => pair.slice(pair.indexOf(' ') + 1)))];
|
|
431
|
-
const said = [...stay.flatMap((one) => plan.prior?.get(one) ?? []), ...[...mine].flatMap((place) => fresh.get(place) ?? [])];
|
|
432
|
-
keepObservation(store, plan.key, said);
|
|
433
|
-
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
434
|
-
out.push(...plan.kept, ...said);
|
|
435
|
-
}
|
|
436
|
-
const spent = Date.now() - at;
|
|
437
|
-
const over = bytesDigest(store, new TextEncoder().encode(due.map((d) => d.digest).sort().join('\n'))).replace(/^sha256:/, '');
|
|
438
|
-
level.push(canonical({ scope: `cost/reader/${reader.module}`, role: 'writes', form: 'interval', measure: 'ms', value: `${spent}..${spent}`, by: `${speaker}:${by}`, at: `place:${over}` }));
|
|
439
|
-
if (lineage.at(-1) !== by)
|
|
440
|
-
level.push(canonical({ scope: `reader/${reader.module}`, role: 'writes', form: 'alphabet', measure: 'identity', value: by, by: `${speaker}:${by}`, at: `place:${by}` }));
|
|
441
|
-
rmSync(storeAt(store, 'pending', by), { force: true });
|
|
442
|
-
}
|
|
443
|
-
for (const plan of plans.filter((one) => one.said !== undefined)) {
|
|
444
|
-
if (!plan.found)
|
|
445
|
-
keepObservation(store, plan.key, plan.said);
|
|
446
|
-
if (plan.shard.keys.get(by) !== plan.key || plan.kept.some((line) => plan.shard.places.get(`${by} ${fieldOf(line, 'scope')}`)?.line !== line)) {
|
|
447
|
-
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
448
|
-
}
|
|
449
|
-
out.push(...plan.kept, ...plan.said);
|
|
476
|
+
finally {
|
|
477
|
+
endAct();
|
|
450
478
|
}
|
|
451
|
-
if (level.length)
|
|
452
|
-
landShard(store, speaker, home, level);
|
|
453
|
-
if (Date.now() - started > 250)
|
|
454
|
-
spent.push(`${reader.module.split('/').pop()} ${identified - started}+${Date.now() - identified}`);
|
|
455
|
-
if (kept !== undefined)
|
|
456
|
-
landMemo(store, `observed/${kept}`, new TextEncoder().encode(out.slice(mark).join('\n')));
|
|
457
|
-
return false;
|
|
458
479
|
}
|
|
459
480
|
if (spent.length)
|
|
460
481
|
process.stderr.write(`SPENT observe ${spent.join(' · ')} ms\n`);
|
|
461
482
|
if (legacy)
|
|
462
483
|
setAside(store, speaker);
|
|
463
484
|
process.stderr.write(`OBSERVE ${ran} run · ${read} read by key${whole_ ? ` · ${whole_} readers kept whole` : ''} · ${Date.now() - began} ms\n`);
|
|
485
|
+
if (wait && refused.length)
|
|
486
|
+
throw new Error(refused.join('\n'));
|
|
464
487
|
return [...new Set(out)].sort();
|
|
465
488
|
}
|
package/dist/fold/readers.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Capsule } from '../host/capsule.ts';
|
|
1
2
|
import type { CoordinateRole } from './wire.ts';
|
|
2
3
|
import { capsulesFor } from './reach.ts';
|
|
3
4
|
import type { Kind } from '../observe/run.ts';
|
|
@@ -8,6 +9,7 @@ export interface ReaderClaim {
|
|
|
8
9
|
readonly module: string;
|
|
9
10
|
readonly kind: Kind;
|
|
10
11
|
readonly capsule?: {
|
|
12
|
+
readonly selected?: Capsule;
|
|
11
13
|
readonly digest: string;
|
|
12
14
|
readonly entry: string;
|
|
13
15
|
readonly region: string;
|
package/dist/fold/readers.js
CHANGED
|
@@ -62,13 +62,13 @@ export function capsuleReadersOf(root, store, standing, lines, resolve = capsule
|
|
|
62
62
|
const pkg = fieldOf(by?.offer ?? '', 'scope').split('/')[1] ?? '';
|
|
63
63
|
if (by === undefined || reaching.has(`${pkg} ${region} ${where || '/'}`))
|
|
64
64
|
continue;
|
|
65
|
-
const key = `${by.capsule.digest} ${region}`;
|
|
66
|
-
const group = groups.get(key) ?? groups.set(key, { module: `${pkg}/${region}`, digest: by.capsule.digest, entry: fieldOf(by.offer, 'shape'), region, reads: by.capsule.declaration.regions[region] ?? [], globs: new Map() }).get(key);
|
|
65
|
+
const key = `${by.capsule.selection ?? by.capsule.digest} ${region}`;
|
|
66
|
+
const group = groups.get(key) ?? groups.set(key, { module: `${pkg}/${region}`, digest: by.capsule.digest, entry: fieldOf(by.offer, 'shape'), selected: by.capsule, region, reads: by.capsule.declaration.regions[region] ?? [], globs: new Map() }).get(key);
|
|
67
67
|
group.globs.set(where, fieldOf(line, 'value').split('|').filter(Boolean));
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
return [...groups.values()].map((group) => ({ id: group.module, shape: ['run'], where: [...group.globs.keys()], module: group.module, kind: 'process',
|
|
71
|
-
capsule: { digest: group.digest, entry: group.entry, region: group.region, reads: group.reads, globs: group.globs } }));
|
|
71
|
+
capsule: { selected: group.selected, digest: group.digest, entry: group.entry, region: group.region, reads: group.reads, globs: group.globs } }));
|
|
72
72
|
}
|
|
73
73
|
export function handedAt(root, reader, roleOf, look, place) {
|
|
74
74
|
const { reads, globs } = reader.capsule;
|
package/dist/fold/resolved.js
CHANGED
|
@@ -95,20 +95,24 @@ export function keepCarried(store, lines, from) {
|
|
|
95
95
|
* the caller refuses rather than read the lines that only name it.
|
|
96
96
|
*/
|
|
97
97
|
export function carriedFrom(store, shipped) {
|
|
98
|
-
|
|
98
|
+
// Signed receipts inside the observed set are evidence, not its framing.
|
|
99
|
+
const headerOf = (line) => fieldOf(line, 'scope') === 'receipts' && fieldOf(line, 'measure') === 'digest' && fieldOf(line, 'by') === 'bound' && !fieldOf(line, 'sig');
|
|
100
|
+
const named = shipped.find(headerOf);
|
|
99
101
|
if (named === undefined)
|
|
100
102
|
return shipped;
|
|
101
103
|
// Resolution 8 may precede its authenticated summary in the same public file.
|
|
102
104
|
// It is evidence only if its exact bytes meet the summary's carrier digest.
|
|
103
105
|
for (let i = 1; i < shipped.length; i += 1) {
|
|
104
106
|
const header = shipped[i];
|
|
105
|
-
if (
|
|
107
|
+
if (!headerOf(header))
|
|
106
108
|
continue;
|
|
107
109
|
const digest = fieldOf(header, 'at').replace(/^place:sha256:/, '');
|
|
108
110
|
const body = shipped.slice(0, i);
|
|
109
111
|
if (/^[0-9a-f]{64}$/.test(digest) && createHash('sha256').update(body.map(line => `${line}\n`).join('')).digest('hex') === digest)
|
|
110
112
|
return body;
|
|
111
113
|
}
|
|
114
|
+
if (shipped.indexOf(named) > 0)
|
|
115
|
+
throw Error('REFUSE·receipt public carrier bytes mismatch; a cached carrier cannot replace supplied evidence');
|
|
112
116
|
const hex = fieldOf(named, 'at').replace(/^place:(?:sha256:)?/, '');
|
|
113
117
|
const text = /^[0-9a-f]{64}$/.test(hex) ? verifiedCarried(storeAt(store, 'cas', 'carried', hex), hex) : undefined;
|
|
114
118
|
return text === undefined ? undefined : text.split('\n').filter(isWire);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** A sequence of declared acts is not one act. Unpartitioned host work still has a cost. */
|
|
2
|
+
export declare class ActCosts {
|
|
3
|
+
private readonly began;
|
|
4
|
+
private occupied;
|
|
5
|
+
private largest;
|
|
6
|
+
private depth;
|
|
7
|
+
private readonly clock;
|
|
8
|
+
constructor(clock?: () => number);
|
|
9
|
+
begin(): () => void;
|
|
10
|
+
measure<T>(work: () => T): T;
|
|
11
|
+
seconds(): number;
|
|
12
|
+
}
|
|
13
|
+
export declare const actCosts: ActCosts;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** A sequence of declared acts is not one act. Unpartitioned host work still has a cost. */
|
|
2
|
+
export class ActCosts {
|
|
3
|
+
constructor(clock = () => performance.now()) {
|
|
4
|
+
this.occupied = 0;
|
|
5
|
+
this.largest = 0;
|
|
6
|
+
this.depth = 0;
|
|
7
|
+
this.clock = clock;
|
|
8
|
+
this.began = clock();
|
|
9
|
+
}
|
|
10
|
+
begin() {
|
|
11
|
+
const started = this.clock(), outer = this.depth++ === 0;
|
|
12
|
+
let closed = false;
|
|
13
|
+
return () => { if (closed)
|
|
14
|
+
throw Error('Act measurement closed twice'); closed = true; const elapsed = Math.max(0, this.clock() - started); this.depth--; this.largest = Math.max(this.largest, elapsed); if (outer)
|
|
15
|
+
this.occupied += elapsed; };
|
|
16
|
+
}
|
|
17
|
+
measure(work) { const end = this.begin(); try {
|
|
18
|
+
return work();
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
end();
|
|
22
|
+
} }
|
|
23
|
+
seconds() { return Math.max(0, Math.round(Math.max(this.largest, this.clock() - this.began - this.occupied) / 1000)); }
|
|
24
|
+
}
|
|
25
|
+
export const actCosts = new ActCosts();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function moduleImports(source: string, coordinate: string): readonly string[];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as nodeModule from 'node:module';
|
|
2
|
+
import { extname } from 'node:path';
|
|
3
|
+
/** Host module syntax, never wire or domain interpretation. Reading imports executes no module. */
|
|
4
|
+
// The package's minimum Node runtime provides this API; older build-only Node types do not.
|
|
5
|
+
const require = nodeModule.createRequire(import.meta.url);
|
|
6
|
+
const { parse } = require('acorn');
|
|
7
|
+
const stripTypeScriptTypes = nodeModule.stripTypeScriptTypes;
|
|
8
|
+
export function moduleImports(source, coordinate) {
|
|
9
|
+
const code = extname(coordinate) === '.ts' ? stripTypeScriptTypes(source, { mode: 'transform' }) : source;
|
|
10
|
+
let tree;
|
|
11
|
+
try {
|
|
12
|
+
tree = parse(code, { ecmaVersion: 'latest', sourceType: 'module', allowReturnOutsideFunction: true, allowHashBang: true });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw Error('REFUSE·source module syntax cannot be read: ' + coordinate);
|
|
16
|
+
}
|
|
17
|
+
const found = new Set();
|
|
18
|
+
const literal = (node) => {
|
|
19
|
+
if (!node || typeof node !== 'object')
|
|
20
|
+
return;
|
|
21
|
+
const n = node;
|
|
22
|
+
return n['type'] === 'Literal' && typeof n['value'] === 'string' ? n['value'] : undefined;
|
|
23
|
+
};
|
|
24
|
+
const visit = (node) => {
|
|
25
|
+
if (!node || typeof node !== 'object')
|
|
26
|
+
return;
|
|
27
|
+
if (Array.isArray(node)) {
|
|
28
|
+
for (const child of node)
|
|
29
|
+
visit(child);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const n = node;
|
|
33
|
+
let specifier;
|
|
34
|
+
if (n['type'] === 'ImportDeclaration' || n['type'] === 'ExportNamedDeclaration' || n['type'] === 'ExportAllDeclaration' || n['type'] === 'ImportExpression')
|
|
35
|
+
specifier = literal(n['source']);
|
|
36
|
+
if (n['type'] === 'CallExpression') {
|
|
37
|
+
const callee = n['callee'];
|
|
38
|
+
if (callee?.['type'] === 'Identifier' && callee['name'] === 'require')
|
|
39
|
+
specifier = literal(n['arguments'][0]);
|
|
40
|
+
}
|
|
41
|
+
if (specifier !== undefined)
|
|
42
|
+
found.add(specifier);
|
|
43
|
+
for (const child of Object.values(n))
|
|
44
|
+
visit(child);
|
|
45
|
+
};
|
|
46
|
+
visit(tree);
|
|
47
|
+
return [...found];
|
|
48
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Reference HTTP adapter. Location and budgets are already selected by admitted host policy. */
|
|
2
|
+
export async function fetchBytes(location, limits) {
|
|
3
|
+
const signal = AbortSignal.timeout(limits.timeoutMs), response = await fetch(location, { signal });
|
|
4
|
+
if (!response.ok)
|
|
5
|
+
throw Error(`HTTP ${response.status}`);
|
|
6
|
+
const reader = response.body?.getReader();
|
|
7
|
+
if (!reader)
|
|
8
|
+
return new Uint8Array();
|
|
9
|
+
const chunks = [];
|
|
10
|
+
let size = 0;
|
|
11
|
+
try {
|
|
12
|
+
for (;;) {
|
|
13
|
+
const part = await reader.read();
|
|
14
|
+
if (part.done)
|
|
15
|
+
break;
|
|
16
|
+
size += part.value.length;
|
|
17
|
+
if (size > limits.responseBytes) {
|
|
18
|
+
await reader.cancel();
|
|
19
|
+
throw Error('response exceeds declared byte bound');
|
|
20
|
+
}
|
|
21
|
+
chunks.push(part.value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
reader.releaseLock();
|
|
26
|
+
}
|
|
27
|
+
return Buffer.concat(chunks);
|
|
28
|
+
}
|