@lapxo/bound 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 (186) hide show
  1. package/CONTRIBUTING.md +44 -0
  2. package/LICENSE +22 -0
  3. package/README.md +97 -0
  4. package/TARGET.bound +361 -0
  5. package/dist/cli/accept.d.ts +23 -0
  6. package/dist/cli/accept.js +423 -0
  7. package/dist/cli/args.d.ts +10 -0
  8. package/dist/cli/args.js +34 -0
  9. package/dist/cli/host.d.ts +1 -0
  10. package/dist/cli/host.js +32 -0
  11. package/dist/cli/names.d.ts +25 -0
  12. package/dist/cli/names.js +12 -0
  13. package/dist/cli/observe.d.ts +1 -0
  14. package/dist/cli/observe.js +6 -0
  15. package/dist/cli/owner.d.ts +35 -0
  16. package/dist/cli/owner.js +119 -0
  17. package/dist/cli/pass.d.ts +6 -0
  18. package/dist/cli/pass.js +309 -0
  19. package/dist/cli/place.d.ts +74 -0
  20. package/dist/cli/place.js +290 -0
  21. package/dist/cli/takes.d.ts +30 -0
  22. package/dist/cli/takes.js +108 -0
  23. package/dist/cli/verb.d.ts +2 -0
  24. package/dist/cli/verb.js +478 -0
  25. package/dist/fold/ceilings.d.ts +24 -0
  26. package/dist/fold/ceilings.js +129 -0
  27. package/dist/fold/claims.d.ts +25 -0
  28. package/dist/fold/claims.js +107 -0
  29. package/dist/fold/closed.d.ts +15 -0
  30. package/dist/fold/closed.js +168 -0
  31. package/dist/fold/configures.d.ts +4 -0
  32. package/dist/fold/configures.js +22 -0
  33. package/dist/fold/digests.d.ts +29 -0
  34. package/dist/fold/digests.js +211 -0
  35. package/dist/fold/evidence.d.ts +32 -0
  36. package/dist/fold/evidence.js +22 -0
  37. package/dist/fold/fold-worker.d.ts +1 -0
  38. package/dist/fold/fold-worker.js +13 -0
  39. package/dist/fold/kept.d.ts +29 -0
  40. package/dist/fold/kept.js +142 -0
  41. package/dist/fold/keys.d.ts +17 -0
  42. package/dist/fold/keys.js +84 -0
  43. package/dist/fold/laid.d.ts +17 -0
  44. package/dist/fold/laid.js +90 -0
  45. package/dist/fold/nos.d.ts +8 -0
  46. package/dist/fold/nos.js +48 -0
  47. package/dist/fold/object.d.ts +17 -0
  48. package/dist/fold/object.js +38 -0
  49. package/dist/fold/observed.d.ts +33 -0
  50. package/dist/fold/observed.js +444 -0
  51. package/dist/fold/opened.d.ts +5 -0
  52. package/dist/fold/opened.js +33 -0
  53. package/dist/fold/own.d.ts +24 -0
  54. package/dist/fold/own.js +215 -0
  55. package/dist/fold/paid.d.ts +11 -0
  56. package/dist/fold/paid.js +92 -0
  57. package/dist/fold/place-inputs.d.ts +17 -0
  58. package/dist/fold/place-inputs.js +70 -0
  59. package/dist/fold/places.d.ts +20 -0
  60. package/dist/fold/places.js +84 -0
  61. package/dist/fold/rank.d.ts +3 -0
  62. package/dist/fold/rank.js +9 -0
  63. package/dist/fold/reach.d.ts +13 -0
  64. package/dist/fold/reach.js +65 -0
  65. package/dist/fold/readers.d.ts +44 -0
  66. package/dist/fold/readers.js +132 -0
  67. package/dist/fold/receipts.d.ts +8 -0
  68. package/dist/fold/receipts.js +47 -0
  69. package/dist/fold/region.d.ts +72 -0
  70. package/dist/fold/region.js +286 -0
  71. package/dist/fold/release-policy.d.ts +67 -0
  72. package/dist/fold/release-policy.js +103 -0
  73. package/dist/fold/resolved.d.ts +18 -0
  74. package/dist/fold/resolved.js +123 -0
  75. package/dist/fold/roles.d.ts +17 -0
  76. package/dist/fold/roles.js +59 -0
  77. package/dist/fold/second.d.ts +18 -0
  78. package/dist/fold/second.js +68 -0
  79. package/dist/fold/shards.d.ts +29 -0
  80. package/dist/fold/shards.js +100 -0
  81. package/dist/fold/signed.d.ts +18 -0
  82. package/dist/fold/signed.js +75 -0
  83. package/dist/fold/signers.d.ts +24 -0
  84. package/dist/fold/signers.js +58 -0
  85. package/dist/fold/stranger.d.ts +3 -0
  86. package/dist/fold/stranger.js +24 -0
  87. package/dist/fold/views.d.ts +27 -0
  88. package/dist/fold/views.js +66 -0
  89. package/dist/fold/wire.d.ts +19 -0
  90. package/dist/fold/wire.js +41 -0
  91. package/dist/fold/zoom.d.ts +16 -0
  92. package/dist/fold/zoom.js +80 -0
  93. package/dist/host/adapters/keys/ed25519.d.ts +7 -0
  94. package/dist/host/adapters/keys/ed25519.js +30 -0
  95. package/dist/host/archive.d.ts +11 -0
  96. package/dist/host/archive.js +152 -0
  97. package/dist/host/capsule-process.d.ts +20 -0
  98. package/dist/host/capsule-process.js +54 -0
  99. package/dist/host/capsule.d.ts +13 -0
  100. package/dist/host/capsule.js +149 -0
  101. package/dist/host/cell-receipts.d.ts +2 -0
  102. package/dist/host/cell-receipts.js +124 -0
  103. package/dist/host/digest.d.ts +3 -0
  104. package/dist/host/digest.js +15 -0
  105. package/dist/host/hash.d.ts +2 -0
  106. package/dist/host/hash.js +3 -0
  107. package/dist/host/io.d.ts +9 -0
  108. package/dist/host/io.js +9 -0
  109. package/dist/host/located.d.ts +4 -0
  110. package/dist/host/located.js +105 -0
  111. package/dist/host/objects.d.ts +5 -0
  112. package/dist/host/objects.js +18 -0
  113. package/dist/host/ports/store.d.ts +2 -0
  114. package/dist/host/ports/store.js +18 -0
  115. package/dist/host/provide.d.ts +23 -0
  116. package/dist/host/provide.js +81 -0
  117. package/dist/host/release.d.ts +12 -0
  118. package/dist/host/release.js +213 -0
  119. package/dist/host/selected-capsules.d.ts +8 -0
  120. package/dist/host/selected-capsules.js +19 -0
  121. package/dist/host/selected-topos.d.ts +17 -0
  122. package/dist/host/selected-topos.js +61 -0
  123. package/dist/host/source-content.d.ts +10 -0
  124. package/dist/host/source-content.js +94 -0
  125. package/dist/host/timing.d.ts +2 -0
  126. package/dist/host/timing.js +7 -0
  127. package/dist/judge/late.d.ts +7 -0
  128. package/dist/judge/late.js +33 -0
  129. package/dist/judge/take.d.ts +57 -0
  130. package/dist/judge/take.js +75 -0
  131. package/dist/judge/widening.d.ts +1 -0
  132. package/dist/judge/widening.js +25 -0
  133. package/dist/land/act.d.ts +4 -0
  134. package/dist/land/act.js +209 -0
  135. package/dist/land/alias.d.ts +10 -0
  136. package/dist/land/alias.js +41 -0
  137. package/dist/land/held.d.ts +7 -0
  138. package/dist/land/held.js +23 -0
  139. package/dist/land/inert.d.ts +26 -0
  140. package/dist/land/inert.js +33 -0
  141. package/dist/land/leaving.d.ts +14 -0
  142. package/dist/land/leaving.js +51 -0
  143. package/dist/land/ledger.d.ts +28 -0
  144. package/dist/land/ledger.js +148 -0
  145. package/dist/land/owned.d.ts +23 -0
  146. package/dist/land/owned.js +65 -0
  147. package/dist/land/sign.d.ts +16 -0
  148. package/dist/land/sign.js +30 -0
  149. package/dist/land/vouched.d.ts +21 -0
  150. package/dist/land/vouched.js +112 -0
  151. package/dist/land/withdraw.d.ts +14 -0
  152. package/dist/land/withdraw.js +24 -0
  153. package/dist/observe/files.d.ts +14 -0
  154. package/dist/observe/files.js +68 -0
  155. package/dist/observe/identity.d.ts +19 -0
  156. package/dist/observe/identity.js +61 -0
  157. package/dist/observe/match.d.ts +1 -0
  158. package/dist/observe/match.js +8 -0
  159. package/dist/observe/regions.d.ts +29 -0
  160. package/dist/observe/regions.js +200 -0
  161. package/dist/observe/run.d.ts +30 -0
  162. package/dist/observe/run.js +34 -0
  163. package/dist/observe/runner.d.ts +16 -0
  164. package/dist/observe/runner.js +83 -0
  165. package/dist/render/capsule.d.ts +24 -0
  166. package/dist/render/capsule.js +30 -0
  167. package/dist/render/field.d.ts +9 -0
  168. package/dist/render/field.js +46 -0
  169. package/dist/render/names.d.ts +3 -0
  170. package/dist/render/names.js +4 -0
  171. package/dist/render/open.d.ts +27 -0
  172. package/dist/render/open.js +61 -0
  173. package/dist/render/page.d.ts +3 -0
  174. package/dist/render/page.js +7 -0
  175. package/dist/render/receipts.d.ts +3 -0
  176. package/dist/render/receipts.js +46 -0
  177. package/dist/render/shape.d.ts +16 -0
  178. package/dist/render/shape.js +74 -0
  179. package/dist/render/target.d.ts +18 -0
  180. package/dist/render/target.js +52 -0
  181. package/dist/render/view.d.ts +22 -0
  182. package/dist/render/view.js +191 -0
  183. package/docs/readme-assets/atlas.svg +169 -0
  184. package/docs/usage.md +53 -0
  185. package/package.json +48 -0
  186. package/receipts.bound +59 -0
@@ -0,0 +1,7 @@
1
+ import { fieldOf } from "../fold/claims.js";
2
+ import { ownLock } from "../observe/runner.js";
3
+ import { steps } from '@lapxo/topos/wire';
4
+ const own = (scope, measure) => fieldOf(ownLock().find((line) => fieldOf(line, 'scope') === scope && (!measure || fieldOf(line, 'measure') === measure) && fieldOf(line, 'value') !== 'withdraw') ?? '', 'value');
5
+ export const secondName = () => steps(own('head/second'))[0] ?? '';
6
+ export const nameOf = (fold) => (fold.under ?? '').replace(/\/$/, '')
7
+ || fieldOf(fold.standing.find((line) => fieldOf(line, 'scope') === 'publish/bootstrap' && fieldOf(line, 'value') !== 'withdraw') ?? '', 'value');
@@ -0,0 +1,3 @@
1
+ import type { PlaceFold } from '../cli/place.ts';
2
+ /** Native receipts are a protocol projection of actual fold observations, not a domain renderer or an acceptance verdict. */
3
+ export declare function receiptRegion(fold: PlaceFold, at: number): readonly string[];
@@ -0,0 +1,46 @@
1
+ import { canonical } from '@lapxo/topos/wire';
2
+ import { selectedWireList } from "../host/selected-topos.js";
3
+ import { fieldOf } from "../fold/claims.js";
4
+ import { ownLockOf } from "../fold/signed.js";
5
+ import { receiptProjection } from '@lapxo/topos/receipts';
6
+ import { receiptsOf, keepCarried } from "../fold/resolved.js";
7
+ import { bytesDigest } from "../fold/digests.js";
8
+ import { keyOf } from "../fold/keys.js";
9
+ import { fileRegionDigest } from "../fold/closed.js";
10
+ import { readersBehind } from "../fold/observed.js";
11
+ import { instrumentDigest } from "../fold/digests.js";
12
+ import { ownLock, ownRoot, sourceOf } from "../observe/runner.js";
13
+ import { wireLine } from "../fold/wire.js";
14
+ /** Native receipts are a protocol projection of actual fold observations, not a domain renderer or an acceptance verdict. */
15
+ export function receiptRegion(fold, at) {
16
+ const local = fold.under ? ownLockOf(fold.root, fold.under.replace(/\/$/, '')) : fold.standing;
17
+ const prefix = 'uses/';
18
+ const pins = local.filter(line => {
19
+ const scope = fieldOf(line, 'scope');
20
+ return scope.startsWith(prefix) && scope.length > prefix.length && !scope.slice(prefix.length).includes('/') && fieldOf(line, 'value') !== 'withdraw';
21
+ });
22
+ const fields = selectedWireList(local, fold.epoch, pins, 'receipt-fields');
23
+ const contract = wireLine(ownLock(), 'receipt-instrument');
24
+ const coordinate = contract === undefined ? undefined : fieldOf(contract, 'value');
25
+ const implementation = coordinate === undefined ? undefined : instrumentDigest(fold.store, sourceOf(), [], ownRoot(), ownLock());
26
+ const observed = [...receiptsOf(fold), ...(coordinate && implementation ? [canonical({ scope: coordinate, role: 'writes', form: 'alphabet', measure: 'digest', value: implementation, at: `place:${implementation}`, by: 'bound' })] : [])];
27
+ const hash = (bytes) => bytesDigest(fold.store, new TextEncoder().encode(bytes));
28
+ const head = keyOf(fold.store, 'attest');
29
+ const projection = receiptProjection(observed, fields, hash, head ? [head] : []);
30
+ const set = keepCarried(fold.store, observed);
31
+ if (at === 0)
32
+ return [canonical({ scope: 'receipts', role: 'writes', form: 'alphabet', measure: 'digest', value: projection.root, at: `place:${set}`, by: 'bound' })];
33
+ if (at === 1) {
34
+ const algorithm = hash('').split(':')[0];
35
+ const current = readersBehind().length === 0;
36
+ return projection.regions.flatMap(region => {
37
+ const bytes = fileRegionDigest(fold.root, fold.under ?? '', region.scope, algorithm, observed);
38
+ const count = canonical({ scope: region.scope, role: 'writes', form: 'interval', measure: 'count', value: `${region.count}..${region.count}`, at: `place:${region.digest}`, by: 'bound' });
39
+ // A stale reading remains visible but cannot seal the current file bytes.
40
+ return current ? [count, canonical({ scope: region.scope, role: 'writes', form: 'alphabet', measure: 'bytes', value: bytes, at: `place:${bytes}`, by: 'bound' })] : [count];
41
+ });
42
+ }
43
+ if (at === 8)
44
+ return observed;
45
+ throw Error(`REFUSE·receipt unsupported resolution ${at}`);
46
+ }
@@ -0,0 +1,16 @@
1
+ import type { PlaceFold } from '../cli/place.ts';
2
+ export declare function shapeLines(fold: PlaceFold): readonly string[];
3
+ /**
4
+ * What moved a verdict: the world under it, or a head that signed. A demand whose judgement came out differently than
5
+ * the time before moved by observation unless its own line was re-signed, in which case the lock moved and not the
6
+ * world; a verdict moved by a signature is not the world moving.
7
+ */
8
+ export declare function moved(fold: PlaceFold): {
9
+ readonly observation: number;
10
+ readonly signature: number;
11
+ };
12
+ /** The source coordinates of the tree no reader has reached: the number that falls when knowing advances. The walk never enters the store or a foreign directory, whose coordinates are never sources. */
13
+ export declare function horizonOf(fold: PlaceFold): {
14
+ readonly unread: number;
15
+ readonly sources: number;
16
+ };
@@ -0,0 +1,74 @@
1
+ import { join } from "../host/io.js";
2
+ import { coherence } from '@lapxo/obligations/views/field';
3
+ import { fieldOf } from "../fold/claims.js";
4
+ import { ledgerLines } from "../land/ledger.js";
5
+ import { coordinatesUnder } from "../observe/files.js";
6
+ import { rolesOf } from "../fold/roles.js";
7
+ const headOf = (scope) => scope.split('/')[0] ?? scope;
8
+ /**
9
+ * A region of the tree read as a cell of the field: its origins are everyone who spoke there, the head that signed a
10
+ * line and the reader that landed a reading alike, and its epochs are when they spoke. A scope only one of them ever
11
+ * touched is where the region rests on a single origin. What figure the region makes, whether it holds without any
12
+ * one of them, how wide in epochs it is and how together its lines arrived are the five questions the algebra asks of
13
+ * a cell, asked of the lock that measures the tree.
14
+ */
15
+ function regionsOf(fold) {
16
+ const origins = new Map();
17
+ const epochs = new Map();
18
+ const scopes = new Map();
19
+ const spoke = (at, by, epoch) => {
20
+ const head = headOf(at);
21
+ origins.set(head, (origins.get(head) ?? new Set()).add(by));
22
+ epochs.set(head, [...(epochs.get(head) ?? []), epoch]);
23
+ const inside = scopes.get(head) ?? new Map();
24
+ inside.set(at, (inside.get(at) ?? new Set()).add(by));
25
+ scopes.set(head, inside);
26
+ };
27
+ for (const line of fold.standing)
28
+ spoke(fieldOf(line, 'scope'), fieldOf(line, 'by'), Number(fieldOf(line, 'epoch')) || 0);
29
+ for (const line of fold.observed)
30
+ spoke(fieldOf(line, 'scope'), fieldOf(line, 'by'), fold.epoch);
31
+ return new Map([...origins].sort().map(([head, said]) => [head, {
32
+ origins: said,
33
+ epochs: epochs.get(head) ?? [],
34
+ alone: [...(scopes.get(head) ?? new Map()).values()].filter((by) => by.size < 2).length,
35
+ scopes: (scopes.get(head) ?? new Map()).size,
36
+ }]));
37
+ }
38
+ const shapeOf = (held) => (held.origins.size > 2 ? 'polygon' : held.origins.size === 2 ? 'edge' : 'point');
39
+ const widthOf = (held) => Math.max(...held.epochs) - Math.min(...held.epochs);
40
+ const rhythmOf = (held) => `${new Set(held.epochs).size} of ${widthOf(held) + 1}`;
41
+ const periodOf = (fold) => fold.epoch + 1;
42
+ const togetherness = (held, period) => coherence(held.epochs.map((epoch, i) => ({ origin: String(i), epoch, span: { lo: 0, hi: 1 } })), period);
43
+ export function shapeLines(fold) {
44
+ const period = periodOf(fold);
45
+ return [...regionsOf(fold)].map(([head, held]) => `SHAPE ${head} · ${shapeOf(held)} of ${held.origins.size} · ${held.alone ? 'fragile' : 'robust'} · coherence ${togetherness(held, period).toFixed(2)}`
46
+ + ` · width ${widthOf(held)} · rhythm ${rhythmOf(held)}`);
47
+ }
48
+ /**
49
+ * What moved a verdict: the world under it, or a head that signed. A demand whose judgement came out differently than
50
+ * the time before moved by observation unless its own line was re-signed, in which case the lock moved and not the
51
+ * world; a verdict moved by a signature is not the world moving.
52
+ */
53
+ export function moved(fold) {
54
+ const said = new Map();
55
+ const turned = new Set();
56
+ for (const line of ledgerLines(fold.store, 'judge')) {
57
+ const at = fieldOf(line, 'scope');
58
+ const value = fieldOf(line, 'value');
59
+ if (said.has(at) && said.get(at) !== value)
60
+ turned.add(at);
61
+ said.set(at, value);
62
+ }
63
+ const resigned = new Set(fold.history.map((line) => fieldOf(line, 'scope')));
64
+ const signature = [...turned].filter((at) => resigned.has(at)).length;
65
+ return { observation: turned.size - signature, signature };
66
+ }
67
+ /** The source coordinates of the tree no reader has reached: the number that falls when knowing advances. The walk never enters the store or a foreign directory, whose coordinates are never sources. */
68
+ export function horizonOf(fold) {
69
+ const role = rolesOf(fold.store);
70
+ const sources = coordinatesUnder(join(fold.root, fold.under ?? ''), fold.root, (dir) => dir === '' || (role(dir) !== 'foreign' && join(fold.root, dir) !== fold.store))
71
+ .filter((coordinate) => role(coordinate) === 'source');
72
+ const seen = new Set(fold.observed.map((line) => fieldOf(line, 'scope')));
73
+ return { unread: sources.filter((coordinate) => !seen.has(coordinate)).length, sources: sources.length };
74
+ }
@@ -0,0 +1,18 @@
1
+ import type { Fork } from '../fold/claims.ts';
2
+ /** TARGET is a render of the fold of the owner's ledger: a fork shows every signed line that stands, under a note. */
3
+ export declare function renderTarget(ownerLines: readonly string[], today?: string, authority?: (line: string) => {
4
+ readonly kind: string;
5
+ readonly why?: string;
6
+ }, receipts?: readonly {
7
+ readonly scope: string;
8
+ readonly moved: readonly string[];
9
+ }[]): {
10
+ readonly text: string;
11
+ readonly standing: number;
12
+ readonly superseded: readonly string[];
13
+ readonly retracted: readonly string[];
14
+ readonly forks: readonly Fork[];
15
+ readonly grey: readonly string[];
16
+ readonly refused: readonly string[];
17
+ readonly vacuous: readonly string[];
18
+ };
@@ -0,0 +1,52 @@
1
+ import { claimKey, foldClaims } from "../fold/claims.js";
2
+ import { PROTOCOL } from '@lapxo/topos/wire';
3
+ const scopeOf = (line) => /(?:^|\s)scope=([^\s]+)/.exec(line)?.[1] ?? '';
4
+ /** A root policy line: signed by a key the authority reads, or one of the few scopes that bind unsigned. */
5
+ function isRootPolicyLine(line) {
6
+ if (!line.startsWith(PROTOCOL))
7
+ return false;
8
+ return /\bsig=/.test(line);
9
+ }
10
+ const isExpired = (line, today) => ((expires) => expires !== undefined && expires < today)(/(?:^|\s)expires=(\d{4}-\d{2}-\d{2})/.exec(line)?.[1]);
11
+ /** TARGET is a render of the fold of the owner's ledger: a fork shows every signed line that stands, under a note. */
12
+ export function renderTarget(ownerLines, today = new Date().toISOString().slice(0, 10), authority, receipts) {
13
+ const folded = foldClaims(ownerLines.filter((line) => isRootPolicyLine(line) && !isExpired(line, today)), receipts);
14
+ const forked = new Map(folded.forks.map((f) => [f.key, f]));
15
+ const sorted = folded.standing.slice().sort((a, b) => {
16
+ const left = `${scopeOf(a)} ${claimKey(a)}`;
17
+ const right = `${scopeOf(b)} ${claimKey(b)}`;
18
+ return left < right ? -1 : left > right ? 1 : 0;
19
+ });
20
+ const body = [];
21
+ const announced = new Set();
22
+ const grey = [];
23
+ const refused = [];
24
+ for (const line of sorted) {
25
+ const key = claimKey(line);
26
+ const fork = forked.get(key);
27
+ if (fork && !announced.has(key)) {
28
+ announced.add(key);
29
+ body.push(`# fork ${key} · ${fork.lines.length} signed lines stand; narrow or withdraw one`);
30
+ }
31
+ const verdict = authority?.(line);
32
+ if (verdict?.kind === 'grey') {
33
+ grey.push(line);
34
+ body.push(`# grey ${scopeOf(line)} · ${verdict.why ?? ''}`);
35
+ }
36
+ else if (verdict?.kind === 'refuse') {
37
+ refused.push(line);
38
+ body.push(`# refuse ${scopeOf(line)} · ${verdict.why ?? ''}`);
39
+ }
40
+ body.push(line);
41
+ }
42
+ return {
43
+ text: `# rendered from ledger/owner\n${body.join('\n')}${body.length ? '\n' : ''}`,
44
+ standing: folded.standing.length,
45
+ superseded: folded.superseded,
46
+ retracted: folded.retracted,
47
+ forks: folded.forks,
48
+ grey,
49
+ refused,
50
+ vacuous: folded.vacuous,
51
+ };
52
+ }
@@ -0,0 +1,22 @@
1
+ import type { PlaceFold } from '../cli/place.ts';
2
+ import type { View } from '../fold/views.ts';
3
+ export declare const askWhy: (fold: PlaceFold, coord: string) => void;
4
+ export declare const unrendered: (fold: PlaceFold, view: {
5
+ readonly regions: readonly {
6
+ readonly name: string;
7
+ }[];
8
+ }) => readonly string[];
9
+ export declare function render(fold: PlaceFold, regions: readonly {
10
+ readonly name: string;
11
+ readonly at: number;
12
+ }[], shape?: string): string;
13
+ export declare function prerender(fold: PlaceFold, views: readonly View[]): void;
14
+ export declare function viewOf(fold: PlaceFold, view: View): string;
15
+ export declare function renderedOf(fold: PlaceFold, view: {
16
+ readonly name: string;
17
+ readonly shape: string;
18
+ readonly regions: readonly {
19
+ readonly name: string;
20
+ readonly at: number;
21
+ }[];
22
+ }): readonly string[];
@@ -0,0 +1,191 @@
1
+ import { receiptRegion } from "./receipts.js";
2
+ import { releasePolicyLines } from "../fold/release-policy.js";
3
+ import { join } from "../host/io.js";
4
+ import { observeText } from "../observe/files.js";
5
+ import { secondName } from "./page.js";
6
+ import { capsuleAnswers, capsuleRegions, capsuleViews } from "./capsule.js";
7
+ import { declaredBy } from "../observe/regions.js";
8
+ import { readersBehind } from "../fold/observed.js";
9
+ import { LOCK, canonical, matches } from '@lapxo/topos/wire';
10
+ import { STATES } from '@lapxo/obligations';
11
+ import { fieldOf, foldClaims, selfName } from "../fold/claims.js";
12
+ import { keepRender, keptRender } from "../fold/kept.js";
13
+ import { bytesDigest } from "../fold/digests.js";
14
+ import { writerFor } from "../fold/signers.js";
15
+ import { lockLines } from "../fold/keys.js";
16
+ import { horizonOf, moved, shapeLines } from "./shape.js";
17
+ import { rankLines } from "../fold/rank.js";
18
+ import { differing, regionsOf } from "../fold/region.js";
19
+ import { verdictsFor } from "../fold/paid.js";
20
+ import { field } from "./field.js";
21
+ import { helpLines } from "../cli/names.js";
22
+ const block = (name, lines) => (lines.length ? [`## ${name}`, '', '```', ...lines, '```'] : []);
23
+ const seconds = (ms) => `${(ms / 1000).toFixed(1)} s`;
24
+ const statesOf = (fold) => {
25
+ const held = new Map();
26
+ for (const one of Object.values(verdictsFor(fold)))
27
+ held.set(one, (held.get(one) ?? 0) + 1);
28
+ return STATES.flatMap((one) => (held.get(one) ? [`${one} ${held.get(one)}`] : []));
29
+ };
30
+ const programs = (fold) => {
31
+ const median = fold.costs.length ? [...fold.costs].sort((a, b) => a - b)[Math.floor(fold.costs.length / 2)] ?? 0 : 0;
32
+ const turned = moved(fold);
33
+ const far = horizonOf(fold);
34
+ const refusedByReader = fold.observed.filter((line) => fieldOf(line, 'scope') === 'refuse/reader').length;
35
+ const c = fold.ceilings;
36
+ return block('programs', [
37
+ `PROGRAMS${fold.under ? ` ${fold.under}` : ''} ${[`${fold.claims} claims`, ...statesOf(fold), `${fold.history.length} history`].join(' · ')}`
38
+ + ` · ${fold.forks.length + (fold.second.verdict === 'forks' ? 1 : 0)} forks · ${fold.grey.length} grey · ${fold.refused.length} refused · ${fold.vacuous} vacuous`
39
+ + ` · ${secondName()} ${fold.second.verdict}${fold.second.verdict === 'agrees' && !fold.second.external ? ', never disagreed' : ''}`
40
+ + ` · store ${fold.observed.length} observed${refusedByReader ? ` · ${refusedByReader} refuse·reader` : ''}`
41
+ + (fold.under ? ` · ceilings ${c.read} met here · ${c.short.length} short · ${c.over.length} over`
42
+ : ` · ceilings ${c.read}/${c.total} read · ${c.short.length} short · ${c.over.length} over · ${c.unread.length} unread${c.vacuous.length ? ` · ${c.vacuous.length} vacuous unread` : ''}`)
43
+ + ` · ${fold.effects} · take ${fold.costs.length ? seconds(fold.costs[fold.costs.length - 1] ?? 0) : 'none'} last`
44
+ + (fold.costs.length > 1 ? ` · ${seconds(median)} median of ${fold.costs.length}` : ''),
45
+ ...shapeLines(fold),
46
+ ...rankLines(fold),
47
+ `OPEN ${(fold.open ?? []).length} unresolved decisions`,
48
+ `MOVED ${turned.observation + turned.signature} verdicts turned · ${turned.observation} by observation · ${turned.signature} by signature`,
49
+ `HORIZON ${far.unread} of ${far.sources} sources no reader reaches`,
50
+ ...releasePolicyLines(fold.release),
51
+ ...asCells(fold),
52
+ ]);
53
+ };
54
+ /**
55
+ * A key and a lock are cells under the one relation: what each has signed is its floor, what it may sign its ceiling,
56
+ * and it stays grey while it is its own only origin. What a place has already been observed to be travels with it as
57
+ * the reader's own lines, digest and all: a clone lands them and runs nothing, since running is how a second head
58
+ * disagrees and not how the first one sees green.
59
+ */
60
+ function asCells(fold) {
61
+ const signed = fold.signed;
62
+ const coverage = new Map();
63
+ for (const line of fold.standing) {
64
+ const key = /^keys\/([^/]+)$/.exec(fieldOf(line, 'scope'))?.[1];
65
+ if (key && fieldOf(line, 'measure') === 'coverage')
66
+ coverage.set(key, fieldOf(line, 'value'));
67
+ }
68
+ const colour = fold.second.verdict === 'agrees' ? 'green' : fold.second.verdict === 'forks' ? 'fork' : 'grey';
69
+ const alone = fold.second.verdict === 'agrees' ? 0 : fold.claims;
70
+ return [
71
+ ...[...coverage].sort().map(([key, covers]) => `KEY keys/${key} · floor ${signed.get(key) ?? 0} lines signed · ceiling ${covers} · ${signed.get(key) ? colour : 'grey'}`),
72
+ `LOCK ${fold.under ?? ''}${LOCK} · floor ${fold.landed} sources vouched · ceiling ${fold.lock} lines · ${colour}`,
73
+ `BLIND ${alone} lines stand on one origin · second head ${fold.second.verdict === 'stale' && fold.second.epoch ? `agreed at epoch ${fold.second.epoch}, elsewhere` : fold.second.verdict}`,
74
+ ];
75
+ }
76
+ const whyAt = new WeakMap();
77
+ export const askWhy = (fold, coord) => { whyAt.set(fold, coord); };
78
+ const why = (fold) => {
79
+ const coord = whyAt.get(fold);
80
+ if (coord) {
81
+ const hits = fold.standing.filter((line) => fieldOf(line, 'needs').split('|').includes(coord) || fieldOf(line, 'shape') === coord);
82
+ return block('why', hits.length ? hits.map((line) => `WHY ${fieldOf(line, 'scope')} · ${fieldOf(line, 'at')} · ${fieldOf(line, 'value')}`) : [`OPEN ${coord}`]);
83
+ }
84
+ const named = fold.standing.find((line) => fieldOf(line, 'scope') === `${fold.under ?? ''}view/why` || fieldOf(line, 'scope') === 'view/why');
85
+ const at = fieldOf(named ?? '', 'shape') || fieldOf(named ?? '', 'needs').split('|').find(Boolean) || '';
86
+ const held = at ? observeText(join(fold.root, fold.under ?? '', at)) : undefined;
87
+ if (held === undefined)
88
+ return [];
89
+ const prose = held.replace(/^---\n[\s\S]*?\n---\n/, '').split('\n\n')
90
+ .map((part) => part.trim()).filter((part) => part && !part.startsWith('#'))
91
+ .map((part) => part.split('\n').join(' '));
92
+ return prose.length ? ['## why', '', ...prose.flatMap((part) => [part, ''])].slice(0, -1) : [];
93
+ };
94
+ const sign = (fold) => {
95
+ const keys = fold.standing
96
+ .filter((line) => /^keys\/[^/]+$/.test(fieldOf(line, 'scope')) && fieldOf(line, 'measure') === 'class')
97
+ .map((line) => `KEY ${fieldOf(line, 'scope').slice('keys/'.length)} · ${fieldOf(line, 'value')}`);
98
+ return block('sign', [
99
+ `SIGN write the lines you propose as by=target, then: ${selfName()} sign <file> --key-file <pem of an admitted key>`,
100
+ 'SIGN a line is never edited in place: withdraw the old content in the same batch, or the fold forks',
101
+ ...keys,
102
+ ]);
103
+ };
104
+ const take = (fold) => block('take', [
105
+ `TAKE ${selfName()} land <batch> --key-file <pem> · the land that takes a demand pays it by moving what it asks about`,
106
+ ...fold.missing.map((line) => `TAKE ${fieldOf(line, 'scope')} · open: sign it again in a batch`),
107
+ ]);
108
+ const red = (fold) => block('red', [
109
+ ...readersBehind().map((one) => `GREY reading: ${one.module} · ${one.places} places · lands behind this fold`),
110
+ ...fold.ceilings.short.map((s) => `SHORT ${s}`),
111
+ ...fold.ceilings.over.map((o) => `OVER ${o}`),
112
+ ...fold.missing.map((line) => `MISSING ${fieldOf(line, 'scope')}`),
113
+ ...fold.orphans.map((file) => `ORPHAN ${file}`),
114
+ ...(fold.under ? [] : [
115
+ ...(fold.ceilings.unread.length ? [`UNREAD ${fold.ceilings.unread.join(' · ')}`] : []),
116
+ ...(fold.ceilings.vacuous.length ? [`VACUOUS ${fold.ceilings.vacuous.join(' · ')}`] : []),
117
+ ...fold.refused.map((line) => `REFUSED ${fieldOf(line, 'scope')}`),
118
+ ]),
119
+ ]);
120
+ const forks = (fold) => block('forks', [
121
+ ...fold.forks.map((fork) => `FORK ${fork.key}`),
122
+ ...(fold.second.verdict === 'forks' ? [`FORK ${secondName()}/target · the second head finds other lines standing`] : []),
123
+ ]);
124
+ const history = (fold) => {
125
+ const lines = lockLines(fold.store);
126
+ const at = (epoch) => foldClaims(lines.filter((line) => (Number(fieldOf(line, 'epoch')) || 0) <= epoch)).standing;
127
+ const moved = differing(regionsOf(at(fold.epoch - 1), at(fold.epoch - 1), []), regionsOf(at(fold.epoch), at(fold.epoch), []));
128
+ return block('history', [
129
+ ...fold.history.map((line) => `HISTORY ${fieldOf(line, 'scope')} · ${fieldOf(line, 'at')}`),
130
+ ...moved.regions.map((region) => `MOVED ${region || '.'} · at epoch ${fold.epoch} · ${moved.compared} regions compared`),
131
+ ]);
132
+ };
133
+ const REGIONS = {
134
+ receipts: (fold) => receiptRegion(fold, 1),
135
+ help: () => helpLines(),
136
+ lines: (fold) => fold.standing,
137
+ red, forks, programs, why, sign, take, history,
138
+ field: (fold) => field(fold, 3),
139
+ };
140
+ const AT = { field, receipts: receiptRegion };
141
+ export const unrendered = (fold, view) => ((declared) => view.regions.map((asked) => asked.name).filter((name) => REGIONS[name] === undefined && !declared.some((glob) => matches(glob, name))))(declaredBy(fold));
142
+ export function render(fold, regions, shape = '') {
143
+ const out = [];
144
+ const capsules = capsuleRegions(fold, regions.filter((asked) => REGIONS[asked.name] === undefined), shape);
145
+ for (const asked of regions) {
146
+ const region = REGIONS[asked.name];
147
+ const resolving = AT[asked.name];
148
+ const body = resolving !== undefined ? resolving(fold, asked.at) : region !== undefined ? read(region(fold), asked.at) : capsules.get(asked.name) ?? [];
149
+ if (body.length)
150
+ out.push(...body, '');
151
+ }
152
+ return `${out.join('\n').replace(/\n+$/, '')}\n`;
153
+ }
154
+ function read(body, at) {
155
+ if (at >= 3 || !body.length)
156
+ return body;
157
+ const head = body.findIndex((line) => line.startsWith('```'));
158
+ if (head < 0)
159
+ return at > 1 ? body : body.slice(0, 3);
160
+ const lines = body.slice(head + 1, body.lastIndexOf('```'));
161
+ const kept = at <= 1 ? lines.slice(0, 1) : lines.map((line) => line.split(' · ').slice(0, at).join(' · '));
162
+ return [...body.slice(0, head + 1), ...kept, '```'];
163
+ }
164
+ export function prerender(fold, views) {
165
+ const due = views.filter((view) => 'regions' in view && !(fold.key && keptRender(fold.store, fold.key, view.name) !== undefined));
166
+ capsuleViews(fold, due.map((view) => ({ asking: view.regions.filter((asked) => REGIONS[asked.name] === undefined), shape: view.shape })));
167
+ }
168
+ export function viewOf(fold, view) {
169
+ const live = !view.shape;
170
+ const kept = !live && fold.key ? keptRender(fold.store, fold.key, view.name) : undefined;
171
+ if (kept !== undefined)
172
+ return kept;
173
+ const text = render(fold, view.regions, view.shape);
174
+ const folder = writerFor(fold.standing, 'fold');
175
+ if (!live && fold.key && folder !== undefined)
176
+ keepRender(fold.store, folder, fold.key, view.name, fold.epoch, text);
177
+ return text;
178
+ }
179
+ export function renderedOf(fold, view) {
180
+ const answers = capsuleAnswers(fold, [{ asking: view.regions.filter((asked) => REGIONS[asked.name] === undefined), shape: view.shape }])[0];
181
+ const cone = view.shape ? bytesDigest(fold.store, new TextEncoder().encode(fold.standing.filter((line) => fieldOf(line, 'shape') === view.shape || fieldOf(line, 'needs').split('|').includes(view.shape)).join('\n'))) : '';
182
+ const head = cone ? [canonical({ scope: `rendered/${view.name}/cone`, role: 'writes', form: 'alphabet', measure: 'digest', value: cone, at: `place:${view.shape}`, by: 'target' })] : [];
183
+ return [...head, ...view.regions.flatMap((asked) => {
184
+ const answer = answers?.get(asked.name);
185
+ const by = REGIONS[asked.name] !== undefined ? fold.instrument : answer?.by;
186
+ if (!by)
187
+ return [];
188
+ const line = (measure, value) => canonical({ scope: `rendered/${view.name}/${asked.name}`, role: 'writes', form: 'alphabet', measure, value, at: `place:${by}`, by: 'target' });
189
+ return [line('digest', by), ...(answer ? [line('pin', `uses/${by}`)] : [])];
190
+ })];
191
+ }
@@ -0,0 +1,169 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="760" viewBox="0 0 1200 760" role="img" aria-labelledby="title desc">
2
+ <title id="title">One history, several views</title><desc id="desc">Illustrative spatial view of B, C, G and independent D. C rests on B and G rests on C. B's travelling ceiling is 0 to 50 and travelling floor 40 to 100. Its local join widens only B to 0 to 100. Region receipts retain selected topos and implementation identity. Background points are decorative, not additional measured cells.</desc>
3
+ <defs>
4
+ <radialGradient id="bg"><stop stop-color="#163445"/><stop offset="1" stop-color="#080f1c"/></radialGradient>
5
+ <radialGradient id="dot"><stop stop-color="#fff"/><stop offset=".4" stop-color="#b4f8ed"/><stop offset="1" stop-color="#2c8c9a"/></radialGradient>
6
+ <linearGradient id="edge"><stop stop-color="#62d9c4"/><stop offset="1" stop-color="#8da4ef"/></linearGradient>
7
+ <filter id="glow" x="-100%" y="-100%" width="300%" height="300%"><feGaussianBlur stdDeviation="12"/></filter>
8
+ <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse"><path d="M0 0 10 5 0 10" fill="#7ce4d1"/></marker>
9
+ </defs>
10
+ <style>text{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;fill:#eef5fa}.muted{fill:#aac0ce;font-size:16px}.small{fill:#aac0ce;font-size:14px}.label{font-size:19px;font-weight:600}.mono{font-family:Menlo,Consolas,monospace;font-size:15px;fill:#8fe7d7}</style>
11
+ <rect width="1200" height="760" rx="24" fill="url(#bg)"/>
12
+ <text x="64" y="68" class="small" letter-spacing="3">THE ALGEBRA IN 60 SECONDS</text>
13
+ <text x="64" y="118" font-size="38" font-weight="600">One history. Several views.</text>
14
+ <text x="64" y="152" class="muted">A cell keeps its readings. A connection carries only what travels.</text>
15
+
16
+ <path d="M687.2 493.6 Q622.5 471.6 557.7 519.6" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
17
+ <path d="M687.2 493.6 Q611.8 400.4 536.4 377.1" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
18
+ <circle cx="687.2" cy="493.6" r="1.6" fill="#8cafbc" opacity=".4"/>
19
+ <path d="M557.7 519.6 Q599.3 438.0 640.9 426.5" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
20
+ <path d="M557.7 519.6 Q694.0 469.6 830.3 489.7" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
21
+ <circle cx="557.7" cy="519.6" r="1.6" fill="#8cafbc" opacity=".4"/>
22
+ <path d="M640.9 426.5 Q679.9 444.8 718.9 533.1" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
23
+ <path d="M640.9 426.5 Q532.8 423.4 424.6 490.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
24
+ <circle cx="640.9" cy="426.5" r="1.6" fill="#8cafbc" opacity=".4"/>
25
+ <path d="M718.9 533.1 Q593.9 462.3 468.9 461.5" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
26
+ <path d="M718.9 533.1 Q723.2 406.5 727.6 349.8" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
27
+ <circle cx="718.9" cy="533.1" r="1.6" fill="#8cafbc" opacity=".4"/>
28
+ <path d="M468.9 461.5 Q624.8 408.6 780.7 425.6" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
29
+ <path d="M468.9 461.5 Q584.9 469.0 701.0 546.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
30
+ <circle cx="468.9" cy="461.5" r="1.6" fill="#8cafbc" opacity=".4"/>
31
+ <path d="M780.7 425.6 Q680.5 453.3 580.3 550.9" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
32
+ <path d="M780.7 425.6 Q600.0 368.0 419.3 380.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
33
+ <circle cx="780.7" cy="425.6" r="1.6" fill="#8cafbc" opacity=".4"/>
34
+ <path d="M580.3 550.9 Q558.3 429.0 536.4 377.1" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
35
+ <path d="M580.3 550.9 Q726.7 444.2 873.2 407.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
36
+ <circle cx="580.3" cy="550.9" r="1.6" fill="#8cafbc" opacity=".4"/>
37
+ <path d="M536.4 377.1 Q683.3 398.4 830.3 489.7" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
38
+ <path d="M536.4 377.1 Q510.2 415.4 484.0 523.6" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
39
+ <circle cx="536.4" cy="377.1" r="1.6" fill="#8cafbc" opacity=".4"/>
40
+ <path d="M830.3 489.7 Q627.4 455.0 424.6 490.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
41
+ <path d="M830.3 489.7 Q713.5 362.0 596.8 304.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
42
+ <circle cx="830.3" cy="489.7" r="1.6" fill="#8cafbc" opacity=".4"/>
43
+ <path d="M424.6 490.3 Q576.1 385.1 727.6 349.8" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
44
+ <path d="M424.6 490.3 Q627.3 458.2 829.9 496.0" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
45
+ <circle cx="424.6" cy="490.3" r="1.6" fill="#8cafbc" opacity=".4"/>
46
+ <path d="M727.6 349.8 Q714.3 413.1 701.0 546.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
47
+ <path d="M727.6 349.8 Q546.7 349.1 365.9 418.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
48
+ <circle cx="727.6" cy="349.8" r="1.6" fill="#8cafbc" opacity=".4"/>
49
+ <path d="M701.0 546.4 Q560.2 428.4 419.3 380.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
50
+ <path d="M701.0 546.4 Q760.0 399.0 818.9 321.5" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
51
+ <circle cx="701.0" cy="546.4" r="1.6" fill="#8cafbc" opacity=".4"/>
52
+ <path d="M419.3 380.4 Q646.3 358.9 873.2 407.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
53
+ <path d="M419.3 380.4 Q518.5 419.1 617.6 527.9" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
54
+ <circle cx="419.3" cy="380.4" r="1.6" fill="#8cafbc" opacity=".4"/>
55
+ <path d="M873.2 407.4 Q678.6 430.5 484.0 523.6" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
56
+ <path d="M873.2 407.4 Q665.3 320.4 457.4 303.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
57
+ <circle cx="873.2" cy="407.4" r="1.6" fill="#8cafbc" opacity=".4"/>
58
+ <path d="M484.0 523.6 Q540.4 378.9 596.8 304.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
59
+ <path d="M484.0 523.6 Q690.8 432.3 897.5 411.0" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
60
+ <circle cx="484.0" cy="523.6" r="1.6" fill="#8cafbc" opacity=".4"/>
61
+ <path d="M596.8 304.3 Q713.4 365.1 829.9 496.0" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
62
+ <path d="M596.8 304.3 Q502.6 347.7 408.4 461.0" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
63
+ <circle cx="596.8" cy="304.3" r="1.6" fill="#8cafbc" opacity=".4"/>
64
+ <path d="M829.9 496.0 Q597.9 422.2 365.9 418.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
65
+ <path d="M829.9 496.0 Q759.5 344.6 689.1 263.2" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
66
+ <circle cx="829.9" cy="496.0" r="1.6" fill="#8cafbc" opacity=".4"/>
67
+ <path d="M365.9 418.3 Q592.4 334.9 818.9 321.5" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
68
+ <path d="M365.9 418.3 Q564.7 418.1 763.4 487.9" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
69
+ <circle cx="365.9" cy="418.3" r="1.6" fill="#8cafbc" opacity=".4"/>
70
+ <path d="M818.9 321.5 Q718.3 389.7 617.6 527.9" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
71
+ <path d="M818.9 321.5 Q597.5 294.7 376.0 338.0" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
72
+ <circle cx="818.9" cy="321.5" r="1.6" fill="#8cafbc" opacity=".4"/>
73
+ <path d="M617.6 527.9 Q537.5 380.6 457.4 303.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
74
+ <path d="M617.6 527.9 Q743.8 388.6 869.9 319.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
75
+ <circle cx="617.6" cy="527.9" r="1.6" fill="#8cafbc" opacity=".4"/>
76
+ <path d="M457.4 303.3 Q677.5 322.2 897.5 411.0" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
77
+ <path d="M457.4 303.3 Q493.2 355.5 529.1 477.7" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
78
+ <circle cx="457.4" cy="303.3" r="1.6" fill="#8cafbc" opacity=".4"/>
79
+ <path d="M897.5 411.0 Q653.0 401.0 408.4 461.0" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
80
+ <path d="M897.5 411.0 Q720.1 295.0 542.7 248.9" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
81
+ <circle cx="897.5" cy="411.0" r="1.6" fill="#8cafbc" opacity=".4"/>
82
+ <path d="M408.4 461.0 Q548.8 327.1 689.1 263.2" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
83
+ <path d="M408.4 461.0 Q631.6 401.1 854.7 411.2" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
84
+ <circle cx="408.4" cy="461.0" r="1.6" fill="#8cafbc" opacity=".4"/>
85
+ <path d="M689.1 263.2 Q726.2 340.6 763.4 487.9" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
86
+ <path d="M689.1 263.2 Q539.2 287.2 389.2 381.2" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
87
+ <circle cx="689.1" cy="263.2" r="1.6" fill="#8cafbc" opacity=".4"/>
88
+ <path d="M763.4 487.9 Q569.7 377.9 376.0 338.0" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
89
+ <path d="M763.4 487.9 Q762.5 335.1 761.7 252.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
90
+ <circle cx="763.4" cy="487.9" r="1.6" fill="#8cafbc" opacity=".4"/>
91
+ <path d="M376.0 338.0 Q623.0 293.7 869.9 319.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
92
+ <path d="M376.0 338.0 Q523.1 359.3 670.2 450.6" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
93
+ <circle cx="376.0" cy="338.0" r="1.6" fill="#8cafbc" opacity=".4"/>
94
+ <path d="M869.9 319.4 Q699.5 363.5 529.1 477.7" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
95
+ <path d="M869.9 319.4 Q659.0 260.9 448.0 272.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
96
+ <circle cx="869.9" cy="319.4" r="1.6" fill="#8cafbc" opacity=".4"/>
97
+ <path d="M529.1 477.7 Q535.9 328.3 542.7 248.9" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
98
+ <path d="M529.1 477.7 Q690.3 366.0 851.4 324.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
99
+ <circle cx="529.1" cy="477.7" r="1.6" fill="#8cafbc" opacity=".4"/>
100
+ <path d="M542.7 248.9 Q698.7 295.1 854.7 411.2" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
101
+ <path d="M542.7 248.9 Q514.0 289.8 485.2 400.6" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
102
+ <circle cx="542.7" cy="248.9" r="1.6" fill="#8cafbc" opacity=".4"/>
103
+ <path d="M854.7 411.2 Q621.9 361.2 389.2 381.2" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
104
+ <path d="M854.7 411.2 Q742.8 285.3 631.0 229.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
105
+ <circle cx="854.7" cy="411.2" r="1.6" fill="#8cafbc" opacity=".4"/>
106
+ <path d="M389.2 381.2 Q575.4 281.7 761.7 252.3" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
107
+ <path d="M389.2 381.2 Q574.0 347.8 758.8 384.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
108
+ <circle cx="389.2" cy="381.2" r="1.6" fill="#8cafbc" opacity=".4"/>
109
+ <path d="M761.7 252.3 Q716.0 316.4 670.2 450.6" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
110
+ <path d="M761.7 252.3 Q606.9 244.3 452.2 306.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
111
+ <circle cx="761.7" cy="252.3" r="1.6" fill="#8cafbc" opacity=".4"/>
112
+ <path d="M670.2 450.6 Q559.1 326.5 448.0 272.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
113
+ <path d="M670.2 450.6 Q715.3 322.0 760.3 263.5" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
114
+ <circle cx="670.2" cy="450.6" r="1.6" fill="#8cafbc" opacity=".4"/>
115
+ <path d="M448.0 272.4 Q649.7 263.4 851.4 324.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
116
+ <path d="M448.0 272.4 Q525.9 286.7 603.9 371.1" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
117
+ <circle cx="448.0" cy="272.4" r="1.6" fill="#8cafbc" opacity=".4"/>
118
+ <path d="M851.4 324.4 Q668.3 327.5 485.2 400.6" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
119
+ <path d="M851.4 324.4 Q707.4 252.6 563.4 250.8" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
120
+ <circle cx="851.4" cy="324.4" r="1.6" fill="#8cafbc" opacity=".4"/>
121
+ <path d="M485.2 400.6 Q558.1 280.0 631.0 229.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
122
+ <path d="M485.2 400.6 Q604.9 318.9 724.5 307.1" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
123
+ <circle cx="485.2" cy="400.6" r="1.6" fill="#8cafbc" opacity=".4"/>
124
+ <path d="M631.0 229.4 Q694.9 271.9 758.8 384.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
125
+ <path d="M631.0 229.4 Q605.0 231.0 579.1 302.5" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
126
+ <circle cx="631.0" cy="229.4" r="1.6" fill="#8cafbc" opacity=".4"/>
127
+ <path d="M758.8 384.4 Q605.5 310.4 452.2 306.4" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
128
+ <circle cx="758.8" cy="384.4" r="1.6" fill="#8cafbc" opacity=".4"/>
129
+ <path d="M452.2 306.4 Q606.2 249.9 760.3 263.5" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
130
+ <circle cx="452.2" cy="306.4" r="1.6" fill="#8cafbc" opacity=".4"/>
131
+ <path d="M760.3 263.5 Q682.1 282.3 603.9 371.1" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
132
+ <circle cx="760.3" cy="263.5" r="1.6" fill="#8cafbc" opacity=".4"/>
133
+ <path d="M603.9 371.1 Q583.6 276.0 563.4 250.8" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
134
+ <circle cx="603.9" cy="371.1" r="1.6" fill="#8cafbc" opacity=".4"/>
135
+ <path d="M563.4 250.8 Q644.0 244.0 724.5 307.1" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
136
+ <circle cx="563.4" cy="250.8" r="1.6" fill="#8cafbc" opacity=".4"/>
137
+ <path d="M724.5 307.1 Q651.8 269.8 579.1 302.5" stroke="#588d9e" stroke-width=".6" opacity=".13" fill="none"/>
138
+ <circle cx="724.5" cy="307.1" r="1.6" fill="#8cafbc" opacity=".4"/>
139
+ <circle cx="579.1" cy="302.5" r="1.6" fill="#8cafbc" opacity=".4"/>
140
+ <ellipse cx="557" cy="422" rx="240" ry="132" transform="rotate(-16 557 422)" stroke="#547c96" stroke-dasharray="3 9" fill="#71b9bd" fill-opacity=".025"/>
141
+ <path d="M391 402 Q469 255 583 351" stroke="url(#edge)" stroke-width="2.6" fill="none" marker-end="url(#arrow)"/>
142
+ <path d="M603 367 Q681 397 689 457" stroke="url(#edge)" stroke-width="2.6" fill="none" marker-end="url(#arrow)"/>
143
+ <ellipse cx="379" cy="420" rx="57" ry="24" transform="rotate(-20 379 420)" stroke="#bba4f8" stroke-width="2" fill="#bba4f8" fill-opacity=".06"/>
144
+ <path d="M356 439 Q334 464 317 481" stroke="#bba4f8" opacity=".7" fill="none"/>
145
+ <text x="65" y="283" class="label">Travelling bounds</text>
146
+ <text x="65" y="313" class="mono">ceiling 0..50</text><text x="65" y="340" class="mono">floor 40..100</text>
147
+ <text x="65" y="369" class="small">B → C → G</text>
148
+ <path d="M243 319 289 319 336 398" stroke="#608797" fill="none"/>
149
+ <text x="65" y="498" class="label">Local join</text>
150
+ <text x="65" y="528" font-size="16" fill="#c3b2fa">B widens to 0..100</text>
151
+ <text x="65" y="555" class="small">The travelling sign stays live.</text>
152
+ <path d="M240 511 284 511 317 481" stroke="#847fa1" fill="none"/>
153
+
154
+ <circle cx="379" cy="420" r="22" fill="#78d7c8" opacity=".4" filter="url(#glow)"/><circle cx="379" cy="420" r="8" fill="url(#dot)"/><text x="397" y="411" class="label">B</text>
155
+ <circle cx="590" cy="354" r="22" fill="#78d7c8" opacity=".4" filter="url(#glow)"/><circle cx="590" cy="354" r="8" fill="url(#dot)"/><text x="608" y="345" class="label">C</text>
156
+ <circle cx="690" cy="468" r="22" fill="#78d7c8" opacity=".4" filter="url(#glow)"/><circle cx="690" cy="468" r="8" fill="url(#dot)"/><text x="708" y="459" class="label">G</text>
157
+ <circle cx="927" cy="302" r="22" fill="#78d7c8" opacity=".4" filter="url(#glow)"/><circle cx="927" cy="302" r="8" fill="url(#dot)"/><text x="945" y="293" class="label">D</text>
158
+ <text x="621" y="331" class="small">ceiling 0..50</text>
159
+ <text x="718" y="496" class="small">ceiling 0..50</text>
160
+ <text x="868" y="251" class="label">Independent cell</text>
161
+ <text x="868" y="278" class="small">D declares no rest on B.</text>
162
+ <text x="449" y="573" class="small">Region: the cells selected by a view</text>
163
+ <path d="M64 624H1136" stroke="#304658"/>
164
+ <text x="64" y="664" class="label">Selected topos</text>
165
+ <text x="64" y="693" class="small">Forms and views · pinned folded standing</text>
166
+ <text x="655" y="664" class="label">Receipts at resolution 1</text>
167
+ <text x="655" y="693" class="small">Semantic inputs + selected implementation</text>
168
+ <text x="64" y="733" font-size="12" fill="#8ea6b7">Illustrative layout · connections denote declared rest · position and size do not measure importance</text>
169
+ </svg>