@prestack/thread 0.0.1 → 0.0.3
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.
- package/dist/core/actor.ts +20 -0
- package/dist/core/asking.d.ts +0 -1
- package/dist/core/asking.js +0 -1
- package/dist/core/asking.ts +1342 -0
- package/dist/core/attach.js +2 -2
- package/dist/core/attach.ts +585 -0
- package/dist/core/chain.js +8 -1
- package/dist/core/chain.ts +148 -0
- package/dist/core/cli.js +97 -18
- package/dist/core/cli.ts +2004 -0
- package/dist/core/condition.ts +227 -0
- package/dist/core/descend.d.ts +1 -0
- package/dist/core/descend.js +36 -0
- package/dist/core/descend.ts +1001 -0
- package/dist/core/finding.ts +38 -0
- package/dist/core/generators/meta.js +6 -0
- package/dist/core/generators/meta.ts +1006 -0
- package/dist/core/init.ts +170 -0
- package/dist/core/invoked.ts +47 -0
- package/dist/core/round.ts +57 -0
- package/dist/core/rows.ts +369 -0
- package/dist/core/scope.ts +18 -0
- package/dist/core/thread.ts +29 -0
- package/dist/core/tree.ts +67 -0
- package/dist/core/types.ts +350 -0
- package/dist/derivation/attach.js +1 -1
- package/dist/derivation/attach.ts +68 -0
- package/dist/derivation/generators/build.ts +232 -0
- package/dist/derivation/generators/check.ts +118 -0
- package/dist/derivation/generators/derivation.ts +347 -0
- package/dist/derivation/generators/determinism.ts +145 -0
- package/dist/derivation/generators/release.js +19 -9
- package/dist/derivation/generators/release.ts +213 -0
- package/dist/derivation/makers/release.ts +135 -0
- package/dist/derivation/release.ts +23 -0
- package/dist/integrations/attach.js +2 -2
- package/dist/integrations/attach.ts +207 -0
- package/dist/integrations/generators/read.ts +127 -0
- package/dist/integrations/generators/secret.ts +289 -0
- package/dist/integrations/generators/target.js +16 -6
- package/dist/integrations/generators/target.ts +944 -0
- package/dist/integrations/generators/vault.js +46 -14
- package/dist/integrations/generators/vault.ts +212 -0
- package/dist/integrations/{sdk.js → module.js} +4 -4
- package/dist/integrations/module.ts +189 -0
- package/dist/integrations/secret.ts +50 -0
- package/dist/integrations/target.d.ts +3 -1
- package/dist/integrations/target.js +14 -11
- package/dist/integrations/target.ts +340 -0
- package/dist/integrations/vault.d.ts +2 -0
- package/dist/integrations/vault.js +4 -2
- package/dist/integrations/vault.ts +136 -0
- package/dist/integrity/attach.ts +234 -0
- package/dist/integrity/check-threads.js +2 -26
- package/dist/integrity/check-threads.ts +166 -0
- package/dist/integrity/constraint.ts +1202 -0
- package/dist/integrity/generators/compiler.ts +161 -0
- package/dist/integrity/generators/gate.ts +201 -0
- package/dist/integrity/generators/issues.ts +160 -0
- package/dist/integrity/generators/publish.js +14 -5
- package/dist/integrity/generators/publish.ts +247 -0
- package/dist/integrity/generators/rule.ts +271 -0
- package/dist/integrity/generators/seal.ts +117 -0
- package/dist/integrity/generators/trial.ts +320 -0
- package/dist/integrity/lint.ts +227 -0
- package/dist/integrity/lsp/bin.ts +42 -0
- package/dist/integrity/lsp/diagnostics.ts +92 -0
- package/dist/integrity/lsp/findings.ts +63 -0
- package/dist/integrity/lsp/lint.ts +140 -0
- package/dist/integrity/lsp/protocol.ts +78 -0
- package/dist/integrity/lsp/server.ts +224 -0
- package/dist/integrity/sealed.js +2 -2
- package/dist/integrity/sealed.ts +56 -0
- package/dist/integrity/slopnator.ts +350 -0
- package/dist/integrity/slopnators/sl_broken_lineage.ts +36 -0
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +39 -0
- package/dist/integrity/slopnators/sl_derived_drift.ts +98 -0
- package/dist/integrity/slopnators/sl_hardcoded_style.ts +55 -0
- package/dist/integrity/slopnators/sl_inert_island.ts +91 -0
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +93 -0
- package/dist/integrity/slopnators/sl_missing_row.ts +182 -0
- package/dist/integrity/slopnators/sl_no_barrel.ts +48 -0
- package/dist/integrity/slopnators/sl_no_child_process.ts +74 -0
- package/dist/integrity/slopnators/sl_no_comment.ts +43 -0
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +328 -0
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +50 -0
- package/dist/integrity/slopnators/sl_no_such_command.ts +64 -0
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +63 -0
- package/dist/integrity/slopnators/sl_secret_literal.ts +100 -0
- package/dist/integrity/slopnators/sl_stale_path.ts +94 -0
- package/dist/integrity/slopnators/sl_twice_meant.ts +31 -0
- package/dist/integrity/slopnators/sl_twice_parented.ts +37 -0
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +87 -0
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +65 -0
- package/dist/integrity/slopnators/sl_undeclared_element.ts +44 -0
- package/dist/integrity/slopnators/sl_undeclared_node.ts +69 -0
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +147 -0
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +148 -0
- package/dist/integrity/slopnators/sl_undescribed.ts +65 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +61 -0
- package/dist/integrity/slopnators/sl_unkept_rows.ts +111 -0
- package/dist/integrity/slopnators/sl_unnamed_island.ts +41 -0
- package/dist/integrity/slopnators/sl_unpaired_member.ts +131 -0
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +58 -0
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +136 -0
- package/dist/integrity/slopnators/sl_unseamed_reach.ts +79 -0
- package/dist/integrity/slopnators/sl_unthreaded.ts +419 -0
- package/dist/integrity/slopnators/sl_untranslated_key.ts +164 -0
- package/dist/integrity/slopnators/sl_unversioned.ts +36 -0
- package/dist/integrity/sweep.ts +307 -0
- package/dist/knowledge/attach.ts +350 -0
- package/dist/knowledge/generators/docsgen.ts +174 -0
- package/dist/knowledge/generators/glossary.ts +73 -0
- package/dist/knowledge/generators/skills.ts +263 -0
- package/dist/knowledge/glossary.ts +6 -0
- package/dist/operations/access.ts +88 -0
- package/dist/operations/attach.ts +475 -0
- package/dist/operations/capabilities.ts +65 -0
- package/dist/operations/cost.ts +58 -0
- package/dist/operations/dispatch.ts +348 -0
- package/dist/operations/error-results.ts +31 -0
- package/dist/operations/errors/catalog.ts +168 -0
- package/dist/operations/errors/classes.ts +51 -0
- package/dist/operations/errors/errors.ts +314 -0
- package/dist/operations/generators/capability.js +29 -19
- package/dist/operations/generators/capability.ts +704 -0
- package/dist/operations/generators/compile.js +20 -1
- package/dist/operations/generators/compile.ts +643 -0
- package/dist/operations/generators/grant.ts +336 -0
- package/dist/operations/generators/operation.ts +972 -0
- package/dist/operations/generators/permits.ts +223 -0
- package/dist/operations/generators/seed.ts +347 -0
- package/dist/operations/generators/trial.js +4 -1
- package/dist/operations/generators/trial.ts +529 -0
- package/dist/operations/http.ts +164 -0
- package/dist/operations/makers/capability.ts +166 -0
- package/dist/operations/makers/trial.ts +520 -0
- package/dist/operations/memory.ts +470 -0
- package/dist/operations/metering.ts +328 -0
- package/dist/operations/nodes.ts +54 -0
- package/dist/operations/operations.ts +32 -0
- package/dist/operations/queue-policy.ts +10 -0
- package/dist/operations/result.ts +28 -0
- package/dist/operations/seams.d.ts +4 -4
- package/dist/operations/seams.ts +364 -0
- package/dist/operations/store.ts +29 -0
- package/package.json +8 -1
- package/dist/integrations/generators/sdk.d.ts +0 -1
- package/dist/integrations/generators/sdk.js +0 -148
- /package/dist/integrations/{sdk.d.ts → module.d.ts} +0 -0
|
@@ -0,0 +1,1202 @@
|
|
|
1
|
+
import type { Threaded } from '../core/thread.ts'
|
|
2
|
+
import { named } from '../core/scope.ts'
|
|
3
|
+
|
|
4
|
+
export interface Constraint extends Threaded {
|
|
5
|
+
readonly version: number
|
|
6
|
+
readonly where: { readonly paths: readonly string[]; readonly except?: readonly string[] }
|
|
7
|
+
readonly bans: string
|
|
8
|
+
readonly because: string
|
|
9
|
+
readonly instead: string
|
|
10
|
+
readonly wrong: string
|
|
11
|
+
readonly right: string
|
|
12
|
+
readonly governs?: string
|
|
13
|
+
readonly severity?: 'forbidden' | 'warning'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const CONSTRAINTS: readonly Constraint[] = [
|
|
17
|
+
{
|
|
18
|
+
id: 'no-comment',
|
|
19
|
+
at: './constraint.ts',
|
|
20
|
+
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.',
|
|
21
|
+
version: 1,
|
|
22
|
+
where: { paths: ['.'], except: ['_generated/', 'slopnators/'] },
|
|
23
|
+
bans: 'a comment of any kind — block, line or JSDoc',
|
|
24
|
+
because:
|
|
25
|
+
'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`',
|
|
26
|
+
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`',
|
|
27
|
+
wrong: '/** THREAD — the root. The only node that exists without being declared. */',
|
|
28
|
+
right: "{ id: 'thread', means: 'The root. The only node that exists without being declared.' }",
|
|
29
|
+
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.' }],
|
|
30
|
+
seeds: [
|
|
31
|
+
{
|
|
32
|
+
id: 'slopnator',
|
|
33
|
+
at: './slopnators/sl_no_comment.ts',
|
|
34
|
+
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.',
|
|
35
|
+
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.' }],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
derives: [
|
|
39
|
+
{
|
|
40
|
+
id: 'trial',
|
|
41
|
+
at: '_threads/_generated/trials.ts',
|
|
42
|
+
by: 'thread trials',
|
|
43
|
+
runs: 'thread trials',
|
|
44
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
45
|
+
cites: [
|
|
46
|
+
{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' },
|
|
47
|
+
{ on: '2026-08-14', said: 'maybe laws + slopnators produce a trial through law instead? That also works for me.' },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
id: 'secret-literal',
|
|
55
|
+
at: './constraint.ts',
|
|
56
|
+
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.',
|
|
57
|
+
version: 1,
|
|
58
|
+
where: { paths: ['.'], except: ['_generated/', 'node_modules/', 'dist/', 'slopnators/', 'thread/integrity/constraint.ts'] },
|
|
59
|
+
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',
|
|
60
|
+
because:
|
|
61
|
+
'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, `unseamed-reach` asks what a node imports, and neither reads what a string contains',
|
|
62
|
+
instead:
|
|
63
|
+
'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',
|
|
64
|
+
wrong: "export const stripe = { apiKey: 'sk_live_51H8xQvL2eZvKYlo2C0Nx9K4mQwErTyUiOp' }",
|
|
65
|
+
right: "export const node = nd_action(async (hands) => await hands.ht_stripe.charge({ json: { amount: 100 } }))",
|
|
66
|
+
cites: [
|
|
67
|
+
{ 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' },
|
|
68
|
+
{ on: '2026-08-29', said: 'we need constraints.ts to have ways to protect us' },
|
|
69
|
+
],
|
|
70
|
+
seeds: [
|
|
71
|
+
{
|
|
72
|
+
id: 'slopnator',
|
|
73
|
+
at: './slopnators/sl_secret_literal.ts',
|
|
74
|
+
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.',
|
|
75
|
+
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.' }],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
id: 'unseamed-reach',
|
|
82
|
+
at: './constraint.ts',
|
|
83
|
+
describes: 'The refusal of a node taking a capability from the runtime instead of from the seams it is handed — everything through the seams is recorded under the node\'s own id, counted against the run\'s quota and permitted by its grants, and everything around them is none of those things.',
|
|
84
|
+
version: 1,
|
|
85
|
+
where: { paths: ['operations/', 'nodes/'], except: ['thread/operations/', '_generated/', 'node_modules/'] },
|
|
86
|
+
bans: 'a file under an operation or a node type importing a runtime built-in — anything `node:`, by that name or the bare one',
|
|
87
|
+
because:
|
|
88
|
+
'measured 2026-08-27 in an app installed from a packed build: a node whose row says `nd_read` was given `import { readFileSync } from \'node:fs\'` and a body that read /etc/hosts, and `thread gate` passed 5 of 5 — the compiler, the descent, the sweep, the build and the seed check all agreed, and the run\'s record said the node ran and read nothing. Nothing else refuses this, because every other rule reads what a file declares rather than what it reaches: the descent asks who named the file, `no-dispatch-bypass` asks who called the operation, and neither asks what the body touches. An operation whose nodes can reach the disk directly is an operation whose log describes a different program, and the log is the whole reason a node has an id',
|
|
89
|
+
instead:
|
|
90
|
+
'take it from the seams the node is handed — `store` and `search` on a read node, the whole of `seams` on an action node. If what you need is not there, it is a seam the framework has not declared yet, and that is a question for the founder rather than an import',
|
|
91
|
+
wrong: "import { readFileSync } from 'node:fs'",
|
|
92
|
+
right: 'export const node = nd_read<In>(async (hands) => await hands.store.get(\'customer\', held.input.id))',
|
|
93
|
+
cites: [{ on: '2026-08-27', said: 'node:fs law' }],
|
|
94
|
+
seeds: [
|
|
95
|
+
{
|
|
96
|
+
id: 'slopnator',
|
|
97
|
+
at: './slopnators/sl_unseamed_reach.ts',
|
|
98
|
+
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.',
|
|
99
|
+
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.' }],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
{
|
|
105
|
+
id: 'no-import-cycle',
|
|
106
|
+
at: './constraint.ts',
|
|
107
|
+
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.',
|
|
108
|
+
version: 1,
|
|
109
|
+
where: { paths: ['threads/'] },
|
|
110
|
+
bans: 'a file importing a file that imports it back',
|
|
111
|
+
because:
|
|
112
|
+
'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',
|
|
113
|
+
instead:
|
|
114
|
+
'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',
|
|
115
|
+
wrong: "import type { Threaded } from './probe.tsx'",
|
|
116
|
+
right: "import type { Threaded } from '../core/thread.ts'",
|
|
117
|
+
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.' }],
|
|
118
|
+
seeds: [
|
|
119
|
+
{
|
|
120
|
+
id: 'slopnator',
|
|
121
|
+
at: './slopnators/sl_no_import_cycle.ts',
|
|
122
|
+
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.',
|
|
123
|
+
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.' }],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
derives: [
|
|
127
|
+
{
|
|
128
|
+
id: 'trial',
|
|
129
|
+
at: '_threads/_generated/trials.ts',
|
|
130
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
131
|
+
cites: [
|
|
132
|
+
{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' },
|
|
133
|
+
{ on: '2026-08-14', said: 'maybe laws + slopnators produce a trial through law instead? That also works for me.' },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
id: 'undescribed',
|
|
141
|
+
at: './constraint.ts',
|
|
142
|
+
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.',
|
|
143
|
+
version: 1,
|
|
144
|
+
where: { paths: ['threads/'], except: ['threads/glossary.ts'] },
|
|
145
|
+
bans: 'a `describes` or `means` that is empty, or whose every word beyond the common ones already appears in the id beside it',
|
|
146
|
+
because:
|
|
147
|
+
'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',
|
|
148
|
+
instead:
|
|
149
|
+
'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',
|
|
150
|
+
wrong: "const M = { id: 'rule', describes: 'The rule.' }",
|
|
151
|
+
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.' }",
|
|
152
|
+
cites: [{ on: '2026-08-12', said: 'And you know, I don\'t see the "undescribed" error in your lists.' }],
|
|
153
|
+
seeds: [
|
|
154
|
+
{
|
|
155
|
+
id: 'slopnator',
|
|
156
|
+
at: './slopnators/sl_undescribed.ts',
|
|
157
|
+
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.',
|
|
158
|
+
cites: [{ on: '2026-08-14', said: 'I for sure need all errors to be defined, easy to understand, well explained, with fix suggestions.' }],
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
derives: [
|
|
162
|
+
{
|
|
163
|
+
id: 'trial',
|
|
164
|
+
at: '_threads/_generated/trials.ts',
|
|
165
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
166
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
{
|
|
172
|
+
id: 'unthreaded',
|
|
173
|
+
at: './constraint.ts',
|
|
174
|
+
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.',
|
|
175
|
+
version: 1,
|
|
176
|
+
where: { paths: ['threads/', '.'], except: ['_generated/', '.claude/'] },
|
|
177
|
+
bans: 'a file that no `at:` names from inside a node annotated `Threaded`, `threads/thread.ts` excepted because the root exists by fiat',
|
|
178
|
+
because:
|
|
179
|
+
'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',
|
|
180
|
+
instead:
|
|
181
|
+
'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',
|
|
182
|
+
wrong: 'export const ORPHAN = 1',
|
|
183
|
+
right: "export const HELD: Threaded = {\n declares: [\n {\n at: 'threads/probe.tsx',\n },\n ],\n}",
|
|
184
|
+
cites: [
|
|
185
|
+
{ on: '2026-08-12', said: 'Not untraced, unthreaded. Let\'s enforce the naming.' },
|
|
186
|
+
{ on: '2026-08-14', said: 'Cause we need unthreaded, redthread, broken lineage, and the request/produce errors....' },
|
|
187
|
+
],
|
|
188
|
+
seeds: [
|
|
189
|
+
{
|
|
190
|
+
id: 'slopnator',
|
|
191
|
+
at: './slopnators/sl_unthreaded.ts',
|
|
192
|
+
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.',
|
|
193
|
+
cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law.' }],
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
derives: [
|
|
197
|
+
{
|
|
198
|
+
id: 'trial',
|
|
199
|
+
at: '_threads/_generated/trials.ts',
|
|
200
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
201
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
{
|
|
207
|
+
id: 'twice-parented',
|
|
208
|
+
at: './constraint.ts',
|
|
209
|
+
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.',
|
|
210
|
+
version: 1,
|
|
211
|
+
where: { paths: ['.'], except: ['_generated/'] },
|
|
212
|
+
bans: 'one `at` claimed by two `declares` rows, wherever the two rows live',
|
|
213
|
+
because:
|
|
214
|
+
'`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',
|
|
215
|
+
instead:
|
|
216
|
+
'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',
|
|
217
|
+
wrong: "const M: Threaded = {\n declares: [\n {\n at: 'threads/one.ts',\n },\n {\n at: 'threads/one.ts',\n },\n ],\n}",
|
|
218
|
+
right: "const M: Threaded = {\n declares: [\n {\n at: 'threads/one.ts',\n },\n {\n at: 'threads/two.ts',\n },\n ],\n}",
|
|
219
|
+
cites: [{ on: '2026-08-14', said: 'Cause we need unthreaded, redthread, broken lineage, and the request/produce errors....' }],
|
|
220
|
+
seeds: [
|
|
221
|
+
{
|
|
222
|
+
id: 'slopnator',
|
|
223
|
+
at: './slopnators/sl_twice_parented.ts',
|
|
224
|
+
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.',
|
|
225
|
+
cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law.' }],
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
derives: [
|
|
229
|
+
{
|
|
230
|
+
id: 'trial',
|
|
231
|
+
at: '_threads/_generated/trials.ts',
|
|
232
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
233
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
{
|
|
239
|
+
id: 'broken-lineage',
|
|
240
|
+
at: './constraint.ts',
|
|
241
|
+
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.',
|
|
242
|
+
version: 1,
|
|
243
|
+
where: { paths: ['threads/', '.'], except: ['_generated/', '.claude/'] },
|
|
244
|
+
bans: 'a file that names children while its own origin does not trace back to `threads/`',
|
|
245
|
+
because:
|
|
246
|
+
'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',
|
|
247
|
+
instead:
|
|
248
|
+
'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',
|
|
249
|
+
wrong: "export const M: Threaded = {\n declares: [\n {\n at: 'scripts/finding.ts',\n },\n ],\n}",
|
|
250
|
+
right: 'export const PLAIN = 1',
|
|
251
|
+
cites: [
|
|
252
|
+
{ on: '2026-08-14', said: 'Red thread is broken lineage.' },
|
|
253
|
+
{ 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.' },
|
|
254
|
+
],
|
|
255
|
+
seeds: [
|
|
256
|
+
{
|
|
257
|
+
id: 'slopnator',
|
|
258
|
+
at: './slopnators/sl_broken_lineage.ts',
|
|
259
|
+
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.',
|
|
260
|
+
cites: [{ on: '2026-08-14', said: 'Red thread is broken lineage.' }],
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
derives: [
|
|
264
|
+
{
|
|
265
|
+
id: 'trial',
|
|
266
|
+
at: '_threads/_generated/trials.ts',
|
|
267
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
268
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
{
|
|
274
|
+
id: 'stale-path',
|
|
275
|
+
at: './constraint.ts',
|
|
276
|
+
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.',
|
|
277
|
+
version: 1,
|
|
278
|
+
where: { paths: ['.'], except: ['slopnators/', '_generated/', 'thread/integrity/sealed.ts'] },
|
|
279
|
+
bans: 'a string naming a path in this repository that is not on disk',
|
|
280
|
+
because:
|
|
281
|
+
'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',
|
|
282
|
+
instead:
|
|
283
|
+
'fix the path, or delete the sentence naming it. A path that carries a `{variable}` states the directory it writes into, and that directory must exist',
|
|
284
|
+
wrong: "const at = 'threads/nope.ts'",
|
|
285
|
+
right: "const at = 'packages/core/integrity/src/constraint.ts'",
|
|
286
|
+
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.' }],
|
|
287
|
+
seeds: [
|
|
288
|
+
{
|
|
289
|
+
id: 'slopnator',
|
|
290
|
+
at: './slopnators/sl_stale_path.ts',
|
|
291
|
+
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.',
|
|
292
|
+
cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law.' }],
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
derives: [
|
|
296
|
+
{
|
|
297
|
+
id: 'trial',
|
|
298
|
+
at: '_threads/_generated/trials.ts',
|
|
299
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
300
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
{
|
|
306
|
+
id: 'missing-row',
|
|
307
|
+
at: './constraint.ts',
|
|
308
|
+
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.',
|
|
309
|
+
version: 1,
|
|
310
|
+
where: { paths: ['.'], except: ['slopnators/', '_generated/'] },
|
|
311
|
+
bans: 'a tree holding fewer rows than the package it attached from declares',
|
|
312
|
+
because:
|
|
313
|
+
'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',
|
|
314
|
+
instead:
|
|
315
|
+
'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',
|
|
316
|
+
wrong: "const M: Threaded = {\n declares: [],\n}",
|
|
317
|
+
right: `const M: Threaded = {\n declares: [\n {\n at: 'node_modules/${named('ui')}/dist/tokens/colour.js',\n },\n ],\n}`,
|
|
318
|
+
cites: [
|
|
319
|
+
{ 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' },
|
|
320
|
+
{ 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.' },
|
|
321
|
+
],
|
|
322
|
+
seeds: [
|
|
323
|
+
{
|
|
324
|
+
id: 'slopnator',
|
|
325
|
+
at: './slopnators/sl_missing_row.ts',
|
|
326
|
+
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.',
|
|
327
|
+
cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law.' }],
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
derives: [
|
|
331
|
+
{
|
|
332
|
+
id: 'trial',
|
|
333
|
+
at: '_threads/_generated/trials.ts',
|
|
334
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
335
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
{
|
|
341
|
+
id: 'no-such-command',
|
|
342
|
+
at: './constraint.ts',
|
|
343
|
+
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.',
|
|
344
|
+
version: 1,
|
|
345
|
+
where: { paths: ['threads/'] },
|
|
346
|
+
bans: 'a `runs` or `by` naming a script this repository does not have',
|
|
347
|
+
because:
|
|
348
|
+
'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',
|
|
349
|
+
instead: 'add the script to package.json, or fix the name. A row naming a command is a promise that the command exists',
|
|
350
|
+
wrong: "const M = {\n runs: 'pnpm nope:check',\n}",
|
|
351
|
+
right: "const M = {\n runs: 'pnpm typecheck',\n}",
|
|
352
|
+
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.' }],
|
|
353
|
+
seeds: [
|
|
354
|
+
{
|
|
355
|
+
id: 'slopnator',
|
|
356
|
+
at: './slopnators/sl_no_such_command.ts',
|
|
357
|
+
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.',
|
|
358
|
+
cites: [{ on: '2026-08-14', said: 'I think you need to study how a file gets parented and make proper error handling for everything.' }],
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
derives: [
|
|
362
|
+
{
|
|
363
|
+
id: 'trial',
|
|
364
|
+
at: '_threads/_generated/trials.ts',
|
|
365
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
366
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
{
|
|
372
|
+
id: 'unpaired-slopnator',
|
|
373
|
+
at: './constraint.ts',
|
|
374
|
+
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.',
|
|
375
|
+
version: 1,
|
|
376
|
+
where: { paths: ['threads/', 'thread/integrity/slopnators/'], except: ['_generated/'] },
|
|
377
|
+
bans: 'a rule naming a slopnator that is not on disk, and a slopnator in the slopnators directory that no rule names',
|
|
378
|
+
because:
|
|
379
|
+
'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',
|
|
380
|
+
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',
|
|
381
|
+
wrong: "const CONSTRAINTS = [\n {\n id: 'nothing-names-this-slopnator',\n },\n]",
|
|
382
|
+
right: "const CONSTRAINTS = [\n {\n id: 'no-comment',\n },\n]",
|
|
383
|
+
cites: [
|
|
384
|
+
{ 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.' },
|
|
385
|
+
{ on: '2026-08-15', said: 'Does a slopnator without a law or a law without a slopnator exist? This should be an error....' },
|
|
386
|
+
],
|
|
387
|
+
seeds: [
|
|
388
|
+
{
|
|
389
|
+
id: 'slopnator',
|
|
390
|
+
at: './slopnators/sl_unpaired_slopnator.ts',
|
|
391
|
+
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.',
|
|
392
|
+
cites: [{ on: '2026-08-15', said: 'Does a slopnator without a law or a law without a slopnator exist? This should be an error....' }],
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
derives: [
|
|
396
|
+
{
|
|
397
|
+
id: 'trial',
|
|
398
|
+
at: '_threads/_generated/trials.ts',
|
|
399
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
400
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
{
|
|
406
|
+
id: 'twice-meant',
|
|
407
|
+
at: './constraint.ts',
|
|
408
|
+
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.',
|
|
409
|
+
version: 1,
|
|
410
|
+
where: { paths: ['.'], except: ['_generated/'] },
|
|
411
|
+
bans: 'two rows coining the same id while stating different `describes`',
|
|
412
|
+
because:
|
|
413
|
+
'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',
|
|
414
|
+
instead:
|
|
415
|
+
'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',
|
|
416
|
+
wrong: "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}",
|
|
417
|
+
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}",
|
|
418
|
+
cites: [
|
|
419
|
+
{ on: '2026-08-15', said: 'the ids thing. We can\'t have that, the id\'s being the same.' },
|
|
420
|
+
{ 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.' },
|
|
421
|
+
{ on: '2026-08-15', said: 'Yes, rows carry category meaning. No individual ids.' },
|
|
422
|
+
],
|
|
423
|
+
seeds: [
|
|
424
|
+
{
|
|
425
|
+
id: 'slopnator',
|
|
426
|
+
at: './slopnators/sl_twice_meant.ts',
|
|
427
|
+
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.',
|
|
428
|
+
cites: [{ on: '2026-08-15', said: 'Yes, rows carry category meaning. No individual ids.' }],
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
derives: [
|
|
432
|
+
{
|
|
433
|
+
id: 'trial',
|
|
434
|
+
at: '_threads/_generated/trials.ts',
|
|
435
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
436
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
{
|
|
442
|
+
id: 'derived-drift',
|
|
443
|
+
at: './constraint.ts',
|
|
444
|
+
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.',
|
|
445
|
+
version: 1,
|
|
446
|
+
where: { paths: ['threads/'] },
|
|
447
|
+
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',
|
|
448
|
+
because:
|
|
449
|
+
'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',
|
|
450
|
+
instead:
|
|
451
|
+
'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',
|
|
452
|
+
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}",
|
|
453
|
+
right: "export const M: Threaded = {\n derives: [\n {\n at: 'scripts/_generated/gate.mjs',\n by: 'pnpm gate:generate',\n runs: 'pnpm gate:check',\n },\n ],\n}",
|
|
454
|
+
cites: [
|
|
455
|
+
{ 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.' },
|
|
456
|
+
{ on: '2026-08-15', said: 'So slopnators should have kinds. walk, check, whatever else it needs so it works.' },
|
|
457
|
+
],
|
|
458
|
+
seeds: [
|
|
459
|
+
{
|
|
460
|
+
id: 'slopnator',
|
|
461
|
+
at: './slopnators/sl_derived_drift.ts',
|
|
462
|
+
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.',
|
|
463
|
+
cites: [{ on: '2026-08-15', said: 'make them into laws that derive the exact same checks' }],
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
derives: [
|
|
467
|
+
{
|
|
468
|
+
id: 'trial',
|
|
469
|
+
at: '_threads/_generated/trials.ts',
|
|
470
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
471
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
id: 'undeclared-crossing',
|
|
477
|
+
at: './constraint.ts',
|
|
478
|
+
describes: 'The refusal that makes a declared operation the only thing that can act — the network, the clock, randomness, the queue and files are reachable from a workflow node or a view and from nowhere else, so everything an app does is something it declared it would do.',
|
|
479
|
+
version: 1,
|
|
480
|
+
where: { paths: ['.'], except: ['_generated/', 'node_modules/', '_threads/', 'thread/'] },
|
|
481
|
+
bans: 'a boundary crossing written outside a declared node — the network, the clock and its timers and its animations, randomness, the queue or files',
|
|
482
|
+
because:
|
|
483
|
+
'the trial, the replay, the trace, the cost record and the compensation all hang off a declared node, and a crossing written beside one has none of them: it cannot be replayed because nothing recorded it, cannot be retried because nothing owns it, cannot be priced because it left no record, and cannot be unwound because no compensation names it. 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 and randomness belong here for the same reason a network call does: a run that reads `Date.now()` cannot be replayed, so a trial over it proves nothing and passes. A timer is the clock scheduled rather than read, and it is refused by the same row rather than a second one: it re-renders on a schedule nothing declared, and a rule naming only `setInterval` leaves the self-rescheduling `setTimeout` behind, which is an interval wearing a different name. `element.animate()` is the same clock again, and the worst of them, because it takes its own duration and its own easing curve as arguments: every other crossing borrows a value from somewhere, and this one invents a whole motion vocabulary in place, past every `motion` and `ease` row the style guide holds. Measured 2026-09-02: three compositions in one app each hard-coded `cubic-bezier(0.2, 0, 0, 1)` and 180 or 200 milliseconds, while the same app declared 25 animations and named one of them anywhere',
|
|
484
|
+
instead:
|
|
485
|
+
'declare the node, name the seam it needs, and write the crossing in its body — or read through a declared view, which is the other half of what may act',
|
|
486
|
+
wrong: "const res = await fetch('https://api.stripe.com/v1/charges')",
|
|
487
|
+
right: "{ id: 'st_charge', kind: 'action', action: 'payment.charge', declares: { seams: ['http'] } }",
|
|
488
|
+
cites: [
|
|
489
|
+
{ 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.' },
|
|
490
|
+
{ on: '2026-08-16', said: 'No operation without a workflow or view.' },
|
|
491
|
+
{ on: '2026-08-16', said: 'And make it ubiquitous.' },
|
|
492
|
+
],
|
|
493
|
+
seeds: [
|
|
494
|
+
{
|
|
495
|
+
id: 'slopnator',
|
|
496
|
+
at: './slopnators/sl_undeclared_crossing.ts',
|
|
497
|
+
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.',
|
|
498
|
+
cites: [{ on: '2026-08-16', said: 'No operation without a workflow or view.' }],
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
derives: [
|
|
502
|
+
{
|
|
503
|
+
id: 'trial',
|
|
504
|
+
at: '_threads/_generated/trials.ts',
|
|
505
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
506
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
id: 'hardcoded-style',
|
|
512
|
+
at: './constraint.ts',
|
|
513
|
+
describes:
|
|
514
|
+
'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.',
|
|
515
|
+
version: 1,
|
|
516
|
+
where: { paths: ['.'], except: ['_generated/', 'node_modules/', 'dist/', '_threads/', 'slopnators/'] },
|
|
517
|
+
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',
|
|
518
|
+
because:
|
|
519
|
+
'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.',
|
|
520
|
+
instead:
|
|
521
|
+
'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.',
|
|
522
|
+
wrong: "lines.push(' body {', ' background: #f8fbfa;', ' }')",
|
|
523
|
+
right: "lines.push(' body {', ' background: var(--colour-page);', ' }')",
|
|
524
|
+
cites: [
|
|
525
|
+
{ 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.' },
|
|
526
|
+
{ 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.' },
|
|
527
|
+
{ on: '2026-09-02', said: 'It shouldn\'t match any CSS rule because it should never speak CSS.' },
|
|
528
|
+
],
|
|
529
|
+
seeds: [
|
|
530
|
+
{
|
|
531
|
+
id: 'slopnator',
|
|
532
|
+
at: './slopnators/sl_hardcoded_style.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-09-03', said: 'nothing in the compiler can EVER hardcode a value' }],
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
derives: [
|
|
538
|
+
{
|
|
539
|
+
id: 'trial',
|
|
540
|
+
at: '_threads/_generated/trials.ts',
|
|
541
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
542
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
id: 'undeclared-destination',
|
|
548
|
+
at: './constraint.ts',
|
|
549
|
+
governs: 'navigation',
|
|
550
|
+
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.',
|
|
551
|
+
version: 1,
|
|
552
|
+
|
|
553
|
+
where: { paths: ['.'], except: ['app/_generated/', 'thread/ui/elements/'] },
|
|
554
|
+
bans: 'a destination reached without naming a declared screen — an `href`, a `window.location`, or a push, replace or prefetch on a router, a navigation or a history',
|
|
555
|
+
because:
|
|
556
|
+
'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',
|
|
557
|
+
instead: 'name the screen — `navigateTo="Order"` — and let the map carry the destination, so a link exists in the island that reasons about links',
|
|
558
|
+
wrong: '<a href="/orders/42">{label}</a>',
|
|
559
|
+
right: '<Link.label navigateTo="Order" labelKey="order.title" />',
|
|
560
|
+
cites: [
|
|
561
|
+
{ 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.' },
|
|
562
|
+
{ on: '2026-08-16', said: 'But they must be app wide.' },
|
|
563
|
+
{ on: '2026-08-16', said: 'And make it ubiquitous.' },
|
|
564
|
+
{ on: '2026-08-16', said: 'Here\'s a new law I need you to add: No island without law.' },
|
|
565
|
+
],
|
|
566
|
+
seeds: [
|
|
567
|
+
{
|
|
568
|
+
id: 'slopnator',
|
|
569
|
+
at: './slopnators/sl_undeclared_destination.ts',
|
|
570
|
+
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.',
|
|
571
|
+
cites: [{ on: '2026-08-16', said: 'The ones most important are the ones that enforce UI through elements and screens' }],
|
|
572
|
+
},
|
|
573
|
+
],
|
|
574
|
+
derives: [
|
|
575
|
+
{
|
|
576
|
+
id: 'trial',
|
|
577
|
+
at: '_threads/_generated/trials.ts',
|
|
578
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
579
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
580
|
+
},
|
|
581
|
+
],
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
id: 'undeclared-operation',
|
|
585
|
+
at: './constraint.ts',
|
|
586
|
+
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.',
|
|
587
|
+
version: 1,
|
|
588
|
+
|
|
589
|
+
where: { paths: ['.'], except: ['app/_generated/', 'thread/operations/', 'thread/ui/'] },
|
|
590
|
+
bans: 'dispatch handed a name nothing declares, or a name this file does not write down',
|
|
591
|
+
because:
|
|
592
|
+
'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',
|
|
593
|
+
instead:
|
|
594
|
+
'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',
|
|
595
|
+
wrong: "dispatch('wf_not_declared')",
|
|
596
|
+
right: "dispatch('wf_send_invoice')",
|
|
597
|
+
cites: [
|
|
598
|
+
{ on: '2026-08-25', said: 'And make sure the composition can only dispatch existing operations.' },
|
|
599
|
+
{ on: '2026-08-25', said: 'Yes — reading a view is dispatching too' },
|
|
600
|
+
{ 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.' },
|
|
601
|
+
],
|
|
602
|
+
seeds: [
|
|
603
|
+
{
|
|
604
|
+
id: 'slopnator',
|
|
605
|
+
at: './slopnators/sl_undeclared_operation.ts',
|
|
606
|
+
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.',
|
|
607
|
+
cites: [{ on: '2026-08-25', said: 'And make sure the composition can only dispatch existing operations.' }],
|
|
608
|
+
},
|
|
609
|
+
],
|
|
610
|
+
derives: [
|
|
611
|
+
{
|
|
612
|
+
id: 'trial',
|
|
613
|
+
at: '_threads/_generated/trials.ts',
|
|
614
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
615
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
},
|
|
619
|
+
|
|
620
|
+
{
|
|
621
|
+
id: 'undeclared-variant',
|
|
622
|
+
at: './constraint.ts',
|
|
623
|
+
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.',
|
|
624
|
+
version: 1,
|
|
625
|
+
|
|
626
|
+
where: { paths: ['.'], except: ['app/_generated/'] },
|
|
627
|
+
bans: 'an element wearing a member that no variant row declares, or that a variant row declares for a different element',
|
|
628
|
+
because:
|
|
629
|
+
'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',
|
|
630
|
+
instead:
|
|
631
|
+
'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',
|
|
632
|
+
wrong: '<Button.primary />',
|
|
633
|
+
right: '<Button.buttonPrimary />',
|
|
634
|
+
cites: [
|
|
635
|
+
{ 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.' },
|
|
636
|
+
{ 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?' },
|
|
637
|
+
],
|
|
638
|
+
seeds: [
|
|
639
|
+
{
|
|
640
|
+
id: 'slopnator',
|
|
641
|
+
at: './slopnators/sl_undeclared_variant.ts',
|
|
642
|
+
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.',
|
|
643
|
+
cites: [{ on: '2026-08-25', said: 'Variants themselves declare what elements they relate to, in their declarations.' }],
|
|
644
|
+
},
|
|
645
|
+
],
|
|
646
|
+
derives: [
|
|
647
|
+
{
|
|
648
|
+
id: 'trial',
|
|
649
|
+
at: '_threads/_generated/trials.ts',
|
|
650
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
651
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
},
|
|
655
|
+
|
|
656
|
+
{
|
|
657
|
+
id: 'undeclared-element',
|
|
658
|
+
at: './constraint.ts',
|
|
659
|
+
governs: 'elements',
|
|
660
|
+
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.',
|
|
661
|
+
version: 1,
|
|
662
|
+
|
|
663
|
+
where: { paths: ['.'], except: ['app/_generated/', 'thread/ui/'] },
|
|
664
|
+
bans: 'a host element — any lowercase-initial tag, not only `<div>` — written anywhere in an app',
|
|
665
|
+
because:
|
|
666
|
+
'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',
|
|
667
|
+
instead:
|
|
668
|
+
'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',
|
|
669
|
+
wrong: '<div><Box.card /></div>',
|
|
670
|
+
right: '<Column><Box.card /></Column>',
|
|
671
|
+
cites: [
|
|
672
|
+
{ 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.' },
|
|
673
|
+
{ on: '2026-08-16', said: 'But they must be app wide.' },
|
|
674
|
+
{ on: '2026-08-16', said: 'And make it ubiquitous.' },
|
|
675
|
+
{ on: '2026-08-16', said: 'Here\'s a new law I need you to add: No island without law.' },
|
|
676
|
+
],
|
|
677
|
+
seeds: [
|
|
678
|
+
{
|
|
679
|
+
id: 'slopnator',
|
|
680
|
+
at: './slopnators/sl_undeclared_element.ts',
|
|
681
|
+
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.',
|
|
682
|
+
cites: [{ on: '2026-08-16', said: 'The ones most important are the ones that enforce UI through elements and screens' }],
|
|
683
|
+
},
|
|
684
|
+
],
|
|
685
|
+
derives: [
|
|
686
|
+
{
|
|
687
|
+
id: 'trial',
|
|
688
|
+
at: '_threads/_generated/trials.ts',
|
|
689
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
690
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
id: 'unpaired-member',
|
|
696
|
+
at: './constraint.ts',
|
|
697
|
+
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.',
|
|
698
|
+
version: 1,
|
|
699
|
+
where: { paths: ['threads/', 'operations/', 'entities/'], except: ['_generated/'] },
|
|
700
|
+
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',
|
|
701
|
+
because:
|
|
702
|
+
'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',
|
|
703
|
+
instead:
|
|
704
|
+
'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',
|
|
705
|
+
wrong: "export const OPERATIONS: readonly Member[] = [\n {\n id: 'wf_nothing_seeded_this',\n },\n]",
|
|
706
|
+
right: "export const OPERATIONS: readonly Member[] = []",
|
|
707
|
+
cites: [
|
|
708
|
+
{ 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.' },
|
|
709
|
+
{ 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.' },
|
|
710
|
+
{ on: '2026-08-17', said: 'No wf_ without a row in workflows.ts' },
|
|
711
|
+
{ 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.' },
|
|
712
|
+
],
|
|
713
|
+
seeds: [
|
|
714
|
+
{
|
|
715
|
+
id: 'slopnator',
|
|
716
|
+
at: './slopnators/sl_unpaired_member.ts',
|
|
717
|
+
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.',
|
|
718
|
+
cites: [{ on: '2026-08-17', said: 'No wf_ without a row in workflows.ts' }],
|
|
719
|
+
},
|
|
720
|
+
],
|
|
721
|
+
derives: [
|
|
722
|
+
{
|
|
723
|
+
id: 'trial',
|
|
724
|
+
at: '_threads/_generated/trials.ts',
|
|
725
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
726
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
727
|
+
},
|
|
728
|
+
],
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
id: 'unversioned',
|
|
732
|
+
at: './constraint.ts',
|
|
733
|
+
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.',
|
|
734
|
+
version: 1,
|
|
735
|
+
where: { paths: ['.'], except: ['_generated/', 'slopnators/', 'docs/', 'AGENTS.md'] },
|
|
736
|
+
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`',
|
|
737
|
+
because:
|
|
738
|
+
'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',
|
|
739
|
+
instead: 'state a `version` on the node, and a `specVersion` on the island schema — one integer, raised when what it governs changes shape',
|
|
740
|
+
wrong: "export const M: Threaded = { id: 'a-meta', at: 'threads/integrity.ts', describes: 'What it is and what it solves.' }",
|
|
741
|
+
right: "export const M: Threaded = { id: 'a-meta', at: 'threads/integrity.ts', describes: 'What it is and what it solves.', version: 1 }",
|
|
742
|
+
cites: [
|
|
743
|
+
{ 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.' },
|
|
744
|
+
{ 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.' },
|
|
745
|
+
{ on: '2026-08-17', said: 'make it universal for thread related stuff' },
|
|
746
|
+
],
|
|
747
|
+
seeds: [
|
|
748
|
+
{
|
|
749
|
+
id: 'slopnator',
|
|
750
|
+
at: './slopnators/sl_unversioned.ts',
|
|
751
|
+
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.',
|
|
752
|
+
cites: [{ on: '2026-08-17', said: 'The things that govern other things, we need those to be versioned.' }],
|
|
753
|
+
},
|
|
754
|
+
],
|
|
755
|
+
derives: [
|
|
756
|
+
{
|
|
757
|
+
id: 'trial',
|
|
758
|
+
at: '_threads/_generated/trials.ts',
|
|
759
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
760
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
761
|
+
},
|
|
762
|
+
],
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
id: 'declared-elsewhere',
|
|
766
|
+
at: './constraint.ts',
|
|
767
|
+
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.',
|
|
768
|
+
version: 1,
|
|
769
|
+
where: { paths: ['.'], except: ['AGENTS.md', '_generated/', 'slopnators/', 'threads/', 'docs/'] },
|
|
770
|
+
bans: 'a declaration of a kind an island governs, written anywhere but where that island governs',
|
|
771
|
+
because:
|
|
772
|
+
'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',
|
|
773
|
+
instead: 'declare it in the file the island governs, and import it where it is needed',
|
|
774
|
+
wrong: "export default definePermissions({ specVersion: 1, entities: [{ entity: 'ent_order' }] })",
|
|
775
|
+
right: "import permissions from 'app/_declare/permissions'",
|
|
776
|
+
cites: [
|
|
777
|
+
{ on: '2026-08-17', said: 'If there\'s something that is govern by an island but it\'s propelry done through island, error.' },
|
|
778
|
+
{ on: '2026-08-17', said: 'Each island governs it\'s thing. Laws make sure that this is true and no island is inert.' },
|
|
779
|
+
{ on: '2026-08-16', said: 'EVERYTHING must be SSOT. Why? Because AIs are lazy and they want to create things in place.' },
|
|
780
|
+
],
|
|
781
|
+
seeds: [
|
|
782
|
+
{
|
|
783
|
+
id: 'slopnator',
|
|
784
|
+
at: './slopnators/sl_declared_elsewhere.ts',
|
|
785
|
+
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.',
|
|
786
|
+
cites: [{ on: '2026-08-17', said: 'Each island governs it\'s thing.' }],
|
|
787
|
+
},
|
|
788
|
+
],
|
|
789
|
+
derives: [
|
|
790
|
+
{
|
|
791
|
+
id: 'trial',
|
|
792
|
+
at: '_threads/_generated/trials.ts',
|
|
793
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
794
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
795
|
+
},
|
|
796
|
+
],
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
id: 'inert-island',
|
|
800
|
+
at: './constraint.ts',
|
|
801
|
+
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.',
|
|
802
|
+
version: 1,
|
|
803
|
+
where: { paths: ['threads/'] },
|
|
804
|
+
bans: 'an island that states nothing it governs',
|
|
805
|
+
because:
|
|
806
|
+
'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',
|
|
807
|
+
instead: 'state what the island governs — a file where its kind is declared, or a folder whose implementation it governs',
|
|
808
|
+
wrong: "const M: Islanding = { declares: [{ id: 'events', at: 'packages/core/spec/src/islands/events.ts' }] }",
|
|
809
|
+
right: "const M: Islanding = { declares: [{ id: 'events', at: 'packages/core/spec/src/islands/events.ts', governs: ['app/_declare/events.ts'] }] }",
|
|
810
|
+
cites: [
|
|
811
|
+
{ on: '2026-08-17', said: 'An island extends threader. It declares what it governs.' },
|
|
812
|
+
{ on: '2026-08-17', said: 'If there\'s an island that doesn\'t state what it governs, error.' },
|
|
813
|
+
{ on: '2026-08-17', said: 'An island governs either folders or file types or implementation.' },
|
|
814
|
+
],
|
|
815
|
+
seeds: [
|
|
816
|
+
{
|
|
817
|
+
id: 'slopnator',
|
|
818
|
+
at: './slopnators/sl_inert_island.ts',
|
|
819
|
+
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.',
|
|
820
|
+
cites: [{ on: '2026-08-17', said: 'no island is inert' }],
|
|
821
|
+
},
|
|
822
|
+
],
|
|
823
|
+
derives: [
|
|
824
|
+
{
|
|
825
|
+
id: 'trial',
|
|
826
|
+
at: '_threads/_generated/trials.ts',
|
|
827
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
828
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
829
|
+
},
|
|
830
|
+
],
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
id: 'unnamed-island',
|
|
834
|
+
at: './constraint.ts',
|
|
835
|
+
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.',
|
|
836
|
+
version: 1,
|
|
837
|
+
where: { paths: ['.'], except: ['AGENTS.md', '_generated/', 'slopnators/', 'threads/', 'docs/'] },
|
|
838
|
+
bans: 'a module exporting an island schema that no row in the tree names',
|
|
839
|
+
because:
|
|
840
|
+
'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',
|
|
841
|
+
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',
|
|
842
|
+
wrong: 'export const ThingIslandSchema = z.strictObject({ specVersion: z.literal(1) })',
|
|
843
|
+
right: 'export const ThingSchema = z.strictObject({ name: z.string() })',
|
|
844
|
+
cites: [
|
|
845
|
+
{ on: '2026-08-17', said: 'If there\'s an island that doesn\'t extends threader, error.' },
|
|
846
|
+
{ on: '2026-08-17', said: 'An island extends threader. It declares what it governs.' },
|
|
847
|
+
{ on: '2026-08-16', said: 'other packages must be able to contribute both islands AND laws that governs their islands' },
|
|
848
|
+
],
|
|
849
|
+
seeds: [
|
|
850
|
+
{
|
|
851
|
+
id: 'slopnator',
|
|
852
|
+
at: './slopnators/sl_unnamed_island.ts',
|
|
853
|
+
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.',
|
|
854
|
+
cites: [{ on: '2026-08-17', said: 'If there\'s an island that doesn\'t extends threader, error.' }],
|
|
855
|
+
},
|
|
856
|
+
],
|
|
857
|
+
derives: [
|
|
858
|
+
{
|
|
859
|
+
id: 'trial',
|
|
860
|
+
at: '_threads/_generated/trials.ts',
|
|
861
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
862
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
863
|
+
},
|
|
864
|
+
],
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
id: 'ungoverned-island',
|
|
868
|
+
at: './constraint.ts',
|
|
869
|
+
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.',
|
|
870
|
+
version: 1,
|
|
871
|
+
where: { paths: ['threads/'] },
|
|
872
|
+
bans: 'a rule whose `governs` names an island the tree does not declare',
|
|
873
|
+
because:
|
|
874
|
+
'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',
|
|
875
|
+
instead:
|
|
876
|
+
'name an island the tree declares, or drop the `governs` — a rule about a kind nobody can declare is a rule about nothing',
|
|
877
|
+
wrong: "export const L: Constraint = {\n id: 'a-rule',\n governs: 'no-island-by-this-name',\n}",
|
|
878
|
+
right: "export const L: Constraint = {\n id: 'a-rule',\n governs: 'entities',\n}",
|
|
879
|
+
cites: [
|
|
880
|
+
{ on: '2026-08-16', said: 'Here\'s a new law I need you to add: No island without law.' },
|
|
881
|
+
{ 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.' },
|
|
882
|
+
{ on: '2026-08-16', said: 'NOTHING can exist that has no island for the things island govern.' },
|
|
883
|
+
],
|
|
884
|
+
seeds: [
|
|
885
|
+
{
|
|
886
|
+
id: 'slopnator',
|
|
887
|
+
at: './slopnators/sl_ungoverned_island.ts',
|
|
888
|
+
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.',
|
|
889
|
+
cites: [{ on: '2026-08-16', said: 'Here\'s a new law I need you to add: No island without law.' }],
|
|
890
|
+
},
|
|
891
|
+
],
|
|
892
|
+
derives: [
|
|
893
|
+
{
|
|
894
|
+
id: 'trial',
|
|
895
|
+
at: '_threads/_generated/trials.ts',
|
|
896
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
897
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
id: 'no-barrel',
|
|
903
|
+
at: './constraint.ts',
|
|
904
|
+
version: 1,
|
|
905
|
+
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.',
|
|
906
|
+
where: { paths: ['.'], except: ['_generated/', 'node_modules/'] },
|
|
907
|
+
bans: 'a barrel — a file named index.ts, and any file whose whole content is re-exports',
|
|
908
|
+
because:
|
|
909
|
+
'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',
|
|
910
|
+
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',
|
|
911
|
+
wrong: "export * from './one'\nexport * from './two'\nexport * from './three'\n",
|
|
912
|
+
right: 'export function coined(word: string): string {\n return word.trim()\n}\n',
|
|
913
|
+
cites: [
|
|
914
|
+
{ on: '2026-08-18', said: 'another barrel. You love barrels huh. You love files that hold lists. why?' },
|
|
915
|
+
{ 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.' },
|
|
916
|
+
{ 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' },
|
|
917
|
+
],
|
|
918
|
+
seeds: [
|
|
919
|
+
{
|
|
920
|
+
id: 'slopnator',
|
|
921
|
+
at: './slopnators/sl_no_barrel.ts',
|
|
922
|
+
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.',
|
|
923
|
+
cites: [{ on: '2026-08-18', said: 'no barrels allowed' }],
|
|
924
|
+
},
|
|
925
|
+
],
|
|
926
|
+
derives: [
|
|
927
|
+
{
|
|
928
|
+
id: 'trial',
|
|
929
|
+
at: '_threads/_generated/trials.ts',
|
|
930
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
931
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
932
|
+
},
|
|
933
|
+
],
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
id: 'inert-slopnator',
|
|
937
|
+
at: './constraint.ts',
|
|
938
|
+
version: 1,
|
|
939
|
+
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.',
|
|
940
|
+
where: { paths: ['threads/slopnators/', 'thread/integrity/slopnators/'] },
|
|
941
|
+
bans: 'a slopnator still returning `NOT_WRITTEN`, or one whose `proves` states nothing it refuses',
|
|
942
|
+
because:
|
|
943
|
+
'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',
|
|
944
|
+
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',
|
|
945
|
+
wrong: "export const slopnator: Slopnator = {\n on: [],\n holds: () => NOT_WRITTEN,\n proves: { wrong: [], right: [] },\n}",
|
|
946
|
+
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}",
|
|
947
|
+
cites: [
|
|
948
|
+
{ 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.' },
|
|
949
|
+
{ on: '2026-08-19', said: 'Then on seed, we have this other state which is a blank slopnator, also not acceptable.' },
|
|
950
|
+
],
|
|
951
|
+
seeds: [
|
|
952
|
+
{
|
|
953
|
+
id: 'slopnator',
|
|
954
|
+
at: './slopnators/sl_inert_slopnator.ts',
|
|
955
|
+
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.',
|
|
956
|
+
cites: [{ on: '2026-08-19', said: 'No inert slopnator.' }],
|
|
957
|
+
},
|
|
958
|
+
],
|
|
959
|
+
derives: [
|
|
960
|
+
{
|
|
961
|
+
id: 'trial',
|
|
962
|
+
at: '_threads/_generated/trials.ts',
|
|
963
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
964
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
965
|
+
},
|
|
966
|
+
],
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
id: 'no-child-process',
|
|
970
|
+
at: './constraint.ts',
|
|
971
|
+
version: 1,
|
|
972
|
+
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.',
|
|
973
|
+
where: { paths: ['threads/slopnators/', 'thread/integrity/slopnators/'] },
|
|
974
|
+
bans: 'a slopnator importing `node:child_process`, by any spelling',
|
|
975
|
+
because:
|
|
976
|
+
'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',
|
|
977
|
+
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',
|
|
978
|
+
wrong: "import { spawnSync } from 'node:child_process'",
|
|
979
|
+
right: "import { readFileSync } from 'node:fs'",
|
|
980
|
+
cites: [
|
|
981
|
+
{ 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.' },
|
|
982
|
+
{ on: '2026-08-19', said: 'You can put a the no node child thing in place already. Make it real.' },
|
|
983
|
+
],
|
|
984
|
+
seeds: [
|
|
985
|
+
{
|
|
986
|
+
id: 'slopnator',
|
|
987
|
+
at: './slopnators/sl_no_child_process.ts',
|
|
988
|
+
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.',
|
|
989
|
+
cites: [{ on: '2026-08-19', said: 'You can put a the no node child thing in place already.' }],
|
|
990
|
+
},
|
|
991
|
+
],
|
|
992
|
+
derives: [
|
|
993
|
+
{
|
|
994
|
+
id: 'trial',
|
|
995
|
+
at: '_threads/_generated/trials.ts',
|
|
996
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
997
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
998
|
+
},
|
|
999
|
+
],
|
|
1000
|
+
},
|
|
1001
|
+
|
|
1002
|
+
{
|
|
1003
|
+
id: 'no-dispatch-bypass',
|
|
1004
|
+
at: './constraint.ts',
|
|
1005
|
+
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.',
|
|
1006
|
+
version: 1,
|
|
1007
|
+
where: { paths: ['.'], except: ['_generated/', 'thread/operations/'] },
|
|
1008
|
+
bans: 'reaching into an imported operation, calling it, or passing it anywhere but `dispatch`',
|
|
1009
|
+
because:
|
|
1010
|
+
'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',
|
|
1011
|
+
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',
|
|
1012
|
+
wrong: "import { operation } from './operations/wf_send_invoice.ts'\n\nawait operation.run(hands, held)",
|
|
1013
|
+
right: "import { dispatch } from './operations/_dispatch.ts'\n\nimport { operation } from './operations/wf_send_invoice.ts'\n\nawait dispatch(operation, input, actor, seams)",
|
|
1014
|
+
cites: [
|
|
1015
|
+
{ on: '2026-08-17', said: 'That way we can enforce how things run, and they can only run through dispatch.' },
|
|
1016
|
+
{ 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.' },
|
|
1017
|
+
{ on: '2026-08-22', said: 'I guess you should do both then.' },
|
|
1018
|
+
{ on: '2026-08-24', said: 'dispatch is the only way to run an operation' },
|
|
1019
|
+
],
|
|
1020
|
+
seeds: [
|
|
1021
|
+
{
|
|
1022
|
+
id: 'slopnator',
|
|
1023
|
+
at: './slopnators/sl_no_dispatch_bypass.ts',
|
|
1024
|
+
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.',
|
|
1025
|
+
cites: [{ on: '2026-08-22', said: 'I guess you should do both then.' }],
|
|
1026
|
+
},
|
|
1027
|
+
],
|
|
1028
|
+
derives: [
|
|
1029
|
+
{
|
|
1030
|
+
id: 'trial',
|
|
1031
|
+
at: '_threads/_generated/trials.ts',
|
|
1032
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
1033
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
1034
|
+
},
|
|
1035
|
+
],
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
{
|
|
1039
|
+
id: 'undeclared-node',
|
|
1040
|
+
at: './constraint.ts',
|
|
1041
|
+
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.',
|
|
1042
|
+
version: 1,
|
|
1043
|
+
where: { paths: ['operations/'], except: ['_generated/'] },
|
|
1044
|
+
bans: 'an operation reaching a node under `nodes/` that the tree does not declare',
|
|
1045
|
+
because:
|
|
1046
|
+
'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',
|
|
1047
|
+
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',
|
|
1048
|
+
wrong: "import { node as ghost } from '../nodes/nd_ghost.ts'",
|
|
1049
|
+
right: "import { node as read } from '../nodes/nd_read.ts'",
|
|
1050
|
+
cites: [
|
|
1051
|
+
{ on: '2026-08-22', said: 'Operations are bilt of nodes, only nodes that are declared.' },
|
|
1052
|
+
{ on: '2026-08-22', said: 'That\'s why it grows. We declare, operations can do more.' },
|
|
1053
|
+
{ on: '2026-08-26', said: '1-this is a clear BUG' },
|
|
1054
|
+
],
|
|
1055
|
+
seeds: [
|
|
1056
|
+
{
|
|
1057
|
+
id: 'slopnator',
|
|
1058
|
+
at: './slopnators/sl_undeclared_node.ts',
|
|
1059
|
+
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.',
|
|
1060
|
+
cites: [{ on: '2026-08-22', said: 'Operations are bilt of nodes, only nodes that are declared.' }],
|
|
1061
|
+
},
|
|
1062
|
+
],
|
|
1063
|
+
derives: [
|
|
1064
|
+
{
|
|
1065
|
+
id: 'trial',
|
|
1066
|
+
at: '_threads/_generated/trials.ts',
|
|
1067
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
1068
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
1069
|
+
},
|
|
1070
|
+
],
|
|
1071
|
+
},
|
|
1072
|
+
|
|
1073
|
+
{
|
|
1074
|
+
id: 'unprefixed-member',
|
|
1075
|
+
at: './constraint.ts',
|
|
1076
|
+
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.',
|
|
1077
|
+
version: 1,
|
|
1078
|
+
where: { paths: ['.'], except: ['_generated/'] },
|
|
1079
|
+
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',
|
|
1080
|
+
because:
|
|
1081
|
+
'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',
|
|
1082
|
+
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',
|
|
1083
|
+
wrong: "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]",
|
|
1084
|
+
right: "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]",
|
|
1085
|
+
cites: [
|
|
1086
|
+
{ 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.' },
|
|
1087
|
+
{ on: '2026-08-25', said: '3 fix it please.' },
|
|
1088
|
+
],
|
|
1089
|
+
seeds: [
|
|
1090
|
+
{
|
|
1091
|
+
id: 'slopnator',
|
|
1092
|
+
at: './slopnators/sl_unprefixed_member.ts',
|
|
1093
|
+
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.',
|
|
1094
|
+
cites: [{ on: '2026-08-22', said: 'I guess you should do both then.' }],
|
|
1095
|
+
},
|
|
1096
|
+
],
|
|
1097
|
+
derives: [
|
|
1098
|
+
{
|
|
1099
|
+
id: 'trial',
|
|
1100
|
+
at: '_threads/_generated/trials.ts',
|
|
1101
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
1102
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
1103
|
+
},
|
|
1104
|
+
],
|
|
1105
|
+
},
|
|
1106
|
+
|
|
1107
|
+
{
|
|
1108
|
+
id: 'unkept-rows',
|
|
1109
|
+
at: './constraint.ts',
|
|
1110
|
+
describes:
|
|
1111
|
+
'The refusal of an app that declares somewhere to keep its rows and then keeps them somewhere else. A target that holds entities derives a store of its own; if what says where rows go names none of them, the tables are written, the migrations are written, the gate is green, and the database is never opened.',
|
|
1112
|
+
version: 1,
|
|
1113
|
+
where: { paths: ['.'], except: ['_generated/', 'node_modules/'] },
|
|
1114
|
+
bans: 'a store derived for a declared target while what says where rows go names none of them',
|
|
1115
|
+
because:
|
|
1116
|
+
'until 2026-09-01 there was exactly one implementation of the store this framework ships, and it held rows in a Map. A target derived a schema and its migrations and nothing that could talk to the database those describe — so an app declared postgres, had its tables written, passed every one of its checks, and then kept every row in memory, losing them all on restart with nothing anywhere saying so. The store exists now; this is what stops the same silence coming back, because every surface that could have noticed the old state reported success',
|
|
1117
|
+
instead:
|
|
1118
|
+
'run `thread capabilities`, which writes where rows go from what this app declares it keeps. If what it wrote is the in-memory stand-in, then this app declares no target that keeps entities — declare one with `thread create target` and put an entity in it with `thread create entity`',
|
|
1119
|
+
wrong: 'a store is derived for a declared target and where rows go still names the in-memory stand-in',
|
|
1120
|
+
right: 'a store is derived for a declared target and where rows go names that store',
|
|
1121
|
+
cites: [
|
|
1122
|
+
{
|
|
1123
|
+
on: '2026-09-01',
|
|
1124
|
+
said: 'What the fuck does that mean? Cause this seems to be the thing that NEVER IS READY. WHY IS THIS NOT READY? WHAT NEEDS TO CHANGE???????',
|
|
1125
|
+
},
|
|
1126
|
+
{ on: '2026-09-01', said: 'Yes — all three' },
|
|
1127
|
+
],
|
|
1128
|
+
seeds: [
|
|
1129
|
+
{
|
|
1130
|
+
id: 'slopnator',
|
|
1131
|
+
at: './slopnators/sl_unkept_rows.ts',
|
|
1132
|
+
describes:
|
|
1133
|
+
'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.',
|
|
1134
|
+
cites: [{ on: '2026-08-22', said: 'I guess you should do both then.' }],
|
|
1135
|
+
},
|
|
1136
|
+
],
|
|
1137
|
+
derives: [
|
|
1138
|
+
{
|
|
1139
|
+
id: 'trial',
|
|
1140
|
+
at: '_threads/_generated/trials.ts',
|
|
1141
|
+
describes:
|
|
1142
|
+
'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
1143
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
1144
|
+
},
|
|
1145
|
+
],
|
|
1146
|
+
},
|
|
1147
|
+
|
|
1148
|
+
{
|
|
1149
|
+
id: 'untranslated-key',
|
|
1150
|
+
at: './constraint.ts',
|
|
1151
|
+
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.',
|
|
1152
|
+
version: 1,
|
|
1153
|
+
where: { paths: ['.'], except: ['_generated/', 'node_modules/'] },
|
|
1154
|
+
bans: 'a message key named by an element and left empty, or absent, in any language the app declares',
|
|
1155
|
+
because:
|
|
1156
|
+
'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',
|
|
1157
|
+
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`',
|
|
1158
|
+
wrong: '<Button label="checkout.confirm" /> with { "checkout.confirm": "" } in i18n/pt-BR.json',
|
|
1159
|
+
right: '<Button label="checkout.confirm" /> with { "checkout.confirm": "Confirmar" } in i18n/pt-BR.json',
|
|
1160
|
+
cites: [
|
|
1161
|
+
{ 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.' },
|
|
1162
|
+
{ on: '2026-08-29', said: 'i18n ships by default, nothing hardcoded in the UI at all' },
|
|
1163
|
+
],
|
|
1164
|
+
seeds: [
|
|
1165
|
+
{
|
|
1166
|
+
id: 'slopnator',
|
|
1167
|
+
at: './slopnators/sl_untranslated_key.ts',
|
|
1168
|
+
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.',
|
|
1169
|
+
cites: [{ on: '2026-08-22', said: 'I guess you should do both then.' }],
|
|
1170
|
+
},
|
|
1171
|
+
],
|
|
1172
|
+
derives: [
|
|
1173
|
+
{
|
|
1174
|
+
id: 'trial',
|
|
1175
|
+
at: '_threads/_generated/trials.ts',
|
|
1176
|
+
describes: 'The proof that a rule does what its row claims — the rule fired at the form it says it refuses and at the fix it recommends, derived from the row and the slopnator it names rather than written by hand.',
|
|
1177
|
+
cites: [{ on: '2026-08-14', said: 'Slopnators though also need to run. So they derive their own tests.' }],
|
|
1178
|
+
},
|
|
1179
|
+
],
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
id: 'only-takes-a-literal',
|
|
1183
|
+
at: './constraint.ts',
|
|
1184
|
+
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.',
|
|
1185
|
+
version: 1,
|
|
1186
|
+
where: { paths: ['.'] },
|
|
1187
|
+
bans: 'an array literal as the second argument of `only`',
|
|
1188
|
+
because:
|
|
1189
|
+
'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',
|
|
1190
|
+
instead:
|
|
1191
|
+
'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',
|
|
1192
|
+
wrong: "only(piped, ['name', 'describes', 'address'], 'a target')",
|
|
1193
|
+
right: 'only(piped, TAKES, \'a target\')',
|
|
1194
|
+
seeds: [
|
|
1195
|
+
{
|
|
1196
|
+
id: 'slopnator',
|
|
1197
|
+
at: './slopnators/sl_only_takes_a_literal.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
|
+
]
|