@lapxo/bound 0.1.4 → 0.1.6
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 +84 -19
- package/dist/cli/accept.d.ts +5 -0
- package/dist/cli/accept.js +60 -14
- package/dist/cli/act-result.d.ts +3 -0
- package/dist/cli/act-result.js +48 -0
- package/dist/cli/owner.d.ts +2 -0
- package/dist/cli/owner.js +18 -2
- package/dist/cli/pass.js +15 -10
- package/dist/cli/place.js +8 -1
- package/dist/cli/verb.js +48 -2
- package/dist/fold/admission-history.d.ts +6 -0
- package/dist/fold/admission-history.js +22 -0
- package/dist/fold/claims.js +5 -1
- package/dist/fold/closed.js +44 -13
- package/dist/fold/digests.js +18 -11
- package/dist/fold/kept.js +9 -4
- package/dist/fold/keys.js +9 -6
- package/dist/fold/observed.js +196 -174
- package/dist/fold/resolved.js +6 -2
- package/dist/fold/signed.d.ts +5 -0
- package/dist/fold/signed.js +19 -15
- 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/capsule-process.js +22 -19
- package/dist/host/capsule.d.ts +2 -1
- package/dist/host/capsule.js +13 -12
- package/dist/host/io.d.ts +1 -1
- package/dist/host/io.js +1 -1
- package/dist/host/ports/ingress.d.ts +24 -0
- package/dist/host/ports/ingress.js +57 -0
- package/dist/host/ports/local-act.d.ts +34 -0
- package/dist/host/ports/local-act.js +100 -0
- package/dist/host/ports/publication.d.ts +10 -0
- package/dist/host/ports/publication.js +56 -0
- package/dist/host/reading-closures.d.ts +31 -0
- package/dist/host/reading-closures.js +146 -0
- package/dist/host/release.js +24 -2
- package/dist/host/selected-capsules.js +4 -4
- package/dist/host/walk.d.ts +6 -0
- package/dist/host/walk.js +193 -0
- package/dist/land/ledger.d.ts +1 -0
- package/dist/land/ledger.js +21 -2
- package/dist/land/owned.d.ts +1 -1
- package/dist/land/owned.js +5 -4
- package/dist/observe/files.js +12 -2
- package/dist/observe/regions.js +3 -5
- 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/view.js +16 -11
- package/docs/contracts/archive.md +11 -0
- package/docs/contracts/cli.md +19 -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 +367 -77
package/dist/fold/observed.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { actCosts } from "../host/act-cost.js";
|
|
2
|
+
import { readingClosures } from "../host/reading-closures.js";
|
|
1
3
|
import { readerLifetime, readerAllowsEmpty, assertReaderInputs, parse as parseLifetime } from '@lapxo/topos/wire';
|
|
2
4
|
import { readOnly } from "../host/read-only.js";
|
|
3
5
|
import { basename, dirname, existsSync, mkdirSync, resolve, rmSync, spawn, statSync, writeFileSync } from "../host/io.js";
|
|
@@ -265,203 +267,223 @@ export function observedClaims(root, store, options = {}) {
|
|
|
265
267
|
}
|
|
266
268
|
function observeOne(reader, started) {
|
|
267
269
|
const module = resolve(root, reader.module);
|
|
270
|
+
const lifetime = readerLifetime(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), { id: reader.id, module: reader.module });
|
|
268
271
|
const asked = reader.capsule === undefined ? undefined : reader.capsule.selected ?? capsuleAt(reader.capsule.digest, reader.capsule.entry);
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
].join('\n')) : undefined;
|
|
279
|
-
const stored = kept === undefined ? undefined : observeText(memoAt(store, `observed/${kept}`));
|
|
280
|
-
if (stored !== undefined) {
|
|
281
|
-
out.push(...stored.split('\n').filter(isWire));
|
|
282
|
-
read += offered.length;
|
|
283
|
-
whole_ += 1;
|
|
272
|
+
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');
|
|
273
|
+
if (projection.length) {
|
|
274
|
+
if (projection.length !== 1 || !asked)
|
|
275
|
+
throw Error('REFUSE·input projection is ambiguous ' + reader.module);
|
|
276
|
+
const files = [...new Set(reader.where.flatMap(where => handedAt(root, reader, roleOf, look, where)))].sort().map(place => ({ place, text: observeText(resolve(root, place)) ?? '' }));
|
|
277
|
+
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'), readerReference: { id: reader.id, module: reader.module }, lifetime, 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') });
|
|
278
|
+
out.push(...result.lines);
|
|
279
|
+
ran += result.executed;
|
|
280
|
+
read += result.read - result.opened;
|
|
284
281
|
return false;
|
|
285
282
|
}
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
283
|
+
const endAct = actCosts.begin();
|
|
284
|
+
try {
|
|
285
|
+
const source = reader.capsule === undefined ? observeFile(module) : asked === undefined ? undefined : new Uint8Array();
|
|
286
|
+
const bundle = source && reader.capsule === undefined ? bundleOf(module) ?? bytesDigest(store, source) : 'none';
|
|
287
|
+
const offered = offersOf(root, store, reader, roleOf, look, (where) => reached(trimmed(where)));
|
|
288
|
+
assertReaderInputs(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module, offered.length);
|
|
289
|
+
const vectors = reader.capsule === undefined ? observeText(resolve(root, vectorsOf(reader.module, standing))) : undefined;
|
|
290
|
+
const kept = whole && legacy === undefined && answered === undefined && source ? sha([
|
|
291
|
+
speaker, reader.module, JSON.stringify(lifetime), reader.kind, reader.shape.join('|'), reader.where.join('|'), bundle, vectors === undefined ? 'no vectors' : sha(vectors),
|
|
292
|
+
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(' ')}`,
|
|
293
|
+
...offered.map((offer) => `${offer.place} ${offer.digest?.() ?? bytesDigest(store, offer.bytes())}`),
|
|
294
|
+
].join('\n')) : undefined;
|
|
295
|
+
const stored = kept === undefined ? undefined : observeText(memoAt(store, `observed/${kept}`));
|
|
296
|
+
if (stored !== undefined) {
|
|
297
|
+
out.push(...stored.split('\n').filter(isWire));
|
|
298
|
+
read += offered.length;
|
|
299
|
+
whole_ += 1;
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
const mark = out.length;
|
|
303
|
+
const home = regionIn(regions, reader.module);
|
|
304
|
+
const own = shardLines(store, speaker, home).filter((line) => fieldOf(line, 'scope') === `reader/${reader.module}`);
|
|
305
|
+
const lineage = own.filter((line) => fieldOf(line, 'measure') === 'identity').map((line) => fieldOf(line, 'value'));
|
|
306
|
+
const sum = source && reader.capsule === undefined ? bytesDigest(store, new TextEncoder().encode(`${reader.module} ${bundle}${lifetime === undefined ? "" : "\n" + JSON.stringify(lifetime)}`)).replace(/^sha256:/, '').slice(0, 12) : 'none';
|
|
307
|
+
const measured = reader.capsule !== undefined ? { by: source ? sha(`${reader.capsule.selected?.selection ?? reader.capsule.digest}\n${reader.capsule.region}${lifetime === undefined ? "" : "\n" + JSON.stringify(lifetime)}`).slice(0, 12) : 'none', lines: [] }
|
|
308
|
+
: source ? identityOf({ store, root, speaker, module: reader.module, kind: reader.kind, code: sum, lineage, lifetime, kept: own.filter((line) => /(?:^|\s)measure=(?:extension|emits)\s/.test(line)) })
|
|
309
|
+
: { by: 'none', lines: [] };
|
|
310
|
+
const by = measured.by;
|
|
311
|
+
const level = [...measured.lines];
|
|
312
|
+
const identified = Date.now();
|
|
313
|
+
const lines = [...lineage, by];
|
|
314
|
+
const before = [...lineage].reverse().find((one) => one !== by);
|
|
315
|
+
const regionsOf = new Map();
|
|
316
|
+
for (const offer of offered) {
|
|
317
|
+
const region = regionIn(regions, offer.place);
|
|
318
|
+
if (reached(region))
|
|
319
|
+
regionsOf.set(region, [...(regionsOf.get(region) ?? []), offer]);
|
|
320
|
+
}
|
|
321
|
+
const planned = [...regionsOf].map(([region, offers]) => {
|
|
322
|
+
const shard = open(region);
|
|
323
|
+
const places = offers.map((offer) => offer.place);
|
|
324
|
+
const pairs = [];
|
|
325
|
+
const kept = [];
|
|
326
|
+
const moved = [];
|
|
327
|
+
const all = [];
|
|
328
|
+
for (const offer of offers) {
|
|
329
|
+
const known = shard.places.get(`${by} ${offer.place}`);
|
|
330
|
+
if (known && fieldOf(known.line, 'value') === offer.seen) {
|
|
331
|
+
pairs.push(`${offer.place} ${known.at}`);
|
|
332
|
+
kept.push(known.line);
|
|
333
|
+
all.push({ ...offer, digest: known.at.slice('place:'.length) });
|
|
334
|
+
read += 1;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const bytes = offer.digest ? undefined : offer.bytes();
|
|
338
|
+
const digest = offer.digest?.() ?? bytesDigest(store, bytes);
|
|
339
|
+
pairs.push(`${offer.place} place:${digest}`);
|
|
340
|
+
kept.push(canonical({ scope: offer.place, role: 'writes', form: 'alphabet', measure: 'observed', value: offer.seen, by: `${speaker}:${by}`, at: `place:${digest}` }));
|
|
341
|
+
all.push({ ...offer, digest, bytes: () => bytes ?? offer.bytes() });
|
|
342
|
+
if (known?.at === `place:${digest}`)
|
|
343
|
+
read += 1;
|
|
344
|
+
else
|
|
345
|
+
moved.push({ place: offer.place, seen: offer.seen, digest, bytes: bytes ?? offer.bytes() });
|
|
346
|
+
}
|
|
347
|
+
return { region, shard, places, pairs, kept, moved, all, key: keyOf(by, region, pairs) };
|
|
348
|
+
});
|
|
349
|
+
if (answered !== undefined && planned.every((plan) => !plan.moved.length && observedAt(store, plan.key))) {
|
|
350
|
+
for (const plan of planned) {
|
|
351
|
+
for (const [at, digest] of digestsAt(plan.key))
|
|
352
|
+
answered.set(`${by} ${at}`, digest);
|
|
353
|
+
if (plan.shard.keys.get(by) !== plan.key || plan.kept.some((line) => plan.shard.places.get(`${by} ${fieldOf(line, 'scope')}`)?.line !== line)) {
|
|
354
|
+
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
355
|
+
}
|
|
356
|
+
out.push(...plan.kept);
|
|
320
357
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
kept.push(canonical({ scope: offer.place, role: 'writes', form: 'alphabet', measure: 'observed', value: offer.seen, by: `${speaker}:${by}`, at: `place:${digest}` }));
|
|
325
|
-
all.push({ ...offer, digest, bytes: () => bytes ?? offer.bytes() });
|
|
326
|
-
if (known?.at === `place:${digest}`)
|
|
327
|
-
read += 1;
|
|
328
|
-
else
|
|
329
|
-
moved.push({ place: offer.place, seen: offer.seen, digest, bytes: bytes ?? offer.bytes() });
|
|
358
|
+
if (level.length)
|
|
359
|
+
landShard(store, speaker, home, level);
|
|
360
|
+
return true;
|
|
330
361
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
362
|
+
const plans = planned.map(({ region, shard, places, pairs, kept, moved, all, key }) => {
|
|
363
|
+
const prior = lastOf(shard, by, places);
|
|
364
|
+
if (before !== undefined)
|
|
365
|
+
lastOf(shard, before, places);
|
|
366
|
+
const ats = [...new Set(pairs.map((pair) => pair.slice(pair.indexOf(' ') + 1)))];
|
|
367
|
+
const found = byAt(key);
|
|
368
|
+
const said = found !== undefined ? ats.flatMap((at) => found.get(at) ?? [])
|
|
369
|
+
: moved.length || prior === undefined ? undefined : ats.flatMap((at) => prior.get(at) ?? []);
|
|
370
|
+
const due = said === undefined && !moved.length ? all.map((one) => ({ place: one.place, seen: one.seen, digest: one.digest, bytes: one.bytes() })) : moved;
|
|
371
|
+
return { region, shard, places, pairs, kept, due, key, said, prior, found: found !== undefined };
|
|
372
|
+
});
|
|
373
|
+
const due = plans.flatMap((plan) => (plan.said === undefined ? plan.due.map((one) => ({ ...one, region: plan.region })) : []));
|
|
374
|
+
const heldOf = (place, region) => {
|
|
375
|
+
for (const one of [...lines].reverse()) {
|
|
376
|
+
const shard = open(region);
|
|
377
|
+
const mine = servedAt(shard, one, place, lastOf(shard, one, [place])).slice(1);
|
|
378
|
+
if (mine.length)
|
|
379
|
+
return mine.map((line) => [fieldOf(line, 'scope'), fieldOf(line, 'value')]);
|
|
339
380
|
}
|
|
340
|
-
|
|
381
|
+
return [];
|
|
382
|
+
};
|
|
383
|
+
if (due.length && !source) {
|
|
384
|
+
refused.push(`REFUSE·reader ${reader.module} unavailable · ${due.length} readings remain due`);
|
|
385
|
+
return false;
|
|
341
386
|
}
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
387
|
+
if (due.length && !wait) {
|
|
388
|
+
for (const plan of plans) {
|
|
389
|
+
if (plan.said !== undefined) {
|
|
390
|
+
out.push(...plan.kept, ...plan.said);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
for (const place of plan.places) {
|
|
394
|
+
const mine = servedAt(plan.shard, by, place, plan.prior);
|
|
395
|
+
out.push(...(mine.length || before === undefined ? mine : servedAt(plan.shard, before, place, lastOf(plan.shard, before, plan.places))));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const marker = storeAt(store, 'pending', by);
|
|
399
|
+
const pid = Number((observeText(marker) ?? '').split('\n')[1]);
|
|
400
|
+
const running = existsSync(marker) && Date.now() - statSync(marker).mtimeMs < 30 * 60 * 1000 && Number.isInteger(pid) && pid > 0 && alive(pid);
|
|
401
|
+
if (!running) {
|
|
402
|
+
mkdirSync(dirname(marker), { recursive: true });
|
|
403
|
+
const child = spawn(process.execPath, [...childFlags(), processOf('observe'), root, reader.module], { cwd: root, detached: true, stdio: 'ignore' });
|
|
404
|
+
child.unref();
|
|
405
|
+
writeFileSync(marker, `${reader.module}\n${child.pid ?? ''}\n`);
|
|
406
|
+
}
|
|
407
|
+
behind.push({ module: reader.module, places: due.length });
|
|
408
|
+
process.stderr.write(`GREY reading: ${reader.module} · ${due.length} places · runs behind${running ? ', already running' : ''}\n`);
|
|
409
|
+
if (level.length)
|
|
410
|
+
landShard(store, speaker, home, level);
|
|
411
|
+
if (Date.now() - started > 250)
|
|
412
|
+
spent.push(`${reader.module.split('/').pop()} behind ${Date.now() - started}`);
|
|
413
|
+
return false;
|
|
364
414
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
415
|
+
if (due.length) {
|
|
416
|
+
const at = Date.now();
|
|
417
|
+
let rows;
|
|
418
|
+
try {
|
|
419
|
+
rows = reader.capsule !== undefined && asked !== undefined
|
|
420
|
+
? asked.ask(due.map((d) => ({
|
|
421
|
+
protocol: PROTOCOL, verb: 'read', rootScope: d.place, region: reader.capsule.region, held: heldOf(d.place, d.region),
|
|
422
|
+
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)) ?? '' })),
|
|
423
|
+
})), { id: reader.id, module: reader.module }).map((answer) => {
|
|
424
|
+
if (answer?.kind !== 'fact')
|
|
425
|
+
throw new Error(`REFUSE·reader ${reader.module} · ${answer?.why ?? 'no capsule answer'}`);
|
|
426
|
+
return (answer.claims ?? []);
|
|
427
|
+
})
|
|
428
|
+
: runReader(module, root, due.map((d) => ({ place: d.place, ...(reader.kind === 'js' ? { text: new TextDecoder().decode(d.bytes) } : {}), held: heldOf(d.place, d.region) })), lifetime);
|
|
376
429
|
}
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
430
|
+
catch (error) {
|
|
431
|
+
const why = `REFUSE·reader ${reader.module} · ${error instanceof Error ? error.message : String(error)}`;
|
|
432
|
+
refused.push(why);
|
|
433
|
+
process.stderr.write(`${why}\n`);
|
|
434
|
+
return false;
|
|
380
435
|
}
|
|
436
|
+
if (rows.every((mine) => !mine.length) && !readerAllowsEmpty(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module)) {
|
|
437
|
+
const why = `REFUSE·reader ${reader.module} · answered none of ${due.length} places · nothing landed, they stay due`;
|
|
438
|
+
refused.push(why);
|
|
439
|
+
process.stderr.write(`${why}\n`);
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
ran += due.length;
|
|
443
|
+
process.stderr.write(`READER ${reader.module} · ${due.length} run · ${Date.now() - at} ms\n`);
|
|
444
|
+
const fresh = new Map();
|
|
445
|
+
due.forEach((d, i) => fresh.set(d.place, (rows[i] ?? []).map((row) => lineOf(row, d.digest, by, speaker))));
|
|
446
|
+
for (const plan of plans.filter((one) => one.said === undefined)) {
|
|
447
|
+
const mine = new Set(plan.due.map((d) => d.place));
|
|
448
|
+
const stay = [...new Set(plan.pairs.filter((pair) => !mine.has(pair.slice(0, pair.indexOf(' ')))).map((pair) => pair.slice(pair.indexOf(' ') + 1)))];
|
|
449
|
+
const said = [...stay.flatMap((one) => plan.prior?.get(one) ?? []), ...[...mine].flatMap((place) => fresh.get(place) ?? [])];
|
|
450
|
+
keepObservation(store, plan.key, said);
|
|
451
|
+
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
452
|
+
out.push(...plan.kept, ...said);
|
|
453
|
+
}
|
|
454
|
+
const spent = Date.now() - at;
|
|
455
|
+
const over = bytesDigest(store, new TextEncoder().encode(due.map((d) => d.digest).sort().join('\n'))).replace(/^sha256:/, '');
|
|
456
|
+
level.push(canonical({ scope: `cost/reader/${reader.module}`, role: 'writes', form: 'interval', measure: 'ms', value: `${spent}..${spent}`, by: `${speaker}:${by}`, at: `place:${over}` }));
|
|
457
|
+
if (lineage.at(-1) !== by)
|
|
458
|
+
level.push(canonical({ scope: `reader/${reader.module}`, role: 'writes', form: 'alphabet', measure: 'identity', value: by, by: `${speaker}:${by}`, at: `place:${by}` }));
|
|
459
|
+
rmSync(storeAt(store, 'pending', by), { force: true });
|
|
381
460
|
}
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
writeFileSync(marker, `${reader.module}\n${child.pid ?? ''}\n`);
|
|
461
|
+
for (const plan of plans.filter((one) => one.said !== undefined)) {
|
|
462
|
+
if (!plan.found)
|
|
463
|
+
keepObservation(store, plan.key, plan.said);
|
|
464
|
+
if (plan.shard.keys.get(by) !== plan.key || plan.kept.some((line) => plan.shard.places.get(`${by} ${fieldOf(line, 'scope')}`)?.line !== line)) {
|
|
465
|
+
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
466
|
+
}
|
|
467
|
+
out.push(...plan.kept, ...plan.said);
|
|
390
468
|
}
|
|
391
|
-
behind.push({ module: reader.module, places: due.length });
|
|
392
|
-
process.stderr.write(`GREY reading: ${reader.module} · ${due.length} places · runs behind${running ? ', already running' : ''}\n`);
|
|
393
469
|
if (level.length)
|
|
394
470
|
landShard(store, speaker, home, level);
|
|
395
471
|
if (Date.now() - started > 250)
|
|
396
|
-
spent.push(`${reader.module.split('/').pop()}
|
|
472
|
+
spent.push(`${reader.module.split('/').pop()} ${identified - started}+${Date.now() - identified}`);
|
|
473
|
+
if (kept !== undefined)
|
|
474
|
+
landMemo(store, `observed/${kept}`, new TextEncoder().encode(out.slice(mark).join('\n')));
|
|
397
475
|
return false;
|
|
398
476
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
let rows;
|
|
402
|
-
try {
|
|
403
|
-
rows = reader.capsule !== undefined && asked !== undefined
|
|
404
|
-
? asked.ask(due.map((d) => ({
|
|
405
|
-
protocol: PROTOCOL, verb: 'read', rootScope: d.place, region: reader.capsule.region, held: heldOf(d.place, d.region),
|
|
406
|
-
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)) ?? '' })),
|
|
407
|
-
}))).map((answer) => {
|
|
408
|
-
if (answer?.kind !== 'fact')
|
|
409
|
-
throw new Error(`REFUSE·reader ${reader.module} · ${answer?.why ?? 'no capsule answer'}`);
|
|
410
|
-
return (answer.claims ?? []);
|
|
411
|
-
})
|
|
412
|
-
: 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));
|
|
413
|
-
}
|
|
414
|
-
catch (error) {
|
|
415
|
-
const why = `REFUSE·reader ${reader.module} · ${error instanceof Error ? error.message : String(error)}`;
|
|
416
|
-
refused.push(why);
|
|
417
|
-
process.stderr.write(`${why}\n`);
|
|
418
|
-
return false;
|
|
419
|
-
}
|
|
420
|
-
if (rows.every((mine) => !mine.length) && !readerAllowsEmpty(standing.flatMap(line => { const p = parseLifetime(line); return p.kind === 'fact' ? [p.value.fields] : []; }), reader.module)) {
|
|
421
|
-
const why = `REFUSE·reader ${reader.module} · answered none of ${due.length} places · nothing landed, they stay due`;
|
|
422
|
-
refused.push(why);
|
|
423
|
-
process.stderr.write(`${why}\n`);
|
|
424
|
-
return false;
|
|
425
|
-
}
|
|
426
|
-
ran += due.length;
|
|
427
|
-
process.stderr.write(`READER ${reader.module} · ${due.length} run · ${Date.now() - at} ms\n`);
|
|
428
|
-
const fresh = new Map();
|
|
429
|
-
due.forEach((d, i) => fresh.set(d.place, (rows[i] ?? []).map((row) => lineOf(row, d.digest, by, speaker))));
|
|
430
|
-
for (const plan of plans.filter((one) => one.said === undefined)) {
|
|
431
|
-
const mine = new Set(plan.due.map((d) => d.place));
|
|
432
|
-
const stay = [...new Set(plan.pairs.filter((pair) => !mine.has(pair.slice(0, pair.indexOf(' ')))).map((pair) => pair.slice(pair.indexOf(' ') + 1)))];
|
|
433
|
-
const said = [...stay.flatMap((one) => plan.prior?.get(one) ?? []), ...[...mine].flatMap((place) => fresh.get(place) ?? [])];
|
|
434
|
-
keepObservation(store, plan.key, said);
|
|
435
|
-
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
436
|
-
out.push(...plan.kept, ...said);
|
|
437
|
-
}
|
|
438
|
-
const spent = Date.now() - at;
|
|
439
|
-
const over = bytesDigest(store, new TextEncoder().encode(due.map((d) => d.digest).sort().join('\n'))).replace(/^sha256:/, '');
|
|
440
|
-
level.push(canonical({ scope: `cost/reader/${reader.module}`, role: 'writes', form: 'interval', measure: 'ms', value: `${spent}..${spent}`, by: `${speaker}:${by}`, at: `place:${over}` }));
|
|
441
|
-
if (lineage.at(-1) !== by)
|
|
442
|
-
level.push(canonical({ scope: `reader/${reader.module}`, role: 'writes', form: 'alphabet', measure: 'identity', value: by, by: `${speaker}:${by}`, at: `place:${by}` }));
|
|
443
|
-
rmSync(storeAt(store, 'pending', by), { force: true });
|
|
444
|
-
}
|
|
445
|
-
for (const plan of plans.filter((one) => one.said !== undefined)) {
|
|
446
|
-
if (!plan.found)
|
|
447
|
-
keepObservation(store, plan.key, plan.said);
|
|
448
|
-
if (plan.shard.keys.get(by) !== plan.key || plan.kept.some((line) => plan.shard.places.get(`${by} ${fieldOf(line, 'scope')}`)?.line !== line)) {
|
|
449
|
-
landShard(store, speaker, plan.region, [...plan.kept, pointer(speaker, by, plan.region, plan.key, plan.pairs)]);
|
|
450
|
-
}
|
|
451
|
-
out.push(...plan.kept, ...plan.said);
|
|
477
|
+
finally {
|
|
478
|
+
endAct();
|
|
452
479
|
}
|
|
453
|
-
if (level.length)
|
|
454
|
-
landShard(store, speaker, home, level);
|
|
455
|
-
if (Date.now() - started > 250)
|
|
456
|
-
spent.push(`${reader.module.split('/').pop()} ${identified - started}+${Date.now() - identified}`);
|
|
457
|
-
if (kept !== undefined)
|
|
458
|
-
landMemo(store, `observed/${kept}`, new TextEncoder().encode(out.slice(mark).join('\n')));
|
|
459
|
-
return false;
|
|
460
480
|
}
|
|
461
481
|
if (spent.length)
|
|
462
482
|
process.stderr.write(`SPENT observe ${spent.join(' · ')} ms\n`);
|
|
463
483
|
if (legacy)
|
|
464
484
|
setAside(store, speaker);
|
|
465
485
|
process.stderr.write(`OBSERVE ${ran} run · ${read} read by key${whole_ ? ` · ${whole_} readers kept whole` : ''} · ${Date.now() - began} ms\n`);
|
|
486
|
+
if (wait && refused.length)
|
|
487
|
+
throw new Error(refused.join('\n'));
|
|
466
488
|
return [...new Set(out)].sort();
|
|
467
489
|
}
|
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);
|
package/dist/fold/signed.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { authorityFor } from './signers.ts';
|
|
1
2
|
/** Lines told to a place's own lock for a preview: folded as if the file held them, and standing only as its signed lines do. */
|
|
2
3
|
export declare const tellOwn: (place: string, lines: readonly string[]) => void;
|
|
3
4
|
/**
|
|
@@ -16,3 +17,7 @@ export declare const admittedIn: (root: string, place: string, line: string) =>
|
|
|
16
17
|
export declare const appliedIn: (root: string, place: string) => readonly string[];
|
|
17
18
|
export declare const forksIn: (root: string, place: string) => readonly string[];
|
|
18
19
|
export declare const writtenIn: (root: string, place: string) => number;
|
|
20
|
+
/** Authenticated history excludes prospective lines told only for admission. */
|
|
21
|
+
export declare const appliedHistoryIn: (root: string, place: string) => readonly string[];
|
|
22
|
+
/** Shared signature/window/coordinate coverage boundary for own history. */
|
|
23
|
+
export declare function ownRecordAdmitted(line: string, place: string, family: string, signers: ReturnType<typeof authorityFor>['admitted'], algorithms: readonly string[]): boolean;
|
package/dist/fold/signed.js
CHANGED
|
@@ -13,7 +13,7 @@ const held = new Map();
|
|
|
13
13
|
const told = new Map();
|
|
14
14
|
/** Lines told to a place's own lock for a preview: folded as if the file held them, and standing only as its signed lines do. */
|
|
15
15
|
export const tellOwn = (place, lines) => void told.set(place, lines);
|
|
16
|
-
function verified(root, place) {
|
|
16
|
+
function verified(root, place, preview = true) {
|
|
17
17
|
const store = storeOf(root);
|
|
18
18
|
const lock = lockLines(store);
|
|
19
19
|
const publicText = observeText(join(root, place, LOCK)) ?? '';
|
|
@@ -21,7 +21,7 @@ function verified(root, place) {
|
|
|
21
21
|
const algorithms = hasAuthority ? signaturesOf(store).admitted : [];
|
|
22
22
|
const signers = algorithms.length ? authorityFor(lock, rootSigner(root), algorithms).admitted : [];
|
|
23
23
|
const history = signers.flatMap(signer => place ? shardLines(store, signer.id, `${place}/${LOCK}`) : ledgerLines(store, signer.id)).filter(line => fieldOf(line, 'sig') !== '');
|
|
24
|
-
const text = [publicText, ...history, ...(told.get(place) ?? [])].join('\n');
|
|
24
|
+
const text = [publicText, ...history, ...(preview ? told.get(place) ?? [] : [])].join('\n');
|
|
25
25
|
const key = `${root} ${place}`;
|
|
26
26
|
const last = held.get(key);
|
|
27
27
|
if (last !== undefined && last.text === text && last.lock === lock)
|
|
@@ -30,19 +30,7 @@ function verified(root, place) {
|
|
|
30
30
|
// Unrestricted coverage does not construct a region coordinate. Restricted
|
|
31
31
|
// coverage still requires the lock's declared namespace before comparison.
|
|
32
32
|
const family = signers.some(signer => !signer.coverage.includes('*')) ? wordOf(lockStanding(store), 'families', 'region') : '';
|
|
33
|
-
const stands = (line) =>
|
|
34
|
-
const got = parse(line);
|
|
35
|
-
if (got.kind !== 'fact' || got.value.fields['sig'] === undefined)
|
|
36
|
-
return false;
|
|
37
|
-
const fields = got.value.fields;
|
|
38
|
-
const signer = signers.find((one) => one.id === fields['by']);
|
|
39
|
-
const epoch = Number(fields['epoch']) || 0;
|
|
40
|
-
if (signer === undefined || !verifiesFields(fields, signer.publicKey, algorithms))
|
|
41
|
-
return false;
|
|
42
|
-
const window = windowAt(signer, epoch);
|
|
43
|
-
return epoch >= window.start && (window.close === null || epoch <= window.close)
|
|
44
|
-
&& signer.coverage.some((prefix) => prefix === '*' || within(region(`${family}/${place}/${fields['scope'] ?? ''}`, './'), region(prefix, './')));
|
|
45
|
-
};
|
|
33
|
+
const stands = (line) => ownRecordAdmitted(line, place, family, signers, algorithms);
|
|
46
34
|
const applied = new Set(lines.filter(stands));
|
|
47
35
|
// A published copy of an authenticated delivery is not another inscription.
|
|
48
36
|
const projected = new Set([...applied].map(sayingOf));
|
|
@@ -75,3 +63,19 @@ export const admittedIn = (root, place, line) => verified(root, place).applied.h
|
|
|
75
63
|
export const appliedIn = (root, place) => [...verified(root, place).applied];
|
|
76
64
|
export const forksIn = (root, place) => ((got) => foldClaims(got.lines.filter((line) => got.applied.has(line) || (!/ sig=/.test(line) && !got.projected.has(sayingOf(line))))).forks.map((fork) => fork.key))(verified(root, place));
|
|
77
65
|
export const writtenIn = (root, place) => ((got) => got.lines.filter((line) => !got.applied.has(line)).length)(verified(root, place));
|
|
66
|
+
/** Authenticated history excludes prospective lines told only for admission. */
|
|
67
|
+
export const appliedHistoryIn = (root, place) => [...verified(root, place, false).applied];
|
|
68
|
+
/** Shared signature/window/coordinate coverage boundary for own history. */
|
|
69
|
+
export function ownRecordAdmitted(line, place, family, signers, algorithms) {
|
|
70
|
+
const got = parse(line);
|
|
71
|
+
if (got.kind !== 'fact' || got.value.fields['sig'] === undefined)
|
|
72
|
+
return false;
|
|
73
|
+
const fields = got.value.fields;
|
|
74
|
+
const signer = signers.find((one) => one.id === fields['by']);
|
|
75
|
+
const epoch = Number(fields['epoch']) || 0;
|
|
76
|
+
if (signer === undefined || !verifiesFields(fields, signer.publicKey, algorithms))
|
|
77
|
+
return false;
|
|
78
|
+
const window = windowAt(signer, epoch);
|
|
79
|
+
return epoch >= window.start && (window.close === null || epoch <= window.close)
|
|
80
|
+
&& signer.coverage.some((prefix) => prefix === '*' || within(region(`${family}/${place}/${fields['scope'] ?? ''}`, './'), region(prefix, './')));
|
|
81
|
+
}
|
|
@@ -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
|
+
}
|