@lapxo/topos 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -0
  3. package/TARGET.bound +567 -0
  4. package/dist/authority/admit.d.ts +2 -0
  5. package/dist/authority/admit.js +5 -0
  6. package/dist/authority/index.d.ts +4 -0
  7. package/dist/authority/index.js +4 -0
  8. package/dist/authority/motion.d.ts +11 -0
  9. package/dist/authority/motion.js +3 -0
  10. package/dist/capsule/asked.d.ts +28 -0
  11. package/dist/capsule/asked.js +9 -0
  12. package/dist/capsule/declaration.d.ts +26 -0
  13. package/dist/capsule/declaration.js +45 -0
  14. package/dist/capsule/index.d.ts +3 -0
  15. package/dist/capsule/index.js +3 -0
  16. package/dist/capsule/shell.d.ts +51 -0
  17. package/dist/capsule/shell.js +64 -0
  18. package/dist/classes/scale.d.ts +16 -0
  19. package/dist/classes/scale.js +1 -0
  20. package/dist/contract/index.d.ts +27 -0
  21. package/dist/contract/index.js +63 -0
  22. package/dist/forms/alphabet.d.ts +3 -0
  23. package/dist/forms/alphabet.js +19 -0
  24. package/dist/forms/budget.d.ts +3 -0
  25. package/dist/forms/budget.js +11 -0
  26. package/dist/forms/distribution.d.ts +3 -0
  27. package/dist/forms/distribution.js +36 -0
  28. package/dist/forms/form.d.ts +31 -0
  29. package/dist/forms/form.js +42 -0
  30. package/dist/forms/index.d.ts +12 -0
  31. package/dist/forms/index.js +11 -0
  32. package/dist/forms/interval.d.ts +3 -0
  33. package/dist/forms/interval.js +11 -0
  34. package/dist/forms/ladder.d.ts +3 -0
  35. package/dist/forms/ladder.js +11 -0
  36. package/dist/forms/lattice.d.ts +3 -0
  37. package/dist/forms/lattice.js +18 -0
  38. package/dist/forms/phase.d.ts +3 -0
  39. package/dist/forms/phase.js +11 -0
  40. package/dist/forms/rank.d.ts +3 -0
  41. package/dist/forms/rank.js +11 -0
  42. package/dist/forms/stretch.d.ts +3 -0
  43. package/dist/forms/stretch.js +11 -0
  44. package/dist/readers/bindings.d.ts +21 -0
  45. package/dist/readers/bindings.js +1 -0
  46. package/dist/readers/define.d.ts +37 -0
  47. package/dist/readers/define.js +59 -0
  48. package/dist/readers/index.d.ts +3 -0
  49. package/dist/readers/index.js +2 -0
  50. package/dist/wire/claim.d.ts +4 -0
  51. package/dist/wire/claim.js +43 -0
  52. package/dist/wire/claimline.d.ts +78 -0
  53. package/dist/wire/claimline.js +339 -0
  54. package/dist/wire/classes.d.ts +17 -0
  55. package/dist/wire/classes.js +39 -0
  56. package/dist/wire/descriptor.d.ts +45 -0
  57. package/dist/wire/descriptor.js +8 -0
  58. package/dist/wire/evidence.d.ts +2 -0
  59. package/dist/wire/evidence.js +2 -0
  60. package/dist/wire/grammar.d.ts +22 -0
  61. package/dist/wire/grammar.js +42 -0
  62. package/dist/wire/index.d.ts +14 -0
  63. package/dist/wire/index.js +11 -0
  64. package/dist/wire/line.d.ts +10 -0
  65. package/dist/wire/line.js +118 -0
  66. package/dist/wire/measures.d.ts +2 -0
  67. package/dist/wire/measures.js +13 -0
  68. package/dist/wire/names.d.ts +8 -0
  69. package/dist/wire/names.js +8 -0
  70. package/dist/wire/outcome.d.ts +18 -0
  71. package/dist/wire/outcome.js +3 -0
  72. package/dist/wire/sig.d.ts +15 -0
  73. package/dist/wire/sig.js +15 -0
  74. package/dist/wire/spec.d.ts +28 -0
  75. package/dist/wire/spec.js +1 -0
  76. package/dist/wire/types.d.ts +73 -0
  77. package/dist/wire/types.js +2 -0
  78. package/dist/wire/values.d.ts +23 -0
  79. package/dist/wire/values.js +80 -0
  80. package/package.json +68 -0
@@ -0,0 +1,2 @@
1
+ /** Segment boundary, not string prefix — `acme/user` must not bind `acme/userdata`. */
2
+ export declare function under(prefix: string, scope: string): boolean;
@@ -0,0 +1,5 @@
1
+ import { region, within } from '@lapxo/obligations/field';
2
+ /** Segment boundary, not string prefix — `acme/user` must not bind `acme/userdata`. */
3
+ export function under(prefix, scope) {
4
+ return prefix !== '' && within(region(scope, './'), region(prefix, './'));
5
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Owner subpath `@lapxo/topos/authority` — prefix binds and key class.
3
+ */
4
+ export { keyClassFor } from './motion.ts';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Owner subpath `@lapxo/topos/authority` — prefix binds and key class.
3
+ */
4
+ export { keyClassFor } from "./motion.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Authority is the direction of the move, not the kind of actor.
3
+ * Narrow is attest. Widen is authorize.
4
+ *
5
+ * Seven state actions. `foreign` is a location (out of domain), not a verb.
6
+ * `derives` is a reference relation, not a movement.
7
+ */
8
+ type Motion = 'narrow' | 'widen';
9
+ export type KeyClass = 'attest' | 'authorize';
10
+ export declare function keyClassFor(motion: Motion): KeyClass;
11
+ export {};
@@ -0,0 +1,3 @@
1
+ export function keyClassFor(motion) {
2
+ return motion === 'widen' ? 'authorize' : 'attest';
3
+ }
@@ -0,0 +1,28 @@
1
+ export type Handed = Readonly<Record<string, string>>;
2
+ interface Held {
3
+ readonly lines: readonly Handed[];
4
+ readonly receipts: readonly Handed[];
5
+ }
6
+ /**
7
+ * What a render capsule is asked: one region of one place, at a resolution, the reads it was handed by and the lines
8
+ * they gave, and for each region of the place it reads, that region's lines and its receipts; each handed as its
9
+ * fields, never as its text.
10
+ */
11
+ export interface Asked {
12
+ readonly region: string;
13
+ readonly at: number;
14
+ readonly shape: string;
15
+ readonly name: string;
16
+ readonly reads: readonly string[];
17
+ readonly lines: readonly Handed[];
18
+ readonly regions: Readonly<Record<string, Held>>;
19
+ }
20
+ export declare const of: (line: Handed | undefined, field: string) => string;
21
+ export declare const found: (asked: Asked, scope: string) => Handed | undefined;
22
+ export declare const value: (asked: Asked, scope: string) => string | undefined;
23
+ export declare const listed: (asked: Asked, scope: string) => readonly string[];
24
+ export declare const lang: (asked: Asked) => string;
25
+ export declare const receipts: (asked: Asked, region: string) => readonly Handed[];
26
+ export declare const placeOf: (line: Handed) => string;
27
+ export declare const regionLines: (asked: Asked, region: string) => readonly Handed[];
28
+ export {};
@@ -0,0 +1,9 @@
1
+ import { alphabet } from "../wire/grammar.js";
2
+ export const of = (line, field) => line?.[field] ?? '';
3
+ export const found = (asked, scope) => asked.lines.find((line) => of(line, 'scope') === scope);
4
+ export const value = (asked, scope) => (found(asked, scope) === undefined ? undefined : of(found(asked, scope), 'value'));
5
+ export const listed = (asked, scope) => alphabet(value(asked, scope) ?? '').members;
6
+ export const lang = (asked) => value(asked, 'lang') ?? '';
7
+ export const receipts = (asked, region) => asked.regions[region]?.receipts ?? [];
8
+ export const placeOf = (line) => of(line, 'at').replace(/^[^:]*:/, '');
9
+ export const regionLines = (asked, region) => asked.regions[region]?.lines ?? [];
@@ -0,0 +1,26 @@
1
+ import type { Handed } from './asked.ts';
2
+ export interface Declaration {
3
+ readonly domain: string;
4
+ readonly runtime?: string;
5
+ readonly holds?: string;
6
+ readonly pins: Readonly<Record<string, string>>;
7
+ readonly renders: readonly string[];
8
+ readonly reads: readonly string[];
9
+ readonly regions: Readonly<Record<string, readonly string[]>>;
10
+ readonly writes: Readonly<Record<string, string>>;
11
+ readonly effects: readonly string[];
12
+ }
13
+ /**
14
+ * What a capsule's own lock says of it: the domain it serves, the runtime the host starts it with, where its world holds
15
+ * its values, the release it pins of each package it bundles, and one line per region — what the region reads, and its
16
+ * role: a render writes a page, a receipt is a reader's claims. The paths a place's regions cover are lines of another
17
+ * measure and no region of the capsule. A lock of the older shape names its regions in one list and their reads one line
18
+ * each; one that reads as one is read as one. A region no line names reads nothing and is refused.
19
+ */
20
+ export declare function declarationOf(lines: readonly string[]): Declaration;
21
+ export declare const readsOf: (declaration: Declaration, region: string) => readonly string[] | undefined;
22
+ /**
23
+ * The lines a capsule sees, handed by the contract: the standing lines of the place that lie in a region its own lock
24
+ * reads, parsed by the wire. Nothing else of the lock reaches it, and no file does.
25
+ */
26
+ export declare function handed(reads: readonly string[], lines: readonly string[]): readonly Handed[];
@@ -0,0 +1,45 @@
1
+ import { matches } from "../wire/classes.js";
2
+ import { alphabet } from "../wire/grammar.js";
3
+ import { parse } from "../wire/line.js";
4
+ const standing = (lines) => lines.flatMap((line) => {
5
+ const got = parse(line);
6
+ return got.kind === 'fact' && got.value.fields['value'] !== 'withdraw' ? [got.value.fields] : [];
7
+ });
8
+ /**
9
+ * What a capsule's own lock says of it: the domain it serves, the runtime the host starts it with, where its world holds
10
+ * its values, the release it pins of each package it bundles, and one line per region — what the region reads, and its
11
+ * role: a render writes a page, a receipt is a reader's claims. The paths a place's regions cover are lines of another
12
+ * measure and no region of the capsule. A lock of the older shape names its regions in one list and their reads one line
13
+ * each; one that reads as one is read as one. A region no line names reads nothing and is refused.
14
+ */
15
+ export function declarationOf(lines) {
16
+ const held = standing(lines);
17
+ const said = (scope) => held.filter((fields) => fields['scope'] === scope)
18
+ .flatMap((fields) => alphabet(fields['value'] ?? '').members);
19
+ const own = held.filter((fields) => (fields['scope'] ?? '').startsWith('region/') && fields['measure'] === 'reads');
20
+ const named = (fields, prefix) => (fields['scope'] ?? '').slice(prefix.length);
21
+ const reading = (scope) => own.filter((fields) => fields['scope'] === scope).flatMap((fields) => alphabet(fields['value'] ?? '').members);
22
+ const regions = Object.fromEntries(own.length ? own.map((fields) => [named(fields, 'region/'), reading(fields['scope'] ?? '')])
23
+ : held.filter((fields) => (fields['scope'] ?? '').startsWith('capsule/reads/')).map((fields) => [named(fields, 'capsule/reads/'), said(fields['scope'] ?? '')]));
24
+ const [runtime, holds] = [said('capsule/runtime')[0], said('capsule/holds')[0]];
25
+ return {
26
+ domain: said('capsule/domain')[0] ?? '',
27
+ ...(runtime === undefined ? {} : { runtime }),
28
+ ...(holds === undefined ? {} : { holds }),
29
+ pins: Object.fromEntries(held.filter((fields) => (fields['scope'] ?? '').startsWith('capsule/') && fields['measure'] === 'digest')
30
+ .map((fields) => [named(fields, 'capsule/'), fields['value'] ?? ''])),
31
+ renders: own.length ? own.filter((fields) => fields['role'] === 'render').map((fields) => named(fields, 'region/')) : said('capsule/regions'),
32
+ reads: [...new Set([...said('capsule/reads'), ...Object.values(regions).flat()])],
33
+ regions,
34
+ writes: Object.fromEntries(own.map((fields) => [named(fields, 'region/'), fields['role'] ?? ''])),
35
+ effects: said('capsule/effects'),
36
+ };
37
+ }
38
+ export const readsOf = (declaration, region) => (Object.keys(declaration.regions).length ? declaration.regions[region] : declaration.reads);
39
+ /**
40
+ * The lines a capsule sees, handed by the contract: the standing lines of the place that lie in a region its own lock
41
+ * reads, parsed by the wire. Nothing else of the lock reaches it, and no file does.
42
+ */
43
+ export function handed(reads, lines) {
44
+ return standing(lines).filter((fields) => reads.some((region) => matches(region, fields['scope'] ?? '')));
45
+ }
@@ -0,0 +1,3 @@
1
+ export * from './asked.ts';
2
+ export * from './declaration.ts';
3
+ export * from './shell.ts';
@@ -0,0 +1,3 @@
1
+ export * from "./asked.js";
2
+ export * from "./declaration.js";
3
+ export * from "./shell.js";
@@ -0,0 +1,51 @@
1
+ import type { Asked } from './asked.ts';
2
+ type Region = (asked: Asked) => readonly string[];
3
+ /**
4
+ * The shell a capsule's rendered index hands its regions to: a region is answered only when it was handed every read
5
+ * its own line declares, and one handed less is refused — the no every render capsule carries without writing it.
6
+ */
7
+ export declare function shell(regions: Readonly<Record<string, {
8
+ readonly reads: readonly string[];
9
+ readonly region: Region;
10
+ }>>): Region;
11
+ type Handed = readonly {
12
+ readonly place: string;
13
+ readonly text: string;
14
+ }[];
15
+ type Observe = (bytes: Uint8Array, place: string, files: Handed) => readonly unknown[];
16
+ type Run = (place: string, self: string, held: readonly (readonly [string, string])[]) => readonly unknown[];
17
+ type Reading = {
18
+ readonly reads: readonly string[];
19
+ readonly observe?: Observe;
20
+ readonly run?: Run;
21
+ };
22
+ /**
23
+ * The shell a reader capsule's rendered index hands its readers to: each is asked as the region its own line names, and
24
+ * reads only what that line declares it reads — a region fits a read when some tail of its steps matches the read's glob,
25
+ * and what follows its `@` is the resolution it is handed at. A region no line names is refused, and so is a file of another shape.
26
+ */
27
+ export declare function readers(regions: Readonly<Record<string, Reading>>): {
28
+ readonly observe: (bytes: Uint8Array, place: string, region?: string, files?: Handed) => readonly unknown[];
29
+ readonly run: (place: string, self: string, held: readonly (readonly [string, string])[], region?: string) => readonly unknown[];
30
+ };
31
+ type Measured = {
32
+ readonly scope: string;
33
+ readonly measure: string;
34
+ readonly role: 'reads';
35
+ readonly bound: {
36
+ readonly kind: 'interval';
37
+ readonly lo: number;
38
+ readonly hi: number;
39
+ };
40
+ };
41
+ export declare const counted: (asked: Asked, what: string, n: number, measure: string) => readonly Measured[];
42
+ /**
43
+ * The shell a capsule's rendered index hands its receipt regions to: a region answers claims, and only when it was
44
+ * handed every read its own line declares; one handed less is refused, as a render is. A reading it answers is `counted`:
45
+ * a count, named by the region that read it and by what it counted.
46
+ */
47
+ export declare function receiptShell(regions: Readonly<Record<string, {
48
+ readonly reads: readonly string[];
49
+ readonly region: (asked: Asked) => readonly unknown[];
50
+ }>>): (asked: Asked) => readonly unknown[];
51
+ export {};
@@ -0,0 +1,64 @@
1
+ import { matches } from "../wire/classes.js";
2
+ import { atResolution, steps } from "../wire/grammar.js";
3
+ /**
4
+ * The shell a capsule's rendered index hands its regions to: a region is answered only when it was handed every read
5
+ * its own line declares, and one handed less is refused — the no every render capsule carries without writing it.
6
+ */
7
+ export function shell(regions) {
8
+ return (asked) => {
9
+ const one = regions[asked.region];
10
+ if (one === undefined)
11
+ throw new Error(`REFUSE·region ${asked.region}: no line of this capsule renders it`);
12
+ const short = one.reads.filter((read) => !asked.reads.includes(read));
13
+ if (short.length)
14
+ throw new Error(`REFUSE·region ${asked.region}: handed less than it declares, without ${short.join(' · ')}`);
15
+ return one.region(asked);
16
+ };
17
+ }
18
+ const fits = (read, place) => {
19
+ const glob = atResolution(read).name;
20
+ const [bare, tail] = place.endsWith('/') ? [place.slice(0, -1), '/'] : [place, ''];
21
+ const held = steps(bare);
22
+ return held.some((_, i) => matches(glob, `${steps(held.slice(i))}${tail}`));
23
+ };
24
+ /**
25
+ * The shell a reader capsule's rendered index hands its readers to: each is asked as the region its own line names, and
26
+ * reads only what that line declares it reads — a region fits a read when some tail of its steps matches the read's glob,
27
+ * and what follows its `@` is the resolution it is handed at. A region no line names is refused, and so is a file of another shape.
28
+ */
29
+ export function readers(regions) {
30
+ const one = (region, places) => {
31
+ const held = region === undefined ? undefined : regions[region];
32
+ if (held === undefined)
33
+ throw new Error(`REFUSE·region ${region ?? ''}: no line of this capsule reads with it`);
34
+ const outside = places.filter((place) => !held.reads.some((read) => fits(read, place)));
35
+ if (outside.length)
36
+ throw new Error(`REFUSE·region ${region}: ${outside.join(' · ')} is none of ${held.reads.join(' · ')}`);
37
+ return held;
38
+ };
39
+ return {
40
+ observe: (bytes, place, region, files = []) => ((held) => (held.observe ? held.observe(bytes, place, files) : []))(one(region, files.length ? files.map((file) => file.place) : [place])),
41
+ run: (place, self, held, region) => ((reading) => {
42
+ if (!reading.run)
43
+ throw new Error(`REFUSE·region ${region ?? ''}: it reads the files it is handed, and none was handed`);
44
+ return reading.run(place, self, held);
45
+ })(one(region, [place])),
46
+ };
47
+ }
48
+ export const counted = (asked, what, n, measure) => [{ scope: `${asked.region}/${what}`, measure, role: 'reads', bound: { kind: 'interval', lo: n, hi: n } }];
49
+ /**
50
+ * The shell a capsule's rendered index hands its receipt regions to: a region answers claims, and only when it was
51
+ * handed every read its own line declares; one handed less is refused, as a render is. A reading it answers is `counted`:
52
+ * a count, named by the region that read it and by what it counted.
53
+ */
54
+ export function receiptShell(regions) {
55
+ return (asked) => {
56
+ const one = regions[asked.region];
57
+ if (one === undefined)
58
+ throw new Error(`REFUSE·region ${asked.region}: no line of this capsule answers it`);
59
+ const short = one.reads.filter((read) => !asked.reads.includes(read));
60
+ if (short.length)
61
+ throw new Error(`REFUSE·region ${asked.region}: handed less than it declares, without ${short.join(' · ')}`);
62
+ return one.region(asked);
63
+ };
64
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * One scale constructor. The class name on the wire stays `forms`.
3
+ * Lattice and count are inert references — type-only algebra ports; no runtime import.
4
+ */
5
+ export interface ScaleDescriptor {
6
+ readonly id: string;
7
+ readonly universe: string;
8
+ readonly order: string;
9
+ readonly generators?: readonly string[];
10
+ readonly lattice?: string;
11
+ readonly count?: string;
12
+ readonly sample: {
13
+ readonly yes: readonly unknown[];
14
+ readonly no: readonly unknown[];
15
+ };
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import type { Asked } from '../capsule/index.ts';
2
+ import type { Request, Response } from '../wire/spec.ts';
3
+ export type { Request, Response } from '../wire/spec.ts';
4
+ type Answering = {
5
+ readonly observe?: (bytes: Uint8Array, place: string, region?: string, files?: readonly {
6
+ readonly place: string;
7
+ readonly text: string;
8
+ }[]) => readonly unknown[];
9
+ readonly run?: (place: string, self: string, held: readonly (readonly [string, string])[], region?: string) => readonly unknown[];
10
+ readonly render?: (asked: Asked) => readonly string[];
11
+ readonly receipt?: (asked: Asked) => readonly unknown[];
12
+ };
13
+ /**
14
+ * The one contract across processes, and the one serve: a module's one export answers each request, a reader's by
15
+ * observing the file it is handed or, handed none, running the place it names, as the region asked when it holds several, a capsule's
16
+ * by rendering a region, or by answering the claims of a receipt region, from the lines its own lock reads, parsed here and handed over. What throws is a refusal, what the module has no export for is an
17
+ * abstention, and a reader that can only observe is refused when no file is handed: it never reads an empty one. The host reads and writes, this never does.
18
+ */
19
+ export declare function answer(module: Answering, request: Request, self: string): Response;
20
+ /**
21
+ * Observation is the contract's, not a global's: a runner listens once, and a harness hands each sample it loaded through
22
+ * `observed`, which notes every field of a case a block reads by where the case came from; nobody listening, nothing is noted.
23
+ */
24
+ export declare const listen: (note: ((at: string) => void) | undefined) => ((at: string) => void) | undefined;
25
+ export declare const observed: <T extends Readonly<Record<string, unknown>>>(held: T, at: string) => T;
26
+ export declare const respond: (module: Answering, input: string, self: string) => string;
27
+ export declare function responsesOf(printed: string, asked: number): readonly Response[];
@@ -0,0 +1,63 @@
1
+ /** It answers what a world answers — render, receipt, observe, run — what does a world owe? */
2
+ import { handed } from "../capsule/index.js";
3
+ import { PROTOCOL } from "../wire/line.js";
4
+ /**
5
+ * The one contract across processes, and the one serve: a module's one export answers each request, a reader's by
6
+ * observing the file it is handed or, handed none, running the place it names, as the region asked when it holds several, a capsule's
7
+ * by rendering a region, or by answering the claims of a receipt region, from the lines its own lock reads, parsed here and handed over. What throws is a refusal, what the module has no export for is an
8
+ * abstention, and a reader that can only observe is refused when no file is handed: it never reads an empty one. The host reads and writes, this never does.
9
+ */
10
+ export function answer(module, request, self) {
11
+ try {
12
+ const asked = () => {
13
+ const reads = request.reads ?? [];
14
+ const regions = Object.fromEntries(Object.entries(request.regions ?? {}).filter(([name]) => reads.includes(`region/${name}`))
15
+ .map(([name, held]) => [name, { lines: handed(['**'], held.lines), receipts: handed(['**'], held.receipts) }]));
16
+ return { region: request.region ?? '', at: request.at ?? 3, shape: request.shape ?? '', name: request.name ?? '', reads, lines: handed(reads, request.lines ?? []), regions };
17
+ };
18
+ if (request.verb === 'render' && module.render)
19
+ return { protocol: PROTOCOL, kind: 'fact', lines: module.render(asked()) };
20
+ if (request.verb === 'read' && module.receipt && request.reads !== undefined)
21
+ return { protocol: PROTOCOL, kind: 'fact', claims: module.receipt(asked()) };
22
+ const file = request.files[0];
23
+ if (request.verb === 'read' && module.observe && file !== undefined) {
24
+ return { protocol: PROTOCOL, kind: 'fact', claims: module.observe(new TextEncoder().encode(file.text), request.rootScope, request.region, request.files) };
25
+ }
26
+ if (request.verb === 'read' && module.run)
27
+ return { protocol: PROTOCOL, kind: 'fact', claims: module.run(request.rootScope, self, request.held ?? [], request.region) };
28
+ if (request.verb === 'read' && module.observe)
29
+ return { protocol: PROTOCOL, kind: 'refuse', why: 'no file was handed to observe' };
30
+ return { protocol: PROTOCOL, kind: 'abstain', why: `no export answers ${request.verb}` };
31
+ }
32
+ catch (thrown) {
33
+ return { protocol: PROTOCOL, kind: 'refuse', why: String(thrown) };
34
+ }
35
+ }
36
+ /**
37
+ * Observation is the contract's, not a global's: a runner listens once, and a harness hands each sample it loaded through
38
+ * `observed`, which notes every field of a case a block reads by where the case came from; nobody listening, nothing is noted.
39
+ */
40
+ export const listen = (note) => {
41
+ const was = globalThis.__boundRead;
42
+ globalThis.__boundRead = note;
43
+ return was;
44
+ };
45
+ export const observed = (held, at) => {
46
+ const note = globalThis.__boundRead;
47
+ if (note === undefined || !Array.isArray(held['cases']))
48
+ return held;
49
+ const cases = held['cases'].map((one, i) => new Proxy(one, {
50
+ get: (target, field, receiver) => {
51
+ if (typeof field === 'string')
52
+ note(`${at}#${i}:${field}`);
53
+ return Reflect.get(target, field, receiver);
54
+ },
55
+ }));
56
+ return { ...held, cases };
57
+ };
58
+ export const respond = (module, input, self) => JSON.stringify(JSON.parse(input).map((request) => answer(module, request, self)));
59
+ export function responsesOf(printed, asked) {
60
+ const last = /(?:^|\n)([^\n]*)$/.exec(printed.trimEnd())?.[1] ?? '';
61
+ const held = last.startsWith('[') ? JSON.parse(last) : [];
62
+ return Array.from({ length: asked }, (_, i) => held[i] ?? { protocol: PROTOCOL, kind: 'refuse', why: 'no answer' });
63
+ }
@@ -0,0 +1,3 @@
1
+ import { WideMask } from '@lapxo/obligations';
2
+ import type { WireForm } from './form.ts';
3
+ export declare const alphabetOfForm: WireForm<WideMask>;
@@ -0,0 +1,19 @@
1
+ import { alphabetForm, WideMask } from '@lapxo/obligations';
2
+ import { alphabet } from "../wire/grammar.js";
3
+ import { refuseForm, wordsAt, written } from "./form.js";
4
+ const tokensOf = (params) => ({ tokens: wordsAt('alphabet', params, 'tokens') });
5
+ export const alphabetOfForm = {
6
+ id: 'alphabet',
7
+ lattice: (params) => alphabetForm.lattice(tokensOf(params)),
8
+ parse(params, text) {
9
+ const { tokens } = tokensOf(params);
10
+ const read = alphabet(text);
11
+ const stray = read.members.find((one) => !tokens.includes(one));
12
+ if (stray !== undefined)
13
+ refuseForm('alphabet', `\`${stray}\` is not one of its tokens`);
14
+ return WideMask.fromTokens(tokens, new Set(read.polarity === 'forbid' ? tokens.filter((one) => !read.members.includes(one)) : read.members));
15
+ },
16
+ emit: (params, value) => written(value.members(tokensOf(params).tokens)),
17
+ show: (params, value) => alphabetForm.lattice(tokensOf(params)).show(value),
18
+ points: (params, seed, n) => alphabetForm.points(tokensOf(params), seed, n),
19
+ };
@@ -0,0 +1,3 @@
1
+ import type { Interval } from '@lapxo/obligations';
2
+ import type { WireForm } from './form.ts';
3
+ export declare const budgetForm: WireForm<Interval>;
@@ -0,0 +1,11 @@
1
+ import { continuousForm } from '@lapxo/obligations';
2
+ import { bandOf, bandText, numberAt } from "./form.js";
3
+ const span = (params) => ({ lo: numberAt('budget', params, 'lo'), hi: numberAt('budget', params, 'hi') });
4
+ export const budgetForm = {
5
+ id: 'budget',
6
+ lattice: (params) => continuousForm.lattice(span(params)),
7
+ parse: (params, text) => bandOf('budget', span(params), text),
8
+ emit: (_params, value) => bandText(value),
9
+ show: (params, value) => continuousForm.lattice(span(params)).show(value),
10
+ points: (params, seed, n) => continuousForm.points(span(params), seed, n),
11
+ };
@@ -0,0 +1,3 @@
1
+ import type { Unit } from '@lapxo/obligations';
2
+ import type { WireForm } from './form.ts';
3
+ export declare const distributionForm: WireForm<Unit>;
@@ -0,0 +1,36 @@
1
+ import { latticeForm } from '@lapxo/obligations';
2
+ import { alphabet } from "../wire/grammar.js";
3
+ import { levelsOf, refuseForm, wordsAt, written } from "./form.js";
4
+ /** A distribution is a product of levels by bucket: every tuple of one level per bucket, ordered bucket by bucket. */
5
+ function product(params) {
6
+ const buckets = wordsAt('distribution', params, 'buckets');
7
+ const levels = wordsAt('distribution', params, 'levels');
8
+ const tuples = buckets.reduce((held) => held.flatMap((tuple) => levels.map((level) => [...tuple, level])), [[]]);
9
+ return { buckets, levels, tuples };
10
+ }
11
+ const orderOver = (params) => {
12
+ const { levels, tuples } = product(params);
13
+ const at = (tuple) => tuple.map((level) => levels.findIndex((one) => one === level));
14
+ return { levels: tuples.map((tuple) => JSON.stringify(tuple)), leq: tuples.map((low) => tuples.map((high) => at(low).every((rank, i) => rank <= (at(high)[i] ?? -1)))) };
15
+ };
16
+ export const distributionForm = {
17
+ id: 'distribution',
18
+ lattice: (params) => latticeForm.lattice(orderOver(params)),
19
+ parse(params, text) {
20
+ const { buckets, levels } = product(params);
21
+ const bucketOf = (part) => buckets.find((bucket) => !bucket.includes(':') && part.startsWith(`${bucket}:`));
22
+ const members = alphabet(text).members;
23
+ const said = new Map(members.flatMap((part) => ((bucket) => (bucket === undefined ? [] : [[bucket, part.slice(bucket.length + 1)]]))(bucketOf(part))));
24
+ const stray = members.find((part) => bucketOf(part) === undefined) ?? buckets.find((bucket) => !said.has(bucket));
25
+ if (stray !== undefined)
26
+ refuseForm('distribution', `\`${stray}\` is not the bucket list it is given, every bucket named once`);
27
+ const bands = buckets.map((bucket) => levelsOf('distribution', levels, said.get(bucket) ?? ''));
28
+ return { subject: '', floor: JSON.stringify(bands.map((band) => band.floor)), ceiling: JSON.stringify(bands.map((band) => band.ceiling)) };
29
+ },
30
+ emit(params, value) {
31
+ const [lows, highs] = [JSON.parse(value.floor), JSON.parse(value.ceiling)];
32
+ return written(product(params).buckets.map((bucket, i) => (lows[i] === highs[i] ? `${bucket}:${lows[i]}` : `${bucket}:${lows[i]}..${highs[i]}`)));
33
+ },
34
+ show: (params, value) => latticeForm.lattice(orderOver(params)).show(value),
35
+ points: (params, seed, n) => latticeForm.points(orderOver(params), seed, n),
36
+ };
@@ -0,0 +1,31 @@
1
+ import type { Interval, Lattice, Unit } from '@lapxo/obligations';
2
+ /**
3
+ * A form of the wire is how a line writes a value and the lattice of the algebra that value is read in: an id, the
4
+ * parameters that lattice takes, the text a line carries and the points every reading over forms samples. A form here
5
+ * orders nothing of its own; each hands over a lattice the algebra already offers, parametrised, and what a form would
6
+ * need that the algebra does not offer is the algebra's to add.
7
+ */
8
+ export interface WireForm<T = unknown> {
9
+ readonly id: string;
10
+ lattice(params: unknown): Lattice<T>;
11
+ parse(params: unknown, text: string): T;
12
+ emit(params: unknown, value: T): string;
13
+ show(params: unknown, value: T): string;
14
+ points(params: unknown, seed: number, n: number): readonly T[];
15
+ }
16
+ export declare const violationsIn: (lattice: ReturnType<WireForm["lattice"]>, points: ReturnType<WireForm["points"]>) => readonly string[];
17
+ type Fields = Readonly<Record<string, unknown>>;
18
+ export declare const paramsOf: (params: unknown) => Fields;
19
+ export declare function refuseForm(id: string, why: string): never;
20
+ export declare function numberAt(id: string, params: unknown, name: string): number;
21
+ export declare function wordsAt(id: string, params: unknown, name: string): readonly string[];
22
+ export declare const written: (words: readonly string[]) => string;
23
+ /**
24
+ * A band a line writes over an interval it is given: `lo..hi`, one end alone, `*` for an end left open, or `empty`.
25
+ * Its ends are read by the wire's own interval form, the one a claim's value is read by, and one end alone is that end twice.
26
+ */
27
+ export declare function bandOf(id: string, whole: Interval, text: string): Interval;
28
+ export declare const bandText: (value: Interval) => string;
29
+ /** A band a line writes over levels it is given, `lo..hi` or one level alone: the band of them whose written text it is. */
30
+ export declare function levelsOf(id: string, held: readonly string[], text: string): Unit;
31
+ export {};
@@ -0,0 +1,42 @@
1
+ import { violationsIn as lawsBrokenIn } from '@lapxo/obligations';
2
+ import { alphabet } from "../wire/grammar.js";
3
+ import { fromLine } from "../wire/claimline.js";
4
+ import { byBytes, canonical } from "../wire/line.js";
5
+ import { decimal } from "../wire/values.js";
6
+ export const violationsIn = (lattice, points) => lawsBrokenIn(lattice, points);
7
+ export const paramsOf = (params) => (params !== null && typeof params === 'object' ? params : {});
8
+ export function refuseForm(id, why) {
9
+ throw new Error(`REFUSE·form ${id}: ${why}`);
10
+ }
11
+ export function numberAt(id, params, name) {
12
+ const held = paramsOf(params)[name];
13
+ if (typeof held !== 'number' || !isFinite(held))
14
+ refuseForm(id, `takes ${name} as a number, and a value it is not given is not invented`);
15
+ return held;
16
+ }
17
+ export function wordsAt(id, params, name) {
18
+ const held = paramsOf(params)[name];
19
+ if (!Array.isArray(held) || !held.length || held.some((one) => typeof one !== 'string'))
20
+ refuseForm(id, `takes ${name} as a list of words`);
21
+ return held;
22
+ }
23
+ export const written = (words) => alphabet({ polarity: 'permit', members: [...new Set(words)].sort(byBytes) });
24
+ /**
25
+ * A band a line writes over an interval it is given: `lo..hi`, one end alone, `*` for an end left open, or `empty`.
26
+ * Its ends are read by the wire's own interval form, the one a claim's value is read by, and one end alone is that end twice.
27
+ */
28
+ export function bandOf(id, whole, text) {
29
+ if (text === 'empty')
30
+ return { lo: whole.hi, hi: whole.lo };
31
+ const read = (value) => ((got) => (got.kind === 'fact' && got.value.bound.kind === 'interval' ? { lo: got.value.bound.lo ?? whole.lo, hi: got.value.bound.hi ?? whole.hi } : undefined))(fromLine(canonical({ form: 'interval', value }), null));
32
+ return read(text) ?? read(`${text}..${text}`) ?? refuseForm(id, `\`${text}\` is not a band: lo..hi or one end, each a number or *`);
33
+ }
34
+ export const bandText = (value) => ((end) => (value.lo > value.hi ? 'empty' : `${end(value.lo)}..${end(value.hi)}`))((n) => (isFinite(n) ? decimal(n) : '*'));
35
+ /** A band a line writes over levels it is given, `lo..hi` or one level alone: the band of them whose written text it is. */
36
+ export function levelsOf(id, held, text) {
37
+ if (held.some((level) => level.includes('..')))
38
+ refuseForm(id, 'a level holds no band mark `..`');
39
+ const band = held.flatMap((floor) => held.map((ceiling) => ({ subject: '', floor, ceiling }))).find(({ floor, ceiling }) => text === `${floor}..${ceiling}`)
40
+ ?? (text.includes('..') ? undefined : held.filter((level) => level === text).map((level) => ({ subject: '', floor: level, ceiling: level }))[0]);
41
+ return band ?? refuseForm(id, `\`${text}\` is not one of its levels, nor two of them as lo..hi`);
42
+ }
@@ -0,0 +1,12 @@
1
+ import { alphabetOfForm } from './alphabet.ts';
2
+ import { budgetForm } from './budget.ts';
3
+ import { distributionForm } from './distribution.ts';
4
+ import { intervalForm } from './interval.ts';
5
+ import { ladderOfForm } from './ladder.ts';
6
+ import { latticeOfForm } from './lattice.ts';
7
+ import { phaseForm } from './phase.ts';
8
+ import { rankForm } from './rank.ts';
9
+ import { stretchForm } from './stretch.ts';
10
+ export { violationsIn } from './form.ts';
11
+ export type { WireForm } from './form.ts';
12
+ export { alphabetOfForm, budgetForm, distributionForm, intervalForm, ladderOfForm, latticeOfForm, phaseForm, rankForm, stretchForm };
@@ -0,0 +1,11 @@
1
+ import { alphabetOfForm } from "./alphabet.js";
2
+ import { budgetForm } from "./budget.js";
3
+ import { distributionForm } from "./distribution.js";
4
+ import { intervalForm } from "./interval.js";
5
+ import { ladderOfForm } from "./ladder.js";
6
+ import { latticeOfForm } from "./lattice.js";
7
+ import { phaseForm } from "./phase.js";
8
+ import { rankForm } from "./rank.js";
9
+ import { stretchForm } from "./stretch.js";
10
+ export { violationsIn } from "./form.js";
11
+ export { alphabetOfForm, budgetForm, distributionForm, intervalForm, ladderOfForm, latticeOfForm, phaseForm, rankForm, stretchForm };
@@ -0,0 +1,3 @@
1
+ import type { Interval } from '@lapxo/obligations';
2
+ import type { WireForm } from './form.ts';
3
+ export declare const intervalForm: WireForm<Interval>;
@@ -0,0 +1,11 @@
1
+ import { continuousForm } from '@lapxo/obligations';
2
+ import { bandOf, bandText, numberAt } from "./form.js";
3
+ const span = (params) => ({ lo: numberAt('interval', params, 'lo'), hi: numberAt('interval', params, 'hi') });
4
+ export const intervalForm = {
5
+ id: 'interval',
6
+ lattice: (params) => continuousForm.lattice(span(params)),
7
+ parse: (params, text) => bandOf('interval', span(params), text),
8
+ emit: (_params, value) => bandText(value),
9
+ show: (params, value) => continuousForm.lattice(span(params)).show(value),
10
+ points: (params, seed, n) => continuousForm.points(span(params), seed, n),
11
+ };
@@ -0,0 +1,3 @@
1
+ import type { Unit } from '@lapxo/obligations';
2
+ import type { WireForm } from './form.ts';
3
+ export declare const ladderOfForm: WireForm<Unit>;
@@ -0,0 +1,11 @@
1
+ import { ladderForm } from '@lapxo/obligations';
2
+ import { levelsOf, wordsAt } from "./form.js";
3
+ const over = (params) => ({ levels: wordsAt('ladder', params, 'levels') });
4
+ export const ladderOfForm = {
5
+ id: 'ladder',
6
+ lattice: (params) => ladderForm.lattice(over(params)),
7
+ parse: (params, text) => levelsOf('ladder', over(params).levels, text),
8
+ emit: (_params, value) => (value.floor === value.ceiling ? value.floor : `${value.floor}..${value.ceiling}`),
9
+ show: (params, value) => ladderForm.lattice(over(params)).show(value),
10
+ points: (params, seed, n) => ladderForm.points(over(params), seed, n),
11
+ };
@@ -0,0 +1,3 @@
1
+ import type { Unit } from '@lapxo/obligations';
2
+ import type { WireForm } from './form.ts';
3
+ export declare const latticeOfForm: WireForm<Unit>;