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