@prestack/thread 0.1.21 → 0.1.23

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 (138) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/dist/core/attach.js +21 -0
  3. package/dist/core/descend.js +6 -3
  4. package/dist/core/generators/commit.js +55 -3
  5. package/dist/core/generators/dev.d.ts +1 -0
  6. package/dist/core/generators/dev.js +69 -0
  7. package/dist/derivation/generators/build.d.ts +3 -1
  8. package/dist/derivation/generators/build.js +104 -22
  9. package/dist/derivation/generators/release.js +74 -25
  10. package/dist/derivation/release.js +3 -3
  11. package/dist/integrations/cli.d.ts +0 -1
  12. package/dist/integrations/cli.js +69 -11
  13. package/dist/integrations/declared.d.ts +2 -0
  14. package/dist/integrations/declared.js +6 -1
  15. package/dist/integrations/pglite.js +13 -3
  16. package/dist/integrations/target.d.ts +3 -0
  17. package/dist/integrity/attach.js +1 -5
  18. package/dist/integrity/slopnators/sl_stale_path.js +10 -0
  19. package/dist/operations/access.js +3 -2
  20. package/dist/operations/capabilities.d.ts +2 -0
  21. package/dist/operations/capabilities.js +2 -0
  22. package/dist/operations/generators/capability.js +237 -24
  23. package/dist/operations/generators/compile.js +150 -50
  24. package/dist/operations/generators/seed.d.ts +3 -0
  25. package/dist/operations/generators/seed.js +25 -3
  26. package/dist/operations/nodes.d.ts +2 -2
  27. package/package.json +1 -1
  28. package/dist/core/actor.ts +0 -21
  29. package/dist/core/attach.ts +0 -613
  30. package/dist/core/chain.ts +0 -145
  31. package/dist/core/condition.ts +0 -227
  32. package/dist/core/descend.ts +0 -1185
  33. package/dist/core/errors/catalogue.ts +0 -314
  34. package/dist/core/errors/classes.ts +0 -51
  35. package/dist/core/errors/error.ts +0 -152
  36. package/dist/core/errors/namespaces.ts +0 -19
  37. package/dist/core/finding.ts +0 -37
  38. package/dist/core/generators/commit.ts +0 -157
  39. package/dist/core/init.ts +0 -222
  40. package/dist/core/invoked.ts +0 -52
  41. package/dist/core/round.ts +0 -74
  42. package/dist/core/rows.ts +0 -506
  43. package/dist/core/scope.ts +0 -20
  44. package/dist/core/thread.ts +0 -29
  45. package/dist/core/tree.ts +0 -90
  46. package/dist/core/types.ts +0 -351
  47. package/dist/derivation/attach.ts +0 -57
  48. package/dist/derivation/generators/build.ts +0 -371
  49. package/dist/derivation/generators/check.ts +0 -118
  50. package/dist/derivation/generators/determinism.ts +0 -145
  51. package/dist/derivation/generators/release.ts +0 -236
  52. package/dist/derivation/release.ts +0 -43
  53. package/dist/integrations/attach.ts +0 -187
  54. package/dist/integrations/cli.ts +0 -606
  55. package/dist/integrations/declared.ts +0 -34
  56. package/dist/integrations/disk.ts +0 -466
  57. package/dist/integrations/generators/read.ts +0 -127
  58. package/dist/integrations/generators/vault.ts +0 -210
  59. package/dist/integrations/https.ts +0 -216
  60. package/dist/integrations/pglite.ts +0 -206
  61. package/dist/integrations/secret.ts +0 -54
  62. package/dist/integrations/target.ts +0 -672
  63. package/dist/integrations/vault.ts +0 -148
  64. package/dist/integrity/attach.ts +0 -150
  65. package/dist/integrity/check-threads.ts +0 -166
  66. package/dist/integrity/constraint.ts +0 -1246
  67. package/dist/integrity/generators/compiler.ts +0 -138
  68. package/dist/integrity/generators/gate.ts +0 -227
  69. package/dist/integrity/generators/issues.ts +0 -160
  70. package/dist/integrity/generators/publish.ts +0 -233
  71. package/dist/integrity/generators/trial.ts +0 -359
  72. package/dist/integrity/lint.ts +0 -214
  73. package/dist/integrity/lsp/bin.ts +0 -44
  74. package/dist/integrity/lsp/diagnostics.ts +0 -92
  75. package/dist/integrity/lsp/findings.ts +0 -63
  76. package/dist/integrity/lsp/lint.ts +0 -124
  77. package/dist/integrity/lsp/protocol.ts +0 -78
  78. package/dist/integrity/lsp/server.ts +0 -224
  79. package/dist/integrity/slopnator.ts +0 -324
  80. package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
  81. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
  82. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
  83. package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
  84. package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
  85. package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
  86. package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
  87. package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
  88. package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
  89. package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
  90. package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
  91. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
  92. package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
  93. package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
  94. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
  95. package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
  96. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
  97. package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
  98. package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
  99. package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
  100. package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
  101. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
  102. package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
  103. package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
  104. package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
  105. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
  106. package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
  107. package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
  108. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
  109. package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
  110. package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
  111. package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
  112. package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
  113. package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
  114. package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
  115. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
  116. package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
  117. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
  118. package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
  119. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
  120. package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
  121. package/dist/integrity/sweep.ts +0 -395
  122. package/dist/knowledge/attach.ts +0 -327
  123. package/dist/knowledge/generators/docsgen.ts +0 -186
  124. package/dist/knowledge/generators/glossary.ts +0 -73
  125. package/dist/knowledge/glossary.ts +0 -6
  126. package/dist/operations/access.ts +0 -131
  127. package/dist/operations/attach.ts +0 -408
  128. package/dist/operations/capabilities.ts +0 -88
  129. package/dist/operations/dispatch.ts +0 -294
  130. package/dist/operations/error-results.ts +0 -31
  131. package/dist/operations/generators/capability.ts +0 -1054
  132. package/dist/operations/generators/compile.ts +0 -701
  133. package/dist/operations/generators/permits.ts +0 -229
  134. package/dist/operations/generators/seed.ts +0 -359
  135. package/dist/operations/generators/trial.ts +0 -666
  136. package/dist/operations/nodes.ts +0 -53
  137. package/dist/operations/operations.ts +0 -91
  138. package/dist/operations/result.ts +0 -28
@@ -1,236 +0,0 @@
1
- import { join } from 'node:path'
2
- import { argv, stderr, stdout } from 'node:process'
3
-
4
- import { existsSync, readFileSync, writeFileSync } from '../../integrations/disk.ts'
5
- import { exit, invoked } from '../../core/invoked.ts'
6
- import { marked, valuesIn } from '../../core/rows.ts'
7
- import { ours } from '../../core/scope.ts'
8
- import { findTree, inTree } from '../../core/tree.ts'
9
-
10
- import { CHECKED_BY, KINDS, LISTENS, SAID, WRITES, WRITTEN_BY, kindOf } from '../release.ts'
11
-
12
- const TREE = findTree()
13
-
14
- const MANIFEST = 'package.json'
15
-
16
- interface Row {
17
- readonly id?: string
18
- readonly at?: string
19
- readonly derives?: readonly Row[]
20
- }
21
-
22
- const addressed = (rows: readonly Row[]): readonly string[] =>
23
- rows.flatMap((one) => [...(one.at === undefined ? [] : [one.at]), ...addressed(one.derives ?? [])])
24
-
25
- const mark = (one: Row): string => marked([one.id ?? '', one.at ?? ''])
26
-
27
- function releases(): readonly Row[] {
28
- const at = inTree(TREE, 'derivation.ts')
29
- if (!existsSync(at)) return []
30
- try {
31
- const held = valuesIn(readFileSync(at, 'utf8')) as { DERIVATION?: { readonly derives?: readonly Row[] } }
32
- return held.DERIVATION?.derives ?? []
33
- } catch {
34
- return []
35
- }
36
- }
37
-
38
- function manager(): string {
39
- try {
40
- const held = JSON.parse(readFileSync(join(TREE.at, MANIFEST), 'utf8')) as { packageManager?: unknown }
41
- const said = typeof held.packageManager === 'string' ? held.packageManager.split('@')[0] : undefined
42
- return said === undefined || said === '' ? 'npm' : said
43
- } catch {
44
- return 'npm'
45
- }
46
- }
47
-
48
- function scripted(): { readonly build: string; readonly start: string } {
49
- const tool = manager()
50
- return { build: `${tool} run build`, start: tool === 'npm' ? 'npm start' : `${tool} start` }
51
- }
52
-
53
- function dockerfile(build: string, start: string): string {
54
- const tool = manager()
55
- const corepack = tool === 'npm' ? [] : ['RUN corepack enable']
56
- const lock = tool === 'npm' ? 'package-lock.json*' : `${tool}-lock.yaml*`
57
- const first =
58
- tool === 'npm'
59
- ? 'RUN npm ci --omit=dev --ignore-scripts || npm install --omit=dev --ignore-scripts'
60
- : `RUN ${tool} install --prod --frozen-lockfile --ignore-scripts || ${tool} install --prod --ignore-scripts`
61
- const all = tool === 'npm' ? 'RUN npm install --ignore-scripts' : `RUN ${tool} install --ignore-scripts`
62
-
63
- return [
64
- 'FROM node:22-slim AS build',
65
- 'WORKDIR /app',
66
- ...corepack,
67
- `COPY package.json ${lock} ./`,
68
- first,
69
- 'COPY . .',
70
- all,
71
- `RUN ${build}`,
72
- '',
73
- 'FROM node:22-slim',
74
- 'WORKDIR /app',
75
- ...corepack,
76
- 'ENV NODE_ENV=production',
77
- 'COPY --from=build /app ./',
78
- `EXPOSE ${String(LISTENS)}`,
79
- `CMD ${JSON.stringify(start.split(' '))}`,
80
- '',
81
- ].join('\n')
82
- }
83
-
84
- function compose(id: string): string {
85
- return [
86
- 'services:',
87
- ` ${id}:`,
88
- ' build: .',
89
- ' ports:',
90
- ` - "${String(LISTENS)}:${String(LISTENS)}"`,
91
- '',
92
- ].join('\n')
93
- }
94
-
95
- function withheld(): string {
96
- return [
97
- '.thread',
98
- 'node_modules',
99
- '.git',
100
- '',
101
- ].join('\n')
102
- }
103
-
104
- function made(): ReadonlyMap<string, string> {
105
- const found = new Map<string, string>()
106
- const rows = releases()
107
- const { build, start } = scripted()
108
-
109
- for (const one of rows) {
110
- const id = one.id
111
- if (id === undefined) continue
112
- const kind = kindOf(id)
113
- if (kind === undefined) continue
114
- if (kind === 'docker') {
115
- found.set('Dockerfile', dockerfile(build, start))
116
- found.set('compose.yaml', compose(id))
117
- found.set('.dockerignore', withheld())
118
- }
119
- }
120
- return found
121
- }
122
-
123
- function installed(): readonly string[] {
124
- const at = join(TREE.at, MANIFEST)
125
- if (!existsSync(at)) {
126
- throw new Error(`this app has no ${MANIFEST}, so what a host would install cannot be read`)
127
- }
128
- const held = JSON.parse(readFileSync(at, 'utf8')) as { dependencies?: Readonly<Record<string, string>> }
129
- return Object.keys(held.dependencies ?? {}).filter((one) => ours(one)).sort()
130
- }
131
-
132
- function run(): number {
133
- const rows = releases()
134
- if (rows.length === 0) {
135
- stdout.write(
136
- `release: this app declares no release, so there is nowhere for it to go yet. Declare one with ` +
137
- `\`thread create release\` — it is ${KINDS.join(' or ')}, and the prefix on its id says which.\n`,
138
- )
139
- return 0
140
- }
141
-
142
- const words = argv.slice(2).filter((one) => !one.startsWith('-'))
143
- if (words[0] !== undefined && words[0] !== 'read') {
144
- stderr.write(
145
- 'release: `' + words[0] + '` is not something this command does. `thread release` writes the release, and ' +
146
- '`thread release read <mark>` prints one. Nothing was written.\n',
147
- )
148
- return 1
149
- }
150
- if (words[0] === 'read') {
151
- const which = words[1]
152
- const one = rows.find((row) => mark(row) === which)
153
- if (one === undefined) {
154
- stderr.write(
155
- `release: \`${which ?? '(nothing)'}\` is not a mark this tree issued. This app declares ` +
156
- `${rows.map((row) => `[${mark(row)}] ${String(row.id)}`).join(', ')} — read one of those, or run ` +
157
- '`thread release` to write it, or `thread create release` to declare another.\n',
158
- )
159
- return 1
160
- }
161
-
162
- const kind = kindOf(one.id ?? '')
163
- stdout.write(`${String(one.id)} — ${kind === undefined ? 'a release of a kind this generator does not recognise' : SAID[kind]}\n`)
164
- for (const one of kind === undefined ? [] : WRITES[kind]) {
165
- stdout.write(`\n${one.at} — ${one.describes}\n`)
166
- try {
167
- stdout.write(readFileSync(join(TREE.at, one.at), 'utf8'))
168
- } catch {
169
- stdout.write('(nothing is there)\n')
170
- }
171
- }
172
- return 0
173
- }
174
-
175
- const files = made()
176
- const checking = argv.includes('--check')
177
-
178
- const stated = new Set(addressed(rows))
179
- const stray = [...files.keys()].filter((one) => !stated.has(one))
180
- if (stray.length > 0) {
181
- stderr.write(
182
- `release: this release produces ${stray.join(', ')}, and no row names ${stray.length === 1 ? 'it' : 'them'}. ` +
183
- `Writing ${stray.length === 1 ? 'it' : 'them'} would leave a file this tree does not declare, and the descent ` +
184
- 'reports that three steps later as a file nobody can account for. Take the release out with `thread remove ' +
185
- '<its id>` and declare it again — the maker writes one row per file this kind produces, so all of them arrive ' +
186
- 'together. Nothing was written.\n',
187
- )
188
- return 1
189
- }
190
-
191
- const shipped = installed()
192
- if (shipped.length > 0) {
193
- stderr.write(
194
- `release: this app installs ${shipped.join(', ')} as a dependency, and what a host runs may hold ` +
195
- 'nothing of this framework. A package here is scaffolding: it writes the app, and the bundler folds ' +
196
- 'whatever the app imports into what it builds, so nothing of it is fetched again where this runs. ' +
197
- `Install ${shipped.length === 1 ? 'it' : 'them'} with \`npm i -D\` instead, or move ${shipped.length === 1 ? 'it' : 'them'} ` +
198
- 'to `devDependencies` and run this again. Nothing was written.\n',
199
- )
200
- return 1
201
- }
202
-
203
- if (checking) {
204
- for (const [at, text] of files) {
205
- let current = ''
206
- try {
207
- current = readFileSync(join(TREE.at, at), 'utf8')
208
- } catch {
209
- current = ''
210
- }
211
- if (current === text) continue
212
- stderr.write(
213
- current === ''
214
- ? `release: ${at} is declared and not on disk. Run \`${WRITTEN_BY}\` and commit what it writes.\n`
215
- : `release: ${at} is not what the declarations produce. A derived file that differs from its ` +
216
- `generator is a hand edit wearing a generated name. Run \`${WRITTEN_BY}\` and commit what it writes.\n`,
217
- )
218
- return 1
219
- }
220
- stdout.write(`release: ${[...files.keys()].join(', ')} ${files.size === 1 ? 'is' : 'are'} what the declarations produce\n`)
221
- return 0
222
- }
223
-
224
- for (const [at, text] of files) writeFileSync(join(TREE.at, at), text, 'utf8')
225
- stdout.write(
226
- 'release: wrote ' + [...files.keys()].join(', ') + ' — built and started by what this app\'s own manifest ' +
227
- 'declares, listening on ' + String(LISTENS) + '.\n' +
228
- 'release: no secret is named there. A secret is read from the sealed store at the moment it is used, and ' +
229
- 'naming it here would be telling whoever deploys this to put a credential in the environment instead.\n',
230
- )
231
- return 0
232
- }
233
-
234
- if (invoked(import.meta.url)) exit(run())
235
-
236
- export { CHECKED_BY, WRITES, made }
@@ -1,43 +0,0 @@
1
-
2
-
3
- export const PREFIXES = { docker: 'dk_' } as const
4
-
5
- export const KINDS = ['docker'] as const
6
-
7
- export interface Written {
8
- readonly at: string
9
- readonly describes: string
10
- }
11
-
12
- export const WRITES: Readonly<Record<(typeof KINDS)[number], readonly Written[]>> = {
13
- docker: [
14
- {
15
- at: 'Dockerfile',
16
- describes:
17
- 'How this app becomes an image and what starts it inside one — the build and start commands its own manifest declares, run with the package manager that manifest names, and the port its server listens on.',
18
- },
19
- {
20
- at: 'compose.yaml',
21
- describes: 'What runs the image this release builds, named beside it because one row states one address.',
22
- },
23
- {
24
- at: '.dockerignore',
25
- describes:
26
- 'What a build of this image must never carry out of this directory: the sealed store and the key that opens it, the installed packages, and the history. Without it the build copies everything beside it, and every sealed value goes into the image and into whoever pulls it.',
27
- },
28
- ],
29
- }
30
-
31
- export const LISTENS = 3000
32
-
33
- export const SAID: Readonly<Record<(typeof KINDS)[number], string>> = {
34
- docker:
35
- 'A container image built from this app and the compose file that runs it. It is the one that needs no account and no network to be proven, so it is what an app can be taken end to end with before anybody has chosen where it really lives. What it holds is derived: the build and start commands this app already declares, the port its own server listens on, and the name of every secret it says it must be given — the names only, because a value is typed by a person and a row has never held one.',
36
- }
37
-
38
- export const kindOf = (id: string): (typeof KINDS)[number] | undefined =>
39
- KINDS.find((one) => id.startsWith(PREFIXES[one]))
40
-
41
- export const WRITTEN_BY = 'thread release'
42
-
43
- export const CHECKED_BY = 'thread release --check'
@@ -1,187 +0,0 @@
1
- import type { Attaching } from '../core/attach.ts'
2
- import { SHAPED } from '../core/init.ts'
3
- import { NAMED } from '../core/scope.ts'
4
-
5
- const VERSION = 1
6
-
7
- const DESCRIBES =
8
- 'The system that states every place this app reaches and every credential it reaches with. A target is a destination and the prefix on its id is how it is reached; what it accepts is entities if it keeps them, and calls if it is called: an endpoint over https, a subcommand of a program, a method of a package; a disk accepts nothing, because every disk answers the same orders; what it requires is secrets, and the target it is reached through when the place can only be got at by way of another one; where it is goes on the row as its address when anybody may read it, and is one of the secrets when it carries a password. Nothing else in this app names a host, a connection string or an environment variable, so an operation says which place it needs and the rest is worked out from the declaration.'
9
-
10
- const TARGETS =
11
- 'Where this app states everything outside itself that it deals with, and every way in. An integration is one outside thing — a vendor, a service, a program on the machine — and nothing is reached that is not part of one; declare it with `thread create integration`. A target is one way out of this app into an integration: `thread create target` prints every way a place can be reached, asks which, and puts that prefix on the id, so how it is reached is the prefix and no field says so. A call is one thing a target can be asked to do, declared beside it with `thread create target call`. A webhook is the way back in: `thread create webhook` asks which integration it sits under, the path something outside posts to, and the operation it invokes, and that operation names the webhook back, so neither half opens a door on its own. Nobody writes the file that answers that address, because `thread capabilities` derives it from those two rows. What a target accepts goes in `takes` — entities where it keeps things, calls where it is called, and nothing for a disk. What it needs goes in `requires`: its credentials, and the target it is reached through; where it is goes in address, and a header every call carries goes in headers. This is the one place the split between what reaches a server and what stays local is written down.'
12
-
13
- const SECRETS =
14
- 'Where this app states every value it must be given and may not hold. A row here is a name and a sentence and never a value, and every id wears sec_, because only a server may ever hold a secret: a value anybody may read is not one, and goes on the row that needs it instead, as the address of a target or a header it sends. A secret with a sends goes into a header on every call, one with a signs is the key a body is signed with, and one with neither is the connection string of a database, the one address that carries a password.'
15
-
16
- const VAULT =
17
- 'What opens a secret\'s value at the moment of use, written once and then yours. It reads the names off _threads/secrets.ts and opens the values sealed beside the tree, refusing any other name; add a secret with `thread create secret`, which is the only thing that ever takes a value. To open values from somewhere else in production, replace what opens them here.'
18
-
19
- const SECRET =
20
- 'A value this app must be given and may never hold: a key, a token, a connection string. The row names it and says what it is for, and there is no field on it for the value, because a row is read by everything and a credential is read by one thing at one moment. Every id wears sec_, and that prefix says only a server may ever hold it: everything a browser bundle reaches is in the bundle, so a target requiring a secret is refused a browser entry when it is built. A value anybody may read is not a secret and is never sealed; it is written on the row that needs it, as the address of a target or a header every call carries.'
21
-
22
- const CALL =
23
- 'One thing a target can be asked to do, named so that a node names the call and never writes a url, a subcommand or a method. Over https it is a verb and a path; on a program it is a subcommand; on a package it is a method. It is a row rather than a field because it is a thing with a name and a sentence like everything else here, and because what a place offers is a list that grows: an app reaches one more endpoint, subcommand or method by declaring one more of these, not by editing a body.'
24
-
25
- const REACH =
26
- 'How a target is got at — over a network to a postgres, inside this process, in the browser\'s own memory, over https, as a package this app calls in its own process, as a program run at a command line, or as the disk under it. It is the prefix on the target\'s id and never a field, because how a thing is reached decides everything else that may be said about it: what it accepts, whether it can be reached from a page at all, and whether anything has to say where it is. A field beside the id would be a second answer to a question the id already answers.'
27
-
28
- const INTEGRATION =
29
- 'Something outside this app that it reaches into: a vendor, a service, a program on the machine. Every target says which integration it is part of, so supabase is one thing made of its postgres, its https endpoint, the calls on each and the secrets they need, rather than five rows that happen to share a word. Declare it before the first target that reaches into it.'
30
-
31
- const WEBHOOK =
32
- 'A way in that something outside uses: it posts to one address this app declares, and that address is the whole of what anybody out there is given. It sits under an integration beside the targets that reach the other way, and it names the operation it runs and the secret its signature is checked against, so what arrives is refused or dispatched and never anything else. Declare one with `thread create webhook`.'
33
-
34
- const INVOKES =
35
- 'Which operation a webhook runs when something posts to it. That operation names the webhook back, so both halves agree before anything outside reaches it, and a pairing only one of them states is refused.'
36
-
37
- const ARRIVES =
38
- 'Which header a value comes in on. A webhook says which one carries its signature, and an entity that names its callers says which one carries their token — one word, because it answers the same question about a request.'
39
-
40
- const SIGNS =
41
- 'What a secret does to a body on its way out: its value is the key a signature is computed with, and the digest goes on the header named here. It is `sends` turned around — `sends` puts the value itself on a header, and this puts the value nowhere at all.'
42
-
43
- const ADDRESS =
44
- 'Where a target reached over https is: the one absolute url every call on it is written from, stated on the row because a place is a fact anybody may read. Only the address of a database is sealed instead, because a connection string carries a password.'
45
-
46
- const HEADERS =
47
- 'The headers every call on a target carries, stated on its row as name and value: the version of an API this app is written against, or a fixed way of asking. A credential never goes here, because anybody may read a row; a value that has to be withheld is a secret with a sends.'
48
-
49
- const NEEDS =
50
- 'The packages that must be installed for this to work, each named with the versions it works with: a range from npm, a git link or a tarball. Attaching a package, or creating any row that states them, adds the ones the app does not already name to what it depends on and fetches them. A name the app already carries is left alone, so nothing overwrites a choice somebody made.'
51
-
52
- const SSOT = { on: '2026-08-29', said: 'SSOT for the integrations, as a whole meta inside thread core' }
53
-
54
- const RULED = { on: '2026-09-22', said: 'remove pub then as an acceptable thing, that\'s definitely slop and it\'s not the first time such a fucking thing has surfaced. Delete and redo it properly.' }
55
-
56
- export const CONCEPTS: readonly { readonly id: string; readonly describes: string; readonly cites: readonly { readonly on: string; readonly said: string }[] }[] = [
57
- { id: 'secret', describes: SECRET, cites: [SSOT] },
58
- { id: 'webhook', describes: WEBHOOK, cites: [SSOT] },
59
- { id: 'invokes', describes: INVOKES, cites: [SSOT] },
60
- { id: 'arrives', describes: ARRIVES, cites: [SSOT] },
61
- { id: 'signs', describes: SIGNS, cites: [SSOT] },
62
- { id: 'address', describes: ADDRESS, cites: [RULED] },
63
- { id: 'headers', describes: HEADERS, cites: [RULED] },
64
- { id: 'call', describes: CALL, cites: [SSOT] },
65
- { id: 'reach', describes: REACH, cites: [SSOT] },
66
- { id: 'integration', describes: INTEGRATION, cites: [SSOT] },
67
- { id: 'needs', describes: NEEDS, cites: [] },
68
- ]
69
-
70
- import { quoted } from '../core/rows.ts'
71
-
72
- const concept = (one: (typeof CONCEPTS)[number]): string =>
73
- [
74
- ' {',
75
- ` id: '${one.id}',`,
76
- " at: '_threads/concepts.ts',",
77
- ` describes: ${quoted(one.describes)},`,
78
- ` version: ${String(VERSION)},`,
79
- ...(one.cites.length === 0
80
- ? [' cites: [],']
81
- : [' cites: [', ...one.cites.map((each) => " { on: '" + each.on + "', said: " + quoted(each.said) + ' },'), ' ],']),
82
- ' },',
83
- ].join('\n')
84
-
85
- const INTEGRATIONS_SOURCE = `import type { Threaded } from '${SHAPED}'
86
-
87
- export const INTEGRATIONS: Threaded = {
88
- id: 'integrations',
89
- at: '_threads/integrations.ts',
90
- describes: ${quoted(DESCRIBES)},
91
- version: ${String(VERSION)},
92
- declares: [
93
- {
94
- id: 'targets',
95
- at: '_threads/targets.ts',
96
- describes: ${quoted(TARGETS)},
97
- version: ${String(VERSION)},
98
- },
99
- {
100
- id: 'secrets',
101
- at: '_threads/secrets.ts',
102
- describes: ${quoted(SECRETS)},
103
- version: ${String(VERSION)},
104
- },
105
- ],
106
- }
107
- `
108
-
109
- const TARGETS_SOURCE = `import type { Call, Integration, Target, Webhook } from '${NAMED}/integrations/target'
110
-
111
- export const INTEGRATIONS: readonly Integration[] = []
112
-
113
- export const TARGETS: readonly Target[] = []
114
-
115
- export const CALLS: readonly Call[] = []
116
-
117
- export const WEBHOOKS: readonly Webhook[] = []
118
- `
119
-
120
- const SECRETS_SOURCE = `import type { Secret } from '${NAMED}/integrations/secret'
121
-
122
- export const SECRETS: readonly Secret[] = []
123
- `
124
-
125
- const OWN: readonly { readonly into: string; readonly list: string; readonly id: string; readonly source: string }[] = [
126
- ...CONCEPTS.map((one) => ({
127
- into: 'concepts.ts',
128
- list: 'CONCEPTS',
129
- id: one.id,
130
- source: concept(one),
131
- })),
132
- {
133
- into: 'integrations.ts',
134
- list: 'declares',
135
- id: 'vault',
136
- source: [
137
- ' {',
138
- " id: 'vault',",
139
- " at: 'src/vault.ts',",
140
- ` describes: ${quoted(VAULT)},`,
141
- ` version: ${String(VERSION)},`,
142
- " by: 'thread vault',",
143
- " runs: 'thread vault --check',",
144
- ' },',
145
- ].join('\n'),
146
- },
147
- ]
148
-
149
- const SEEDED = {
150
- attaches: ['@prestack/thread/derivation/attach', '@prestack/thread/knowledge/attach'] as const,
151
- id: 'integrations',
152
- declares: [
153
- ' {',
154
- ' id: \'integrations\',',
155
- ' at: \'_threads/integrations.ts\',',
156
- ' describes: \'The system that states every place this app reaches and every credential it reaches with. A target is a destination and the prefix on its id is how it is reached; what it accepts is entities if it keeps them, and calls if it is called: an endpoint over https, a subcommand of a program, a method of a package; a disk accepts nothing, because every disk answers the same orders; what it requires is secrets, and the target it is reached through when the place can only be got at by way of another one; where it is goes on the row as its address when anybody may read it, and is one of the secrets when it carries a password. Nothing else in this app names a host, a connection string or an environment variable, so an operation says which place it needs and the rest is worked out from the declaration.\',',
157
- ' version: 1,',
158
- ' },',
159
- ].join('\n'),
160
- }
161
-
162
- const SHIPPED: readonly { readonly into: string; readonly list: string; readonly id: string; readonly source: string }[] = [
163
- {
164
- into: 'derivation.ts',
165
- list: 'GENERATORS',
166
- id: 'vault-generator',
167
- source: " {\n id: 'vault-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/vault.js',\n describes: 'The writer of the file that opens this app\\'s sealed store. It reads the secrets this app declares and writes one function that resolves them by name, refusing any name no row declares — so a value can be asked for only by something that declared it needed it, and a secret taken off the tree stops resolving rather than lingering as a name that still works.',\n version: 1,\n by: 'thread vault',\n runs: 'thread vault --check',\n },",
168
- },
169
- {
170
- into: 'derivation.ts',
171
- list: 'GENERATORS',
172
- id: 'inspect-generator',
173
- source: " {\n id: 'inspect-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/read.js',\n describes: 'The one command for reading what this app declares. Asked for nothing it prints every sheet and what each holds; asked for a word it prints every row whose id or sentence carries it. It reads the tree rather than the files, which is the whole reason it exists: a row is a nested TypeScript literal and a search over lines answers questions about text while the questions people actually ask are about rows.',\n version: 1,\n by: 'thread inspect',\n },",
174
- },
175
- ]
176
- export const ATTACH: Attaching = {
177
- attaches: SEEDED.attaches,
178
- id: SEEDED.id,
179
- declares: SEEDED.declares,
180
- writes: [
181
- { file: 'integrations.ts', source: INTEGRATIONS_SOURCE },
182
- { file: 'targets.ts', source: TARGETS_SOURCE },
183
- { file: 'secrets.ts', source: SECRETS_SOURCE },
184
- ],
185
- rows: [...OWN, ...SHIPPED],
186
-
187
- }