@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,1246 +0,0 @@
1
- import type { Threaded } from '../core/thread.ts'
2
- import { named } from '../core/scope.ts'
3
-
4
- export interface Sourced {
5
- readonly at: string
6
- readonly source: string
7
- }
8
-
9
- export type Proof = string | { readonly files: readonly Sourced[]; readonly at: string }
10
-
11
- export function shown(proof: Proof): string {
12
- if (typeof proof === 'string') return proof
13
- const target = proof.files.find((one) => one.at === proof.at) ?? proof.files[0]
14
- const beside = proof.files.filter((one) => one.at !== proof.at).map((one) => one.at)
15
- return `${target?.source ?? ''}${beside.length > 0 ? ` (in a tree holding ${beside.join(', ')})` : ''}`
16
- }
17
-
18
- export interface Constraint extends Threaded {
19
- readonly version: number
20
- readonly where: { readonly paths: readonly string[] }
21
- readonly bans: string
22
- readonly because: string
23
- readonly instead: string
24
- readonly wrong: Proof
25
- readonly right: Proof
26
- readonly governs?: string
27
- readonly severity?: 'forbidden' | 'warning'
28
- }
29
-
30
- export const CONSTRAINTS: readonly Constraint[] = [
31
- {
32
- id: 'no-comment',
33
- at: './constraint.ts',
34
- describes: 'The refusal that keeps meaning in one place — an agent reads prose beside the code, believes it over the code, and writes more of it, so explanation accumulates where nothing checks it while the glossary declared to hold it stays empty.',
35
- version: 1,
36
- where: { paths: ['.'] },
37
- bans: 'a comment of any kind — block, line or JSDoc',
38
- because:
39
- 'measured 2026-08-13: 52,187 of this repository\'s 124,964 lines are comment, and the five thread files carry 739 of them against a glossary holding 27 words with 27 empty meanings — so meaning lives in prose that nothing checks while the one place declared to hold it is empty. A single read of those five files found 17 statements contradicting the code beside them: three teach the `parent:` field `no-child-parent` refuses, one reports a 61-row book that holds 18, and one names `pnpm ci` as the gate when the script is `pnpm gate`',
40
- instead: 'write the meaning in `threads/glossary.ts`, and whatever a file must say about itself in the row that parents it — its `describes` and its `cites`',
41
- wrong: '/** THREAD — the root. The only node that exists without being declared. */',
42
- right: "{ id: 'thread', means: 'The root. The only node that exists without being declared.' }",
43
- cites: [{ on: '2026-08-13', said: 'I want a law, that no comment is allowed. There is a glossary, and meaning goes in the glossary. The code tells what the app does, not comments. AIs read comments, ignore code, and write more comments, and the comments are all slop or lie.' }],
44
- seeds: [
45
- {
46
- id: 'slopnator',
47
- at: './slopnators/sl_no_comment.ts',
48
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
49
- cites: [{ on: '2026-08-14', said: 'A law is written, names a slopnator. Slopnator is seeded magically. Someone write the function. An enforcer picks it up and it just works.' }],
50
- },
51
- ],
52
- },
53
-
54
- {
55
- id: 'secret-literal',
56
- at: './constraint.ts',
57
- describes: 'The refusal of a credential written into a file — the one leak this framework can actually prevent, because a value in a tree is in every copy of it, in its history for ever, and in whatever bundle reaches it.',
58
- version: 1,
59
- where: { paths: ['.'] },
60
- bans: 'a credential written as a literal — a vendor key by its own prefix, a private key block, a password inside a connection string, or a long opaque value assigned to something called a secret, a password, a token or a key',
61
- because:
62
- 'a secret is the one thing here that cannot be taken back. A wrong schema is regenerated and a wrong row is edited, but a credential committed once is in the history of every clone of that repository and has to be rotated at the vendor — and the framework itself never ships, so the only place a value can leak from is an app somebody built with it. Nothing else refuses this: the descent asks who declared a file, `undeclared-crossing` asks what a node reaches, and neither reads what a string contains. A vendor prefix is matched on its own because it is certain rather than probable: nothing else in a source file begins `sk_live_` or `AKIA` followed by twenty characters. A value that does not announce itself needs BOTH halves — a name that means credential AND something long and mixed enough to be one — because either alone is noise, a short assignment being a map key and a long opaque string being as likely a hash, a path or a fixture. Four things are deliberately not findings: a secret this app declares, which is a name and is the whole remedy this law recommends; an environment variable, whose contents this law cannot see and which `unvaulted-secret` refuses outright — a value arriving from the environment never passed the vault, which makes it a second defect rather than the remedy for this one; the obvious placeholders somebody writes while wiring something up; and the sealed store, which is base64 by construction, so the entropy half would find every value in it and recommend the thing the file already is — a law whose refusal has no honest remedy is one people learn to ignore. Measured 2026-09-17, against the rule that a check may hold no list of its own: this one holds twelve and they cannot come from the tree. A tree declares the NAMES of the secrets an app keeps, never the SHAPES of the keys other companies issue, so there is nothing here to read. Every scanner that exists spells the formats, and the one alternative — refusing whatever merely looks random — is the entropy half this row already rejects above. The list stands knowingly, and this is the one place in this framework where a check reaches exactly as far as somebody typed.',
63
- instead:
64
- 'declare it with `thread create secret`, which asks a person for the value at a terminal with the echo off and seals it into the store beside the tree. The row holds the name and the sentence and has no field for a value at all; a target requires the row by name, and dispatch resolves it at the moment of the call',
65
- wrong: "export const stripe = { apiKey: 'sk_live_51H8xQvL2eZvKYlo2C0Nx9K4mQwErTyUiOp' }",
66
- right: "export const node = nd_action(async (opened) => await opened.ht_stripe.charge({ json: { amount: 100 } }))",
67
- cites: [
68
- { on: '2026-08-29', said: 'some of the secrets are server and extremely important, others are public....so we need a way to make this extremely robust to attacks or leaks. Mostly leaks since the framework doesn\'t ship' },
69
- { on: '2026-08-29', said: 'we need constraints.ts to have ways to protect us' },
70
- ],
71
- seeds: [
72
- {
73
- id: 'slopnator',
74
- at: './slopnators/sl_secret_literal.ts',
75
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
76
- cites: [{ on: '2026-08-14', said: 'A law is written, names a slopnator. Slopnator is seeded magically. Someone write the function. An enforcer picks it up and it just works.' }],
77
- },
78
- ],
79
- },
80
-
81
- {
82
- id: 'no-import-cycle',
83
- at: './constraint.ts',
84
- describes: 'The refusal that keeps the tree readable from the top — a ring of files has no first one, so there is no order to descend in, and it loads at all only while every back edge happens to be type-only.',
85
- version: 1,
86
- where: { paths: ['.'] },
87
- bans: 'a file importing a file that imports it back',
88
- because:
89
- 'measured 2026-08-14: threads/ holds six files and three of them — thread.ts, types.ts and meta.ts — import each other in a ring. It compiled only because every back edge happened to be type-only; make one of them a value import and the package stops loading. A ring has no first file, so there is no order to read the tree in, and reading it top down is the whole premise. Measured 2026-08-15, after the shapes became one interface: the ring is gone and every import points down — thread.ts imports nothing, meta.ts and ci.ts take from it, law.ts and glossary.ts take from meta.ts. A guard that has not fired since is a guard doing its job, and the ring took one refactor to reappear once already',
90
- instead:
91
- 'move the thing that is depended upon into the file that is depended upon, so every import points one way. Where that fights the parent that declares it, the depended-upon thing wins — the alternative is code that does not compile',
92
- wrong: "import { Threaded } from './probe.tsx'",
93
- right: "import type { Threaded } from '../core/thread.ts'",
94
- cites: [{ on: '2026-08-14', said: 'does any file import a file that imports it back? Three do right now. That is the whole check, and it is the kind of thing a law could hold. Do this as a law first.' }],
95
- seeds: [
96
- {
97
- id: 'slopnator',
98
- at: './slopnators/sl_no_import_cycle.ts',
99
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
100
- cites: [{ on: '2026-08-14', said: 'A law is written, names a slopnator. Slopnator is seeded magically. Someone write the function. An enforcer picks it up and it just works.' }],
101
- },
102
- ],
103
- },
104
-
105
- {
106
- id: 'undescribed',
107
- at: './constraint.ts',
108
- describes: 'The one refusal a type cannot make — a required string is satisfied by an empty one and by a restatement of the name beside it, so presence is all a compiler ever proves and content has to be checked by something else.',
109
- version: 1,
110
- where: { paths: ['threads/'] },
111
- bans: 'a `describes` or `means` that is empty, or whose every word beyond the common ones already appears in the id beside it',
112
- because:
113
- 'measured 2026-08-14: threads/glossary.ts holds 27 words and 27 empty meanings, and every other check passed them because presence was all anything asked for — so the tree read as described and said nothing. Measured 2026-08-15: 35 words, 35 empty meanings, and this is the only rule that sees them, because a type can require a field and never require it to say anything. A description that restates its own name is the same hole with prose in it: it adds nothing a reader could not already read off the id, and it is what a machine writes when a person was supposed to. Measured 2026-08-15, once the glossary became derived: every `means` in threads/glossary.ts is a copy of the `describes` this rule already reads on the row that coins the word, so scanning the derived file read the same 52 strings a second time and could not report anything the first pass had missed. That file is excepted — a rule reading a copy of what it already read is a second opinion from the same source',
114
- instead:
115
- 'say what the thing is and what it solves, in a sentence somebody who has not seen the code can act on. If the honest answer is the id again, the thing does not need a description — it needs a reason to exist',
116
- wrong: "const M = { id: 'rule', describes: 'The rule.' }",
117
- right: "const M = { id: 'rule', describes: 'A rule that must be guaranteed to be enforced, declared once as a row from which its lint rule, its message and the trial that proves it are all derived.' }",
118
- cites: [{ on: '2026-08-12', said: 'And you know, I don\'t see the "undescribed" error in your lists.' }],
119
- seeds: [
120
- {
121
- id: 'slopnator',
122
- at: './slopnators/sl_undescribed.ts',
123
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
124
- cites: [{ on: '2026-08-14', said: 'I for sure need all errors to be defined, easy to understand, well explained, with fix suggestions.' }],
125
- },
126
- ],
127
- },
128
-
129
- {
130
- id: 'unthreaded',
131
- at: './constraint.ts',
132
- describes: 'The refusal of a file no parent names through the interface, which is how this repository decides what is wired: whatever the descent never reaches is not part of the declared surface, and a reach count printed beside the gap is not a refusal of it.',
133
- version: 1,
134
- where: { paths: ['threads/', '.'] },
135
- bans: 'a file that no `at:` names from inside a node annotated `Threaded`, `threads/thread.ts` excepted because the root exists by fiat',
136
- because:
137
- 'measured 2026-08-14: the descent from threads/thread.ts names 28 of 641 tracked files, and nothing refused the gap — check-threads printed it as a reach number and exited 0, so twelve files in the mechanism\'s own directories read as wired while nothing declared them. Whatever the descent never reaches is not wired, and a count is not a refusal. This rule reads the mentions and `broken-lineage` reads the roots, deliberately: a file in `threads/` that nothing names still threads what it points at here, and it is that file — not the subtree under it — that gets reported, which is one finding at the cause instead of a cascade at the symptoms. Measured 2026-08-15: an `at:` was a mention, not a thread. Eight lines appended to scripts/finding.ts — a bare object with no id, no at, no describes, no cites and no type — threaded packages/core/engine/src/cache.ts on the next run, and nothing refused it. A file must have a parent; a parent must use the `Threaded` interface to parent it; if it does not, the file is unthreaded. So an `at:` counts only inside a node annotated `Threaded` or an interface extending it, and the bare object threads nothing',
138
- instead:
139
- 'declare it from the row that owns it, or delete it. A file that nothing names is either somebody\'s child nobody wrote down, or it is nobody\'s',
140
- wrong: 'export const ORPHAN = 1',
141
- right: {
142
- files: [
143
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
144
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_names_it.ts',\n },\n ],\n}\n" },
145
- { at: 'threads/_names_it.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const HELD: Threaded = {\n declares: [\n {\n at: 'threads/core.ts',\n },\n ],\n}\n" },
146
- { at: 'threads/core.ts', source: 'export const CORE = 1\n' },
147
- ],
148
- at: 'threads/_names_it.ts',
149
- },
150
- cites: [
151
- { on: '2026-08-12', said: 'Not untraced, unthreaded. Let\'s enforce the naming.' },
152
- { on: '2026-08-14', said: 'Cause we need unthreaded, redthread, broken lineage, and the request/produce errors....' },
153
- ],
154
- seeds: [
155
- {
156
- id: 'slopnator',
157
- at: './slopnators/sl_unthreaded.ts',
158
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
159
- cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law.' }],
160
- },
161
- ],
162
- },
163
-
164
- {
165
- id: 'twice-parented',
166
- at: './constraint.ts',
167
- describes: 'The refusal of one address claimed by two owners, because a child declared twice has two answers for who owns it and where it came from, and the tree can give neither — the two rows may sit in different files, so only a slopnator that compares them all can see it.',
168
- version: 1,
169
- where: { paths: ['.'] },
170
- bans: 'one `at` claimed by two `declares` rows, wherever the two rows live',
171
- because:
172
- '`declares` is exactly one parent ever, and with two the child\'s owner, its shape and its origin each have two answers while the tree gives none. Measured 2026-08-14: the two rows may sit in different files, so no slopnator that sees one file can find this — check-threads found it only by comparing parents across the whole descent, which is why it was a check rather than a rule. Measured 2026-08-15: this rule read `threads/` and nothing else, so a file outside the tree that declared a child the tree already declares was invisible to it — and a file outside the tree may name children, which is the whole premise `broken-lineage` rests on. It now counts parents from every file the tree reaches, not from one directory',
173
- instead:
174
- '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',
175
- wrong: {
176
- files: [
177
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
178
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_parented_row.ts',\n },\n ],\n}\n" },
179
- { at: 'threads/_probe_parented_row.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n at: 'threads/one.ts',\n },\n {\n at: 'threads/one.ts',\n },\n ],\n}\n" },
180
- ],
181
- at: 'threads/_probe_parented_row.ts',
182
- },
183
- right: {
184
- files: [
185
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
186
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_once.ts',\n },\n ],\n}\n" },
187
- { at: 'threads/_probe_once.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n at: 'threads/core.ts',\n },\n {\n at: 'threads/data.ts',\n },\n ],\n}\n" },
188
- { at: 'threads/core.ts', source: 'export const CORE = 1\n' },
189
- { at: 'threads/data.ts', source: 'export const DATA = 1\n' },
190
- ],
191
- at: 'threads/_probe_once.ts',
192
- },
193
- cites: [{ on: '2026-08-14', said: 'Cause we need unthreaded, redthread, broken lineage, and the request/produce errors....' }],
194
- seeds: [
195
- {
196
- id: 'slopnator',
197
- at: './slopnators/sl_twice_parented.ts',
198
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
199
- cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law.' }],
200
- },
201
- ],
202
- },
203
-
204
- {
205
- id: 'broken-lineage',
206
- at: './constraint.ts',
207
- describes: 'The refusal of a file that names children while nothing names it — the break is above them, so every child reads as merely unnamed and repairing them one by one fixes nothing, which is the difference between one finding and a subtree of them.',
208
- version: 1,
209
- where: { paths: ['threads/', '.'] },
210
- bans: 'a file that names children while its own origin does not trace back to `threads/`',
211
- because:
212
- 'a file outside `threads/` may name children — that is how `guard.mjs`, once threaded, parents `try-guard.ts` — but the chain has to start somewhere, and the only somewhere is `threads/`. A file that parents while nothing parents IT strands every child it names, and each of those reads as merely unthreaded rather than as the consequence of one break higher up. Measured 2026-08-14: the descent from threads/thread.ts reaches 33 of 646 tracked files, and a single unrooted parent is the difference between one finding and a subtree of them',
213
- instead:
214
- 'thread the file itself — name it from whatever already traces back to `threads/` — or delete it and the children it threaded. Fixing the children first repairs nothing, because the break is above them',
215
- wrong: "export const M: Threaded = {\n declares: [\n {\n at: 'scripts/finding.ts',\n },\n ],\n}",
216
- right: 'export const PLAIN = 1',
217
- cites: [
218
- { on: '2026-08-14', said: 'Red thread is broken lineage.' },
219
- { on: '2026-08-14', said: 'if we have files outside threads naming themselves, they must have their origin threaded to threads, otherwise it\'s a broken-lineage error. It\'s different from unthreaded which is nothing parents it.' },
220
- ],
221
- seeds: [
222
- {
223
- id: 'slopnator',
224
- at: './slopnators/sl_broken_lineage.ts',
225
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
226
- cites: [{ on: '2026-08-14', said: 'Red thread is broken lineage.' }],
227
- },
228
- ],
229
- },
230
-
231
- {
232
- id: 'stale-path',
233
- at: './constraint.ts',
234
- describes: 'The refusal of a written address that is not on disk — a file moves and every sentence naming it becomes a confident lie that still reads as correct, and counting those without judging them is worse than never scanning, because the number reads as a result.',
235
- version: 1,
236
- where: { paths: ['.'] },
237
- bans: 'a string naming a path in this repository that is not on disk',
238
- because:
239
- 'measured 2026-08-10: 91 distinct paths across 240 mentions named files that had moved under `packages/core/`, and every one of them read as correct. Measured 2026-08-14: paths:check scanned 505 files, found 59 more and judged none of them, printing "no findings" while holding the count — a class counted and unjudged is worse than one never scanned, because the number reads as a result. Measured 2026-08-29 on a real install: this refused the glossary file on an app first gate run — a file the glossary command writes and had not written yet — with advice that would have deleted a correct declaration, so a path a row derives is now read off the tree and left alone until its command has run',
240
- instead:
241
- 'fix the path, or delete the sentence naming it. A path that carries a `{variable}` or a `*` names a directory, and that directory must exist',
242
- wrong: {
243
- files: [
244
- { at: 'threads/_probe_there.ts', source: 'export const THERE = 1\n' },
245
- { at: 'probe.tsx', source: "const at = 'threads/nope.ts'\n" },
246
- ],
247
- at: 'probe.tsx',
248
- },
249
- right: "const at = 'thread/integrity/constraint.ts'",
250
- cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law, so we can retire checks and we can have SSOT for laws, and slopnators only for execution.' }],
251
- seeds: [
252
- {
253
- id: 'slopnator',
254
- at: './slopnators/sl_stale_path.ts',
255
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
256
- cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law.' }],
257
- },
258
- ],
259
- },
260
-
261
- {
262
- id: 'missing-row',
263
- at: './constraint.ts',
264
- describes: 'The refusal of a tree that has dropped a row the package it attached from still ships — what a framework offers is not an app\'s to withdraw, and until this existed an app could delete any shipped row and every check still passed.',
265
- version: 1,
266
- where: { paths: ['.'] },
267
- bans: 'a tree holding fewer rows than the package it attached from declares',
268
- because:
269
- 'measured 2026-08-22: the `ease` row was deleted from an app\'s `_threads/tokens.ts` and check-threads reported NOTHING — not one finding, and the exit was unchanged. The descent sweeps an app for files nothing declares; it never asks the opposite question, is anything the PACKAGE declares missing from this list, and a shipped file lives under `node_modules/` where nothing sweeps for orphans. So 84 shipped rows could be deleted one at a time in silence, and every check would report success having measured nothing. It reads those package files as TEXT and imports nothing but node builtins, because eight laws were once added whose walks imported a workspace package, plain Node could not resolve it, the loader swallowed the failure, and all eight then read from every caller exactly like laws finding nothing. What counts as a row was widened three times by measurement: a node may open its list inline or name a declares array beside it, and reading only the first made 107 of the 178 rows one package ships invisible, every one deletable in silence; a row may be an object literal or a call to a factory that takes the id first, which was the other half of the same blindness; and what a PACKAGE ships is only the ids inside a declares list, because reading every id in the file counted a row the package asks an app to append into another list as one it declares. What a TREE holds is the opposite — every id, whichever list it sits in — since gating that on declares made every row in the derivation sheet read as missing while it sat plainly on disk, and running the command this law recommends would not have cleared it. Only packages the tree ATTACHED are asked for: a package nothing points at is a dependency, and a dependency ships nothing this tree promised to hold',
270
- instead:
271
- 'run `thread attach <package>` — it appends any row that has gone and skips every row already there, so one command restores the list without touching what the app added',
272
- wrong: "const M: Threaded = {\n declares: [],\n}",
273
- right: `const M: Threaded = {\n declares: [\n {\n at: 'node_modules/${named('ui')}/dist/tokens/colour.js',\n },\n ],\n}`,
274
- cites: [
275
- { on: '2026-08-21', said: 'users can create new elements freely, but they can\'t delete the ones that exist (build error) and they can\'t just add them in place or create a file, first they must declare in the SSOT' },
276
- { on: '2026-08-22', said: 'KISS and make sure this is not a dead beat law. Make sure we have a way to fix with one command.' },
277
- ],
278
- seeds: [
279
- {
280
- id: 'slopnator',
281
- at: './slopnators/sl_missing_row.ts',
282
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
283
- cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law.' }],
284
- },
285
- ],
286
- },
287
-
288
- {
289
- id: 'no-such-command',
290
- at: './constraint.ts',
291
- describes: 'The refusal of a row promising a script this repository does not have — otherwise the promise breaks only when somebody runs the gate, only at the step that names it, and only after every step before it has already passed.',
292
- version: 1,
293
- where: { paths: ['threads/'] },
294
- bans: 'a `runs` or `by` naming a script this repository does not have',
295
- because:
296
- 'the gate is the commands these name, so one that package.json lacks fails as `Command "nope:check" not found` at the moment it is reached — after every step before it has already run, and only when somebody runs the gate. Measured 2026-08-14: sixteen rows name sixteen commands and nothing has ever compared them against package.json; they are correct today by nobody having mistyped one',
297
- instead: 'add the script to package.json, or fix the name. A row naming a command is a promise that the command exists',
298
- wrong: {
299
- files: [
300
- { at: 'package.json', source: '{\n "scripts": {\n "typecheck": "tsc"\n }\n}\n' },
301
- { at: 'threads/_probe_command.ts', source: "const M = {\n runs: 'pnpm nope:check',\n}\n" },
302
- ],
303
- at: 'threads/_probe_command.ts',
304
- },
305
- right: "const M = {\n runs: 'pnpm typecheck',\n}",
306
- cites: [{ on: '2026-08-14', said: 'I think you need to study how a file gets parented and make proper error handling for everything. No at is just one.' }],
307
- seeds: [
308
- {
309
- id: 'slopnator',
310
- at: './slopnators/sl_no_such_command.ts',
311
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
312
- cites: [{ on: '2026-08-14', said: 'I think you need to study how a file gets parented and make proper error handling for everything.' }],
313
- },
314
- ],
315
- },
316
-
317
- {
318
- id: 'unpaired-slopnator',
319
- at: './constraint.ts',
320
- describes: 'The refusal of half a rule — a row without the function that enforces it, or a function no row threaded — because nothing but their names holds the two together, so either half can go missing while the other reads as working.',
321
- version: 1,
322
- where: { paths: ['threads/', 'thread/integrity/slopnators/'] },
323
- bans: 'a rule naming a slopnator that is not on disk, and a slopnator in the slopnators directory that no rule names',
324
- because:
325
- 'a rule is a row and a slopnator is a function, and nothing but their names holds the two together — so either half can go missing and the other reads as working. A rule whose slopnator is absent registers, loads, reports nothing, and is indistinguishable from a rule being obeyed. A slopnator no rule names is code that runs in no gate and governs nothing, while looking like enforcement to anybody reading the directory. Measured 2026-08-15: this rule was real and enforced, but it lived as a gate step in ci.ts rather than as a rule — one more place where a guarantee could be turned off by editing a list, which is the thing the rules exist to stop. Moving it here deletes the step rather than adding a check',
326
- instead: 'write the rule that needs the slopnator, or delete the slopnator. `pnpm slopnator:seed` writes the scaffold for a rule that has none, and a rename is a `git mv` rather than a second file',
327
- wrong: "const CONSTRAINTS = [\n {\n id: 'nothing-names-this-slopnator',\n },\n]",
328
- right: {
329
- files: [
330
- { at: 'threads/_probe_paired.ts', source: "const CONSTRAINTS = [\n {\n id: 'no-comment',\n },\n]\n" },
331
- { at: 'threads/slopnators/sl_no_comment.ts', source: 'export const slopnator = { on: [], holds: () => false }\n' },
332
- ],
333
- at: 'threads/_probe_paired.ts',
334
- },
335
- cites: [
336
- { on: '2026-08-14', said: 'A law is written, names a slopnator. Slopnator is seeded magically. Someone write the function. An enforcer picks it up and it just works.' },
337
- { on: '2026-08-15', said: 'Does a slopnator without a law or a law without a slopnator exist? This should be an error....' },
338
- ],
339
- seeds: [
340
- {
341
- id: 'slopnator',
342
- at: './slopnators/sl_unpaired_slopnator.ts',
343
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
344
- cites: [{ on: '2026-08-15', said: 'Does a slopnator without a law or a law without a slopnator exist? This should be an error....' }],
345
- },
346
- ],
347
- },
348
-
349
- {
350
- id: 'twice-meant',
351
- at: './constraint.ts',
352
- describes: 'The refusal of one word carrying two meanings — the glossary holds a single row per word and takes it from the `describes` beside the id, so rows that disagree leave the meaning decided by whichever the slopnator reached first.',
353
- version: 1,
354
- where: { paths: ['.'] },
355
- bans: 'two rows coining the same id while stating different `describes`',
356
- because:
357
- 'the glossary is derived: a word\'s meaning is the `describes` on the row that coins it, so a word coined twice with two descriptions has two meanings and the file can hold one. Repetition is not the defect — measured 2026-08-15, the tree coins `slopnator` on twelve rows and `trial` on twelve, deliberately, because the founder asked for the category in the glossary rather than each instance under its own name. Disagreement is the defect: those same twelve rows each stated a different sentence, so the tree asserted twelve meanings for one word and nothing looked. Five words were split this way — `slopnator`, `trial`, `ci`, `build` and `glossary` — and three of them were the same sentence written twice, which is the SSOT failure wearing a second coat',
358
- instead:
359
- 'state the category meaning on every row that coins the word, because the word is what the row names. Where two rows describe the same file from different heights, the row that declares it wins and the node repeats it. Where a row needs to say something only true of its own instance, it needs its own id — and if you do not want that word in the glossary, the sentence does not belong on the row',
360
- wrong: {
361
- files: [
362
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
363
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_meant_row.ts',\n },\n ],\n}\n" },
364
- { at: 'threads/_probe_meant_row.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'one',\n describes: 'A different thing entirely.',\n },\n ],\n}\n" },
365
- ],
366
- at: 'threads/_probe_meant_row.ts',
367
- },
368
- right: "const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'one',\n describes: 'The first thing.',\n },\n ],\n}",
369
- cites: [
370
- { on: '2026-08-15', said: 'the ids thing. We can\'t have that, the id\'s being the same.' },
371
- { on: '2026-08-15', said: 'Each row IS it\'s category. So, doesn\'t the describe in the law declaration in integrity it\'s own means? cause the word is the id in it\'s declaration no? In the threader interface.' },
372
- { on: '2026-08-15', said: 'Yes, rows carry category meaning. No individual ids.' },
373
- ],
374
- seeds: [
375
- {
376
- id: 'slopnator',
377
- at: './slopnators/sl_twice_meant.ts',
378
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
379
- cites: [{ on: '2026-08-15', said: 'Yes, rows carry category meaning. No individual ids.' }],
380
- },
381
- ],
382
- },
383
-
384
- {
385
- id: 'derived-drift',
386
- at: './constraint.ts',
387
- describes: 'The refusal of a generated file that is no longer the bytes its generator writes, and of a byproduct naming no command to write it — either way the file is a hand edit wearing a generated name, and every reader downstream believes the wrong one.',
388
- version: 1,
389
- where: { paths: ['threads/'] },
390
- bans: 'a derived file that is not the bytes its generator writes, and a `derives` row naming a file no row states both a `by` and a `runs` for',
391
- because:
392
- 'a derivation is only worth the name while running it again produces what is on disk — the moment the two differ, the file is a hand edit wearing a generated file\'s name, and every reader downstream believes the wrong one. Measured 2026-08-15: five derivations each carried their own `:check` command as a separate gate step, so the rule was stated five times and nowhere. Measured again 2026-08-15, after this rule existed: the tree holds twenty `derives` rows and this slopnator read five — a row missing `by` or `runs` was dropped at the `if` that collected it, so fifteen declared derivations were never compared to anything and the rule reported zero findings over them. The file they all name survived only because a sixteenth row elsewhere happened to carry the command; delete that one row and fifteen go dark with nothing saying so. `permissions.deny` stops an agent editing a derived file; nothing stopped a shell, a merge, or a row that never named a generator at all',
393
- instead:
394
- 'run the generator the row names in `by` and commit what it writes. Never edit the file — a derived file has no author, and an edit to one is a change to the generator that somebody skipped. Where many rows name one derived file, at least one of them must state the `by` that writes it and the `runs` that proves it',
395
- wrong: "export const M: Threaded = {\n derives: [\n {\n at: 'scripts/_generated/gate.mjs',\n by: 'pnpm gate:generate',\n runs: 'pnpm nope:check',\n },\n ],\n}",
396
- right: "export const M: Threaded = {\n derives: [\n {\n at: 'next.config.js',\n by: 'thread routes',\n runs: 'thread routes --check',\n },\n ],\n}",
397
- cites: [
398
- { on: '2026-08-15', said: 'You\'ll take the checks that exist, make them into laws that derive the exact same checks. The laws create ALL things that check stuff. I do not want multiple sources of truth for anything.' },
399
- { on: '2026-08-15', said: 'So slopnators should have kinds. walk, check, whatever else it needs so it works.' },
400
- ],
401
- seeds: [
402
- {
403
- id: 'slopnator',
404
- at: './slopnators/sl_derived_drift.ts',
405
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
406
- cites: [{ on: '2026-08-15', said: 'make them into laws that derive the exact same checks' }],
407
- },
408
- ],
409
- },
410
- {
411
- id: 'undeclared-crossing',
412
- at: './constraint.ts',
413
- describes: 'The refusal that makes an operation the only thing in this app that does anything. Work is not written where it happens to be wanted; it is declared as an operation, which states what it takes, what it hands back and what it has to reach, and the operation is what runs. Anything that acts outside one is work this app never said it would do, and nothing can replay it, retry it, price it or unwind it afterwards.',
414
- version: 1,
415
- where: { paths: ['.'] },
416
- bans: 'acting outside an operation — anything that reaches past this process, whatever it is called',
417
- because:
418
- 'An operation is how this framework does anything at all: it states what it takes, what it hands back and what it has to reach, and the trial, the replay, the trace, the cost record and the compensation every one hang off that statement. Work written beside one has none of them: nothing recorded it, so it cannot be replayed; nothing owns it, so it cannot be retried; it left no record, so it cannot be priced; no compensation names it, so it cannot be unwound. A `fetch` in a screen is not a smaller operation than a workflow — it is an operation the app cannot see, and the page renders fine either way, which is why nothing catches it without a rule. The clock is not one of these. `Date.now()`, a timer and `new Date()` are each accepted here and have been for as long as this check has run — they sit in its own list of forms it stays silent on. A paragraph stood here saying the opposite, on the grounds that a run reading the clock cannot be replayed, and it described a refusal this check never made; an agent read it, reported the clock as a gap, and was told twice. Founder, 2026-09-17: date is environment, and environment is free. A duration or easing curve typed into an animate call is a separate matter, and nothing refuses it today — hardcoded-style reads a CSS declaration written inside a string, and an object argument is not one. Measured 2026-09-16: the check behind this carried ten ways of acting written out, so starting a process — which nobody had typed — was not a crossing at all, and 48 places that spawn one were green. The law was never about those ten. Measured 2026-09-17, against the rule that a check may hold no list of its own: the twenty-one spellings this reads cannot come from the tree either, and two ways out were tried. Reading the declared doors instead — the rows naming the files that open a disk, a process and a network — refuses every file that legitimately goes through one, which is most of this framework. Reading what those door files themselves import brings node:path in with them, and a path join is not a crossing. What is left is the spelling of the capability, which Node names and this tree never does. It stands, and it is the second place where a check reaches exactly as far as somebody typed.',
419
- instead:
420
- 'Declare an operation and put the work in it. `thread create operation` asks what it takes, what it hands back and which targets it reaches, then writes it; `thread run trial <operation>` runs it against an input you supply, so you see it work before anything else does. An operation reaches outside only through a target it named — that is not a restriction on the work, it is where the work goes.',
421
- wrong: "const res = await fetch('https://api.stripe.com/v1/charges')",
422
- right: "{ id: 'wf_charge_card', describes: 'Takes payment for an order through the stripe target this app declares.', requires: ['ht_stripe'] }",
423
- cites: [
424
- { on: '2026-08-16', said: 'The ones most important are the ones that enforce UI through elements and screens, and the ones that enforce that the only things that can operate are workflows and views.' },
425
- { on: '2026-08-16', said: 'No operation without a workflow or view.' },
426
- { on: '2026-08-16', said: 'And make it ubiquitous.' },
427
- ],
428
- seeds: [
429
- {
430
- id: 'slopnator',
431
- at: './slopnators/sl_undeclared_crossing.ts',
432
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
433
- cites: [{ on: '2026-08-16', said: 'No operation without a workflow or view.' }],
434
- },
435
- ],
436
- },
437
- {
438
- id: 'hardcoded-style',
439
- at: './constraint.ts',
440
- describes:
441
- 'The refusal that keeps the compiler a translator. The style guide, the fonts, the animations and the icons hold the values; CSS is what one of those means said in another language, and a generator is the sentence that carries it across. So a generator may know that a thing becomes `background` and may not know what colour that is. Nothing else in this repository looks here — a row is checked, a variant is checked, an element is checked, and the strings a generator itself emits are checked by nobody. This reads them, and answers on any value typed in rather than taken from the sheet that owns it.',
442
- version: 1,
443
- where: { paths: ['.'] },
444
- bans: 'a value typed into the compiler where a sheet should have supplied it — a CSS declaration whose value is neither the name of a row nor derived from one',
445
- because:
446
- 'a value lives in exactly one sheet and everything reads it from there, so a value typed into the compiler is outside that system while sitting at its centre: it survives a rebrand, ignores every mode and theme, answers to no command, and no sheet knows it exists. It is also the one hardcoding no other law can see — the raw-value refusal reads what a ROW states, and this is written where nothing states anything.',
447
- instead:
448
- 'take it from the sheet that owns it: name the row, `var(--colour-page)` rather than `#f8fbfa`, or derive it the way `color-scheme` is derived from the modes an app declares. The property and the selector are the translation and are the compiler\'s; the value never is.',
449
- wrong: "lines.push(' body {', ' background: #f8fbfa;', ' }')",
450
- right: "lines.push(' body {', ' background: var(--colour-page);', ' }')",
451
- cites: [
452
- { on: '2026-09-03', said: 'You are probably trying to fucking game my system. Trying to bring css to the elements, to the fucking framework. Unacceptable.' },
453
- { on: '2026-09-03', said: 'Then the first thing we must do is make sure that nothing in the compiler can EVER hardcode a value. You are breaking the compiler so it can NEVER FUCKING DO THIS.' },
454
- { on: '2026-09-02', said: 'It shouldn\'t match any CSS rule because it should never speak CSS.' },
455
- ],
456
- seeds: [
457
- {
458
- id: 'slopnator',
459
- at: './slopnators/sl_hardcoded_style.ts',
460
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
461
- cites: [{ on: '2026-09-03', said: 'nothing in the compiler can EVER hardcode a value' }],
462
- },
463
- ],
464
- },
465
- {
466
- id: 'undeclared-destination',
467
- at: './constraint.ts',
468
- governs: 'navigation',
469
- describes: 'The refusal that makes the map the whole of where an app can go — a screen is reached by naming it, so every destination is one the map declares and a link nobody declared cannot exist.',
470
- version: 1,
471
-
472
- where: { paths: ['.'] },
473
- bans: 'a destination reached without naming a declared screen — an `href`, or the address of a declared screen written out as a path',
474
- because:
475
- 'the map island holds the screens and the links between them, and everything that reasons about where an app can go reads `island.links`: `checkMapIsland` refuses a link naming a screen nothing declares, two screens answering one path, a link from a screen to itself and an app with no `entry`, and `orphanScreens` finds every screen no link reaches. Five checks, all of them blind to a destination reached another way — an `href` is not a link the island holds, so the screen it points at reads as unreachable, the app reads as having no path to it, and the page renders and navigates correctly. That is the whole failure: the map is not what the app does, it is what the app declared it does, and the two only stay the same thing if there is no second way to move. Measured 2026-09-17: what this read was a list of nine names — window, globalThis, self, top, parent, document, router, navigation, history — so a mover nobody had typed was not a mover at all, and the rule reached exactly as far as those nine. It reads the tree now: every screen an app declares has an address, and writing that address out rather than naming the screen is the finding, whatever does the writing. What is given up is a destination held in a variable, which was only ever caught when it passed through one of the nine.',
476
- instead: 'name the screen — `navigateTo="Order"` — and let the map carry the destination, so a link exists in the island that reasons about links',
477
- wrong: '<a href="/orders/42">{label}</a>',
478
- right: '<Link.linkInline navigateTo="Order" labelKey="order.title" />',
479
- cites: [
480
- { on: '2026-08-16', said: 'The ones most important are the ones that enforce UI through elements and screens, and the ones that enforce that the only things that can operate are workflows and views.' },
481
- { on: '2026-08-16', said: 'But they must be app wide.' },
482
- { on: '2026-08-16', said: 'And make it ubiquitous.' },
483
- { on: '2026-08-16', said: 'Here\'s a new law I need you to add: No island without law.' },
484
- ],
485
- seeds: [
486
- {
487
- id: 'slopnator',
488
- at: './slopnators/sl_undeclared_destination.ts',
489
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
490
- cites: [{ on: '2026-08-16', said: 'The ones most important are the ones that enforce UI through elements and screens' }],
491
- },
492
- ],
493
- },
494
- {
495
- id: 'undeclared-operation',
496
- at: './constraint.ts',
497
- describes: 'The refusal that keeps a dispatched name answerable to a declaration — a name handed to dispatch is a string like any other, so one that names nothing, or one assembled while the program runs, reaches for an operation that does not exist and fails in front of a person rather than where it was written.',
498
- version: 1,
499
-
500
- where: { paths: ['.'] },
501
- bans: 'dispatch handed a name nothing declares, or a name this file does not write down',
502
- because:
503
- 'measured 2026-08-25: nothing anywhere holds a dispatched name against a declaration. `packages/ui/src/bindings.tsx` exposes `run(workflow: string, input?)`, and that workflow is a bare string passed to a context the UI package never sees — so a name that matches no operation reaches the browser and fails at whatever the host does with an unknown id. The second half is measured from the other side: `dispatch` in packages/core/operations/src/dispatch.ts takes an Operation object rather than a name, so no shipped call site names an operation as a string at all, and the only place a name was written down was that `run`. Measured again the same day, after `packages/ui/src/bindings.tsx` was deleted: `dispatch` is now a seam on the UI config, named once, handed a name — so from here on a dispatched name is always written where it can be read, and this rule is what holds it to a declaration. A name assembled at runtime is the same hole with a lid on it — it reads from every check exactly like a name that was checked and found. Measured 2026-08-31 on a registry install: an operations-only app, everything made by the CLI, failed its own sweep on a file its own generator had just written, naming an operation the same tree declares — and a law that refuses a generated file is an app that cannot ship. So an identifier is accepted now, but only when it IS one of the declared operations; a variable holding a computed name is still refused, because it matches no declaration, and that is the hole this law was written for',
504
- instead:
505
- 'write the name where it can be read, and declare the operation it names — `dispatch(\'wf_send_invoice\')` against a row whose `at` is `operations/wf_send_invoice.ts`. Where the name genuinely varies, the choosing belongs in the declaration, not in a template string',
506
- wrong: {
507
- files: [
508
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
509
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_operations.ts',\n },\n ],\n}\n" },
510
- { at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n" },
511
- { at: 'app/_probe_wrong.tsx', source: "dispatch('wf_not_declared')" },
512
- ],
513
- at: 'app/_probe_wrong.tsx',
514
- },
515
- right: {
516
- files: [
517
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
518
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_operations.ts',\n },\n ],\n}\n" },
519
- { at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n" },
520
- { at: 'app/_probe_right.tsx', source: "dispatch('wf_send_invoice')" },
521
- ],
522
- at: 'app/_probe_right.tsx',
523
- },
524
- cites: [
525
- { on: '2026-08-25', said: 'And make sure the composition can only dispatch existing operations.' },
526
- { on: '2026-08-25', said: 'Yes — reading a view is dispatching too' },
527
- { on: '2026-08-25', said: 'Let\'s pretend dispatch is a magic thing like I say it is, and remove bindings cause that\'s just a way to avoid using dispatch.' },
528
- ],
529
- seeds: [
530
- {
531
- id: 'slopnator',
532
- at: './slopnators/sl_undeclared_operation.ts',
533
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
534
- cites: [{ on: '2026-08-25', said: 'And make sure the composition can only dispatch existing operations.' }],
535
- },
536
- ],
537
- },
538
-
539
- {
540
- id: 'undeclared-variant',
541
- at: './constraint.ts',
542
- describes: 'The refusal that leaves a variant row as the only thing deciding what an element may wear — a member written on an element is a name nothing checks against anything, so one that was never declared, or one declared for a different element, renders carrying a class the stylesheet never writes and reads on screen as lost styling rather than a missing declaration.',
543
- version: 1,
544
-
545
- where: { paths: ['.'] },
546
- bans: 'an element wearing a member that no variant row declares, or that a variant row declares for a different element',
547
- because:
548
- 'measured 2026-08-25: the founder wrote `<Button.primary text="translation.key" />` as the shape a composition takes, and the fourteen variants in packages/ui/src/variants.ts hold no row `primary` — the one that fits is `button-primary`. `element()` in packages/ui/src/wrappers.tsx resolves any member through a proxy, so that call renders a button carrying the class `thread-primary`, which the stylesheet generator never writes because no variant of that name exists to write it from. It renders, it is unstyled, and nothing in the repository says a word. The pairing half is the same hole from the other side: `card` declares fifteen elements that wear it and `Button` is not among them, and nothing stops `<Button.card />` either. Both are the silent-pass shape — a check reporting success because it measured nothing',
549
- instead:
550
- 'declare the variant, and name the element in that row\'s `wears` — the row is what makes the pairing legal, which is why `<Button.buttonPrimary />` holds and `<Button.primary />` does not',
551
- wrong: {
552
- files: [
553
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
554
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_worn.ts',\n },\n ],\n}\n" },
555
- { at: 'threads/_probe_worn.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const ELEMENTS: Threaded = {\n id: 'element',\n declares: [\n {\n id: 'button',\n at: 'app/_probe_button.tsx',\n },\n ],\n}\n\nexport const VARIANTS: Threaded = {\n id: 'variant',\n declares: [\n {\n id: 'button-primary',\n at: 'app/_probe_variant.ts',\n wears: ['Button'],\n },\n ],\n}\n" },
556
- { at: 'app/_probe_wrong.tsx', source: '<Button.primary />' },
557
- ],
558
- at: 'app/_probe_wrong.tsx',
559
- },
560
- right: {
561
- files: [
562
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
563
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_worn.ts',\n },\n ],\n}\n" },
564
- { at: 'threads/_probe_worn.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const ELEMENTS: Threaded = {\n id: 'element',\n declares: [\n {\n id: 'button',\n at: 'app/_probe_button.tsx',\n },\n ],\n}\n\nexport const VARIANTS: Threaded = {\n id: 'variant',\n declares: [\n {\n id: 'button-primary',\n at: 'app/_probe_variant.ts',\n wears: ['Button'],\n },\n ],\n}\n" },
565
- { at: 'app/_probe_right.tsx', source: '<Button.buttonPrimary />' },
566
- ],
567
- at: 'app/_probe_right.tsx',
568
- },
569
- cites: [
570
- { on: '2026-08-25', said: 'Variants themselves declare what elements they relate to, in their declarations. Then the composition says button.primary and if that matches the combination of variant and element it\'s acceptable.' },
571
- { on: '2026-08-25', said: 'If there\'s no primary variant.....that\' an error moron. Didn\'t I said that it can\'t name a variant that doesn\'t exist?' },
572
- ],
573
- seeds: [
574
- {
575
- id: 'slopnator',
576
- at: './slopnators/sl_undeclared_variant.ts',
577
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
578
- cites: [{ on: '2026-08-25', said: 'Variants themselves declare what elements they relate to, in their declarations.' }],
579
- },
580
- ],
581
- },
582
-
583
- {
584
- id: 'undeclared-element',
585
- at: './constraint.ts',
586
- governs: 'elements',
587
- describes: 'The refusal that makes the element catalogue the only thing an interface is built from — a lowercase tag is HTML written in place: it wears no declared style, sits in no catalogue, and is the one shape that renders like an element while being invisible to everything that reads what an app is made of.',
588
- version: 1,
589
-
590
- where: { paths: ['.'] },
591
- bans: 'a host element — any lowercase-initial tag, not only `<div>` — written anywhere in an app',
592
- because:
593
- 'a host element carries neither `category` nor `style`, which are the two fields `ElementSchema` offers and the only places a look is declared, so the style guide cannot show it, a rebrand cannot move it and an overlay cannot vary it — it renders correctly and is absent from every derivation that reads the catalogue, which is why nothing catches it without a rule. Measured 2026-08-16: the rule that refused this was already written, 113 rows naming every HTML tag down to `<wbr>`, held in a book of app rules that nothing in this repository imported — its only mention anywhere was a tree row stating its address, and the book was deleted on 2026-08-17. Enumerated to the last tag, consumed by nothing, refusing nothing: the same shape a check takes when it measured zero files. This one governs the whole app rather than the screens directory, because a host element written in a composition ships to a screen either way',
594
- instead:
595
- 'declare the element in the elements island and write the member that wears its look — `<Box.card>` — using the declared groupings `Column`, `Stack` and `Box` wherever a host element would have gone',
596
- wrong: '<div><Box.card /></div>',
597
- right: '<Column><Box.card /></Column>',
598
- cites: [
599
- { on: '2026-08-16', said: 'The ones most important are the ones that enforce UI through elements and screens, and the ones that enforce that the only things that can operate are workflows and views.' },
600
- { on: '2026-08-16', said: 'But they must be app wide.' },
601
- { on: '2026-08-16', said: 'And make it ubiquitous.' },
602
- { on: '2026-08-16', said: 'Here\'s a new law I need you to add: No island without law.' },
603
- ],
604
- seeds: [
605
- {
606
- id: 'slopnator',
607
- at: './slopnators/sl_undeclared_element.ts',
608
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
609
- cites: [{ on: '2026-08-16', said: 'The ones most important are the ones that enforce UI through elements and screens' }],
610
- },
611
- ],
612
- },
613
- {
614
- id: 'unpaired-member',
615
- at: './constraint.ts',
616
- describes: 'The refusal of half a member — a row that seeded no file, or a file no row brought into existence, so nothing an island governs can exist that was not declared first.',
617
- version: 1,
618
- where: { paths: ['threads/', 'operations/', 'entities/'] },
619
- bans: 'a member declared in an island\'s tree file whose file is not on disk, and a file in an island\'s folder that no row declares',
620
- because:
621
- 'a member is added by declaring it and then written, never the other way round — the same order a rule follows to its slopnator, and for the same reason: the row is what everything downstream reads, so a file that arrived without one is a thing the runtime, the API, the agent tools and the trials have all never heard of, while it sits there able to act. The two halves are held together by nothing but a name, so either can go missing and the other reads as working. Absence is the finding in both directions because there is no third place to look: the rows are what exists, the folder is what was written, and any gap between them is one somebody has to close on purpose. The rule reaches by shape and not by name — it reads every island that seeds its members from the tree, so an island added tomorrow is governed without editing this row',
622
- instead:
623
- 'add the row to the island\'s tree file first and run `pnpm member:seed`, which writes the file that row brings into existence — then write the member into the template it left',
624
- wrong: {
625
- files: [
626
- { at: 'threads/thread.ts', source: "export const THREAD: Threaded = {\n declares: [\n { id: 'operations', at: 'src/islands/operations.ts', governs: ['operations/'] },\n { id: 'operation', at: 'threads/operations.ts' },\n ],\n}\n" },
627
- { at: 'src/islands/operations.ts', source: 'export const OperationsIslandSchema = {}\n' },
628
- { at: 'threads/operations.ts', source: "export const OPERATIONS: readonly Threaded[] = [\n {\n id: 'wf_nothing_seeded_this',\n },\n]\n" },
629
- ],
630
- at: 'threads/operations.ts',
631
- },
632
- right: {
633
- files: [
634
- { at: 'threads/thread.ts', source: "export const THREAD: Threaded = {\n declares: [\n { id: 'operations', at: 'src/islands/operations.ts', governs: ['operations/'] },\n { id: 'operation', at: 'threads/operations.ts' },\n ],\n}\n" },
635
- { at: 'src/islands/operations.ts', source: 'export const OperationsIslandSchema = {}\n' },
636
- { at: 'threads/operations.ts', source: 'export const OPERATIONS: readonly Threaded[] = []\n' },
637
- ],
638
- at: 'threads/operations.ts',
639
- },
640
- cites: [
641
- { on: '2026-08-17', said: 'FIRST we add the new row in workflows.ts, which seeds a new wf_{id} file in a workflows folder.' },
642
- { on: '2026-08-17', said: 'The file is not authored but seeded. We can write the operation after, but it must have a template with instructions maybe of how to build. Like slopnators.' },
643
- { on: '2026-08-17', said: 'No wf_ without a row in workflows.ts' },
644
- { on: '2026-08-17', said: 'Apply the EXACT SAME MODEL as workflow to entities. Should literally be mirrors in how they operate, with laws and all that shit.' },
645
- ],
646
- seeds: [
647
- {
648
- id: 'slopnator',
649
- at: './slopnators/sl_unpaired_member.ts',
650
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
651
- cites: [{ on: '2026-08-17', said: 'No wf_ without a row in workflows.ts' }],
652
- },
653
- ],
654
- },
655
- {
656
- id: 'unversioned',
657
- at: './constraint.ts',
658
- describes: 'The refusal that makes a governing declaration say which one it is — anything other things must conform to states a version, so a change to it is a diff a consumer can see and a gap something can be carried across.',
659
- version: 1,
660
- where: { paths: ['.'] },
661
- bans: 'a declaration that governs and states no version — a node annotated `Threaded` or an interface extending it, and an island schema pinning no `specVersion`',
662
- because:
663
- 'a version is not bookkeeping, it is the only thing that makes an update mechanical rather than archaeological: without it a consumer built against an older shape is indistinguishable from one built against this shape, so nothing can decide whether to migrate and nothing can tell what changed. The mechanism to carry a declaration forward already exists and has never been usable — `packages/core/spec/src/codemods.ts` holds the chain from one grammar version to the next and its own row says the chain is empty, because there was never a second version to carry anything to. Measured 2026-08-17: twelve of the thirteen island schemas pin a `specVersion` and `RegistryIslandSchema` pins none, so one kind of declaration was already unversioned and nothing said so; and the whole of `threads/` — the root, four metas, and every rule that governs a path — stated no version at all. This is the one rule that governs the governors, which is why it reaches by annotation rather than by naming them: a meta added tomorrow is covered without editing this row',
664
- instead: 'state a `version` on the node, and a `specVersion` on the island schema — one integer, raised when what it governs changes shape',
665
- wrong: "export const M: Threaded = { id: 'a-meta', at: 'threads/integrity.ts', describes: 'What it is and what it solves.' }",
666
- right: "export const M: Threaded = { id: 'a-meta', at: 'threads/integrity.ts', describes: 'What it is and what it solves.', version: 1 }",
667
- cites: [
668
- { on: '2026-08-17', said: 'We should add a law that everything must be versioned right? So we can properly update things and do diffs.' },
669
- { on: '2026-08-17', said: 'The things that govern other things, we need those to be versioned. Islands, laws, thread.ts, metas....everything that governs other things.' },
670
- { on: '2026-08-17', said: 'make it universal for thread related stuff' },
671
- ],
672
- seeds: [
673
- {
674
- id: 'slopnator',
675
- at: './slopnators/sl_unversioned.ts',
676
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
677
- cites: [{ on: '2026-08-17', said: 'The things that govern other things, we need those to be versioned.' }],
678
- },
679
- ],
680
- },
681
- {
682
- id: 'declared-elsewhere',
683
- at: './constraint.ts',
684
- describes: 'The refusal that makes an island the only place its kind is declared — one rule for every island, reading what each one governs, so a kind added tomorrow is enforced the moment its row exists.',
685
- version: 1,
686
- where: { paths: ['.'] },
687
- bans: 'a declaration of a kind an island governs, written anywhere but where that island governs',
688
- because:
689
- 'the loader opens the paths an island governs and nothing else, so a declaration written beside the code that needs it is not a smaller declaration — it is no declaration, and every surface downstream reports success over it: it typechecks, the editor completes it, and the generator emits nothing. This is one rule rather than one per island because the rule never differed — measured 2026-08-17: the same sentence was written eleven times, and the island-to-path fact it depends on appeared 130 times across 56 authored files, so eleven copies of a rule drifted against a fact stated everywhere. Here the island states what it governs once, and the slopnator reads it. The call it looks for is read out of the island itself rather than derived from its name, because no convention survives contact with the real set: `sql` exposes `defineSqlFunctions` and `navigation` exposes `defineMap`. An island that governs only folders is one whose kind is derived and authored nowhere, so the call is refused everywhere — which is what `AUTHORED_MAP` did for the map at build.ts:150, now stated on the island instead of in the CLI',
690
- instead: 'declare it in the file the island governs, and import it where it is needed',
691
- wrong: {
692
- files: [
693
- { at: 'threads/thread.ts', source: "export const THREAD: Threaded = {\n declares: [\n { id: 'permissions', at: 'threads/_island_permissions.ts', governs: ['app/_declare/permissions.ts'] },\n ],\n}\n" },
694
- { at: 'threads/_island_permissions.ts', source: 'export const PermissionsIslandSchema = {}\nexport function definePermissions() {}\n' },
695
- { at: 'probe.tsx', source: "export default definePermissions({ specVersion: 1, entities: [{ entity: 'ent_order' }] })\n" },
696
- ],
697
- at: 'probe.tsx',
698
- },
699
- right: "import permissions from 'app/_declare/permissions'",
700
- cites: [
701
- { on: '2026-08-17', said: 'If there\'s something that is govern by an island but it\'s propelry done through island, error.' },
702
- { on: '2026-08-17', said: 'Each island governs it\'s thing. Laws make sure that this is true and no island is inert.' },
703
- { on: '2026-08-16', said: 'EVERYTHING must be SSOT. Why? Because AIs are lazy and they want to create things in place.' },
704
- ],
705
- seeds: [
706
- {
707
- id: 'slopnator',
708
- at: './slopnators/sl_declared_elsewhere.ts',
709
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
710
- cites: [{ on: '2026-08-17', said: 'Each island governs it\'s thing.' }],
711
- },
712
- ],
713
- },
714
- {
715
- id: 'inert-island',
716
- at: './constraint.ts',
717
- describes: 'The refusal that makes an island state its own authority — an island that governs nothing is a shape nothing is held to, and the rule about where its kind may be declared cannot be written at all.',
718
- version: 1,
719
- where: { paths: ['threads/'] },
720
- bans: 'an island that states nothing it governs',
721
- because:
722
- 'what an island governs is the one fact about it that nothing else can supply. It is not derivable from the island\'s name — measured 2026-08-17: `navigation` governs `app/screens/` and its schema lives in `map.ts`, and `sql` exposes `defineSqlFunctions`, so two of thirteen break any convention a generator could apply. Stated as nothing, the rule that refuses a declaration elsewhere has no elsewhere to refuse: no file is the place its kind belongs, which makes every file the place. The island still parses, still typechecks, still appears in the tree, and governs nobody — inert rather than absent, which is the harder kind to notice',
723
- instead: 'state what the island governs — a file where its kind is declared, or a folder whose implementation it governs',
724
- wrong: {
725
- files: [
726
- { at: 'threads/_inert_probe.ts', source: "export const M: Threaded = {\n declares: [\n { id: 'events', at: 'packages/core/spec/src/islands/events.ts' },\n ],\n}\n" },
727
- { at: 'packages/core/spec/src/islands/events.ts', source: 'export const EventsIslandSchema = {}\n' },
728
- ],
729
- at: 'threads/_inert_probe.ts',
730
- },
731
- right: {
732
- files: [
733
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly governs?: readonly string[]\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
734
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_inert_probe.ts',\n },\n ],\n}\n" },
735
- { at: 'threads/_inert_probe.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n { id: 'events', at: 'packages/core/spec/src/islands/events.ts', governs: ['app/_declare/events.ts'] },\n ],\n}\n" },
736
- { at: 'packages/core/spec/src/islands/events.ts', source: 'export const EventsIslandSchema = {}\n' },
737
- ],
738
- at: 'threads/_inert_probe.ts',
739
- },
740
- cites: [
741
- { on: '2026-08-17', said: 'An island extends threader. It declares what it governs.' },
742
- { on: '2026-08-17', said: 'If there\'s an island that doesn\'t state what it governs, error.' },
743
- { on: '2026-08-17', said: 'An island governs either folders or file types or implementation.' },
744
- ],
745
- seeds: [
746
- {
747
- id: 'slopnator',
748
- at: './slopnators/sl_inert_island.ts',
749
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
750
- cites: [{ on: '2026-08-17', said: 'no island is inert' }],
751
- },
752
- ],
753
- },
754
- {
755
- id: 'unnamed-island',
756
- at: './constraint.ts',
757
- describes: 'The refusal that stops an island arriving as a file — a schema the tree does not name states what an app may say while nothing states where it may say it, so the kind is declarable and every rule about it is absent.',
758
- version: 1,
759
- where: { paths: ['.'] },
760
- bans: 'a module exporting an island schema that no row in the tree names',
761
- because:
762
- 'an island is a kind an app may declare, and adding one by writing a file is how a kind arrives with no rule attached — the schema parses, the loader can be pointed at it, and nothing anywhere says which file its declarations belong in. This is the guarantee that has to hold when a package contributes an island rather than the framework: a file alone cannot create a kind, because the row is what carries what it governs, and what it governs is what every rule about it reads. Absence is the finding here for the same reason it is in `unthreaded` — whatever the descent never reaches is not wired, and a schema nobody named is a kind nobody governs',
763
- instead: 'name it from the row that owns it, stating what it governs — or delete the schema, because a kind with no rule is not a kind',
764
- wrong: 'export const ThingIslandSchema = z.strictObject({ specVersion: z.literal(1) })',
765
- right: 'export const ThingSchema = z.strictObject({ name: z.string() })',
766
- cites: [
767
- { on: '2026-08-17', said: 'If there\'s an island that doesn\'t extends threader, error.' },
768
- { on: '2026-08-17', said: 'An island extends threader. It declares what it governs.' },
769
- { on: '2026-08-16', said: 'other packages must be able to contribute both islands AND laws that governs their islands' },
770
- ],
771
- seeds: [
772
- {
773
- id: 'slopnator',
774
- at: './slopnators/sl_unnamed_island.ts',
775
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
776
- cites: [{ on: '2026-08-17', said: 'If there\'s an island that doesn\'t extends threader, error.' }],
777
- },
778
- ],
779
- },
780
- {
781
- id: 'ungoverned-island',
782
- at: './constraint.ts',
783
- describes: 'The refusal that keeps a rule about a kind that exists — a rule naming an island the tree does not declare is a rule about nothing, and it reads as coverage of a kind nobody can declare.',
784
- version: 1,
785
- where: { paths: ['threads/'] },
786
- bans: 'a rule whose `governs` names an island the tree does not declare',
787
- because:
788
- 'a rule that names a kind nobody declares is the most convincing kind of gap: somebody can point at the rule, the plugin registers it, the trial passes, and the thing it governs does not exist. It is the mirror of the silent pass — enforcement over an empty set. This rule used to refuse the other direction too, an island no rule named, and that direction retired on 2026-08-17 when the eleven per-island placement rules collapsed into `declared-elsewhere`: one rule reading what each island governs, so a kind added tomorrow is enforced the moment its row exists. What was checked became structural, and a check that can no longer fail is a check worth deleting rather than keeping for the shape of it',
789
- instead:
790
- 'name an island the tree declares, or drop the `governs` — a rule about a kind nobody can declare is a rule about nothing',
791
- wrong: "export const L: Constraint = {\n id: 'a-rule',\n governs: 'no-island-by-this-name',\n}",
792
- right: {
793
- files: [
794
- { at: 'threads/thread.ts', source: "export const THREAD: Threaded = {\n declares: [\n { id: 'entities', at: 'threads/_island_entities.ts', governs: ['app/_declare/entities.ts'] },\n ],\n}\n" },
795
- { at: 'threads/_island_entities.ts', source: 'export const EntitiesIslandSchema = {}\nexport function defineEntities() {}\n' },
796
- { at: 'threads/_probe_governed.ts', source: "export const L: Constraint = {\n id: 'a-rule',\n governs: 'entities',\n}\n" },
797
- ],
798
- at: 'threads/_probe_governed.ts',
799
- },
800
- cites: [
801
- { on: '2026-08-16', said: 'Here\'s a new law I need you to add: No island without law.' },
802
- { on: '2026-08-16', said: 'So also we need a "law doesn\'t match island" if this is to be done entirely by hand so we catch drifts.' },
803
- { on: '2026-08-16', said: 'NOTHING can exist that has no island for the things island govern.' },
804
- ],
805
- seeds: [
806
- {
807
- id: 'slopnator',
808
- at: './slopnators/sl_ungoverned_island.ts',
809
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
810
- cites: [{ on: '2026-08-16', said: 'Here\'s a new law I need you to add: No island without law.' }],
811
- },
812
- ],
813
- },
814
- {
815
- id: 'no-barrel',
816
- at: './constraint.ts',
817
- version: 1,
818
- describes: 'The refusal that stops a file existing to list what exists — a barrel says a name is there while the tree already says what it is, so the two drift apart and no import names the file anything came from.',
819
- where: { paths: ['.'] },
820
- bans: 'a barrel — a file named index.ts, and any file whose whole content is re-exports',
821
- because:
822
- 'the tree already answers what exists and what each thing is for, so a barrel is a second answer that goes stale on its own. Measured 2026-08-18: packages/core/spec/src/islands/index.ts held seventeen `export *` lines and was hand-edited four times in one session to drop islands that had been deleted, and derived-drift caught it only once the glossary stopped masking it. That same file made `islands.X` the import at 454 sites across 11 packages, so not one file said which islands it used and the dependency graph could not be read from the imports at all. every package here already ships with no barrel: one line of package.json exports maps every file to its own path, measured 2026-08-27 across all eight',
823
- instead: 'export from the file that holds the thing, and let package.json carry a wildcard exports map so every file is importable at its own path. The tree is where you look to see what exists',
824
- wrong: "export * from './one'\nexport * from './two'\nexport * from './three'\n",
825
- right: 'export function coined(word: string): string {\n return word.trim()\n}\n',
826
- cites: [
827
- { on: '2026-08-18', said: 'another barrel. You love barrels huh. You love files that hold lists. why?' },
828
- { on: '2026-08-18', said: 'We do not need a file that holds a list. This is slop. Makes no sense to have a file that exists only to list what exists....what the fuck is this? Remove. Delete.' },
829
- { on: '2026-08-18', said: 'Add a new law: no barrels allowed. Block any file named index.ts at write time and compile and every sort of error' },
830
- ],
831
- seeds: [
832
- {
833
- id: 'slopnator',
834
- at: './slopnators/sl_no_barrel.ts',
835
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
836
- cites: [{ on: '2026-08-18', said: 'no barrels allowed' }],
837
- },
838
- ],
839
- },
840
- {
841
- id: 'inert-slopnator',
842
- at: './constraint.ts',
843
- version: 1,
844
- describes: 'The refusal of a slopnator that was seeded and never written — the file exists, the pair looks complete, and the rule it belongs to enforces nothing.',
845
- where: { paths: ['threads/slopnators/', 'thread/integrity/slopnators/'] },
846
- bans: 'a slopnator still returning `NOT_WRITTEN`, or one whose `proves` states nothing it refuses',
847
- because:
848
- 'seeding creates exactly this state on purpose: the row exists, the file exists, and `unpaired-slopnator` is satisfied because it asks only whether the file is there. Between the seed and the slopnator being written, the pair reads as complete from every direction. Measured 2026-08-19 in a fresh app with one rule declared and its slopnator seeded: the only thing that refused was a hand-written `throw` inside `defineLaw`, which reaches only the surfaces that call it, is counted by no report, and named `packages/core/integrity/src/slopnators/` and `pnpm slopnator:seed` — a directory and a command that do not exist in an app. A refusal that lives in one function instead of a row is the second enforcement mechanism this system exists to not have',
849
- instead: 'write the slopnator — replace the `NOT_WRITTEN` return with what it looks for, and fill `proves` with at least one form it refuses and one it accepts. If the rule was a mistake, delete the row and the file together',
850
- wrong: "export const slopnator: Slopnator = {\n on: [],\n holds: () => NOT_WRITTEN,\n proves: { wrong: [], right: [] },\n}",
851
- right: "export const slopnator: Slopnator = {\n holds: (_node, at) => at.text.includes('console.log') && 'a console.log',\n proves: { wrong: ['console.log(1)'], right: ['const held = 1'] },\n}",
852
- cites: [
853
- { on: '2026-08-19', said: 'No inert slopnator. So when someone adds a rule or law, it immediately shows when a slopnator is inert, or without anything written, any body or code.' },
854
- { on: '2026-08-19', said: 'Then on seed, we have this other state which is a blank slopnator, also not acceptable.' },
855
- ],
856
- seeds: [
857
- {
858
- id: 'slopnator',
859
- at: './slopnators/sl_inert_slopnator.ts',
860
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
861
- cites: [{ on: '2026-08-19', said: 'No inert slopnator.' }],
862
- },
863
- ],
864
- },
865
- {
866
- id: 'no-child-process',
867
- at: './constraint.ts',
868
- version: 1,
869
- describes: 'The refusal of a slopnator that can start a process — enforcement is one process reading files, and a slopnator that shells out can be re-entered by whatever it starts.',
870
- where: { paths: ['threads/slopnators/', 'thread/integrity/slopnators/'] },
871
- bans: 'a slopnator importing the one place this framework starts a process',
872
- because:
873
- 'measured 2026-08-19, twice, on the founder\'s machine: `derived-drift` spawned every command a `derives` row named, one of which was `pnpm trial:try`, which runs the trial for `derived-drift`, which spawned `pnpm trial:try` again. Six-way branching, unbounded depth, each level a fresh process holding the whole engine. Killing the parent did nothing because the children were already orphaned to launchd, and the machine ran out of memory and had to be restarted — twice, an hour apart, from one inverted filter. No comparable tool does this: eslint, semgrep, clippy and ruff are each a single process. The recursion was possible only because a slopnator was allowed to start something, so the rule is about the capability rather than about any one slopnator that misused it',
874
- instead: 'read what you need in this process. If a slopnator cannot see something without running a command, that command is a gate step, declared once and run once in the chain — not a slopnator that fires on every sweep and every file written',
875
- wrong: {
876
- files: [
877
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
878
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_programs.ts',\n },\n ],\n}\n" },
879
- { at: 'threads/_probe_programs.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const INTEGRATIONS: Threaded = {\n id: 'integrations',\n declares: [\n {\n id: 'programs',\n at: 'thread/integrations/cli.ts',\n },\n ],\n}\n" },
880
- { at: 'thread/integrity/slopnators/sl_probe_law.ts', source: "import { starts } from '../../integrations/cli.ts'\n" },
881
- ],
882
- at: 'thread/integrity/slopnators/sl_probe_law.ts',
883
- },
884
- right: "import { readFileSync } from '../../integrations/disk.ts'",
885
- cites: [
886
- { on: '2026-08-19', said: 'this last thing you did killed my mac. It spawned infinite number of processes called node which each took around 75mb, and they killed my memory, my ram, my disk. DO NOT DO THIS AGAIN.' },
887
- { on: '2026-08-19', said: 'You can put a the no node child thing in place already. Make it real.' },
888
- ],
889
- seeds: [
890
- {
891
- id: 'slopnator',
892
- at: './slopnators/sl_no_child_process.ts',
893
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
894
- cites: [{ on: '2026-08-19', said: 'You can put a the no node child thing in place already.' }],
895
- },
896
- ],
897
- },
898
-
899
- {
900
- id: 'no-dispatch-bypass',
901
- at: './constraint.ts',
902
- describes: 'The refusal of a second way in — anything done with an imported operation other than handing it to `dispatch`, so identity, rate, replay, input, quota and rules end up decided by whoever happened to hold the object.',
903
- version: 1,
904
- where: { paths: ['.'] },
905
- bans: 'reaching into an imported operation, calling it, or passing it anywhere but `dispatch`',
906
- because:
907
- 'measured 2026-08-22: nothing stopped it, and the tree said otherwise. The `dispatch` row this framework writes into every app states "a way to run that did not come through here does not exist", and the glossary publishes that sentence as the meaning of the word — while any file could `import { operation }` from a body and call `run` directly, skipping six gates in one line. A promise a tree makes about itself and cannot keep is worse than one it never made, because everything downstream reads the promise. Measured again 2026-08-24: the refusal read one form only, `… from \'<relative path>\'`, so three ways in were invisible — `await import(...)`, `createRequire(...)(...)`, and a path written from the tree root rather than from the file. Each was tried against a real tree and each passed unremarked. Measured a third time 2026-08-25: reading the BODY\'S ADDRESS still left a way in, because a body\'s address does not have to appear anywhere — `await import(\'../operations/\' + name + \'.ts\')` writes only a fragment, and a sweep over a real tree reported that file once, for being unthreaded, and not at all for this. So what is read is the DIRECTORY, not the file in it: an operation is seeded under `operations/`, dispatch reaches it by building the path at run time from the row and never writes it down, and everything else that gets there has to write a path pointing in — whole, or in pieces. Two things are not reached, and neither is an exception to the rule: dispatch, because it writes no such path, and the `at` of a row, read off the syntax tree rather than off the text, because naming a child is what an `at` is for and it is the one place the address belongs. The directory has to be read with its separator. Reading a bare `operations` as well fired on `id: \'operations\'` — the word this whole system is named after, sitting in the row that declares it — in both tree files at once, and the first reading of that misfire blamed the `at` exclusion and replaced it with a weaker one that matched `at:` as text. Nothing was wrong with the `at` exclusion; a word is not a path until it has a slash in it. Measured 2026-08-25, after dispatch was changed to take the operation itself instead of its name: this reading is now behind the rule it states. A caller has to import the body to hand it over, so the one sanctioned call site writes a path into `operations/` and is refused, while the deliberate bypass beside it writes the identical string — the reading cannot tell them apart, and a check that refuses the right answer and the wrong one alike is deciding nothing. What has to be read is what a file DOES with the operation, not whether it named the file: handing it to dispatch is the whole of a caller, and touching what it holds is the finding. Rewritten the same day: the reading is now over USE, not over paths. A file may import an operation — there is no other way to hand one over in this language — and the only thing it may then do with the binding is pass it to `dispatch`. Reaching into it, calling it, or passing it anywhere else is the finding, read off the syntax tree: a member access on the binding, a call whose callee is the binding, or the binding as an argument to anything that is not dispatch. Type-only imports bind nothing that can run and are skipped. Measured against a real tree the same day: the sanctioned caller, which imports the body and hands it to dispatch, is quiet, and the probe beside it, which imports the identical body and reaches into it, fires — the same two files the path reading refused alike. Measured 2026-08-28, after the founder said "I have no interest in shipping the framework. This is just scaffolding": dispatch moved, and its name did not. It was a value imported from `@thread/operations`, which is how ~900 lines of this framework ended up inside every artifact an app shipped; it is now the app\'s own `operations/_dispatch.ts`, written by `thread compile` — the same nine checks in the same order, emitted rather than imported. The first attempt at this renamed it to `gate`, and a real app refused that in two ways within one run of `thread gate`: `gate` was already coined by the command of that name, so the glossary held one word with two meanings, and the emitted file carried explanatory comments, which `no-comment` refuses in an app. Both were the same mistake — changing more than the ask needed. What this rule refuses did not change, the reading did not change, and the word a caller writes did not change. Only the import specifier did. Which files are bodies is asked of the tree through two clauses from two authorities, because each alone was measured wrong: reading the directory refused the woven body for importing its own steps, and asking the owning word called every byproduct of that meta a body — measured 2026-09-14, the four-line file that hands the seams on was refused for spreading a seam table, because this had decided the file it came from was an operation. So a body is a file some row derives with the compile command AND whose word wears an operation prefix, and neither clause is enough alone. Three things bind no operation and are passed: a type-only import, which names a shape nothing can dispatch; a type query or reference, which is erased before anything runs and whose refusal was a false positive that would have refused the probe written to test the reassignment form; and dispatch itself, which lives beside the operations because an app writes its own, so a file importing it from there had the handler added to the set and was then refused for mishandling the handler. The file is parsed here rather than taken from the visitor, because the sweep calls this once per file with no node at all, and what a file DOES with a binding is a question about the whole file rather than about one node in it',
908
- instead: 'hand `dispatch` the operation and let it run. Handing it over is the whole of what a caller does; reaching into what it holds — calling the function it compiled to, reading what it requires — is reaching past every gate the operation sits behind',
909
- wrong: "import { operation } from './operations/wf_send_invoice.ts'\n\nawait operation.run(opened, held)",
910
- right: {
911
- files: [
912
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
913
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_operations.ts',\n },\n ],\n}\n" },
914
- { at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n describes: 'sends an invoice',\n derives: [\n {\n id: 'wf_send_invoice_body',\n at: 'operations/wf_send_invoice.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
915
- { at: 'probe.tsx', source: "import { dispatch } from './operations/_dispatch.ts'\n\nimport { operation } from './operations/wf_send_invoice.ts'\n\nawait dispatch(operation, input, actor, opened)\n" },
916
- ],
917
- at: 'probe.tsx',
918
- },
919
- cites: [
920
- { on: '2026-08-17', said: 'That way we can enforce how things run, and they can only run through dispatch.' },
921
- { on: '2026-08-17', said: 'when something wants to run an operation (meaning an invoke), it\'s a simple, direct, dispatch(wf_) or dispatch(agt_) and it just works, it just runs, and everything is as is.' },
922
- { on: '2026-08-22', said: 'I guess you should do both then.' },
923
- { on: '2026-08-24', said: 'dispatch is the only way to run an operation' },
924
- ],
925
- seeds: [
926
- {
927
- id: 'slopnator',
928
- at: './slopnators/sl_no_dispatch_bypass.ts',
929
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
930
- cites: [{ on: '2026-08-22', said: 'I guess you should do both then.' }],
931
- },
932
- ],
933
- },
934
-
935
- {
936
- id: 'undeclared-node',
937
- at: './constraint.ts',
938
- describes: 'The refusal of a step nobody declared — an operation built from a node the descent never reaches, so the flow reads as agreed while one of its spans is a step this app never said it could take.',
939
- version: 1,
940
- where: { paths: ['operations/'] },
941
- bans: 'an operation reaching a node under `nodes/` that the tree does not declare',
942
- because:
943
- 'measured 2026-08-22: `OperationNode` stated `node` as a bare string, so a body naming `nd_teleport` compiled, loaded and dispatched, and nothing anywhere compared that name against a declared node. The first reading of this rule matched that string. Measured again 2026-08-26, after a node began building its own span: an operation writes no such string any more — it imports a node and calls it — so the reading matched nothing on any real file, and its own proofs were written in the same retired shape, so it passed its trial while measuring nothing. Both halves drifted together, which is the only way a rule goes quiet without anybody noticing. Proved by planting a node body no row declares and running a real app: it ran, answered `ok`, and the sweep named the file for being unthreaded and said nothing about the operation built from it. What an operation says today is the import it reaches its node through, so that is what is read — resolved to a path and checked against the descent, which also catches an import that resolves to nothing at all, where the only other answer was a module-not-found at run time. Measured a third time the same day, an hour after that reading shipped: it matched `import … from` and nothing else, so `await import(...)`, `require(...)` and `export … from` each walked through it, each tried against a real tree and each unremarked. Twice now this rule has enumerated a form instead of stating its rule, which is how a refusal ends up being a list of whatever somebody happened to try. The rule is about the PATH: a string that resolves under `nodes/` names a node, and a node the descent never reaches is a step nobody agreed to — whether it arrives by import, by require, by re-export, or sitting in a const waiting to be used',
944
- instead: 'declare the node in the tree and then build from it. What an app can be asked to do grows by declaring a node, never by importing a file nothing brought into existence',
945
- wrong: {
946
- files: [
947
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
948
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_nodes.ts',\n },\n ],\n}\n" },
949
- { at: 'threads/_probe_nodes.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const NODES: Threaded = {\n id: 'nodes',\n declares: [\n {\n id: 'read',\n at: 'nodes/nd_read.ts',\n },\n ],\n}\n" },
950
- { at: 'operations/_probe_wrong.ts', source: "import { node as ghost } from '../nodes/nd_ghost.ts'" },
951
- ],
952
- at: 'operations/_probe_wrong.ts',
953
- },
954
- right: {
955
- files: [
956
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
957
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_nodes.ts',\n },\n ],\n}\n" },
958
- { at: 'threads/_probe_nodes.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const NODES: Threaded = {\n id: 'nodes',\n declares: [\n {\n id: 'read',\n at: 'nodes/nd_read.ts',\n },\n ],\n}\n" },
959
- { at: 'operations/_probe_right.ts', source: "import { node as read } from '../nodes/nd_read.ts'" },
960
- ],
961
- at: 'operations/_probe_right.ts',
962
- },
963
- cites: [
964
- { on: '2026-08-22', said: 'Operations are bilt of nodes, only nodes that are declared.' },
965
- { on: '2026-08-22', said: 'That\'s why it grows. We declare, operations can do more.' },
966
- { on: '2026-08-26', said: '1-this is a clear BUG' },
967
- ],
968
- seeds: [
969
- {
970
- id: 'slopnator',
971
- at: './slopnators/sl_undeclared_node.ts',
972
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
973
- cites: [{ on: '2026-08-22', said: 'Operations are bilt of nodes, only nodes that are declared.' }],
974
- },
975
- ],
976
- },
977
-
978
- {
979
- id: 'unprefixed-member',
980
- at: './constraint.ts',
981
- describes: 'The refusal of a row wearing the wrong prefix — an operation declared among the nodes or a node among the operations, so what a row IS stops being readable from its name and everything downstream believes the list it happens to sit in.',
982
- version: 1,
983
- where: { paths: ['.'] },
984
- bans: 'a row in a tree file\'s `MEMBERS` whose id does not carry a prefix that list takes — `wf_`, `vw_` or `agt_` in the operations tree, `nd_` in the nodes tree',
985
- because:
986
- 'measured 2026-08-24: a prefix is not bound to a file. Both tree files export a list called `MEMBERS`, both are arrays of rows carrying a string id, and nothing in TypeScript or in any check separated them — a node id placed in the operations list typechecked, attached, seeded a body and read as agreed, and an operation id placed in the nodes list did the same. The prefix is the only thing that says which of the two a row is, and it was decoration. Measured again 2026-08-25, after the first reading of this was deleted for a different reason: the same gap was still open, and the founder named it as one of five mismatches against their stated requirements. Depth is what the reading turns on — a member row states its own id, and the `seeds` and `derives` rows beneath it state theirs, which are bodies rather than operations or nodes, so `wf_send_invoice_body` carries no prefix and must not be refused for it. Only rows sitting directly in the list are read. What a row may wear is not written out here either — the prefixes come from the one list that `thread create` and this law both read, so a kind added there is never a kind this refuses. The braces are read off the code mask this package already ships rather than counted in the raw text, because counting them directly desynced on a describes holding a brace, and a second weaker walker beside the one that exists is the drift it exists to prevent',
987
- instead: 'name the row for what it is. An operation is `wf_`, `vw_` or `agt_` and belongs in the operations tree; a node is `nd_` and belongs in the nodes tree. A row in the wrong list is not a naming slip, it is a declaration of the wrong thing',
988
- wrong: {
989
- files: [
990
- {
991
- at: 'threads/operations.ts',
992
- source:
993
- "export const MEMBERS: readonly Member[] = [\n {\n id: 'nd_teleport',\n at: '_threads/operations.ts',\n describes: 'a node wearing an operation list',\n version: 1,\n },\n]\n",
994
- },
995
- ],
996
- at: 'threads/operations.ts',
997
- },
998
- right: {
999
- files: [
1000
- {
1001
- at: 'threads/operations.ts',
1002
- source:
1003
- "export const MEMBERS: readonly Member[] = [\n {\n id: 'wf_send_invoice',\n at: '_threads/operations.ts',\n describes: 'sends an invoice',\n version: 1,\n },\n]\n",
1004
- },
1005
- ],
1006
- at: 'threads/operations.ts',
1007
- },
1008
- cites: [
1009
- { on: '2026-08-24', said: 'operations.ts, where it declares nodes.ts and dispatch.ts, and member rows in operations are wf_,vw_ and agt_ files.' },
1010
- { on: '2026-08-25', said: '3 fix it please.' },
1011
- ],
1012
- seeds: [
1013
- {
1014
- id: 'slopnator',
1015
- at: './slopnators/sl_unprefixed_member.ts',
1016
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1017
- cites: [{ on: '2026-08-22', said: 'I guess you should do both then.' }],
1018
- },
1019
- ],
1020
- },
1021
-
1022
- {
1023
- id: 'untranslated-key',
1024
- at: './constraint.ts',
1025
- describes: 'The refusal of wording an app asks for and cannot say — a key named in the interface that a declared language answers with nothing, which renders as a blank where a sentence belongs and looks, to whoever sees it, like a broken component rather than an unfinished translation.',
1026
- version: 1,
1027
- where: { paths: ['.'] },
1028
- bans: 'a message key named by an element and left empty, or absent, in any language the app declares',
1029
- because:
1030
- 'until 2026-08-29 an app could not declare a single word of its own: the translator was built over the catalogue compiled into `@thread/ui`, and a key that catalogue did not hold threw at render. So every app showed the framework\'s English and nothing else, and there was no file anywhere for a second language to go in. With the wording generated, the remaining hole is the one that cannot be generated — nobody can invent the sentence, so a key arrives holding an empty string, and an empty string is indistinguishable at every other surface from a translated one. The compiler catches a key a language has no LINE for; only this catches a line with nothing in it. It reads the files that are on disk and the markup that is actually there rather than calling the generator, because a check that runs the thing it is checking passes whenever that thing passes, which is no check at all. And which props carry a key is read off the declarations of the installed package itself: a list written into this law would go stale the first time an element gained a prop — silently, by finding fewer keys than there are, which is the failure this law exists to prevent',
1031
- instead: 'write it with `thread create message`, which asks for every declared language at the moment the key is made and refuses a blank answer, or fill the line `thread i18n` added to each `i18n/<language>.json`',
1032
- wrong: '<Button label="checkout.confirm" /> with { "checkout.confirm": "" } in i18n/pt-BR.json',
1033
- right: '<Button label="checkout.confirm" /> with { "checkout.confirm": "Confirmar" } in i18n/pt-BR.json',
1034
- cites: [
1035
- { on: '2026-08-29', said: 'i18n keys must be translated OR ITS AN ERROR. This should be done 2 ways, CLI requires the keys in i18n in all translations needed AT THE MOMENT OF CREATION, not bypassable + a new constraint.ts row and slopnator that checks i18n entries against the keys in the UI and if there\'s anything missing in whatever file, it\'s a build error, compile error, gate error, everything. Cannot be allowed.' },
1036
- { on: '2026-08-29', said: 'i18n ships by default, nothing hardcoded in the UI at all' },
1037
- ],
1038
- seeds: [
1039
- {
1040
- id: 'slopnator',
1041
- at: './slopnators/sl_untranslated_key.ts',
1042
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1043
- cites: [{ on: '2026-08-22', said: 'I guess you should do both then.' }],
1044
- },
1045
- ],
1046
- },
1047
- {
1048
- id: 'only-takes-a-literal',
1049
- at: './constraint.ts',
1050
- describes: 'The refusal of a key list typed at the check — the keys a command accepts and the questions its receipt prints are one set, so a command that spells them out at `only` has stated that set twice and nothing holds the two together.',
1051
- version: 1,
1052
- where: { paths: ['.'] },
1053
- bans: 'an array literal as the second argument of `only`',
1054
- because:
1055
- 'measured 2026-09-03 across eight receipts: six printed labels that were not the keys their own `only` took. `target new` had two of seven wrong; `target call` printed four labels for five keys and never showed `of` at all, so a piped answer could name a field the receipt had never mentioned; `capability` validated `live` and named it nowhere. Every one of them was a list typed at the check beside a second list typed in the guide, and every correct one already built both from the same rows. The defect is not that somebody mistyped a key, it is that the shape allowed two answers to one question — the same session that fixed three of these shipped a seventh while doing it',
1056
- instead:
1057
- 'state the questions once, as rows carrying the key and what it says, and derive both from them — `const TAKES = ASKS.map((one) => one.key)` for what the command accepts, and the printed lines from the same array. A key that is not asked for cannot then be taken, and a label that is printed cannot then be refused',
1058
- wrong: "only(piped, ['name', 'describes', 'address'], 'a target')",
1059
- right: 'only(piped, TAKES, \'a target\')',
1060
- seeds: [
1061
- {
1062
- id: 'slopnator',
1063
- at: './slopnators/sl_only_takes_a_literal.ts',
1064
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1065
- },
1066
- ],
1067
- },
1068
- {
1069
- id: 'unvaulted-secret',
1070
- at: 'thread/integrity/constraint.ts',
1071
- describes: 'The refusal of a secret taken from the environment instead of the vault. The vault refuses a name no row declares and a value nothing sealed; the environment refuses nothing, so whoever reads it is handed whatever the machine happens to hold under that name.',
1072
- version: 1,
1073
- where: { paths: ['.'] },
1074
- bans: 'reading process.env under a name the file does not state, or under a name wearing a secret prefix',
1075
- because: 'measured 2026-09-15: this repository shipped a resolver that read process.env under the uppercased name of whatever was asked for, and installed it as the vault seam whenever the environment said production. Every gate the vault states — that the name is one a row declares, that a value was sealed for this environment, that the key opens it — was skipped for every secret, and a server credential and a browser value read identically. Nothing refused it, because secret-literal reads what a string contains and a member expression contains nothing, and because that law named an environment read among the things it deliberately does not find. The two halves are refused for different reasons: a name the file states can be compared against the secrets, while a name nobody reading the file can see can be any of them. Measured 2026-09-17, against the rule that a check may hold no list of its own: the two prefixes this read were a list typed into thread/integrations/secret.ts, and I first recorded here that they were where the tree coins them. That was wrong and the founder caught it: `sec_` and `pub_` are coined in the sentence on the secrets row and in the glossary, which is prose the descent cannot read. They come from the declared secrets now — every secret row wears one, and the prefixes are what those ids begin with — so a new kind of secret widens this without anybody typing it.',
1076
- instead: 'ask the vault. A declared secret is opened by the file `thread vault` writes, which refuses a name no row states and a value nothing sealed. Nothing inside an operation ever holds the value: the operation names a target on its `requires`, dispatch opens what that target is wired to send, and it goes straight into the call. If what you are reading is not a secret, give it a name wearing no secret prefix and write that name in the file rather than computing it',
1077
- wrong: 'const key = process.env[named.toUpperCase()]',
1078
- right: 'const mode = process.env[\'NODE_ENV\']',
1079
- seeds: [
1080
- {
1081
- id: 'slopnator',
1082
- at: 'thread/integrity/slopnators/sl_unvaulted_secret.ts',
1083
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1084
- },
1085
- ],
1086
- },
1087
- {
1088
- id: 'undeclared-list',
1089
- at: 'thread/integrity/constraint.ts',
1090
- describes: 'The refusal of a check that decides from a list of words somebody typed. A rule reaches exactly as far as those words, so the first thing built under a name nobody typed walks straight past and the rule reports green over it. The list is followed wherever it was put — wrapped in as const, nested under a key, spread out of a helper, turned into a set of its own keys, or moved into another file and imported back.',
1091
- version: 1,
1092
- where: { paths: ['thread/integrity/slopnators/', 'threads/slopnators/'] },
1093
- bans: 'a check holding its own list of words — a name set to a written-out list, or to something built from one',
1094
- because: 'Measured 2026-09-16: `undeclared-crossing` says only a declared operation may act, and its check carried ten ways of acting written out. Starting a process was not one of them, so 48 places that start one reported green for as long as that list stood. Founder the same day: it must not be a hand written list. Measured 2026-09-17: this reading called every `!X.has(y)` a list the check only skips, so a catch list written as a negated guard that leaves the check — `if (!STARTS_A_PROCESS.has(from)) return false` — read as exempt, and `no-child-process` carried two spellings and reported green over every other way to start a process. What decides it is what the branch does: leaving the check on a membership test is the list deciding the refusal, while a `continue` in a loop is the list stepping over one candidate.',
1095
- instead: 'Ask the tree for the words. Every one this framework can refuse is coined by a row, so `thread inspect <word>` names where any of them is declared, and a check that reads those rows widens when the tree does rather than when somebody remembers to type.',
1096
- wrong: 'const CROSSINGS = [\'fetch\', \'setTimeout\', \'setInterval\']',
1097
- right: 'const CROSSINGS = declared()',
1098
- seeds: [
1099
- {
1100
- id: 'slopnator',
1101
- at: 'thread/integrity/slopnators/sl_undeclared_list.ts',
1102
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1103
- },
1104
- ],
1105
- },
1106
- {
1107
- id: 'screen-holds-only-declared',
1108
- at: 'thread/integrity/constraint.ts',
1109
- describes: 'The refusal of anything in a screen that is not one of the three things a screen is made of. A screen arranges declared elements and compositions and may hand an operation to dispatch; it reaches nothing and computes nothing itself, so a fetch or a clock written into one has nowhere to be rather than a rule standing against it.',
1110
- version: 1,
1111
- where: { paths: ['screens/', 'compositions/'] },
1112
- bans: 'a call in a screen that is not an element, a composition, or an operation handed to dispatch',
1113
- because: 'Measured 2026-09-17: undeclared-crossing carried twenty-one host spellings to catch exactly this, and reported nothing across all 320 files of this repository, because the framework itself always goes through a declared door. A screen is the one place those spellings were for: the screens command seeds an empty screen file and a person fills the body in, so a raw fetch can be typed straight into one. Founder the same day: Fetch just should not have a way to exist. Naming the ways to reach outside is a list that goes stale; naming the three things a screen may hold is not, because those three come from rows.',
1114
- instead: 'put the work in an operation and hand it to dispatch, and put what is drawn in an element or a composition. thread create operation declares the first, thread create element the second, and a screen then names them.',
1115
- wrong: 'export const Home = screen(\'home\', {}, () => fetch(address))',
1116
- right: {
1117
- files: [
1118
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
1119
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_operations.ts',\n },\n ],\n}\n" },
1120
- { at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_place_order',\n describes: 'places an order',\n derives: [\n {\n id: 'wf_place_order_body',\n at: 'operations/wf_place_order.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
1121
- { at: 'screens/probe.tsx', source: "import { dispatch } from '../operations/_dispatch.ts'\n\nimport { operation } from '../operations/wf_place_order.ts'\n\nexport const Home = screen('home', {}, () => dispatch(operation, input, actor, opened))\n" },
1122
- ],
1123
- at: 'screens/probe.tsx',
1124
- },
1125
- seeds: [
1126
- {
1127
- id: 'slopnator',
1128
- at: 'thread/integrity/slopnators/sl_screen_holds_only_declared.ts',
1129
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1130
- },
1131
- ],
1132
- },
1133
- {
1134
- id: 'undeclared-webhook',
1135
- at: 'thread/integrity/constraint.ts',
1136
- describes: 'The refusal of a webhook and an operation that do not agree. A webhook names the operation it invokes and that operation names the webhook back, so nothing outside this app reaches an operation that never said it would take it.',
1137
- version: 1,
1138
- where: { paths: ['threads/', 'app/inbound/'] },
1139
- bans: 'a webhook invoking an operation that does not name it back, and a webhook carrying no signature reaching an operation that takes only signed ones',
1140
- because: 'measured 2026-09-17: a webhook is a url anybody on the internet may post to, so whatever sits behind it is reachable by whoever finds the path. One row saying which operation it runs is half a decision, and the missing half is the one that matters: the operation never agreed to be reached that way, and nothing anywhere recorded that it had.',
1141
- instead: 'declare the webhook with `thread create webhook new <name>`, which asks the target it is `of`, the `path` it lands on, and the operation it `invokes`. Then name it back on that operation with `thread edit <operation> --webhooks <webhook>` — the same command with no field prints what that row says now, every field it takes with it, `webhooks` and `signed` among them. An operation whose row says `signed` needs the other half as well: `arrives` on the webhook, naming the header its signature comes in on, and a secret declared with `thread create secret new <name>` in what that webhook requires. The route is never written by hand — `thread capabilities` derives it from these rows.',
1142
- wrong: "const WH = { id: 'wh_stripe', invokes: 'wf_charge' }",
1143
- right: "const WH = { id: 'wh_stripe', describes: 'Where stripe posts what it has done.' }",
1144
- seeds: [
1145
- {
1146
- id: 'slopnator',
1147
- at: 'thread/integrity/slopnators/sl_undeclared_webhook.ts',
1148
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1149
- },
1150
- ],
1151
- },
1152
- {
1153
- id: 'outside-an-operation',
1154
- at: 'thread/integrity/constraint.ts',
1155
- describes: 'The refusal of a call on what this app hands an operation, made anywhere but inside one. An operation is opened with a store, a target or a program and is the only thing that may use them, so a file that gets hold of one and calls on it is doing an operation work where nothing declared it.',
1156
- version: 1,
1157
- where: { paths: ['.'] },
1158
- bans: 'a call on what this app hands an operation, made outside an operation',
1159
- because: 'Measured 2026-09-17: undeclared-crossing read two different things under one name. One is a reach past this process that goes through no declared integration; the other is a call on what an operation was opened with, made by something that is not an operation. They fire on different forms, they are fixed in different ways, and a single name meant neither could be stated on its own. Founder the same day: Then separate them. One is for things that reach or ways the repo is reached outside the repo and are not integrations, then there is things that reach outside or are reached by an outside thing and are not operations.',
1160
- instead: 'declare an operation and let dispatch open it. What an operation is handed is handed to it, and nothing else in this app is given a way to use it.',
1161
- wrong: {
1162
- files: [
1163
- { at: 'thread/operations/operations.ts', source: 'export interface Store {\n insert(row: unknown): Promise<void>\n}\n\nexport type Opened<R extends string = never> = { readonly [K in R]: Store }\n' },
1164
- { at: 'threads/_probe_target.ts', source: "import type { Opened } from '../thread/operations/operations.ts'\n\nexport const kept = async (opened: Opened<'pg_main'>): Promise<void> => {\n await opened['pg_main']['insert']({})\n}\n" },
1165
- ],
1166
- at: 'threads/_probe_target.ts',
1167
- },
1168
- right: 'export const carried = (opened: Opened<\'pg_main\'>, run: (given: Opened<\'pg_main\'>) => Promise<void>): Promise<void> => run(opened)',
1169
- seeds: [
1170
- {
1171
- id: 'slopnator',
1172
- at: 'thread/integrity/slopnators/sl_outside_an_operation.ts',
1173
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1174
- },
1175
- ],
1176
- },
1177
- {
1178
- id: 'underived-route',
1179
- at: 'thread/integrity/constraint.ts',
1180
- describes: 'The refusal of a route nothing derives. A file that answers a request is written by a generator out of rows, never by hand, so the only shape allowed is a derives row naming it with the command that writes it and the command that catches it drifting.',
1181
- version: 1,
1182
- where: { paths: ['threads/'] },
1183
- bans: 'a route file that is not a derives row carrying both by and runs',
1184
- because: 'measured 2026-09-17: a generator wrote app/inbound/wh_stripe/route.ts while no row anywhere named that address. Nothing refused it, because unthreaded only asks whether some row names a file and derived-drift only guards addresses a derives row already states. A route is the one file anybody on the internet can reach, so a route nobody declared is a door nobody agreed to.',
1185
- instead: 'declare the generator with `thread create generator`, which writes its row into GENERATORS with the `by` command it answers, then give the thing that owns the route a `derives` row naming the route file, with `by` set to that command and `runs` set to that command and --check. Both rows or neither: the generator makes the bytes, and the derives row is what says the address was agreed to.',
1186
- wrong: 'const M = { declares: [{ at: \'app/inbound/wh_stripe/route.ts\' }] }',
1187
- right: {
1188
- files: [
1189
- { at: 'threads/thread.ts', source: "export const THREAD = {\n declares: [\n { at: 'threads/_probe_route.ts' },\n ],\n}\n" },
1190
- { at: 'threads/_probe_route.ts', source: "export const M = {\n derives: [\n { at: 'app/inbound/wh_stripe/route.ts', by: 'thread capabilities', runs: 'thread capabilities --check' },\n ],\n}\n" },
1191
- ],
1192
- at: 'threads/_probe_route.ts',
1193
- },
1194
- seeds: [
1195
- {
1196
- id: 'slopnator',
1197
- at: 'thread/integrity/slopnators/sl_underived_route.ts',
1198
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1199
- },
1200
- ],
1201
- },
1202
- {
1203
- id: 'door-in-a-node',
1204
- at: 'thread/integrity/constraint.ts',
1205
- describes: 'The refusal of a node that reaches outside by importing the framework\'s own way out — the file that starts programs, reads the disk or sends over https — so what it does out there goes through no declared target and nothing prices it, replays it in a trial or refuses it.',
1206
- version: 1,
1207
- where: { paths: ['operations/'] },
1208
- bans: 'a node importing a file that reaches past this process — by a Node builtin, a global like fetch, or a driver — or importing anything that imports one',
1209
- because: 'measured 2026-09-22: `undeclared-crossing` refuses the spellings that reach past this process — `node:child_process`, `node:fs`, `fetch` and the rest — and lets a file declared under `integrations` use them, because those files are the doors this framework opens on an operation\'s behalf. A node that imports one of them writes no such spelling, so that check read it as clean, and it required no target, so no target check had anything to read. Nothing read what the imported file opens, and a node could run git or write the disk with every check green',
1210
- instead: 'require the target on the operation and call what the node is handed. `thread create target` declares the place and `thread create target call` each thing it is asked; the operation\'s `requires` names the target, and the node receives one function per call. A type may be imported from anywhere, because a type opens nothing',
1211
- wrong: {
1212
- files: [
1213
- { at: 'thread/integrations/cli.ts', source: "import { spawn } from 'node:child_process'\n\nexport const starts = (program: string): void => {\n spawn(program)\n}\n" },
1214
- { at: 'operations/_probe_node.ts', source: "import { starts } from '../thread/integrations/cli.ts'\n\nexport const node = (): void => {\n starts('git')\n}\n" },
1215
- ],
1216
- at: 'operations/_probe_node.ts',
1217
- },
1218
- right: 'import { z } from \'zod\'',
1219
- seeds: [
1220
- {
1221
- id: 'slopnator',
1222
- at: 'thread/integrity/slopnators/sl_door_in_a_node.ts',
1223
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1224
- },
1225
- ],
1226
- },
1227
- {
1228
- id: 'unlisted-program',
1229
- at: 'thread/integrity/constraint.ts',
1230
- describes: 'The refusal of a cli target naming a program this tree did not install — a program found on the machine instead of under node_modules/.bin is whatever that machine has, so a target on it bounds nothing and a declared call on it can run any line.',
1231
- version: 1,
1232
- where: { paths: ['threads/'] },
1233
- bans: 'a `cli_` target whose program is neither one this tree installed under node_modules/.bin, nor git, node or thread, nor the package manager the lockfile picks',
1234
- because: 'measured 2026-09-22: the program a cli target starts fell back to the bare name when nothing under node_modules/.bin carried it, so a target `cli_bash` with a call at `-c` would have started a shell with whatever line an operation handed it, and every check stayed green — the prefix was right, the call was declared, the operation required it. Measured the same day: `cli_typescript` was declared naming a program that does not exist, since the installed executable is `tsc`, and nothing said so until a build tried to start it',
1235
- instead: 'name the target after a program this tree installs, so `cli_` and the file under node_modules/.bin agree — `cli_tsc`, not `cli_typescript` — or after git, node, thread or `packager`, the word for whichever package manager the lockfile picks. A program the tree does not install yet is added as a dependency first, so an install brings it and the lockfile records it',
1236
- wrong: 'export const TARGETS: readonly Target[] = [{ id: \'cli_typescript\', of: \'in_typescript\', describes: \'The compiler this tree is checked and built with.\', version: 1, takes: [], requires: [] }]',
1237
- right: 'export const TARGETS: readonly Target[] = [{ id: \'cli_git\', of: \'in_git\', describes: \'The git on the machine a command runs on.\', version: 1, takes: [], requires: [] }]',
1238
- seeds: [
1239
- {
1240
- id: 'slopnator',
1241
- at: 'thread/integrity/slopnators/sl_unlisted_program.ts',
1242
- describes: 'The code that finds what a rule refuses — one per rule, resolved from the rule\'s id, in one of three kinds: a pass over a syntax tree, a pass over a file\'s text, or a command whose output is read.',
1243
- },
1244
- ],
1245
- },
1246
- ]