@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lapxo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ <p align="center"><img src="docs/img/logo.svg" alt="" width="96"></p>
2
+
3
+ # @lapxo/topos
4
+
5
+ ![version 0.1.0](https://img.shields.io/badge/version-0.1.0-8c959f) ![license MIT](https://img.shields.io/badge/license-MIT-8c959f) ![node >=22.12](https://img.shields.io/badge/node-%3E%3D22.12-8c959f) ![dependencies 1](https://img.shields.io/badge/dependencies-1-8c959f) ![cases 12 hold](https://img.shields.io/badge/cases-12_hold-2da44e) ![verify agrees](https://img.shields.io/badge/verify-agrees-2da44e)
6
+
7
+ One line for every fact.
8
+
9
+ [bound's SDK: the wire, the forms, the contract and the shell you build a world with — a reader of any source or a renderer of any artefact, in an afternoon.](docs/what.md)
10
+
11
+ ## Why one line
12
+
13
+ Everything a system says about itself lives where nobody can check it: a config, a README, a CI log. topos is one line format for all of it — a signed line with a floor and a ceiling, that any head can fold and any second head can verify. Two implementations agree only on what passes between them, and what passes between them is a line. So the line is the one thing held still; everything else belongs to a world.
14
+
15
+ <p align="center"><img src="docs/img/world.svg" alt="declares measurements, runs on node, reaches none, 2 regions, written in one file of 92 lines, packed as sha256:3252cbb877d9ca0c9ea990828c1490999de3d4853990d53d6bbd31861e6bb780, a place adopts it with uses/topos-measure and run by the node host" width="640"></p>
16
+
17
+ ## A world, built in front of you
18
+
19
+ ```ts
20
+ // topos-measure built in front of you from its own bytes: its lock, its regions and its vector, every case asked through the one contract, then the line a place adopts it with.
21
+ import { intervals } from '@lapxo/obligations';
22
+ import type { Interval } from '@lapxo/obligations';
23
+ import { cell, encounter, observe, state } from '@lapxo/obligations/views/field';
24
+ import { declarationOf, found, lang, listed, of, shell } from '@lapxo/topos/capsule';
25
+ import type { Asked, Handed } from '@lapxo/topos/capsule';
26
+ import { answer } from '@lapxo/topos/contract';
27
+ import { PROTOCOL, canonical, fromLine, steps } from '@lapxo/topos/wire';
28
+
29
+ const capsule = [
30
+ `bound-lock/1 about="the domain this capsule serves: what several origins measured, each quantity read as one cell" at=policy:topos/capsule by=target form=alphabet measure=id role=writes scope=capsule/domain value=measurements`,
31
+ `bound-lock/1 about="the runtime a host starts this capsule with, whose entry, regions and effects are the runtime's own lines" at=policy:topos/capsule by=target form=alphabet measure=id role=writes scope=capsule/runtime value=node`,
32
+ `bound-lock/1 about="where this capsule's world keeps its values: the place's own files" at=policy:topos/capsule by=target form=alphabet measure=id role=writes scope=capsule/holds value=./`,
33
+ `bound-lock/1 about="the held region" at=policy:topos/capsule by=target form=alphabet measure=reads role=render scope=region/held value=lang|form/prose/**|form/template/**|prose/*/measure/*|measure/**`,
34
+ `bound-lock/1 about="the readings region" at=policy:topos/capsule by=target form=alphabet measure=reads role=render scope=region/readings value=lang|form/prose/**|prose/*/measure/*|measure/**`,
35
+ `bound-lock/1 about="what this capsule reaches beyond the lines it is handed" at=policy:topos/capsule by=target form=alphabet measure=effects role=writes scope=capsule/effects value=none`,
36
+ `bound-lock/1 about="the topos release this capsule is packed against, named by its digest" at=policy:topos/capsule by=target form=alphabet measure=digest role=writes scope=capsule/topos value=sha256:a87ffd5ab77a99b9e40cc720acfdf0ee7f36d156c72085a20db1232b0653c1dd`,
37
+ ];
38
+
39
+ const prose = (asked: Asked, key: string): string | undefined => ((line) => (line === undefined ? undefined : of(line, 'about')))(found(asked, `prose/${lang(asked)}/${key}`));
40
+
41
+ const SPANS = intervals(-Infinity, Infinity);
42
+ const span = (line: Handed): Interval | undefined => ((got) => (got.kind === 'fact' && got.value.bound.kind === 'interval' && got.value.bound.lo !== null && got.value.bound.hi !== null
43
+ ? { lo: got.value.bound.lo, hi: got.value.bound.hi } : undefined))(fromLine(canonical(line), null));
44
+ const say = (asked: Asked, key: string, fields: Readonly<Record<string, string | number>>): string => listed(asked, `form/template/${key}`).reduce((text, field) => text.split(`{${field}}`).join(String(fields[field] ?? '')), prose(asked, key) ?? '');
45
+ const held = (asked: Asked): readonly string[] => {
46
+ const read = asked.lines.filter((line) => steps(of(line, 'scope')).length === 3);
47
+ const numbers = listed(asked, `form/prose/${lang(asked)}/numbers`);
48
+ return [...new Set(read.map((line) => steps(of(line, 'scope'))[1] ?? ''))].map((quantity) => {
49
+ const mine = read.filter((line) => steps(of(line, 'scope'))[1] === quantity);
50
+ const met = mine.reduce((at, line) => ((one) => (one === undefined ? at : observe(at, { origin: steps(of(line, 'scope'))[2] ?? '', span: one })))(span(line)), cell<Interval>(quantity));
51
+ const { origins, held: meet } = encounter(SPANS, met);
52
+ return say(asked, `measure/${state(SPANS, met)}`, { quantity, origins: numbers[origins] ?? String(origins), lo: meet.lo, hi: meet.hi, unit: of(mine[0], 'measure') });
53
+ });
54
+ };
55
+
56
+ const readings = (asked: Asked): readonly string[] => {
57
+ const read = asked.lines.filter((line) => steps(of(line, 'scope')).length === 3);
58
+ return read.length ? [`## ${prose(asked, 'measure/readings') ?? ''}`, '', prose(asked, 'measure/table') ?? '', '|---|---|---|',
59
+ ...read.map((line) => ((at) => `| ${at[1] ?? ''} | ${at[2] ?? ''} | ${of(line, 'value')} ${of(line, 'measure')} |`)(steps(of(line, 'scope'))))] : [];
60
+ };
61
+
62
+ const render = shell({
63
+ held: { reads: ['lang', 'form/prose/**', 'form/template/**', 'prose/*/measure/*', 'measure/**'], region: held },
64
+ readings: { reads: ['lang', 'form/prose/**', 'prose/*/measure/*', 'measure/**'], region: readings },
65
+ });
66
+
67
+ const said = [
68
+ `bound-lock/1 about=Readings at=policy:topos-measure/example by=target form=alphabet measure=text role=writes scope=prose/en/measure/readings value=lock`,
69
+ `bound-lock/1 about="| quantity | origin | reading |" at=policy:topos-measure/example by=target form=alphabet measure=text role=writes scope=prose/en/measure/table value=lock`,
70
+ `bound-lock/1 about="The {origins} origins that measured {quantity} meet: every one of their readings holds {lo}..{hi} {unit}." at=policy:topos-measure/example by=target form=alphabet measure=text role=writes scope=prose/en/measure/FREE value=lock`,
71
+ `bound-lock/1 about="The {origins} origins that measured {quantity} do not meet: no value in {unit} is held by all of their readings, and no average of them closes that." at=policy:topos-measure/example by=target form=alphabet measure=text role=writes scope=prose/en/measure/CONFLICT value=lock`,
72
+ `bound-lock/1 about="Only {origins} origin measured {quantity}: its reading holds nothing yet, until a second origin's reading meets it." at=policy:topos-measure/example by=target form=alphabet measure=text role=writes scope=prose/en/measure/REQUIRED value=lock`,
73
+ `bound-lock/1 about="The {origins} origins that measured {quantity} meet, but outside what the bounds on it allow." at=policy:topos-measure/example by=target form=alphabet measure=text role=writes scope=prose/en/measure/FORBIDDEN value=lock`,
74
+ `bound-lock/1 at=policy:topos-measure/example by=target form=alphabet measure=id role=writes scope=form/template/measure/FREE value=origins|quantity|lo|hi|unit`,
75
+ `bound-lock/1 at=policy:topos-measure/example by=target form=alphabet measure=id role=writes scope=form/template/measure/CONFLICT value=origins|quantity|unit`,
76
+ `bound-lock/1 at=policy:topos-measure/example by=target form=alphabet measure=id role=writes scope=form/template/measure/REQUIRED value=origins|quantity`,
77
+ `bound-lock/1 at=policy:topos-measure/example by=target form=alphabet measure=id role=writes scope=form/template/measure/FORBIDDEN value=origins|quantity`,
78
+ `bound-lock/1 at=policy:topos-measure/example by=target form=alphabet measure=id role=writes scope=form/prose/en/numbers value=zero|one|two|three|four|five|six|seven|eight|nine|ten`,
79
+ `bound-lock/1 at=policy:topos-measure/example by=target form=alphabet measure=id role=writes scope=lang value=en`,
80
+ ];
81
+ const met = [
82
+ { origin: 'a', span: { lo: 19.8, hi: 20.2 } },
83
+ { origin: 'b', span: { lo: 19.9, hi: 20.3 } },
84
+ { origin: 'c', span: { lo: 19.7, hi: 20.1 } },
85
+ ].map(({ origin, span: { lo, hi } }) => canonical({ at: 'policy:topos-measure/example', by: 'target', form: 'interval', measure: 'celsius', role: 'writes', scope: `measure/room/${origin}`, value: `${lo}..${hi}` }));
86
+ const apart = `bound-lock/1 at=policy:topos-measure/example by=target form=interval measure=celsius role=writes scope=measure/room/d value=24..24.4`;
87
+ const worlds: Readonly<Record<string, readonly string[]>> = { 'root:topos-measure': [...said, ...met, apart], 'agreed:topos-measure': [...said, ...met], 'alone:topos-measure': [...said, ...met.slice(0, 1)] };
88
+ const cases = [
89
+ { world: 'root:topos-measure', region: 'held', expected: ['The four origins that measured room do not meet: no value in celsius is held by all of their readings, and no average of them closes that.'] },
90
+ { world: 'agreed:topos-measure', region: 'held', expected: ['The three origins that measured room meet: every one of their readings holds 19.9..20.1 celsius.'] },
91
+ { world: 'alone:topos-measure', region: 'held', expected: [`Only one origin measured room: its reading holds nothing yet, until a second origin's reading meets it.`] },
92
+ { world: 'root:topos-measure', region: 'readings', expected: ['## Readings', '', '| quantity | origin | reading |', '|---|---|---|', '| room | a | 19.8..20.2 celsius |', '| room | b | 19.9..20.3 celsius |', '| room | c | 19.7..20.1 celsius |', '| room | d | 24..24.4 celsius |'] },
93
+ ];
94
+
95
+ const declared = declarationOf(capsule);
96
+ const ask = (world: string, region: string, reads: readonly string[]) =>
97
+ answer({ render }, { protocol: PROTOCOL, verb: 'render', rootScope: '', files: [], lines: worlds[world] ?? [], region, at: 3, shape: 'README.md', name: 'topos-measure', reads }, '');
98
+ for (const line of capsule) console.log(line);
99
+ let [holds, shorts, refused] = [0, 0, 0];
100
+ for (const { world, region, expected } of cases) {
101
+ const reads = declared.regions[region] ?? [];
102
+ const got = ask(world, region, reads);
103
+ const same = JSON.stringify(got) === JSON.stringify({ protocol: PROTOCOL, kind: 'fact', lines: expected });
104
+ [holds, shorts] = [holds + (same ? 1 : 0), shorts + reads.length];
105
+ refused += reads.filter((one) => ask(world, region, reads.filter((other) => other !== one)).kind === 'refuse').length;
106
+ console.log(`${same ? 'HELD ' : 'DIFFERS'} ${world} · ${region}`);
107
+ for (const line of got.lines ?? [got.why ?? '']) console.log(line ? ` ${line}` : '');
108
+ }
109
+ console.log(`HELD ${holds}/${cases.length}`);
110
+ console.log(`REFUSED ${refused}/${shorts}: each case asked again with one read its region declares taken away`);
111
+ console.log(canonical({ at: 'policy:acme/capsules', by: 'target', form: 'alphabet', measure: 'id', role: 'writes', scope: 'uses/topos-measure', value: 'sha256:3252cbb877d9ca0c9ea990828c1490999de3d4853990d53d6bbd31861e6bb780' }));
112
+ ```
113
+
114
+ ```
115
+ bound-lock/1 about="the domain this capsule serves: what several origins measured, each quantity read as one cell" at=policy:topos/capsule by=target form=alphabet measure=id role=writes scope=capsule/domain value=measurements
116
+ bound-lock/1 about="the runtime a host starts this capsule with, whose entry, regions and effects are the runtime's own lines" at=policy:topos/capsule by=target form=alphabet measure=id role=writes scope=capsule/runtime value=node
117
+ bound-lock/1 about="where this capsule's world keeps its values: the place's own files" at=policy:topos/capsule by=target form=alphabet measure=id role=writes scope=capsule/holds value=./
118
+ bound-lock/1 about="the held region" at=policy:topos/capsule by=target form=alphabet measure=reads role=render scope=region/held value=lang|form/prose/**|form/template/**|prose/*/measure/*|measure/**
119
+ bound-lock/1 about="the readings region" at=policy:topos/capsule by=target form=alphabet measure=reads role=render scope=region/readings value=lang|form/prose/**|prose/*/measure/*|measure/**
120
+ bound-lock/1 about="what this capsule reaches beyond the lines it is handed" at=policy:topos/capsule by=target form=alphabet measure=effects role=writes scope=capsule/effects value=none
121
+ bound-lock/1 about="the topos release this capsule is packed against, named by its digest" at=policy:topos/capsule by=target form=alphabet measure=digest role=writes scope=capsule/topos value=sha256:a87ffd5ab77a99b9e40cc720acfdf0ee7f36d156c72085a20db1232b0653c1dd
122
+ HELD root:topos-measure · held
123
+ The four origins that measured room do not meet: no value in celsius is held by all of their readings, and no average of them closes that.
124
+ HELD agreed:topos-measure · held
125
+ The three origins that measured room meet: every one of their readings holds 19.9..20.1 celsius.
126
+ HELD alone:topos-measure · held
127
+ Only one origin measured room: its reading holds nothing yet, until a second origin's reading meets it.
128
+ HELD root:topos-measure · readings
129
+ ## Readings
130
+
131
+ | quantity | origin | reading |
132
+ |---|---|---|
133
+ | room | a | 19.8..20.2 celsius |
134
+ | room | b | 19.9..20.3 celsius |
135
+ | room | c | 19.7..20.1 celsius |
136
+ | room | d | 24..24.4 celsius |
137
+ HELD 4/4
138
+ REFUSED 19/19: each case asked again with one read its region declares taken away
139
+ bound-lock/1 at=policy:acme/capsules by=target form=alphabet measure=id role=writes scope=uses/topos-measure value=sha256:3252cbb877d9ca0c9ea990828c1490999de3d4853990d53d6bbd31861e6bb780
140
+ ```
141
+
142
+ Three meet at 19.9..20.1: free.
143
+
144
+ ## What it claims
145
+
146
+ - **It round-trips: every line of its 3 wire corpora parses and writes back the same bytes.** · [receipt](receipts.bound)
147
+ - **It reads once: a value is read in its wire, by its form, and its ceiling on reading one anywhere else reads 0.** · [receipt](receipts.bound)
148
+ - **It knows no world: it says nothing a world says, and its ceiling on a world's words, markup or figures written in its source reads 0.** · [receipt](receipts.bound)
149
+ - **It writes no order of its own: cells, meets, joins and states come from the object, and its ceiling on an order written here reads 0.** · [receipt](receipts.bound)
150
+
151
+ <p align="center"><img src="docs/img/line.svg" alt="The line wire/line/fields, drawn field by field: 6 fields, each named by the wire" width="640"></p>
152
+
153
+ ```bash
154
+ npm install @lapxo/topos
155
+ ```
156
+
157
+ ## Your first run
158
+
159
+ With it installed, run roundtrip: a line is parsed, written again and compared with the bytes it came from.
160
+
161
+ ```ts
162
+ // A line read and written back to the same bytes, then an alphabet read and written back.
163
+ import { alphabet, canonical, parse } from '@lapxo/topos/wire';
164
+
165
+ const line = 'bound-lock/1 about="a \\"quoted\\" word,\\nthen a second line" at=witness:x by=owner form=interval measure=len role=reads scope=acme/users/email value=0..320';
166
+ const read = parse(line);
167
+ if (read.kind !== 'fact') throw new Error(read.why);
168
+ const again = canonical(read.value.fields);
169
+ console.log(read.value.fields['about']);
170
+ console.log(again);
171
+ console.log('same bytes', again === line);
172
+ const words = 'not:draft|either\\|or';
173
+ console.log(JSON.stringify(alphabet(words)));
174
+ console.log(alphabet(alphabet(words)), 'same bytes', alphabet(alphabet(words)) === words);
175
+ ```
176
+
177
+ ```
178
+ a "quoted" word,
179
+ then a second line
180
+ bound-lock/1 about="a \"quoted\" word,\nthen a second line" at=witness:x by=owner form=interval measure=len role=reads scope=acme/users/email value=0..320
181
+ same bytes true
182
+ {"polarity":"forbid","members":["draft","either|or"]}
183
+ not:draft|either\|or same bytes true
184
+ ```
185
+
186
+ <p align="center"><img src="docs/img/refuses.svg" alt="Handed bound-lock/1 form=interval measure=len role=reads scope=x value=0..1..3, it refuses: `0..1..3` is no interval the wire reads: two ends, each decimal or *" width="640"></p>
187
+
188
+ ## What it refuses
189
+
190
+ Hand it `bound-lock/1 form=interval measure=len role=reads scope=x value=0..1..3` and it refuses: `0..1..3` is no interval the wire reads: two ends, each decimal or *.
191
+
192
+ ## How to read it
193
+
194
+ topos is read one subpath at a time, and each answers one question.
195
+
196
+ <details><summary>The three subpaths and the question each answers</summary>
197
+
198
+ - **contract** · what a world answers — render, receipt, observe, run — what does a world owe?
199
+ - **readers** · the world-neutral readers of lines — what does a line say?
200
+ - **wire** · the line, the forms' encodings, the names — how is a fact written?
201
+
202
+ </details>
203
+
204
+ ## Check
205
+
206
+ ● 12 cases hold
207
+
208
+ ● `npm ci && npm run build`
209
+
210
+ ## Pointers
211
+
212
+ - [Reference](docs/reference.md)
213
+ - [Wire](docs/wire.md)