@prestack/thread 0.0.1

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 (248) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/dist/attach.d.ts +1 -0
  3. package/dist/attach.js +1 -0
  4. package/dist/core/actor.d.ts +12 -0
  5. package/dist/core/actor.js +8 -0
  6. package/dist/core/asking.d.ts +180 -0
  7. package/dist/core/asking.js +1109 -0
  8. package/dist/core/attach.d.ts +54 -0
  9. package/dist/core/attach.js +510 -0
  10. package/dist/core/chain.d.ts +16 -0
  11. package/dist/core/chain.js +114 -0
  12. package/dist/core/cli.d.ts +2 -0
  13. package/dist/core/cli.js +1682 -0
  14. package/dist/core/condition.d.ts +52 -0
  15. package/dist/core/condition.js +198 -0
  16. package/dist/core/descend.d.ts +81 -0
  17. package/dist/core/descend.js +879 -0
  18. package/dist/core/finding.d.ts +12 -0
  19. package/dist/core/finding.js +23 -0
  20. package/dist/core/generators/meta.d.ts +2 -0
  21. package/dist/core/generators/meta.js +823 -0
  22. package/dist/core/init.d.ts +15 -0
  23. package/dist/core/init.js +134 -0
  24. package/dist/core/invoked.d.ts +9 -0
  25. package/dist/core/invoked.js +41 -0
  26. package/dist/core/round.d.ts +1 -0
  27. package/dist/core/round.js +57 -0
  28. package/dist/core/rows.d.ts +26 -0
  29. package/dist/core/rows.js +354 -0
  30. package/dist/core/scope.d.ts +6 -0
  31. package/dist/core/scope.js +12 -0
  32. package/dist/core/thread.d.ts +26 -0
  33. package/dist/core/thread.js +5 -0
  34. package/dist/core/tree.d.ts +12 -0
  35. package/dist/core/tree.js +56 -0
  36. package/dist/core/types.d.ts +49 -0
  37. package/dist/core/types.js +311 -0
  38. package/dist/derivation/attach.d.ts +2 -0
  39. package/dist/derivation/attach.js +61 -0
  40. package/dist/derivation/generators/build.d.ts +16 -0
  41. package/dist/derivation/generators/build.js +217 -0
  42. package/dist/derivation/generators/check.d.ts +18 -0
  43. package/dist/derivation/generators/check.js +78 -0
  44. package/dist/derivation/generators/derivation.d.ts +3 -0
  45. package/dist/derivation/generators/derivation.js +273 -0
  46. package/dist/derivation/generators/determinism.d.ts +20 -0
  47. package/dist/derivation/generators/determinism.js +110 -0
  48. package/dist/derivation/generators/release.d.ts +3 -0
  49. package/dist/derivation/generators/release.js +183 -0
  50. package/dist/derivation/makers/release.d.ts +1 -0
  51. package/dist/derivation/makers/release.js +107 -0
  52. package/dist/derivation/release.d.ts +10 -0
  53. package/dist/derivation/release.js +12 -0
  54. package/dist/integrations/attach.d.ts +6 -0
  55. package/dist/integrations/attach.js +177 -0
  56. package/dist/integrations/generators/read.d.ts +1 -0
  57. package/dist/integrations/generators/read.js +92 -0
  58. package/dist/integrations/generators/sdk.d.ts +1 -0
  59. package/dist/integrations/generators/sdk.js +148 -0
  60. package/dist/integrations/generators/secret.d.ts +1 -0
  61. package/dist/integrations/generators/secret.js +233 -0
  62. package/dist/integrations/generators/target.d.ts +1 -0
  63. package/dist/integrations/generators/target.js +765 -0
  64. package/dist/integrations/generators/vault.d.ts +1 -0
  65. package/dist/integrations/generators/vault.js +164 -0
  66. package/dist/integrations/sdk.d.ts +6 -0
  67. package/dist/integrations/sdk.js +151 -0
  68. package/dist/integrations/secret.d.ts +16 -0
  69. package/dist/integrations/secret.js +30 -0
  70. package/dist/integrations/target.d.ts +49 -0
  71. package/dist/integrations/target.js +225 -0
  72. package/dist/integrations/vault.d.ts +12 -0
  73. package/dist/integrations/vault.js +104 -0
  74. package/dist/integrity/attach.d.ts +2 -0
  75. package/dist/integrity/attach.js +213 -0
  76. package/dist/integrity/check-threads.d.ts +10 -0
  77. package/dist/integrity/check-threads.js +153 -0
  78. package/dist/integrity/constraint.d.ts +16 -0
  79. package/dist/integrity/constraint.js +1105 -0
  80. package/dist/integrity/generators/compiler.d.ts +1 -0
  81. package/dist/integrity/generators/compiler.js +130 -0
  82. package/dist/integrity/generators/gate.d.ts +1 -0
  83. package/dist/integrity/generators/gate.js +172 -0
  84. package/dist/integrity/generators/issues.d.ts +1 -0
  85. package/dist/integrity/generators/issues.js +100 -0
  86. package/dist/integrity/generators/publish.d.ts +1 -0
  87. package/dist/integrity/generators/publish.js +186 -0
  88. package/dist/integrity/generators/rule.d.ts +2 -0
  89. package/dist/integrity/generators/rule.js +213 -0
  90. package/dist/integrity/generators/seal.d.ts +4 -0
  91. package/dist/integrity/generators/seal.js +101 -0
  92. package/dist/integrity/generators/trial.d.ts +39 -0
  93. package/dist/integrity/generators/trial.js +254 -0
  94. package/dist/integrity/lint.d.ts +50 -0
  95. package/dist/integrity/lint.js +161 -0
  96. package/dist/integrity/lsp/bin.d.ts +2 -0
  97. package/dist/integrity/lsp/bin.js +35 -0
  98. package/dist/integrity/lsp/diagnostics.d.ts +25 -0
  99. package/dist/integrity/lsp/diagnostics.js +57 -0
  100. package/dist/integrity/lsp/findings.d.ts +20 -0
  101. package/dist/integrity/lsp/findings.js +39 -0
  102. package/dist/integrity/lsp/lint.d.ts +7 -0
  103. package/dist/integrity/lsp/lint.js +95 -0
  104. package/dist/integrity/lsp/protocol.d.ts +20 -0
  105. package/dist/integrity/lsp/protocol.js +57 -0
  106. package/dist/integrity/lsp/server.d.ts +15 -0
  107. package/dist/integrity/lsp/server.js +171 -0
  108. package/dist/integrity/sealed.d.ts +1 -0
  109. package/dist/integrity/sealed.js +56 -0
  110. package/dist/integrity/slopnator.d.ts +90 -0
  111. package/dist/integrity/slopnator.js +267 -0
  112. package/dist/integrity/slopnators/sl_broken_lineage.d.ts +2 -0
  113. package/dist/integrity/slopnators/sl_broken_lineage.js +35 -0
  114. package/dist/integrity/slopnators/sl_declared_elsewhere.d.ts +2 -0
  115. package/dist/integrity/slopnators/sl_declared_elsewhere.js +33 -0
  116. package/dist/integrity/slopnators/sl_derived_drift.d.ts +8 -0
  117. package/dist/integrity/slopnators/sl_derived_drift.js +91 -0
  118. package/dist/integrity/slopnators/sl_hardcoded_style.d.ts +2 -0
  119. package/dist/integrity/slopnators/sl_hardcoded_style.js +46 -0
  120. package/dist/integrity/slopnators/sl_inert_island.d.ts +2 -0
  121. package/dist/integrity/slopnators/sl_inert_island.js +85 -0
  122. package/dist/integrity/slopnators/sl_inert_slopnator.d.ts +2 -0
  123. package/dist/integrity/slopnators/sl_inert_slopnator.js +76 -0
  124. package/dist/integrity/slopnators/sl_missing_row.d.ts +2 -0
  125. package/dist/integrity/slopnators/sl_missing_row.js +180 -0
  126. package/dist/integrity/slopnators/sl_no_barrel.d.ts +2 -0
  127. package/dist/integrity/slopnators/sl_no_barrel.js +41 -0
  128. package/dist/integrity/slopnators/sl_no_child_process.d.ts +2 -0
  129. package/dist/integrity/slopnators/sl_no_child_process.js +61 -0
  130. package/dist/integrity/slopnators/sl_no_comment.d.ts +2 -0
  131. package/dist/integrity/slopnators/sl_no_comment.js +41 -0
  132. package/dist/integrity/slopnators/sl_no_dispatch_bypass.d.ts +2 -0
  133. package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +301 -0
  134. package/dist/integrity/slopnators/sl_no_import_cycle.d.ts +2 -0
  135. package/dist/integrity/slopnators/sl_no_import_cycle.js +42 -0
  136. package/dist/integrity/slopnators/sl_no_such_command.d.ts +2 -0
  137. package/dist/integrity/slopnators/sl_no_such_command.js +55 -0
  138. package/dist/integrity/slopnators/sl_only_takes_a_literal.d.ts +2 -0
  139. package/dist/integrity/slopnators/sl_only_takes_a_literal.js +59 -0
  140. package/dist/integrity/slopnators/sl_secret_literal.d.ts +2 -0
  141. package/dist/integrity/slopnators/sl_secret_literal.js +89 -0
  142. package/dist/integrity/slopnators/sl_stale_path.d.ts +2 -0
  143. package/dist/integrity/slopnators/sl_stale_path.js +92 -0
  144. package/dist/integrity/slopnators/sl_twice_meant.d.ts +2 -0
  145. package/dist/integrity/slopnators/sl_twice_meant.js +30 -0
  146. package/dist/integrity/slopnators/sl_twice_parented.d.ts +2 -0
  147. package/dist/integrity/slopnators/sl_twice_parented.js +34 -0
  148. package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +2 -0
  149. package/dist/integrity/slopnators/sl_undeclared_crossing.js +78 -0
  150. package/dist/integrity/slopnators/sl_undeclared_destination.d.ts +2 -0
  151. package/dist/integrity/slopnators/sl_undeclared_destination.js +56 -0
  152. package/dist/integrity/slopnators/sl_undeclared_element.d.ts +2 -0
  153. package/dist/integrity/slopnators/sl_undeclared_element.js +34 -0
  154. package/dist/integrity/slopnators/sl_undeclared_node.d.ts +2 -0
  155. package/dist/integrity/slopnators/sl_undeclared_node.js +59 -0
  156. package/dist/integrity/slopnators/sl_undeclared_operation.d.ts +2 -0
  157. package/dist/integrity/slopnators/sl_undeclared_operation.js +120 -0
  158. package/dist/integrity/slopnators/sl_undeclared_variant.d.ts +2 -0
  159. package/dist/integrity/slopnators/sl_undeclared_variant.js +117 -0
  160. package/dist/integrity/slopnators/sl_undescribed.d.ts +2 -0
  161. package/dist/integrity/slopnators/sl_undescribed.js +54 -0
  162. package/dist/integrity/slopnators/sl_ungoverned_island.d.ts +2 -0
  163. package/dist/integrity/slopnators/sl_ungoverned_island.js +58 -0
  164. package/dist/integrity/slopnators/sl_unkept_rows.d.ts +2 -0
  165. package/dist/integrity/slopnators/sl_unkept_rows.js +104 -0
  166. package/dist/integrity/slopnators/sl_unnamed_island.d.ts +2 -0
  167. package/dist/integrity/slopnators/sl_unnamed_island.js +37 -0
  168. package/dist/integrity/slopnators/sl_unpaired_member.d.ts +2 -0
  169. package/dist/integrity/slopnators/sl_unpaired_member.js +114 -0
  170. package/dist/integrity/slopnators/sl_unpaired_slopnator.d.ts +2 -0
  171. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +60 -0
  172. package/dist/integrity/slopnators/sl_unprefixed_member.d.ts +2 -0
  173. package/dist/integrity/slopnators/sl_unprefixed_member.js +126 -0
  174. package/dist/integrity/slopnators/sl_unseamed_reach.d.ts +2 -0
  175. package/dist/integrity/slopnators/sl_unseamed_reach.js +73 -0
  176. package/dist/integrity/slopnators/sl_unthreaded.d.ts +56 -0
  177. package/dist/integrity/slopnators/sl_unthreaded.js +352 -0
  178. package/dist/integrity/slopnators/sl_untranslated_key.d.ts +2 -0
  179. package/dist/integrity/slopnators/sl_untranslated_key.js +157 -0
  180. package/dist/integrity/slopnators/sl_unversioned.d.ts +2 -0
  181. package/dist/integrity/slopnators/sl_unversioned.js +31 -0
  182. package/dist/integrity/sweep.d.ts +24 -0
  183. package/dist/integrity/sweep.js +264 -0
  184. package/dist/knowledge/attach.d.ts +2 -0
  185. package/dist/knowledge/attach.js +339 -0
  186. package/dist/knowledge/generators/docsgen.d.ts +1 -0
  187. package/dist/knowledge/generators/docsgen.js +145 -0
  188. package/dist/knowledge/generators/glossary.d.ts +1 -0
  189. package/dist/knowledge/generators/glossary.js +56 -0
  190. package/dist/knowledge/generators/skills.d.ts +1 -0
  191. package/dist/knowledge/generators/skills.js +222 -0
  192. package/dist/knowledge/glossary.d.ts +6 -0
  193. package/dist/knowledge/glossary.js +1 -0
  194. package/dist/operations/access.d.ts +27 -0
  195. package/dist/operations/access.js +61 -0
  196. package/dist/operations/attach.d.ts +6 -0
  197. package/dist/operations/attach.js +414 -0
  198. package/dist/operations/capabilities.d.ts +37 -0
  199. package/dist/operations/capabilities.js +35 -0
  200. package/dist/operations/cost.d.ts +14 -0
  201. package/dist/operations/cost.js +37 -0
  202. package/dist/operations/dispatch.d.ts +14 -0
  203. package/dist/operations/dispatch.js +327 -0
  204. package/dist/operations/error-results.d.ts +4 -0
  205. package/dist/operations/error-results.js +25 -0
  206. package/dist/operations/errors/catalog.d.ts +58 -0
  207. package/dist/operations/errors/catalog.js +91 -0
  208. package/dist/operations/errors/classes.d.ts +73 -0
  209. package/dist/operations/errors/classes.js +22 -0
  210. package/dist/operations/errors/errors.d.ts +52 -0
  211. package/dist/operations/errors/errors.js +257 -0
  212. package/dist/operations/generators/capability.d.ts +5 -0
  213. package/dist/operations/generators/capability.js +603 -0
  214. package/dist/operations/generators/compile.d.ts +2 -0
  215. package/dist/operations/generators/compile.js +541 -0
  216. package/dist/operations/generators/grant.d.ts +1 -0
  217. package/dist/operations/generators/grant.js +267 -0
  218. package/dist/operations/generators/operation.d.ts +1 -0
  219. package/dist/operations/generators/operation.js +811 -0
  220. package/dist/operations/generators/permits.d.ts +1 -0
  221. package/dist/operations/generators/permits.js +196 -0
  222. package/dist/operations/generators/seed.d.ts +20 -0
  223. package/dist/operations/generators/seed.js +285 -0
  224. package/dist/operations/generators/trial.d.ts +53 -0
  225. package/dist/operations/generators/trial.js +376 -0
  226. package/dist/operations/http.d.ts +30 -0
  227. package/dist/operations/http.js +121 -0
  228. package/dist/operations/makers/capability.d.ts +1 -0
  229. package/dist/operations/makers/capability.js +117 -0
  230. package/dist/operations/makers/trial.d.ts +1 -0
  231. package/dist/operations/makers/trial.js +413 -0
  232. package/dist/operations/memory.d.ts +104 -0
  233. package/dist/operations/memory.js +365 -0
  234. package/dist/operations/metering.d.ts +55 -0
  235. package/dist/operations/metering.js +247 -0
  236. package/dist/operations/nodes.d.ts +35 -0
  237. package/dist/operations/nodes.js +15 -0
  238. package/dist/operations/operations.d.ts +18 -0
  239. package/dist/operations/operations.js +5 -0
  240. package/dist/operations/queue-policy.d.ts +3 -0
  241. package/dist/operations/queue-policy.js +8 -0
  242. package/dist/operations/result.d.ts +19 -0
  243. package/dist/operations/result.js +10 -0
  244. package/dist/operations/seams.d.ts +239 -0
  245. package/dist/operations/seams.js +45 -0
  246. package/dist/operations/store.d.ts +14 -0
  247. package/dist/operations/store.js +1 -0
  248. package/package.json +16 -0
@@ -0,0 +1,603 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
10
+ import { dirname, resolve } from 'node:path';
11
+ import { argv, stdout } from 'node:process';
12
+ import { pathToFileURL } from 'node:url';
13
+ import { marked, wordsIn } from "../../core/asking.js";
14
+ import { ROOT, TREE, descend } from "../../core/descend.js";
15
+ import { exit, invoked } from "../../core/invoked.js";
16
+ import { named as packageOf } from "../../core/scope.js";
17
+ import { PREFIXES } from "../../core/thread.js";
18
+ import { servesBrowser } from "../../integrations/target.js";
19
+ import { CAPABILITIES_AT, CLIENT_AT, DECLARED_SEAMS_AT, MCP_AT, OPENAPI_AT, DISPATCHING_AT, SEAMS_AT, routeAt, SURFACES, switched, } from "../capabilities.js";
20
+ import { ENVIRONMENT } from "../../core/actor.js";
21
+ import { PERMITS_AT } from "../access.js";
22
+ import { SAID as HANDED, supplies } from "../seams.js";
23
+ import { beside, operations, quoted, runtime, types } from "./seed.js";
24
+ const compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
25
+ function stable(value) {
26
+ if (Array.isArray(value))
27
+ return value.map(stable);
28
+ if (typeof value === 'object' && value !== null) {
29
+ const found = {};
30
+ for (const key of Object.keys(value).sort(compare)) {
31
+ found[key] = stable(value[key]);
32
+ }
33
+ return found;
34
+ }
35
+ return value === undefined ? null : value;
36
+ }
37
+ const printed = (value) => `${JSON.stringify(stable(value), null, 2)}\n`;
38
+ function verbOf(id) {
39
+ if (id.startsWith(PREFIXES.view))
40
+ return 'GET';
41
+ if (id.startsWith(PREFIXES.workflow) || id.startsWith(PREFIXES.agent))
42
+ return 'POST';
43
+ return undefined;
44
+ }
45
+ async function schemaOf(at) {
46
+ const from = resolve(ROOT, at);
47
+ if (!existsSync(from))
48
+ return `${at} is not there, so nothing states what this operation takes`;
49
+ const held = (await import(__rewriteRelativeImportExtension(pathToFileURL(from).href)));
50
+ const input = held.input;
51
+ if (input === undefined)
52
+ return `${at} exports no \`input\`, so nothing states what this operation takes`;
53
+ const zod = (await import('zod'));
54
+ if (zod.toJSONSchema === undefined) {
55
+ return 'this app\'s zod states no `toJSONSchema`, so an input schema cannot be written from it';
56
+ }
57
+ try {
58
+ return zod.toJSONSchema(input);
59
+ }
60
+ catch (thrown) {
61
+ return `${at} states an input that cannot be written as a schema: ${thrown instanceof Error ? thrown.message : String(thrown)}`;
62
+ }
63
+ }
64
+ async function sheet(file, list) {
65
+ const at = resolve(ROOT, `_threads/${file}`);
66
+ if (!existsSync(at))
67
+ return [];
68
+ try {
69
+ const held = (await import(__rewriteRelativeImportExtension(pathToFileURL(at).href)));
70
+ return held[list] ?? [];
71
+ }
72
+ catch {
73
+ return [];
74
+ }
75
+ }
76
+ function browsing(requires, targets, secrets) {
77
+ for (const word of requires) {
78
+ const target = targets.find((one) => one.id === word);
79
+ if (target === undefined)
80
+ continue;
81
+ if (servesBrowser(target, secrets) !== true)
82
+ return false;
83
+ }
84
+ return true;
85
+ }
86
+ function locally(requires) {
87
+ for (const word of requires) {
88
+ if (!supplies(word))
89
+ return false;
90
+ if (word === 'sm_vault')
91
+ return false;
92
+ }
93
+ return true;
94
+ }
95
+ export async function derive() {
96
+ const pass = descend();
97
+ const known = types(pass);
98
+ const refusals = [];
99
+ const found = [];
100
+ const targets = await sheet('targets.ts', 'TARGETS');
101
+ const secrets = await sheet('secrets.ts', 'SECRETS');
102
+ const at = resolve(ROOT, CAPABILITIES_AT);
103
+ const already = switched(existsSync(at) ? readFileSync(at, 'utf8') : undefined);
104
+ for (const [id, one] of [...operations(pass, known)].sort((a, b) => compare(a[0], b[0]))) {
105
+ const stated = pass.stated.get(id);
106
+ const describes = stated?.describes;
107
+ if (describes === undefined) {
108
+ refusals.push(`\`${id}\` states no \`describes\`, and a caller told nothing about an operation cannot choose it`);
109
+ continue;
110
+ }
111
+ const method = verbOf(id);
112
+ if (method === undefined) {
113
+ refusals.push(`\`${id}\` wears none of the prefixes an operation may wear (${Object.values(PREFIXES).join(', ')}), so there is no verb to give it`);
114
+ continue;
115
+ }
116
+ const takes = one.takes[0]?.at;
117
+ if (takes === undefined) {
118
+ refusals.push(`\`${id}\` names nothing it takes, so nothing states its input`);
119
+ continue;
120
+ }
121
+ const schema = await schemaOf(takes);
122
+ if (typeof schema === 'string') {
123
+ refusals.push(`\`${id}\`: ${schema}`);
124
+ continue;
125
+ }
126
+ const live = already.get(id) ?? [...SURFACES];
127
+ found.push({
128
+ operation: id,
129
+ describes,
130
+ requires: [...one.requires].sort(compare),
131
+ nodes: one.nodes.map((each) => each.id ?? '').filter((each) => each !== ''),
132
+ input: schema,
133
+ live: [...live].sort(compare),
134
+ browsable: browsing(one.requires, targets, secrets),
135
+ local: browsing(one.requires, targets, secrets) && locally(one.requires),
136
+ mcp: { name: id, description: describes, inputSchema: schema },
137
+ cli: { invocation: `${id} --input '<json>'`, describes },
138
+ api: { method, path: `/operations/${id}` },
139
+ });
140
+ }
141
+ return { found, refusals };
142
+ }
143
+ function named() {
144
+ try {
145
+ const held = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8'));
146
+ return {
147
+ title: typeof held.name === 'string' ? held.name : 'this app',
148
+ version: typeof held.version === 'string' ? held.version : '0.0.0',
149
+ };
150
+ }
151
+ catch {
152
+ return { title: 'this app', version: '0.0.0' };
153
+ }
154
+ }
155
+ function openapi(found) {
156
+ const paths = {};
157
+ for (const one of found) {
158
+ if (!one.live.includes('api'))
159
+ continue;
160
+ const method = one.api.method.toLowerCase();
161
+ const responses = {
162
+ '200': {
163
+ description: 'the operation ran',
164
+ content: { 'application/json': { schema: { type: 'object', properties: { ok: { const: true }, value: {} }, required: ['ok'] } } },
165
+ },
166
+ '400': {
167
+ description: 'dispatch refused it — the code says which gate',
168
+ content: {
169
+ 'application/json': {
170
+ schema: {
171
+ type: 'object',
172
+ properties: {
173
+ ok: { const: false },
174
+ code: { enum: ['denied', 'invalid', 'rate_limited', 'feature_denied', 'quota_exceeded', 'error'] },
175
+ error: { type: 'string' },
176
+ because: { type: 'string' },
177
+ },
178
+ required: ['ok', 'code', 'error'],
179
+ },
180
+ },
181
+ },
182
+ },
183
+ };
184
+ paths[one.api.path] = {
185
+ [method]: {
186
+ operationId: one.operation,
187
+ summary: one.describes,
188
+ ...(one.api.method === 'GET'
189
+ ? { parameters: [{ name: 'input', in: 'query', required: true, content: { 'application/json': { schema: one.input } } }] }
190
+ : { requestBody: { required: true, content: { 'application/json': { schema: one.input } } } }),
191
+ responses,
192
+ },
193
+ };
194
+ }
195
+ const who = named();
196
+ return {
197
+ openapi: '3.1.0',
198
+ info: { title: who.title, version: who.version, description: 'Every operation this app declares that is live on its API surface.' },
199
+ paths,
200
+ };
201
+ }
202
+ const mcp = (found) => ({
203
+ tools: found.filter((one) => one.live.includes('mcp')).map((one) => one.mcp),
204
+ });
205
+ const compiled = (operation) => existsSync(resolve(ROOT, `operations/${operation}.ts`));
206
+ function client(found) {
207
+ const rows = found
208
+ .filter(held)
209
+ .map((one) => [
210
+ ' {',
211
+ ` operation: ${JSON.stringify(one.operation)},`,
212
+ ` describes: ${JSON.stringify(one.describes)},`,
213
+ ` method: ${JSON.stringify(one.api.method)},`,
214
+ ` path: ${JSON.stringify(one.api.path)},`,
215
+ ' },',
216
+ ].join('\n'));
217
+ const near = found.filter((one) => one.local && compiled(one.operation));
218
+ const bound = near.flatMap((one) => [
219
+ `export async function ${one.operation}Locally(input: unknown, actor: Actor, seams: Seams): Promise<Result<unknown>> {`,
220
+ ` const held = await dispatch(${one.operation}, input, actor, seams)`,
221
+ ' return held',
222
+ '}',
223
+ '',
224
+ ]);
225
+ return [
226
+ ...(near.length === 0
227
+ ? []
228
+ : [
229
+ `import type { Actor } from '${runtime(CLIENT_AT, '../../core/actor.ts')}'`,
230
+ `import type { Result } from '${runtime(CLIENT_AT, '../result.ts')}'`,
231
+ `import type { Seams } from '${runtime(CLIENT_AT, '../seams.ts')}'`,
232
+ '',
233
+ "import { dispatch } from './_dispatch.ts'",
234
+ ...near.map((one) => `import { operation as ${one.operation} } from './${one.operation}.ts'`),
235
+ '',
236
+ ]),
237
+ 'export interface Reachable {',
238
+ ' readonly operation: string',
239
+ ' readonly describes: string',
240
+ " readonly method: 'GET' | 'POST'",
241
+ ' readonly path: string',
242
+ '}',
243
+ '',
244
+ `export const CLIENT: readonly Reachable[] = [${rows.length === 0 ? '' : `\n${rows.join('\n')}\n`}]`,
245
+ '',
246
+ 'export const reachable = (operation: string): Reachable | undefined =>',
247
+ ' CLIENT.find((one) => one.operation === operation)',
248
+ '',
249
+ ...bound,
250
+ '',
251
+ ].join('\n');
252
+ }
253
+ function route(one) {
254
+ const at = routeAt(one.operation);
255
+ return [
256
+ "import { existsSync } from 'node:fs'",
257
+ "import { join } from 'node:path'",
258
+ '',
259
+ "import { dispatch } from '../../../operations/_dispatch.ts'",
260
+ `import { actor } from '${beside(at, DECLARED_SEAMS_AT)}'`,
261
+ `import { SEAMS } from '${beside(at, SEAMS_AT)}'`,
262
+ `import { operation } from '../../../operations/${one.operation}.ts'`,
263
+ '',
264
+ 'const STATUS: Record<string, number> = {',
265
+ ' denied: 403,',
266
+ ' invalid: 400,',
267
+ ' rate_limited: 429,',
268
+ ' feature_denied: 403,',
269
+ ' quota_exceeded: 429,',
270
+ ' error: 500,',
271
+ '}',
272
+ '',
273
+ "const PASSED = existsSync(join(process.cwd(), '.thread', 'gate.json'))",
274
+ '',
275
+ `export async function ${one.api.method}(request: Request): Promise<Response> {`,
276
+ ' if (!PASSED) {',
277
+ ' return Response.json(',
278
+ " { ok: false, code: 'error', error: 'failed', because: 'this tree has no passing gate, so nothing in it may answer a request. Run `thread gate` — a gate that has never run is not a gate that passed' },",
279
+ ' { status: 500 },',
280
+ ' )',
281
+ ' }',
282
+ ...(one.api.method === 'GET'
283
+ ? [' const input = Object.fromEntries(new URL(request.url).searchParams)']
284
+ : [' const input = await request.json().catch(() => undefined)']),
285
+ ' const held = await dispatch(operation, input, await actor(request), SEAMS)',
286
+ ' return Response.json(held, { status: held.ok ? 200 : (STATUS[held.code] ?? 500) })',
287
+ '}',
288
+ '',
289
+ ].join('\n');
290
+ }
291
+ const SEAM = /export\s+const\s+store\s*:/;
292
+ const STORES_AT = 'src/stores';
293
+ function kept() {
294
+ const at = resolve(ROOT, STORES_AT);
295
+ if (!existsSync(at))
296
+ return [];
297
+ return readdirSync(at)
298
+ .filter((one) => one.endsWith('.ts'))
299
+ .filter((one) => {
300
+ try {
301
+ return SEAM.test(readFileSync(resolve(at, one), 'utf8'));
302
+ }
303
+ catch {
304
+ return false;
305
+ }
306
+ })
307
+ .sort()
308
+ .map((one) => `${STORES_AT}/${one}`);
309
+ }
310
+ const ENTITIES_AT = `${TREE}entities.ts`;
311
+ const OPERATIONS_AT = `${TREE}operations.ts`;
312
+ const VAULT_AT = `src/vault.${'ts'}`;
313
+ function settled(keeping, stores, ruled, vaulted) {
314
+ const named = stores.map((one, at) => ({ from: beside(DECLARED_SEAMS_AT, one), as: `s${String(at)}` }));
315
+ const only = named[0];
316
+ return [
317
+ `import { ANONYMOUS, type Actor, type Environment } from '${runtime(DECLARED_SEAMS_AT, '../../core/actor.ts')}'`,
318
+ `import { httpSeam } from '${runtime(DECLARED_SEAMS_AT, '../http.ts')}'`,
319
+ `import { arrived, fresh, inMemorySeams } from '${runtime(DECLARED_SEAMS_AT, '../memory.ts')}'`,
320
+ `import type { Seams } from '${runtime(DECLARED_SEAMS_AT, '../seams.ts')}'`,
321
+ ...(keeping ? [`import { ENTITIES } from '${beside(DECLARED_SEAMS_AT, ENTITIES_AT)}'`] : []),
322
+ ...(ruled ? [`import { permits } from '${beside(DECLARED_SEAMS_AT, PERMITS_AT)}'`] : []),
323
+ ...(vaulted ? [`import { vault as opening } from '${beside(DECLARED_SEAMS_AT, VAULT_AT)}'`] : []),
324
+ ...(stores.length === 1 && only !== undefined ? [`import { store as kept } from '${only.from}'`] : []),
325
+ ...(stores.length > 1 ? named.map((one) => `import { keeps as ${one.as}Keeps, store as ${one.as} } from '${one.from}'`) : []),
326
+ '',
327
+ ...(keeping
328
+ ? [
329
+ 'const KEPT = new Map(ENTITIES.map((one) => [one.id, new Set(one.holds.map((each) => each.id))]))',
330
+ '',
331
+ 'const ADDRESSED = new Map(',
332
+ ' ENTITIES.flatMap((one) => {',
333
+ " const held = (one.must ?? []).flatMap((each) => ('unique' in each ? each.unique : []))[0]",
334
+ ' return held === undefined ? [] : [[one.id, held] as const]',
335
+ ' }),',
336
+ ')',
337
+ '',
338
+ 'const made = inMemorySeams({ entities: KEPT, keys: ADDRESSED }).seams',
339
+ ]
340
+ : ['const made = inMemorySeams().seams']),
341
+ '',
342
+ ...(stores.length > 1
343
+ ? [
344
+ "const WHERE = new Map<string, Seams['store']>([",
345
+ ...named.map((one) => ` ...${one.as}Keeps.map((entity) => [entity, ${one.as}] as const),`),
346
+ '])',
347
+ '',
348
+ 'function at(entity: string) {',
349
+ ' const found = WHERE.get(entity)',
350
+ " if (found === undefined) throw new Error(entity + ' is not kept in any target this app declares')",
351
+ ' return found',
352
+ '}',
353
+ '',
354
+ "const store: Seams['store'] = {",
355
+ ' insert: (actor, entity, row) => at(entity).insert(actor, entity, row),',
356
+ ' get: (actor, entity, key, fields) => at(entity).get(actor, entity, key, fields),',
357
+ ' list: (actor, entity, opts) => at(entity).list(actor, entity, opts),',
358
+ ' update: (actor, entity, key, patch, expectedVersion) => at(entity).update(actor, entity, key, patch, expectedVersion),',
359
+ ' remove: (actor, entity, key) => at(entity).remove(actor, entity, key),',
360
+ '}',
361
+ '',
362
+ ]
363
+ : []),
364
+ 'const shared = globalThis as { __threadSeams?: Seams }',
365
+ '',
366
+ 'export const DECLARED: Seams = (shared.__threadSeams ??= {',
367
+ ' ...made,',
368
+ ...(stores.length === 1 ? [' store: kept,'] : []),
369
+ ...(stores.length > 1 ? [' store,'] : []),
370
+ ' clock: { now: arrived },',
371
+ ' random: { next: Math.random, uuid: fresh },',
372
+ ' http: httpSeam(),',
373
+ ...(ruled ? [' permits: { check: (actor, action, resource) => Promise.resolve(permits(actor, action, resource)) },'] : []),
374
+ ...(vaulted ? [' vault: { resolve: opening },'] : []),
375
+ '})',
376
+ '',
377
+ 'function environment(request: Request): Environment {',
378
+ ...ENVIRONMENT.map(([fact, header]) => ` const ${fact} = request.headers.get(${quoted(header)})`),
379
+ ' return {',
380
+ ...ENVIRONMENT.map(([fact]) => ` ...(${fact} === null ? {} : { ${fact} }),`),
381
+ ' }',
382
+ '}',
383
+ '',
384
+ ...(keeping
385
+ ? [
386
+ 'const worn = (held: unknown): readonly string[] =>',
387
+ " Array.isArray(held) ? held.filter((one) => typeof one === 'string') : typeof held === 'string' ? [held] : []",
388
+ '',
389
+ 'function addressed(request: Request, from: string): string | null {',
390
+ " if (from.startsWith('cookie:')) {",
391
+ " const jar = request.headers.get('cookie') ?? ''",
392
+ " const want = `${from.slice('cookie:'.length).trim()}=`",
393
+ " const found = jar.split(';').map((one) => one.trim()).find((one) => one.startsWith(want))",
394
+ ' return found === undefined ? null : decodeURIComponent(found.slice(want.length))',
395
+ ' }',
396
+ ' const held = request.headers.get(from)',
397
+ ' if (held === null) return null',
398
+ " const bare = held.startsWith('Bearer ') ? held.slice('Bearer '.length) : held",
399
+ " return bare.trim() === '' ? null : bare.trim()",
400
+ '}',
401
+ '',
402
+ 'export async function actor(request: Request): Promise<Actor> {',
403
+ ' const anyone: Actor = { ...ANONYMOUS, ...environment(request) }',
404
+ ' const who = ENTITIES.find((one) => one.actor !== undefined)',
405
+ ' if (who?.actor === undefined) return anyone',
406
+ '',
407
+ " const address = (who.must ?? []).flatMap((one) => ('unique' in one ? one.unique : []))[0]",
408
+ ' if (address === undefined) return anyone',
409
+ '',
410
+ ' const held = addressed(request, who.actor.from)',
411
+ ' if (held === null) return anyone',
412
+ '',
413
+ ' const token = who.actor.verifies',
414
+ '',
415
+ ' const row =',
416
+ ' token === undefined',
417
+ ' ? await DECLARED.store.get(anyone, who.id, held, [',
418
+ ' address,',
419
+ ' who.actor.roles,',
420
+ ' ...(who.actor.entitlements === undefined ? [] : [who.actor.entitlements]),',
421
+ ' ])',
422
+ ' : ((await DECLARED.store.list(anyone, who.id, { where: { [token]: held }, limit: 1 }))[0] ?? null)',
423
+ ' if (row === null) return anyone',
424
+ '',
425
+ ' return {',
426
+ ' ...anyone,',
427
+ ' id: String(row[address] ?? held),',
428
+ ' roles: worn(row[who.actor.roles]),',
429
+ ' entitlements: who.actor.entitlements === undefined ? [] : worn(row[who.actor.entitlements]),',
430
+ ' }',
431
+ '}',
432
+ '',
433
+ ]
434
+ : [
435
+ 'export const actor = (request: Request): Promise<Actor> => Promise.resolve({ ...ANONYMOUS, ...environment(request) })',
436
+ '',
437
+ ]),
438
+ ].join('\n');
439
+ }
440
+ const seeded = () => [
441
+ `import { DECLARED } from '${beside(SEAMS_AT, DECLARED_SEAMS_AT)}'`,
442
+ `import type { Seams } from '${runtime(SEAMS_AT, '../seams.ts')}'`,
443
+ '',
444
+ 'export const SEAMS: Seams = {',
445
+ ' ...DECLARED,',
446
+ '}',
447
+ '',
448
+ ].join('\n');
449
+ const width = Math.max(...Object.keys(HANDED).map((one) => one.length));
450
+ const handing = (stores, keeping, vaulted) => [
451
+ `capabilities: seeded ${SEAMS_AT} — what this app hands its operations, and yours from here.`,
452
+ ` It hands on everything ${DECLARED_SEAMS_AT} settles from your declarations: rows go to ` +
453
+ (stores.length === 0 ? 'memory until a target keeps entities' : stores.join(', ')) +
454
+ `, who is asking is ${keeping ? 'read off the entity that names the caller' : 'nobody until an entity names the caller'}, ` +
455
+ `calls leave through http${vaulted ? ' with credentials opened by the vault' : ''}.`,
456
+ ' Replace a seam, or add one, by naming it in SEAMS. Every seam an operation can be handed:',
457
+ ...Object.entries(HANDED).map(([one, says]) => ` ${one.padEnd(width)} ${says}`),
458
+ '',
459
+ ].join('\n');
460
+ const served = (one) => one.live.includes('api') && compiled(one.operation);
461
+ const held = (one) => one.browsable && one.live.includes('api');
462
+ function dispatching(found) {
463
+ const reachable = found.filter(served);
464
+ return [
465
+ '/* GENERATED by thread — do not edit. It is derived from what this app declares. */',
466
+ '',
467
+ `declare module '${packageOf('ui')}/checked' {`,
468
+ ...(reachable.length === 0
469
+ ? [' interface Dispatching {}']
470
+ : [' interface Dispatching {', ...reachable.map((one) => ` readonly ${one.operation}: true`), ' }']),
471
+ '}',
472
+ '',
473
+ 'interface Reaches {',
474
+ ' readonly operation: string',
475
+ " readonly method: 'GET' | 'POST'",
476
+ ' readonly path: string',
477
+ '}',
478
+ '',
479
+ `const REACHES: readonly Reaches[] = [${reachable.length === 0
480
+ ? ''
481
+ : `\n${reachable
482
+ .map((one) => ` { operation: ${JSON.stringify(one.operation)}, method: ${JSON.stringify(one.api?.method)}, path: ${JSON.stringify(one.api?.path)} },`)
483
+ .join('\n')}\n`}]`,
484
+ '',
485
+ 'export async function dispatchTo(operation: string, input?: Readonly<Record<string, unknown>>): Promise<unknown> {',
486
+ ' const one = REACHES.find((each) => each.operation === operation)',
487
+ ' if (one === undefined) {',
488
+ ' throw new Error(',
489
+ " `thread: nothing declares an operation called ${operation}, so there is nowhere to send this. ` +",
490
+ " `This app reaches: ${REACHES.map((each) => each.operation).join(', ') || '(nothing)'}`,",
491
+ ' )',
492
+ ' }',
493
+ ' const sent = await fetch(one.path, {',
494
+ ' method: one.method,',
495
+ " headers: { 'content-type': 'application/json' },",
496
+ " ...(one.method === 'GET' ? {} : { body: JSON.stringify(input ?? {}) }),",
497
+ ' })',
498
+ ' return await sent.json()',
499
+ '}',
500
+ '',
501
+ ].join('\n');
502
+ }
503
+ const installed = () => existsSync(resolve(ROOT, '_threads', 'ui.ts'));
504
+ const files = (found) => [
505
+ { at: CAPABILITIES_AT, text: printed(found) },
506
+ { at: OPENAPI_AT, text: printed(openapi(found)) },
507
+ { at: MCP_AT, text: printed(mcp(found)) },
508
+ { at: CLIENT_AT, text: client(found) },
509
+ ...(installed() ? [{ at: DISPATCHING_AT, text: dispatching(found) }] : []),
510
+ ...found.filter(served).map((one) => ({ at: routeAt(one.operation), text: route(one) })),
511
+ ];
512
+ if (invoked(import.meta.url)) {
513
+ const { found, refusals } = await derive();
514
+ for (const one of refusals)
515
+ console.error(`capabilities: ${one}`);
516
+ const mark = (one) => marked([one.operation, one.describes, one.requires.join(','), one.nodes.join(','), one.live.join(',')]);
517
+ const words = wordsIn(['read']);
518
+ const verb = words[0];
519
+ if (verb === 'read') {
520
+ const which = words[1];
521
+ const one = found.find((each) => mark(each) === which);
522
+ if (one === undefined) {
523
+ console.error(`capabilities: \`${which ?? '(nothing)'}\` is not a mark this tree issued. Run \`thread capabilities\` to see them — each is printed in brackets beside the capability it belongs to.`);
524
+ exit(1);
525
+ }
526
+ stdout.write(`${one.operation} — ${one.describes}\n`);
527
+ stdout.write(`requires ${one.requires.length === 0 ? 'nothing' : one.requires.join(', ')}\n`);
528
+ stdout.write(`nodes: ${one.nodes.length === 0 ? '(none)' : one.nodes.join(', ')}\n`);
529
+ stdout.write(`live: ${SURFACES.map((each) => `${one.live.includes(each) ? '[on] ' : '[off]'} ${each}`).join(' ')}\n`);
530
+ stdout.write(`${one.browsable ? 'may be called from a page' : 'server only — it reaches a target that needs a server secret'}\n`);
531
+ stdout.write(`${one.local ? 'runs where it is called, with nothing sent' : 'reaches a target over the network'}\n`);
532
+ stdout.write(`api: ${one.api.method} ${one.api.path}\n`);
533
+ stdout.write(`cli: ${one.cli.invocation}\n`);
534
+ stdout.write(`mcp: ${one.mcp.name} — ${one.mcp.description}\n`);
535
+ stdout.write('input:\n');
536
+ for (const line of printed(one.input).trimEnd().split('\n'))
537
+ stdout.write(` ${line}\n`);
538
+ exit(0);
539
+ }
540
+ const checking = argv.includes('--check');
541
+ let adrift = 0;
542
+ const keeping = existsSync(resolve(ROOT, ENTITIES_AT));
543
+ if (existsSync(resolve(ROOT, OPERATIONS_AT))) {
544
+ const stores = kept();
545
+ const vaulted = existsSync(resolve(ROOT, VAULT_AT));
546
+ const text = settled(keeping, stores, existsSync(resolve(ROOT, PERMITS_AT)), vaulted);
547
+ const at = resolve(ROOT, DECLARED_SEAMS_AT);
548
+ if ((existsSync(at) ? readFileSync(at, 'utf8') : '') !== text) {
549
+ if (checking) {
550
+ adrift += 1;
551
+ console.error(`capabilities: ${DECLARED_SEAMS_AT} is not what the declarations settle. Run \`thread capabilities\` and commit the result.`);
552
+ }
553
+ else {
554
+ mkdirSync(dirname(at), { recursive: true });
555
+ writeFileSync(at, text, 'utf8');
556
+ console.log(`capabilities: wrote ${DECLARED_SEAMS_AT} — the seams your targets, entities, grants and secrets settle.`);
557
+ }
558
+ }
559
+ const seed = resolve(ROOT, SEAMS_AT);
560
+ if (!existsSync(seed)) {
561
+ if (checking) {
562
+ adrift += 1;
563
+ console.error(`capabilities: ${SEAMS_AT} is not there, and it is what hands every operation its seams. Run \`thread capabilities\` — it seeds the file once, and from then on it is yours.`);
564
+ }
565
+ else {
566
+ mkdirSync(dirname(seed), { recursive: true });
567
+ writeFileSync(seed, seeded(), 'utf8');
568
+ stdout.write(handing(stores, keeping, vaulted));
569
+ }
570
+ }
571
+ }
572
+ if (found.length === 0 && refusals.length === 0) {
573
+ console.log('capabilities: this app declares no operation, so nothing is reachable through one yet.');
574
+ exit(adrift > 0 ? 1 : 0);
575
+ }
576
+ const written = files(found);
577
+ if (checking) {
578
+ const drifted = written.filter((one) => {
579
+ const path = resolve(ROOT, one.at);
580
+ return (existsSync(path) ? readFileSync(path, 'utf8') : '') !== one.text;
581
+ });
582
+ for (const one of drifted) {
583
+ console.error(`capabilities: ${one.at} is not what the operations derive. Run \`thread capabilities\` and commit the result.`);
584
+ }
585
+ const off = found.filter((one) => one.live.length < SURFACES.length);
586
+ console.log(`capabilities: ${String(found.length)} operation(s) reachable, ${String(off.length)} with a surface switched off, ` +
587
+ `${String(drifted.length + adrift)} finding(s).`);
588
+ exit(drifted.length + adrift > 0 || refusals.length > 0 ? 1 : 0);
589
+ }
590
+ if (refusals.length > 0) {
591
+ console.error(`capabilities: ${String(refusals.length)} refusal(s) above, so nothing was written`);
592
+ exit(1);
593
+ }
594
+ for (const one of written) {
595
+ const path = resolve(ROOT, one.at);
596
+ mkdirSync(dirname(path), { recursive: true });
597
+ writeFileSync(path, one.text);
598
+ }
599
+ const shipped = found.filter(held).length;
600
+ stdout.write(`capabilities: wrote ${written.map((one) => one.at).join(', ')} — ${String(found.length)} operation(s), ` +
601
+ `${String(shipped)} of them in the client bundle.\n`);
602
+ exit(0);
603
+ }
@@ -0,0 +1,2 @@
1
+ export declare function parts(text: string): readonly string[];
2
+ export declare function compile(say?: boolean): Promise<number>;