@prestack/thread 0.1.20 → 0.1.22

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 (148) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/dist/core/attach.js +21 -0
  3. package/dist/core/descend.d.ts +2 -0
  4. package/dist/core/descend.js +18 -3
  5. package/dist/core/generators/commit.js +55 -3
  6. package/dist/core/generators/dev.d.ts +1 -0
  7. package/dist/core/generators/dev.js +68 -0
  8. package/dist/core/rows.d.ts +5 -0
  9. package/dist/core/rows.js +45 -0
  10. package/dist/core/tree.d.ts +1 -0
  11. package/dist/core/tree.js +7 -1
  12. package/dist/derivation/generators/build.d.ts +3 -1
  13. package/dist/derivation/generators/build.js +103 -22
  14. package/dist/derivation/generators/release.js +74 -26
  15. package/dist/derivation/release.js +3 -3
  16. package/dist/integrations/cli.d.ts +3 -9
  17. package/dist/integrations/cli.js +96 -36
  18. package/dist/integrations/declared.d.ts +8 -0
  19. package/dist/integrations/declared.js +22 -0
  20. package/dist/integrations/pglite.js +13 -3
  21. package/dist/integrations/target.d.ts +4 -1
  22. package/dist/integrations/target.js +20 -6
  23. package/dist/integrity/attach.js +1 -5
  24. package/dist/integrity/generators/publish.js +2 -1
  25. package/dist/integrity/slopnators/sl_stale_path.js +10 -0
  26. package/dist/integrity/slopnators/sl_unlisted_program.js +2 -2
  27. package/dist/operations/access.js +3 -2
  28. package/dist/operations/capabilities.d.ts +2 -0
  29. package/dist/operations/capabilities.js +2 -0
  30. package/dist/operations/generators/capability.js +234 -24
  31. package/dist/operations/generators/compile.d.ts +21 -0
  32. package/dist/operations/generators/compile.js +151 -51
  33. package/dist/operations/generators/permits.d.ts +2 -1
  34. package/dist/operations/generators/permits.js +52 -47
  35. package/dist/operations/generators/seed.d.ts +3 -0
  36. package/dist/operations/generators/seed.js +25 -3
  37. package/dist/operations/nodes.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/dist/core/actor.ts +0 -21
  40. package/dist/core/attach.ts +0 -613
  41. package/dist/core/chain.ts +0 -145
  42. package/dist/core/condition.ts +0 -227
  43. package/dist/core/descend.ts +0 -1173
  44. package/dist/core/errors/catalogue.ts +0 -314
  45. package/dist/core/errors/classes.ts +0 -51
  46. package/dist/core/errors/error.ts +0 -152
  47. package/dist/core/errors/namespaces.ts +0 -19
  48. package/dist/core/finding.ts +0 -37
  49. package/dist/core/generators/commit.ts +0 -157
  50. package/dist/core/init.ts +0 -222
  51. package/dist/core/invoked.ts +0 -52
  52. package/dist/core/round.ts +0 -74
  53. package/dist/core/rows.ts +0 -459
  54. package/dist/core/scope.ts +0 -20
  55. package/dist/core/thread.ts +0 -29
  56. package/dist/core/tree.ts +0 -90
  57. package/dist/core/types.ts +0 -351
  58. package/dist/derivation/attach.ts +0 -57
  59. package/dist/derivation/generators/build.ts +0 -371
  60. package/dist/derivation/generators/check.ts +0 -118
  61. package/dist/derivation/generators/determinism.ts +0 -145
  62. package/dist/derivation/generators/release.ts +0 -236
  63. package/dist/derivation/release.ts +0 -43
  64. package/dist/integrations/attach.ts +0 -187
  65. package/dist/integrations/cli.ts +0 -615
  66. package/dist/integrations/disk.ts +0 -466
  67. package/dist/integrations/generators/read.ts +0 -127
  68. package/dist/integrations/generators/vault.ts +0 -210
  69. package/dist/integrations/https.ts +0 -216
  70. package/dist/integrations/pglite.ts +0 -206
  71. package/dist/integrations/secret.ts +0 -54
  72. package/dist/integrations/target.ts +0 -656
  73. package/dist/integrations/vault.ts +0 -148
  74. package/dist/integrity/attach.ts +0 -150
  75. package/dist/integrity/check-threads.ts +0 -166
  76. package/dist/integrity/constraint.ts +0 -1246
  77. package/dist/integrity/generators/compiler.ts +0 -138
  78. package/dist/integrity/generators/gate.ts +0 -227
  79. package/dist/integrity/generators/issues.ts +0 -160
  80. package/dist/integrity/generators/publish.ts +0 -232
  81. package/dist/integrity/generators/trial.ts +0 -359
  82. package/dist/integrity/lint.ts +0 -214
  83. package/dist/integrity/lsp/bin.ts +0 -44
  84. package/dist/integrity/lsp/diagnostics.ts +0 -92
  85. package/dist/integrity/lsp/findings.ts +0 -63
  86. package/dist/integrity/lsp/lint.ts +0 -124
  87. package/dist/integrity/lsp/protocol.ts +0 -78
  88. package/dist/integrity/lsp/server.ts +0 -224
  89. package/dist/integrity/slopnator.ts +0 -324
  90. package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
  91. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
  92. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
  93. package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
  94. package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
  95. package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
  96. package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
  97. package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
  98. package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
  99. package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
  100. package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
  101. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
  102. package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
  103. package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
  104. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
  105. package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
  106. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
  107. package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
  108. package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
  109. package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
  110. package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
  111. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
  112. package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
  113. package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
  114. package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
  115. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
  116. package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
  117. package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
  118. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
  119. package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
  120. package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
  121. package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
  122. package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
  123. package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
  124. package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
  125. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
  126. package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
  127. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
  128. package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
  129. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
  130. package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
  131. package/dist/integrity/sweep.ts +0 -395
  132. package/dist/knowledge/attach.ts +0 -327
  133. package/dist/knowledge/generators/docsgen.ts +0 -186
  134. package/dist/knowledge/generators/glossary.ts +0 -73
  135. package/dist/knowledge/glossary.ts +0 -6
  136. package/dist/operations/access.ts +0 -131
  137. package/dist/operations/attach.ts +0 -408
  138. package/dist/operations/capabilities.ts +0 -88
  139. package/dist/operations/dispatch.ts +0 -294
  140. package/dist/operations/error-results.ts +0 -31
  141. package/dist/operations/generators/capability.ts +0 -1054
  142. package/dist/operations/generators/compile.ts +0 -701
  143. package/dist/operations/generators/permits.ts +0 -224
  144. package/dist/operations/generators/seed.ts +0 -359
  145. package/dist/operations/generators/trial.ts +0 -666
  146. package/dist/operations/nodes.ts +0 -53
  147. package/dist/operations/operations.ts +0 -91
  148. package/dist/operations/result.ts +0 -28
@@ -1,131 +0,0 @@
1
- import type { Actor } from '../core/actor.ts'
2
- import { mentionsActor, walkOperands, type Condition } from '../core/condition.ts'
3
- import type { Member } from '../core/thread.ts'
4
-
5
- export const PERMITS_AT = 'src/_generated/permits.ts'
6
-
7
- export function declaredWords(
8
- entities: readonly {
9
- readonly holds: readonly { readonly id: string; readonly options?: readonly string[] }[]
10
- readonly actor?: { readonly roles: string; readonly entitlements?: string }
11
- }[],
12
- which: 'roles' | 'entitlements',
13
- everybody: string,
14
- ): readonly string[] {
15
- const found = entities.flatMap((one) => {
16
- const at = one.actor?.[which]
17
- return at === undefined ? [] : (one.holds.find((each) => each.id === at)?.options ?? [])
18
- })
19
- return [...new Set(which === 'roles' ? [everybody, ...found] : found)]
20
- }
21
-
22
- export function undeclared(answered: string, offers: readonly string[], which: 'roles' | 'entitlements'): string | undefined {
23
- const stray = answered
24
- .split(',')
25
- .map((one) => one.trim())
26
- .filter((one) => one !== '' && !offers.includes(one))
27
- if (stray.length === 0) return undefined
28
-
29
- return (
30
- `this app declares no ${which === 'roles' ? 'role' : 'entitlement'} called ${stray.map((one) => `\`${one}\``).join(', ')} — ` +
31
- (offers.length === 0
32
- ? `it declares none at all. A ${which === 'roles' ? 'role' : 'entitlement'} is one of the words the value an ` +
33
- 'entity names as saying what a caller may do is held to. Declare them with `thread create entity edit` — ' +
34
- 'name that value, give it the `choice` format, and state its options'
35
- : `it declares ${offers.map((one) => `\`${one}\``).join(', ')}`)
36
- )
37
- }
38
-
39
- export interface Declaring {
40
- readonly id: string
41
- readonly holds: readonly { readonly id: string; readonly options?: readonly string[] }[]
42
- readonly rows?: readonly Record<string, unknown>[]
43
- readonly must?: readonly { readonly unique?: readonly string[] }[]
44
- readonly actor?: { readonly roles: string; readonly entitlements?: string }
45
- }
46
-
47
- export interface Caller {
48
- readonly id: string
49
- readonly entity: string
50
- readonly roles: readonly string[]
51
- readonly entitlements: readonly string[]
52
- }
53
-
54
- export const keyIn = (one: Declaring): string | undefined =>
55
- (one.must ?? []).flatMap((each) => (each.unique?.length === 1 ? [each.unique[0] as string] : []))[0]
56
-
57
- const wordsFrom = (held: unknown): readonly string[] => {
58
- if (typeof held === 'string') return [held]
59
- if (Array.isArray(held)) return held.filter((each): each is string => typeof each === 'string')
60
- return []
61
- }
62
-
63
- export const declaredCallers = (entities: readonly Declaring[]): readonly Caller[] =>
64
- entities.flatMap((one) => {
65
- const said = one.actor
66
- const key = keyIn(one)
67
- if (said === undefined || key === undefined) return []
68
- return (one.rows ?? []).flatMap((row): readonly Caller[] => {
69
- const named = row[key]
70
- if (typeof named !== 'string') return []
71
- return [
72
- {
73
- id: named,
74
- entity: one.id,
75
- roles: wordsFrom(row[said.roles]),
76
- entitlements: said.entitlements === undefined ? [] : wordsFrom(row[said.entitlements]),
77
- },
78
- ]
79
- })
80
- })
81
-
82
- export interface Grant extends Member {
83
-
84
- readonly may: readonly string[]
85
-
86
- readonly on?: string
87
-
88
- readonly to?: {
89
- readonly roles?: Actor['roles']
90
- readonly entitlements?: Actor['entitlements']
91
- }
92
-
93
- readonly when?: Condition
94
- }
95
-
96
- export function opensToEverybody(grants: readonly Grant[]): readonly Grant[] {
97
- return grants.filter((one) => one.to === undefined && (one.when === undefined || !mentionsActor(one.when)))
98
- }
99
-
100
- export function grantedTwice(grants: readonly Grant[]): readonly string[] {
101
- const seen = new Set<string>()
102
- const twice = new Set<string>()
103
- for (const one of grants) {
104
- if (seen.has(one.id)) twice.add(one.id)
105
- seen.add(one.id)
106
- }
107
- return [...twice].sort()
108
- }
109
-
110
- function operandsOf(one: Grant): readonly string[] {
111
- if (one.when === undefined) return []
112
- const found: string[] = []
113
- walkOperands(one.when, (each) => {
114
- if ('agg' in each) found.push('agg')
115
- if ('was' in each) found.push('was')
116
- if ('outer' in each) found.push('outer')
117
- })
118
- return found
119
- }
120
-
121
- export function unanswerable(grants: readonly Grant[]): readonly string[] {
122
- return grants.flatMap((one) => {
123
- const held = [...new Set(operandsOf(one))]
124
- if (held.length === 0) return []
125
- const why = held.includes('agg')
126
- ? 'it counts other rows, and a gate is asked about the one in front of it. Put the aggregate in an invariant on the entity, where it runs against the rows themselves'
127
- : 'it reads a value as it was before the write, and at the moment this is asked the write has not happened. Only an invariant, which runs on the write itself, can see a before'
128
- return [`\`${one.id}\` states a condition this gate cannot answer — ${why}`]
129
- })
130
- }
131
-
@@ -1,408 +0,0 @@
1
- import type { Attaching } from '../core/attach.ts'
2
- import { SHAPED } from '../core/init.ts'
3
- import { NAMED, named } from '../core/scope.ts'
4
-
5
- import { LISTS } from '../core/descend.ts'
6
- import { quoted } from '../core/rows.ts'
7
- import type { Threaded } from '../core/thread.ts'
8
-
9
- import { PERMITS_AT } from './access.ts'
10
- import { ACTOR_AT, CAPABILITIES_AT, CLIENT_AT, DEFAULTS_AT, DISPATCHING_AT, MCP_AT, OPENAPI_AT, REACHING_AT } from './capabilities.ts'
11
- import { DISPATCH_AT } from './dispatch.ts'
12
- import { TRIALS_AT } from './operations.ts'
13
- import { MEMBERS_LIST, NODES_FILE, OPERATIONS_FILE } from './operations.ts'
14
-
15
- const HOME = `node_modules/${named('operations')}/dist`
16
-
17
- const VERSION = 1
18
-
19
- const ROOT = 'operations'
20
-
21
- const NODES_AT = '_threads/nodes.ts'
22
-
23
- const OPERATIONS_AT = '_threads/operations.ts'
24
-
25
- const DESCRIBES =
26
- 'Everything this app can be asked to do. Each operation is a named job built from steps, and every one of them runs the same way. Add one with `thread create operation`.'
27
-
28
- export const TAKES =
29
- 'What one operation needs before it will run: the shape of its input, what it is expected to cost, and who is allowed to ask for it. Written once when the operation is created, then yours to change.'
30
-
31
- export const WOVEN =
32
- 'The operation written out as one function. `thread compile` builds it from the steps declared for it, so nothing here is edited by hand — anything added here is gone the next time it is compiled.'
33
-
34
- const NODES =
35
- 'Every kind of step an operation can be built from — a read, a branch, a loop. A step holds no code of its own: the work goes in a file written beside the operation that uses it.'
36
-
37
- const DISPATCH =
38
- 'Every operation runs through here and nowhere else. `thread compile` writes it for this app: before a run starts it checks who is asking, what the run may cost and whether the input is valid, then runs the operation and records what happened.'
39
-
40
- const PROGRAM =
41
- 'Every row this package adds to an app, listed in one place so what it put there can be read rather than guessed at. `thread attach` writes back anything missing or edited.'
42
-
43
- const TRIALS =
44
- 'What each operation actually did when it was run, recorded rather than written by hand. `thread trials` runs them against their example inputs and keeps the results; `thread trials --check` runs them again and refuses anything that has changed since.'
45
-
46
- const CAPABILITIES =
47
- 'Which of this app\'s operations can be reached from outside, and how — as an MCP tool, a terminal command, or a web route. Everything here is worked out, and nothing here is yours to set: which surfaces are on comes from _threads/defaults.ts and from `off` on each operation row, and `thread create capability` changes either.'
48
-
49
- const OPENAPI =
50
- 'This app\'s web API written as an OpenAPI document: the route, the verb and the input shape of every operation that answers on the web. Generated rather than written, so it cannot describe a route that is not there.'
51
-
52
- const TOOLS =
53
- 'The tools an MCP client is offered: every operation switched on for that surface, with its own sentence and the shape of its input. Switching `mcp` off for an operation is how an agent is told it may not call it.'
54
-
55
- const CLIENT =
56
- 'Every operation a page in this app may call. An operation that is switched off is absent from this file rather than listed and disabled, as is any operation needing a credential a browser must not hold.'
57
-
58
- const CALLER =
59
- 'Who is asking, worked out from the request itself. It reads the header or cookie that the entity naming people says carries their token, looks that person up, and answers with their id, their roles and what they hold — or with nobody, when the request carries none. What the request brought with it, the run, the address, the device, is carried through either way. Nobody edits it, because an edit here decides who somebody is, and that comes from the declarations.'
60
-
61
- const REACHING =
62
- 'Every place this app declared it reaches, opened and ready, each carrying only the calls its own row names. It is written from the targets, the calls and the secrets already in the tree, so an operation reaches a place by naming it and never writes a url, a connection string or the name of an environment variable. Nobody edits it, because an edit here hands an operation something no declaration granted.'
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
- const valued = (one: unknown): string => {
88
- if (typeof one === 'string') return quoted(one)
89
- if (Array.isArray(one)) return `[${one.map((each) => valued(each)).join(', ')}]`
90
- if (typeof one === 'object' && one !== null) {
91
- return `{ ${Object.entries(one).map(([key, held]) => `${key}: ${valued(held)}`).join(', ')} }`
92
- }
93
- return String(one)
94
- }
95
-
96
- export const said = (one: Threaded, pad: string): string => {
97
- const { declares, derives, seeds, ...plain } = one
98
- const lines = [`${pad}{`]
99
- for (const [key, held] of Object.entries(plain)) lines.push(`${pad} ${key}: ${valued(held)},`)
100
- for (const list of LISTS) {
101
- const held = one[list]
102
- if (held === undefined) continue
103
- lines.push(`${pad} ${list}: [`)
104
- for (const child of held) lines.push(said(child, `${pad} `))
105
- lines.push(`${pad} ],`)
106
- }
107
- return [...lines, `${pad}},`].join('\n')
108
- }
109
-
110
- const ACCESS =
111
- 'Who may ask this app to do what. A row says which operations it permits, on what, to whom, and under what condition. Nothing is permitted by default: an operation no row grants here cannot be run at all.'
112
-
113
- const PERMITS =
114
- 'The check compiled from the grants in _threads/access.ts: one branch per grant, so who may ask for what comes from one statement. Nobody edits it, because an edit here widens or narrows access nobody granted. An app that grants nothing permits nothing.'
115
-
116
- const ACCESS_SOURCE = `import type { Grant } from '${NAMED}/operations/access'
117
-
118
- export const GRANTS: readonly Grant[] = []
119
- `
120
-
121
- const DEFAULTS =
122
- 'Which surfaces every operation in this app answers on, stated once for all of them. An operation turns one off with `off` on its own row and can never turn on one this row leaves off. With no `live` stated, every operation answers on cli alone, so nothing is reachable from outside the machine until this row says so. `thread create capability edit defaults` changes it.'
123
-
124
- const DEFAULTS_SOURCE = `import type { Defaults } from '${NAMED}/operations/capabilities'
125
-
126
- export const DEFAULTS: Defaults = {
127
- id: 'defaults',
128
- at: ${quoted(DEFAULTS_AT)},
129
- describes: ${quoted(DEFAULTS)},
130
- version: ${String(VERSION)},
131
- }
132
- `
133
-
134
- const shipped = (): Threaded => ({
135
- id: 'shipped',
136
- at: `${HOME}/attach.js`,
137
- describes: PROGRAM,
138
- version: VERSION,
139
- declares: [
140
- {
141
- id: 'operations',
142
- at: OPERATIONS_AT,
143
- describes: DESCRIBES,
144
- version: VERSION,
145
- },
146
- {
147
- id: 'nodes',
148
- at: NODES_AT,
149
- describes: NODES,
150
- version: VERSION,
151
- },
152
- {
153
- id: 'access',
154
- at: '_threads/access.ts',
155
- describes: ACCESS,
156
- version: VERSION,
157
- },
158
- {
159
- id: 'defaults',
160
- at: DEFAULTS_AT,
161
- describes: DEFAULTS,
162
- version: VERSION,
163
- },
164
- {
165
- id: 'permits',
166
- at: PERMITS_AT,
167
- describes: PERMITS,
168
- version: VERSION,
169
- by: 'thread permits',
170
- runs: 'thread permits --check',
171
- },
172
- {
173
- id: 'dispatch',
174
- at: DISPATCH_AT,
175
- describes: DISPATCH,
176
- version: VERSION,
177
- by: 'thread compile',
178
- runs: 'thread compile --check',
179
- },
180
- {
181
- id: 'trials',
182
- at: TRIALS_AT,
183
- describes: TRIALS,
184
- version: VERSION,
185
- by: 'thread trials',
186
- runs: 'thread trials --check',
187
- },
188
- {
189
- id: 'capabilities',
190
- at: CAPABILITIES_AT,
191
- describes: CAPABILITIES,
192
- version: VERSION,
193
- by: 'thread capabilities',
194
- runs: 'thread capabilities --check',
195
- },
196
- {
197
- id: 'openapi',
198
- at: OPENAPI_AT,
199
- describes: OPENAPI,
200
- version: VERSION,
201
- by: 'thread capabilities',
202
- runs: 'thread capabilities --check',
203
- },
204
- {
205
- id: 'tools',
206
- at: MCP_AT,
207
- describes: TOOLS,
208
- version: VERSION,
209
- by: 'thread capabilities',
210
- runs: 'thread capabilities --check',
211
- },
212
- {
213
- id: 'client',
214
- at: CLIENT_AT,
215
- describes: CLIENT,
216
- version: VERSION,
217
- by: 'thread capabilities',
218
- runs: 'thread capabilities --check',
219
- },
220
- {
221
- id: 'dispatching',
222
- at: DISPATCHING_AT,
223
-
224
- describes:
225
- 'The operations this app declares, named wherever something reaches one. The framework ships an empty list and each app widens it, so a name written at a call site is accepted only when this app declared an operation by it.',
226
- version: VERSION,
227
- by: 'thread capabilities',
228
- runs: 'thread capabilities --check',
229
- },
230
- {
231
- id: 'caller',
232
- at: ACTOR_AT,
233
- describes: CALLER,
234
- version: VERSION,
235
- by: 'thread capabilities',
236
- runs: 'thread capabilities --check',
237
- },
238
- {
239
- id: 'reaching',
240
- at: REACHING_AT,
241
- describes: REACHING,
242
- version: VERSION,
243
- by: 'thread capabilities',
244
- runs: 'thread capabilities --check',
245
- },
246
- {
247
- id: 'nd_read',
248
- at: NODES_AT,
249
- describes: 'A node that only reads. It is handed the store and the search and nothing else, so it cannot write — the fetch an operation does before it decides anything.',
250
- version: VERSION,
251
- },
252
- {
253
- id: 'nd_action',
254
- at: NODES_AT,
255
- describes: 'A node that changes something — a write, a call out, a spend. It is handed everything, and it is where an operation keeps its effects rather than spreading them across the flow.',
256
- version: VERSION,
257
- },
258
- {
259
- id: 'nd_branch',
260
- at: NODES_AT,
261
- describes: 'A node that chooses. Its first block returns a name, and the way with that name is what runs, so a fork in the flow is visible in the declaration instead of buried in an if.',
262
- version: VERSION,
263
- },
264
- {
265
- id: 'nd_each',
266
- at: NODES_AT,
267
- describes: 'A node that runs once per item, in order, one at a time. Its block is handed a single item and never the collection, so it cannot reorder them or batch them behind your back.',
268
- version: VERSION,
269
- },
270
- {
271
- id: 'nd_parallel',
272
- at: NODES_AT,
273
- describes: 'A step that runs several blocks at once and answers when all of them have. They cannot see each other, so use it for work that does not depend on itself.',
274
- version: VERSION,
275
- },
276
- {
277
- id: 'nd_repeat',
278
- at: NODES_AT,
279
- describes: 'A step that runs its block again until the block says it is done — a retry, or an agent loop. A limit is required, so it cannot run forever.',
280
- version: VERSION,
281
- },
282
- {
283
- id: 'nd_stop',
284
- at: NODES_AT,
285
- describes: 'A step that ends the operation early with an answer. Whatever its block returns becomes the result, and nothing after it runs.',
286
- version: VERSION,
287
- },
288
-
289
- ],
290
- })
291
-
292
- const SHIPS = shipped()
293
-
294
- const WHERE: Readonly<Record<string, { readonly into: string; readonly list: string }>> = {
295
- nodes: { into: OPERATIONS_FILE, list: 'declares' },
296
-
297
- dispatch: { into: OPERATIONS_FILE, list: 'declares' },
298
- access: { into: OPERATIONS_FILE, list: 'declares' },
299
- defaults: { into: OPERATIONS_FILE, list: 'declares' },
300
- permits: { into: OPERATIONS_FILE, list: 'declares' },
301
- trials: { into: OPERATIONS_FILE, list: 'declares' },
302
- capabilities: { into: OPERATIONS_FILE, list: 'declares' },
303
- openapi: { into: OPERATIONS_FILE, list: 'declares' },
304
- tools: { into: OPERATIONS_FILE, list: 'declares' },
305
- client: { into: OPERATIONS_FILE, list: 'declares' },
306
- dispatching: { into: OPERATIONS_FILE, list: 'declares' },
307
- caller: { into: OPERATIONS_FILE, list: 'declares' },
308
- reaching: { into: OPERATIONS_FILE, list: 'declares' },
309
- }
310
-
311
- const placed = (id: string): { readonly into: string; readonly list: string } =>
312
- WHERE[id] ?? { into: NODES_FILE, list: MEMBERS_LIST }
313
-
314
- const NODES_SOURCE = `import type { Threaded } from '${SHAPED}'
315
- import type { Node } from '${NAMED}/operations/nodes'
316
-
317
- export const NODES: Threaded = {
318
- id: 'nodes',
319
- at: ${quoted(NODES_AT)},
320
- describes: ${quoted(NODES)},
321
- version: ${String(VERSION)},
322
- declares: [],
323
- }
324
-
325
- export const ${MEMBERS_LIST}: readonly Node[] = []
326
- `
327
-
328
- const OPERATIONS_SOURCE = `import type { Threaded } from '${SHAPED}'
329
- import type { OperationMember } from '${NAMED}/operations/operations'
330
-
331
- export const OPERATIONS: Threaded = {
332
- id: 'operations',
333
- at: ${quoted(OPERATIONS_AT)},
334
- describes: ${quoted(DESCRIBES)},
335
- version: ${String(VERSION)},
336
- declares: [],
337
- }
338
-
339
- export const ${MEMBERS_LIST}: readonly OperationMember[] = []
340
- `
341
-
342
- const SEEDED = {
343
- attaches: ['@prestack/thread/derivation/attach'] as const,
344
- id: 'operations',
345
- declares: [
346
- ' {',
347
- ' id: \'operations\',',
348
- ' at: \'_threads/operations.ts\',',
349
- ' describes: \'Everything this app can be asked to do. Each operation is a named job built from steps, and every one of them runs the same way. Add one with `thread create operation`.\',',
350
- ' version: 1,',
351
- ' },',
352
- ].join('\n'),
353
- }
354
-
355
- const SHIPPED: readonly { readonly into: string; readonly list: string; readonly id: string; readonly source: string }[] = [
356
- {
357
- into: 'derivation.ts',
358
- list: 'GENERATORS',
359
- id: 'capability-generator',
360
- source: " {\n id: 'capability-generator',\n at: 'node_modules/@prestack/thread/dist/operations/generators/capability.js',\n describes: 'Writes what can be reached and how, and refuses it once it has drifted. It reads each operation for its name, what it does and the shape of its input, so adding or renaming one changes what is reachable without anybody editing a list.',\n version: 1,\n by: 'thread capabilities',\n runs: 'thread capabilities --check',\n },",
361
- },
362
- {
363
- into: 'derivation.ts',
364
- list: 'GENERATORS',
365
- id: 'seed-generator',
366
- source: " {\n id: 'seed-generator',\n at: 'node_modules/@prestack/thread/dist/operations/generators/seed.js',\n describes: 'Writes every file an operation puts on disk. The wrapper around each step comes from the rows; the code inside it is yours and is put back untouched. Anything that no longer matches what it writes is refused, so a step cannot be added by editing the file.',\n version: 1,\n by: 'thread seed',\n runs: 'thread seed --check',\n },",
367
- },
368
- {
369
- into: 'derivation.ts',
370
- list: 'GENERATORS',
371
- id: 'compile-generator',
372
- source: " {\n id: 'compile-generator',\n at: 'node_modules/@prestack/thread/dist/operations/generators/compile.js',\n describes: 'Turns an operation\\'s steps into one function: each block bound where it is used, and each branch or loop written out as the real thing. What it writes imports nothing from this framework, so what ships is plain code.',\n version: 1,\n by: 'thread compile',\n runs: 'thread compile --check',\n },",
373
- },
374
- {
375
- into: 'derivation.ts',
376
- list: 'GENERATORS',
377
- id: 'permits-generator',
378
- source: " {\n id: 'permits-generator',\n at: 'node_modules/@prestack/thread/dist/operations/generators/permits.js',\n describes: 'Writes the check this app runs before any operation — one branch per grant, so who may ask for what comes from one statement rather than being repeated in every handler. It refuses a grant it cannot check honestly, such as one that would admit a caller nobody authenticated.',\n version: 1,\n by: 'thread permits',\n runs: 'thread permits --check',\n },",
379
- },
380
- {
381
- into: 'derivation.ts',
382
- list: 'GENERATORS',
383
- id: 'trial-generator',
384
- source: " {\n id: 'trial-generator',\n at: 'node_modules/@prestack/thread/dist/operations/generators/trial.js',\n describes: 'Records what an operation does, and refuses it once it has changed. It runs the compiled operation through this app\\'s own dispatch, so what is checked is the code that ships. Time and randomness are pinned, so a run can be repeated.',\n version: 1,\n by: 'thread trials',\n runs: 'thread trials --check',\n },",
385
- },
386
- ]
387
- export const ATTACH: Attaching = {
388
- attaches: SEEDED.attaches,
389
- id: SEEDED.id,
390
- declares: SEEDED.declares,
391
- writes: [
392
- { file: OPERATIONS_FILE, source: OPERATIONS_SOURCE },
393
- { file: NODES_FILE, source: NODES_SOURCE },
394
-
395
- { file: 'access.ts', source: ACCESS_SOURCE },
396
- { file: 'defaults.ts', source: DEFAULTS_SOURCE },
397
- ],
398
-
399
- rows: [
400
- ...(SHIPS.declares ?? [])
401
- .filter((one) => one.id !== ROOT)
402
- .map((one) => {
403
- const where = placed(one.id)
404
- return { into: where.into, list: where.list, id: one.id, source: said(one, where.list === 'declares' ? ' ' : ' ') }
405
- }),
406
- ...SHIPPED,
407
- ],
408
- }
@@ -1,88 +0,0 @@
1
- import type { Threaded } from '../core/thread.ts'
2
-
3
- export const SURFACES = ['cli', 'api', 'mcp', 'webhook'] as const
4
-
5
- export type Surface = (typeof SURFACES)[number]
6
-
7
- export const isSurface = (one: string): one is Surface => (SURFACES as readonly string[]).includes(one)
8
-
9
- export const MEANS: Readonly<Record<Surface, string>> = {
10
- cli: 'a command a terminal runs',
11
- api: 'a route a request arrives on, and the OpenAPI document written from it. An operation that does not answer on api has no route file and no route row at all',
12
- mcp: 'a tool an agent is offered. Switch this off and an AI actor cannot call the operation, while everything else about it goes on working',
13
- webhook: 'an address something outside posts to, once a webhook row invokes the operation and the operation names it back',
14
- }
15
-
16
- export const DEFAULTS_AT = '_threads/defaults.ts'
17
-
18
- export const UNSTATED: readonly Surface[] = ['cli']
19
-
20
- export interface Defaults extends Threaded {
21
- readonly live?: readonly Surface[]
22
- }
23
-
24
- export const CAPABILITIES_AT = 'operations/_capabilities.json'
25
-
26
- export const CLIENT_AT = 'operations/_client.ts'
27
-
28
- export const DISPATCHING_AT = 'app/_generated/dispatching.ts'
29
-
30
- export const COMPILED = 'app/_generated/compiled'
31
-
32
- export const CHECKED_AT = `${COMPILED}/ui/checked.ts`
33
-
34
- export const REACHING_AT = 'src/_generated/reaching.ts'
35
-
36
- export const ACTOR_AT = 'src/_generated/actor.ts'
37
-
38
- export const routeAt = (operation: string): string => `app/operations/${operation}/route.ts`
39
-
40
- export const ROUTE =
41
- "The file that answers a request for this one operation. It imports the operation by name, hands it to this app's own dispatch along with what this app hands its operations and the caller worked out from the request, and answers with the result — or with the status matching whatever dispatch refused it for. One per operation rather than one for all of them: a route that looked its operation up by a name off the path would hand dispatch something no check had ever seen, which reads exactly like a name that was checked. An operation that does not answer on api, because its own row turns api off or the defaults sheet leaves it off, has neither this file nor this row: `thread capabilities` takes both out, so it is not served at all rather than served and turned away."
42
-
43
- export const inboundAt = (webhook: string): string => `app/inbound/${webhook}/route.ts`
44
-
45
- export const OPENAPI_AT = 'operations/_openapi.json'
46
-
47
- export const MCP_AT = 'operations/_mcp.json'
48
-
49
- export interface Capability {
50
- readonly operation: string
51
- readonly describes: string
52
- readonly requires: readonly string[]
53
- readonly nodes: readonly string[]
54
- readonly input: unknown
55
-
56
- readonly live: readonly Surface[]
57
-
58
- readonly webhooks: readonly string[]
59
-
60
- readonly signed: boolean
61
-
62
- readonly browsable: boolean
63
-
64
- readonly mcp: { readonly name: string; readonly description: string; readonly inputSchema: unknown }
65
- readonly cli: { readonly invocation: string; readonly describes: string }
66
- readonly api: { readonly method: 'GET' | 'POST'; readonly path: string }
67
- }
68
-
69
- export const reachable = (one: Capability, surface: Surface): boolean => one.live.includes(surface)
70
-
71
- export function switched(text: string | undefined): ReadonlyMap<string, readonly Surface[]> {
72
- const found = new Map<string, readonly Surface[]>()
73
- if (text === undefined || text.trim() === '') return found
74
- let held: unknown
75
- try {
76
- held = JSON.parse(text)
77
- } catch {
78
- return found
79
- }
80
- if (!Array.isArray(held)) return found
81
- for (const one of held) {
82
- if (typeof one !== 'object' || one === null) continue
83
- const row = one as { readonly operation?: unknown; readonly live?: unknown }
84
- if (typeof row.operation !== 'string' || !Array.isArray(row.live)) continue
85
- found.set(row.operation, row.live.filter((each): each is Surface => typeof each === 'string' && isSurface(each)))
86
- }
87
- return found
88
- }