@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,104 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ export const VAULT_DIR = '.thread';
5
+ export const SEALED_AT = `${VAULT_DIR}/vault.sealed.json`;
6
+ export const KEY_AT = `${VAULT_DIR}/vault.key`;
7
+ export const ALGORITHM = 'aes-256-gcm';
8
+ export const KEY_BYTES = 32;
9
+ const IV_BYTES = 12;
10
+ const noKey = (root) => new Error(`there is no vault key at ${join(root, KEY_AT)}, so nothing here can open a sealed value. It is deliberately not ` +
11
+ 'in version control — whoever set this app up has it. Ask them for it, or run `thread create secret`, which ' +
12
+ 'generates one when there is none.');
13
+ const unsealable = (named, why) => new Error(`"${named}" is in the sealed store and would not open: ${why}. Either the key is not the one it was sealed under, ` +
14
+ 'or the store has been edited by hand — it is ciphertext, and there is nothing in it to edit.');
15
+ const undeclared = (named, known) => new Error(`nothing may ask for a secret this app does not declare, and "${named}" is not one of ` +
16
+ `${known.length === 0 ? '(this app declares none)' : known.join(', ')}. A value is asked for by something that ` +
17
+ 'declared it needed it, or not at all.');
18
+ const unset = (named, env) => new Error(`"${named}" is declared and has no value sealed for \`${env}\`. Run \`thread create secret\` and give it one — a ` +
19
+ 'declared secret nothing was ever given is refused here rather than resolved to an empty string.');
20
+ const store = (root) => {
21
+ const at = join(root, SEALED_AT);
22
+ if (!existsSync(at))
23
+ return {};
24
+ try {
25
+ const held = JSON.parse(readFileSync(at, 'utf8'));
26
+ return typeof held === 'object' && held !== null ? held : {};
27
+ }
28
+ catch {
29
+ return {};
30
+ }
31
+ };
32
+ export const keyOf = (root) => {
33
+ const at = join(root, KEY_AT);
34
+ if (!existsSync(at))
35
+ return undefined;
36
+ try {
37
+ const held = Buffer.from(readFileSync(at, 'utf8').trim(), 'base64');
38
+ return held.length === KEY_BYTES ? held : undefined;
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ };
44
+ export function keyed(root) {
45
+ const held = keyOf(root);
46
+ if (held !== undefined)
47
+ return held;
48
+ const made = randomBytes(KEY_BYTES);
49
+ mkdirSync(join(root, VAULT_DIR), { recursive: true });
50
+ writeFileSync(join(root, KEY_AT), `${made.toString('base64')}\n`, { mode: 0o600 });
51
+ return made;
52
+ }
53
+ export function seal(root, named, env, value) {
54
+ const key = keyed(root);
55
+ const iv = randomBytes(IV_BYTES);
56
+ const cipher = createCipheriv(ALGORITHM, key, iv);
57
+ const body = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]);
58
+ const held = store(root);
59
+ held[named] = { ...held[named], [env]: [iv, cipher.getAuthTag(), body].map((one) => one.toString('base64')).join('.') };
60
+ const sorted = Object.fromEntries(Object.keys(held).sort().map((one) => [one, held[one]]));
61
+ const at = join(root, SEALED_AT);
62
+ mkdirSync(dirname(at), { recursive: true });
63
+ writeFileSync(at, `${JSON.stringify(sorted, null, 2)}\n`);
64
+ }
65
+ export const sealedIn = (root, env) => Object.entries(store(root))
66
+ .filter(([, held]) => held[env] !== undefined)
67
+ .map(([named]) => named)
68
+ .sort();
69
+ function unsealed(key, named, held) {
70
+ const parts = held.split('.');
71
+ if (parts.length !== 3)
72
+ throw unsealable(named, 'it is not three base64 parts separated by dots');
73
+ const [iv, tag, body] = parts.map((one) => Buffer.from(one, 'base64'));
74
+ try {
75
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
76
+ decipher.setAuthTag(tag);
77
+ return `${decipher.update(body).toString('utf8')}${decipher.final('utf8')}`;
78
+ }
79
+ catch (thrown) {
80
+ throw unsealable(named, thrown instanceof Error ? thrown.message : String(thrown));
81
+ }
82
+ }
83
+ export function opened(root, declared, env) {
84
+ const known = new Set(declared);
85
+ return async (refs) => {
86
+ if (refs.length === 0)
87
+ return {};
88
+ const key = keyOf(root);
89
+ if (key === undefined)
90
+ throw noKey(root);
91
+ const held = store(root);
92
+ const out = {};
93
+ for (const named of refs) {
94
+ if (!known.has(named))
95
+ throw undeclared(named, [...known].sort());
96
+ const sealed = held[named]?.[env];
97
+ if (sealed === undefined)
98
+ throw unset(named, env);
99
+ out[named] = unsealed(key, named, sealed);
100
+ }
101
+ return out;
102
+ };
103
+ }
104
+ export const IGNORES = [KEY_AT];
@@ -0,0 +1,2 @@
1
+ import type { Attaching } from '../core/attach.ts';
2
+ export declare const ATTACH: Attaching;
@@ -0,0 +1,213 @@
1
+ import { named } from "../core/scope.js";
2
+ import { quoted } from "../core/rows.js";
3
+ const DESCRIBES = 'What this app refuses, and the checks that make each refusal actually happen. Every check has one owner, so none of them can be switched off somewhere nobody is looking.';
4
+ const CONSTRAINT = 'The rules the framework enforces on every app that installs it. They are read from the installed package rather than from this tree, so there is nothing here to weaken.';
5
+ const RULES = 'What this app refuses on top of what the framework already does. `thread create rule` asks what to ban, where it applies and what to do instead, then writes the row and the check beside it.';
6
+ const HOME = `node_modules/${named('integrity')}/dist`;
7
+ const GATE = 'Every surface that can refuse, each naming where it lives and the command that runs it, so the gate is derived from what this system declares rather than kept as a string beside it.';
8
+ const COMPILER = 'The compiler run over the files this app declares and no others. A type error is the plainest kind of broken build, so it is refused here rather than found by whoever runs the app next.';
9
+ const INTEGRITY_SOURCE = `import type { Threaded } from '../core/thread.ts'
10
+
11
+ export const INTEGRITY: Threaded = {
12
+ id: 'integrity',
13
+ at: '_threads/integrity.ts',
14
+ describes: ${quoted(DESCRIBES)},
15
+ version: 1,
16
+ declares: [
17
+ {
18
+ id: 'compiler',
19
+ at: 'tsconfig.json',
20
+ describes: ${quoted(COMPILER)},
21
+ version: 1,
22
+ by: 'thread compiler',
23
+ },
24
+ {
25
+ id: 'constraint',
26
+ at: '${HOME}/constraint.js',
27
+ describes: ${quoted(CONSTRAINT)},
28
+ version: 1,
29
+ },
30
+ {
31
+ id: 'rules',
32
+ at: '_threads/rules.ts',
33
+ describes: ${quoted(RULES)},
34
+ version: 1,
35
+ },
36
+ {
37
+ id: 'gate',
38
+ at: '_threads/gate.ts',
39
+ describes: ${quoted(GATE)},
40
+ version: 1,
41
+ },
42
+ ],
43
+ }
44
+ `;
45
+ const RULES_SOURCE = `import type { Constraint } from './constraint.ts'
46
+
47
+ export const RULES: readonly Constraint[] = []
48
+ `;
49
+ const GATE_SOURCE = `import type { Threaded } from '../core/thread.ts'
50
+
51
+ export const GATE: Threaded = {
52
+ id: 'gate',
53
+ at: '_threads/gate.ts',
54
+ describes: ${quoted(GATE)},
55
+ version: 1,
56
+ declares: [],
57
+ }
58
+ `;
59
+ const SEEDED = {
60
+ attaches: ['@prestack/thread/derivation/attach'],
61
+ id: 'integrity',
62
+ declares: [
63
+ ' {',
64
+ ' id: \'integrity\',',
65
+ ' at: \'_threads/integrity.ts\',',
66
+ ' describes: \'What this app refuses, and the checks that make each refusal actually happen. Every check has one owner, so none of them can be switched off somewhere nobody is looking.\',',
67
+ ' version: 1,',
68
+ ' },',
69
+ ].join('\n'),
70
+ };
71
+ const SHIPPED = [
72
+ {
73
+ into: 'derivation.ts',
74
+ list: 'GENERATORS',
75
+ id: 'gate-generator',
76
+ source: " {\n id: 'gate-generator',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/gate.js',\n describes: 'The generator of the gate chain — it reads every row that names a command and refuses one naming none, rather than writing a shorter gate.',\n version: 1,\n by: 'thread gate',\n },",
77
+ },
78
+ {
79
+ into: 'derivation.ts',
80
+ list: 'GENERATORS',
81
+ id: 'compiler-generator',
82
+ source: " {\n id: 'compiler-generator',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/compiler.js',\n describes: 'The generator of the compiler config — it walks this tree, leaves out every directory with nothing declared beneath it, and writes that list into the config as what the compiler may not read. It checks the same list before letting the compiler run, because a compile that was pointed at the wrong code is not a compile that passed.',\n version: 1,\n by: 'thread compiler',\n runs: 'thread compiler --check',\n },",
83
+ },
84
+ {
85
+ into: 'derivation.ts',
86
+ list: 'GENERATORS',
87
+ id: 'descent-runner',
88
+ source: " {\n id: 'descent-runner',\n at: 'node_modules/@prestack/thread/dist/integrity/check-threads.js',\n describes: 'The walk over everything this app tracks, refusing any file no row names. It runs before anything is built, so a file nothing declares stops the build rather than reaching a person.',\n version: 1,\n by: 'thread descent',\n runs: 'thread descent',\n },",
89
+ },
90
+ {
91
+ into: 'derivation.ts',
92
+ list: 'GENERATORS',
93
+ id: 'sweep-runner',
94
+ source: " {\n id: 'sweep-runner',\n at: 'node_modules/@prestack/thread/dist/integrity/sweep.js',\n describes: 'Every rule — the framework\\'s and this app\\'s own — run over the files it governs. A rule that could not run is named rather than counted as one that passed.',\n version: 1,\n by: 'thread sweep',\n runs: 'thread sweep',\n },",
95
+ },
96
+ {
97
+ into: 'derivation.ts',
98
+ list: 'GENERATORS',
99
+ id: 'issues-reader',
100
+ source: " {\n id: 'issues-reader',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/issues.js',\n describes: 'Everything wrong with this tree in one listing: files no row declares, files that will not compile, and what the rules refuse. It only reports, so it answers even when every other check has stopped.',\n version: 1,\n by: 'thread issues',\n runs: 'thread issues --check',\n },",
101
+ },
102
+ {
103
+ into: 'derivation.ts',
104
+ list: 'GENERATORS',
105
+ id: 'publish-runner',
106
+ source: " {\n id: 'publish-runner',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/publish.js',\n describes: 'Sends this repository\\'s packages to a registry. It derives what each package says about itself first, then builds, then refuses to send anything until the gate, the typecheck, the lint and the seal have each passed, and commits what it derived once the packages are out. The seal is checked rather than regenerated, because a release that rewrites its own seal can never fail it.',\n version: 1,\n by: 'thread publish',\n },",
107
+ },
108
+ {
109
+ into: 'derivation.ts',
110
+ list: 'GENERATORS',
111
+ id: 'rule-noun',
112
+ source: " {\n id: 'rule-noun',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/rule.js',\n describes: 'Declares a rule this app holds itself to. `thread create rule` writes the row and seeds the check beside it, so a new rule goes through the same wizard as everything else rather than being written by hand.',\n version: 1,\n by: 'thread create rule',\n },",
113
+ },
114
+ {
115
+ into: 'derivation.ts',
116
+ list: 'GENERATORS',
117
+ id: 'seal-generator',
118
+ source: " {\n id: 'seal-generator',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/seal.js',\n describes: 'Writes the seal: a hash of every built file the integrity package ships, so an installed copy can prove it is the one that went out. With --check it writes nothing and refuses a seal that no longer matches the built files.',\n version: 1,\n by: 'thread seal',\n runs: 'thread seal --check',\n },",
119
+ },
120
+ ];
121
+ export const ATTACH = {
122
+ attaches: SEEDED.attaches,
123
+ id: SEEDED.id,
124
+ declares: SEEDED.declares,
125
+ writes: [
126
+ { file: 'integrity.ts', source: INTEGRITY_SOURCE },
127
+ { file: 'rules.ts', source: RULES_SOURCE },
128
+ { file: 'gate.ts', source: GATE_SOURCE },
129
+ ],
130
+ retires: [
131
+ {
132
+ into: 'gate.ts',
133
+ list: 'declares',
134
+ id: 'compiler',
135
+ why: 'moved into `GENERATORS` beside the generator that writes it, which is what makes `tsconfig.json` settle when declarations move. Left here it would be a second gate step running the same command, which the gate refuses.',
136
+ },
137
+ {
138
+ into: 'gate.ts',
139
+ list: 'declares',
140
+ id: 'descent',
141
+ why: 'its step now comes from the `descent-runner` row this package ships into `GENERATORS`, which states `runs` beside `by`. Left here it would be a second gate step running the same command, which the gate refuses.',
142
+ },
143
+ {
144
+ into: 'gate.ts',
145
+ list: 'declares',
146
+ id: 'sweep',
147
+ why: 'its step now comes from the `sweep-runner` row this package ships into `GENERATORS`, which states `runs` beside `by`. Left here it would be a second gate step running the same command, which the gate refuses.',
148
+ },
149
+ {
150
+ into: 'constraint.ts',
151
+ list: 'GENERATORS',
152
+ id: 'sql-duplicate-id',
153
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
154
+ },
155
+ {
156
+ into: 'constraint.ts',
157
+ list: 'GENERATORS',
158
+ id: 'sql-name-collision',
159
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
160
+ },
161
+ {
162
+ into: 'constraint.ts',
163
+ list: 'GENERATORS',
164
+ id: 'sql-duplicate-parameter',
165
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
166
+ },
167
+ {
168
+ into: 'constraint.ts',
169
+ list: 'GENERATORS',
170
+ id: 'sql-unwritable-parameter-type',
171
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
172
+ },
173
+ {
174
+ into: 'constraint.ts',
175
+ list: 'GENERATORS',
176
+ id: 'sql-duplicate-returned-column',
177
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
178
+ },
179
+ {
180
+ into: 'constraint.ts',
181
+ list: 'GENERATORS',
182
+ id: 'sql-unwritable-return-type',
183
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
184
+ },
185
+ {
186
+ into: 'constraint.ts',
187
+ list: 'GENERATORS',
188
+ id: 'sql-unknown-write-target',
189
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
190
+ },
191
+ {
192
+ into: 'constraint.ts',
193
+ list: 'GENERATORS',
194
+ id: 'sql-multi-statement',
195
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
196
+ },
197
+ ],
198
+ ignores: ['.thread/', '.next/', 'node_modules/', 'tsconfig.tsbuildinfo'],
199
+ scripts: {
200
+ gate: 'thread gate',
201
+ predev: 'thread gate',
202
+ dev: 'next dev',
203
+ prebuild: 'thread gate',
204
+ build: 'next build',
205
+ prestart: 'thread gate',
206
+ start: 'next start',
207
+ },
208
+ needs: {
209
+ '@types/node': '^24.0.0',
210
+ typescript: '^6.0.0',
211
+ },
212
+ rows: SHIPPED,
213
+ };
@@ -0,0 +1,10 @@
1
+ import { ROOT, THREAD_AT, TREE, descend, type Finding, type Pass } from '../core/descend.ts';
2
+ export { ROOT, THREAD_AT, TREE, descend };
3
+ export type { Finding, Pass };
4
+ export declare function trackedFiles(): readonly string[];
5
+ export declare function unthreaded(pass: Pass, files: readonly string[]): readonly string[];
6
+ export declare function twiceParented(pass: Pass): readonly {
7
+ readonly at: string;
8
+ readonly parents: readonly string[];
9
+ }[];
10
+ export declare function refusals(pass: Pass, files: readonly string[]): readonly Finding[];
@@ -0,0 +1,153 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readFileSync, readdirSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { argv, stdout } from 'node:process';
5
+ import { exit, invoked } from "../core/invoked.js";
6
+ import { ROOT, THREAD_AT, TREE, configured, descend } from "../core/descend.js";
7
+ import { runChecker } from "../core/finding.js";
8
+ export { ROOT, THREAD_AT, TREE, descend };
9
+ const ELSEWHERE = new Set(['node_modules', '.git']);
10
+ const OWN_IGNORES = (() => {
11
+ if (spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: ROOT, stdio: 'ignore' }).status === 0) {
12
+ return undefined;
13
+ }
14
+ try {
15
+ return readFileSync(join(ROOT, '.gitignore'), 'utf8')
16
+ .split('\n')
17
+ .map((one) => one.trim())
18
+ .filter((one) => one !== '' && !one.startsWith('#') && !one.startsWith('!'))
19
+ .map((one) => one.replace(/^\/+|\/+$/g, ''))
20
+ .filter((one) => one !== '' && !one.includes('*'));
21
+ }
22
+ catch {
23
+ return [];
24
+ }
25
+ })();
26
+ function unkept(at) {
27
+ if (OWN_IGNORES === undefined) {
28
+ const out = spawnSync('git', ['check-ignore', '-q', '--', at], { cwd: ROOT, stdio: 'ignore' });
29
+ return out.status === 0;
30
+ }
31
+ return OWN_IGNORES.some((one) => at === one || at.startsWith(`${one}/`) || at.split('/').includes(one));
32
+ }
33
+ export function trackedFiles() {
34
+ const found = [];
35
+ const walk = (at, under) => {
36
+ for (const one of readdirSync(at, { withFileTypes: true })) {
37
+ if (ELSEWHERE.has(one.name))
38
+ continue;
39
+ const here = under === '' ? one.name : `${under}/${one.name}`;
40
+ if (one.isDirectory()) {
41
+ if (unkept(here))
42
+ continue;
43
+ walk(join(at, one.name), here);
44
+ continue;
45
+ }
46
+ if (unkept(here))
47
+ continue;
48
+ found.push(here);
49
+ }
50
+ };
51
+ walk(ROOT, '');
52
+ return found.sort();
53
+ }
54
+ export function unthreaded(pass, files) {
55
+ return files.filter((one) => one !== THREAD_AT && !pass.threaded.has(one));
56
+ }
57
+ export function twiceParented(pass) {
58
+ const found = [];
59
+ for (const [at, held] of pass.threaded) {
60
+ const parents = held.filter((one) => one.list === 'declares').map((one) => one.by);
61
+ if (parents.length > 1)
62
+ found.push({ at, parents });
63
+ }
64
+ return found;
65
+ }
66
+ export function refusals(pass, files) {
67
+ const found = [...pass.broken, ...pass.findings];
68
+ const held = configured();
69
+ if (held.trouble !== undefined) {
70
+ found.push({
71
+ file: held.trouble.split(' ')[0] ?? 'tsconfig.json',
72
+ detail: `the gate fell back to its own compiler options because ${held.trouble}`,
73
+ instead: 'fix the config, or point the gate at one it can read. Falling back silently means declarations are judged by options nobody chose, and the run says none was found when one was',
74
+ });
75
+ }
76
+ return withFiles(found, pass, files);
77
+ }
78
+ function withFiles(found, pass, files) {
79
+ for (const one of unthreaded(pass, files)) {
80
+ const by = pass.mentioned.get(one) ?? [];
81
+ if (by.length === 0) {
82
+ found.push({
83
+ file: one,
84
+ detail: 'nothing declares this — the descent from the root never reaches it, so it is not wired',
85
+ instead: 'add a row naming this path to the node that owns it, with all four fields, or delete the file. A file that nothing names is either somebody\'s child nobody wrote down, or it is nobody\'s',
86
+ });
87
+ continue;
88
+ }
89
+ const cut = by.filter((one) => one !== THREAD_AT && !pass.threaded.has(one));
90
+ if (cut.length > 0) {
91
+ found.push({
92
+ file: one,
93
+ detail: `broken thread — ${cut.join(' and ')} names this, and nothing declares ${cut.length === 1 ? 'it' : 'them'}, so the descent never arrives`,
94
+ instead: 'thread the parent. The row is written and the file is where it says, but the line back to the root is cut above it, and nothing below the cut is reached',
95
+ });
96
+ continue;
97
+ }
98
+ found.push({
99
+ file: one,
100
+ detail: `broken thread — ${by.join(' and ')} names this and is itself declared, and the descent still never arrived, so the row was read and never walked`,
101
+ instead: 'the cut is inside the parent, not above it. Its node answered its own parent with a shape that row does not state, so the descent refused it and stopped there — fix the shape the parent states, and everything under it joins up',
102
+ });
103
+ }
104
+ for (const one of twiceParented(pass)) {
105
+ found.push({
106
+ file: one.at,
107
+ detail: `declared by ${one.parents.join(' and ')} — declares is exactly one parent ever, so its owner, its shape and its origin each have two answers and the tree gives none`,
108
+ instead: 'keep the row that owns it and delete the other. If both are true, one of them is a `derives` or a `seeds`, which may have many parents because neither of them owns the child',
109
+ });
110
+ }
111
+ return found;
112
+ }
113
+ if (invoked(import.meta.url)) {
114
+ const pass = await descend();
115
+ const tracked = trackedFiles();
116
+ const findings = refusals(pass, tracked);
117
+ if (argv.includes('--json')) {
118
+ stdout.write(`${JSON.stringify(findings)}\n`);
119
+ exit(0);
120
+ }
121
+ const fixed = [...pass.threaded.keys()];
122
+ const reach = new Map();
123
+ for (const one of fixed)
124
+ reach.set(dirname(one), (reach.get(dirname(one)) ?? 0) + 1);
125
+ const held = configured();
126
+ stdout.write(`threads: descended ${String(pass.declarations)} declaration(s) over ${String(pass.rows)} row(s), naming ${String(fixed.length)} file(s) and coining ${String(pass.coined.size)} word(s).\n`);
127
+ stdout.write(held.trouble !== undefined
128
+ ? `threads: compiler options fallen back to — ${held.trouble}\n`
129
+ : held.from === ''
130
+ ? 'threads: compiler options fallen back to — nothing at the root states a tsconfig, so what the build compiles with may differ from what judged this\n'
131
+ : `threads: compiler options taken from ${held.from} at the root — what a package builds with may still differ from this\n`);
132
+ const here = new Set(tracked);
133
+ for (const [dir, held] of [...reach].sort()) {
134
+ const present = tracked.filter((one) => dirname(one) === dir).length;
135
+ const mine = [...pass.threaded.keys()].filter((one) => dirname(one) === dir && here.has(one)).length;
136
+ stdout.write(present === 0
137
+ ? `threads: ${dir} — ${String(held)} file(s) named, none of them tracked here\n`
138
+ : `threads: ${dir} — ${String(mine)} of ${String(present)} tracked file(s) named\n`);
139
+ }
140
+ const named = [...pass.threaded.keys()].filter((one) => here.has(one)).length;
141
+ runChecker('threads', findings, `${String(pass.declarations)} declaration(s), ${String(pass.rows)} row(s), ${String(named)} of ${String(tracked.length)} tracked file(s) named`);
142
+ const warned = findings.filter((one) => one.severity === 'warning');
143
+ const refused = findings.filter((one) => one.severity !== 'warning');
144
+ if (warned.length > 0) {
145
+ stdout.write(`threads: ${String(warned.length)} of these is a derivation nothing has produced yet — the row states what a ` +
146
+ 'generator would write and no file is there. A generator that refuses to write an empty file is doing its ' +
147
+ 'job, so this is said rather than refused. A derived file that is there and differs from what its generator ' +
148
+ 'writes is the other case, and that one fails.\n');
149
+ }
150
+ if (refused.length > 0)
151
+ exit(1);
152
+ exit(0);
153
+ }
@@ -0,0 +1,16 @@
1
+ import type { Threaded } from '../core/thread.ts';
2
+ export interface Constraint extends Threaded {
3
+ readonly version: number;
4
+ readonly where: {
5
+ readonly paths: readonly string[];
6
+ readonly except?: readonly string[];
7
+ };
8
+ readonly bans: string;
9
+ readonly because: string;
10
+ readonly instead: string;
11
+ readonly wrong: string;
12
+ readonly right: string;
13
+ readonly governs?: string;
14
+ readonly severity?: 'forbidden' | 'warning';
15
+ }
16
+ export declare const CONSTRAINTS: readonly Constraint[];