@prestack/thread 0.0.3 → 0.1.0
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 +98 -0
- package/dist/core/{asking.d.ts → answers.d.ts} +6 -14
- package/dist/core/{asking.js → answers.js} +13 -332
- package/dist/core/{asking.ts → answers.ts} +14 -357
- package/dist/core/attach.js +2 -2
- package/dist/core/attach.ts +2 -3
- package/dist/core/chain.js +4 -11
- package/dist/core/chain.ts +4 -3
- package/dist/core/client.d.ts +57 -0
- package/dist/core/client.js +186 -0
- package/dist/core/client.ts +227 -0
- package/dist/core/descend.d.ts +2 -0
- package/dist/core/descend.js +109 -16
- package/dist/core/descend.ts +120 -16
- package/dist/core/files.d.ts +45 -0
- package/dist/core/files.js +95 -0
- package/dist/core/files.ts +137 -0
- package/dist/core/init.d.ts +1 -0
- package/dist/core/init.js +10 -2
- package/dist/core/init.ts +8 -2
- package/dist/core/invoked.js +1 -1
- package/dist/core/invoked.ts +1 -1
- package/dist/core/rows.d.ts +2 -0
- package/dist/core/rows.js +65 -0
- package/dist/core/rows.ts +58 -0
- package/dist/core/tree.js +1 -1
- package/dist/core/tree.ts +1 -1
- package/dist/derivation/attach.js +0 -12
- package/dist/derivation/attach.ts +0 -12
- package/dist/derivation/generators/build.js +8 -7
- package/dist/derivation/generators/build.ts +8 -7
- package/dist/derivation/generators/release.d.ts +1 -1
- package/dist/derivation/generators/release.js +15 -23
- package/dist/derivation/generators/release.ts +15 -15
- package/dist/integrations/attach.js +1 -19
- package/dist/integrations/attach.ts +1 -19
- package/dist/integrations/generators/read.js +2 -2
- package/dist/integrations/generators/read.ts +4 -4
- package/dist/integrations/generators/vault.js +4 -12
- package/dist/integrations/generators/vault.ts +4 -4
- package/dist/integrations/module.js +5 -3
- package/dist/integrations/module.ts +5 -3
- package/dist/integrations/vault.js +5 -5
- package/dist/integrations/vault.ts +5 -5
- package/dist/integrity/attach.js +0 -12
- package/dist/integrity/attach.ts +0 -12
- package/dist/integrity/check-threads.js +1 -1
- package/dist/integrity/check-threads.ts +1 -1
- package/dist/integrity/constraint.d.ts +11 -2
- package/dist/integrity/constraint.js +163 -20
- package/dist/integrity/constraint.ts +174 -22
- package/dist/integrity/generators/compiler.js +1 -1
- package/dist/integrity/generators/compiler.ts +1 -1
- package/dist/integrity/generators/gate.js +8 -16
- package/dist/integrity/generators/gate.ts +8 -7
- package/dist/integrity/generators/publish.js +14 -26
- package/dist/integrity/generators/publish.ts +14 -17
- package/dist/integrity/generators/trial.d.ts +2 -2
- package/dist/integrity/generators/trial.js +108 -50
- package/dist/integrity/generators/trial.ts +107 -48
- package/dist/integrity/lint.js +3 -3
- package/dist/integrity/lint.ts +3 -3
- package/dist/integrity/lsp/diagnostics.js +1 -1
- package/dist/integrity/lsp/diagnostics.ts +1 -1
- package/dist/integrity/lsp/lint.js +1 -1
- package/dist/integrity/lsp/lint.ts +1 -1
- package/dist/integrity/slopnator.d.ts +1 -14
- package/dist/integrity/slopnator.js +8 -40
- package/dist/integrity/slopnator.ts +9 -51
- package/dist/integrity/slopnators/sl_broken_lineage.js +4 -2
- package/dist/integrity/slopnators/sl_broken_lineage.ts +4 -2
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +24 -3
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +24 -3
- package/dist/integrity/slopnators/sl_derived_drift.js +10 -4
- package/dist/integrity/slopnators/sl_derived_drift.ts +10 -4
- package/dist/integrity/slopnators/sl_inert_island.js +7 -13
- package/dist/integrity/slopnators/sl_inert_island.ts +7 -16
- package/dist/integrity/slopnators/sl_inert_slopnator.js +8 -8
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +8 -8
- package/dist/integrity/slopnators/sl_missing_row.js +14 -9
- package/dist/integrity/slopnators/sl_missing_row.ts +14 -10
- package/dist/integrity/slopnators/sl_no_child_process.js +8 -8
- package/dist/integrity/slopnators/sl_no_child_process.ts +8 -8
- package/dist/integrity/slopnators/sl_no_import_cycle.js +1 -1
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +1 -1
- package/dist/integrity/slopnators/sl_no_such_command.js +20 -5
- package/dist/integrity/slopnators/sl_no_such_command.ts +22 -5
- package/dist/integrity/slopnators/sl_only_takes_a_literal.js +8 -8
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +8 -8
- package/dist/integrity/slopnators/sl_stale_path.js +91 -18
- package/dist/integrity/slopnators/sl_stale_path.ts +97 -18
- package/dist/integrity/slopnators/sl_twice_meant.js +25 -2
- package/dist/integrity/slopnators/sl_twice_meant.ts +28 -2
- package/dist/integrity/slopnators/sl_twice_parented.js +26 -5
- package/dist/integrity/slopnators/sl_twice_parented.ts +26 -5
- package/dist/integrity/slopnators/sl_undeclared_node.js +18 -2
- package/dist/integrity/slopnators/sl_undeclared_node.ts +18 -2
- package/dist/integrity/slopnators/sl_undeclared_operation.js +3 -1
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +3 -1
- package/dist/integrity/slopnators/sl_undeclared_variant.js +6 -2
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +6 -2
- package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -4
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +3 -4
- package/dist/integrity/slopnators/sl_unkept_rows.js +1 -1
- package/dist/integrity/slopnators/sl_unkept_rows.ts +1 -1
- package/dist/integrity/slopnators/sl_unpaired_member.js +13 -8
- package/dist/integrity/slopnators/sl_unpaired_member.ts +16 -8
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +16 -3
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +16 -3
- package/dist/integrity/slopnators/sl_unthreaded.d.ts +1 -0
- package/dist/integrity/slopnators/sl_unthreaded.js +18 -10
- package/dist/integrity/slopnators/sl_unthreaded.ts +19 -11
- package/dist/integrity/slopnators/sl_untranslated_key.js +1 -1
- package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -1
- package/dist/integrity/slopnators/sl_unversioned.js +16 -2
- package/dist/integrity/slopnators/sl_unversioned.ts +16 -2
- package/dist/integrity/sweep.js +17 -3
- package/dist/integrity/sweep.ts +20 -3
- package/dist/knowledge/attach.js +0 -6
- package/dist/knowledge/attach.ts +0 -6
- package/dist/knowledge/generators/docsgen.js +12 -3
- package/dist/knowledge/generators/docsgen.ts +12 -2
- package/dist/knowledge/generators/glossary.js +1 -1
- package/dist/knowledge/generators/glossary.ts +1 -1
- package/dist/operations/attach.js +2 -26
- package/dist/operations/attach.ts +2 -27
- package/dist/operations/dispatch.js +2 -3
- package/dist/operations/dispatch.ts +2 -3
- package/dist/operations/generators/capability.js +4 -7
- package/dist/operations/generators/capability.ts +3 -6
- package/dist/operations/generators/compile.js +8 -13
- package/dist/operations/generators/compile.ts +8 -4
- package/dist/operations/generators/permits.js +4 -13
- package/dist/operations/generators/permits.ts +3 -3
- package/dist/operations/generators/seed.js +5 -2
- package/dist/operations/generators/seed.ts +6 -2
- package/dist/operations/generators/trial.js +10 -10
- package/dist/operations/generators/trial.ts +10 -10
- package/dist/operations/http.js +4 -1
- package/dist/operations/http.ts +6 -1
- package/package.json +2 -2
- package/dist/core/cli.d.ts +0 -2
- package/dist/core/cli.js +0 -1761
- package/dist/core/cli.ts +0 -2004
- package/dist/core/generators/meta.d.ts +0 -2
- package/dist/core/generators/meta.js +0 -829
- package/dist/core/generators/meta.ts +0 -1006
- package/dist/derivation/generators/derivation.d.ts +0 -3
- package/dist/derivation/generators/derivation.js +0 -273
- package/dist/derivation/generators/derivation.ts +0 -347
- package/dist/derivation/makers/release.d.ts +0 -1
- package/dist/derivation/makers/release.js +0 -107
- package/dist/derivation/makers/release.ts +0 -135
- package/dist/integrations/generators/secret.d.ts +0 -1
- package/dist/integrations/generators/secret.js +0 -233
- package/dist/integrations/generators/secret.ts +0 -289
- package/dist/integrations/generators/target.d.ts +0 -1
- package/dist/integrations/generators/target.js +0 -775
- package/dist/integrations/generators/target.ts +0 -944
- package/dist/integrity/generators/rule.d.ts +0 -2
- package/dist/integrity/generators/rule.js +0 -213
- package/dist/integrity/generators/rule.ts +0 -271
- package/dist/integrity/generators/seal.d.ts +0 -4
- package/dist/integrity/generators/seal.js +0 -101
- package/dist/integrity/generators/seal.ts +0 -117
- package/dist/integrity/sealed.d.ts +0 -1
- package/dist/integrity/sealed.js +0 -56
- package/dist/integrity/sealed.ts +0 -56
- package/dist/knowledge/generators/skills.d.ts +0 -1
- package/dist/knowledge/generators/skills.js +0 -222
- package/dist/knowledge/generators/skills.ts +0 -263
- package/dist/operations/generators/grant.d.ts +0 -1
- package/dist/operations/generators/grant.js +0 -267
- package/dist/operations/generators/grant.ts +0 -336
- package/dist/operations/generators/operation.d.ts +0 -1
- package/dist/operations/generators/operation.js +0 -811
- package/dist/operations/generators/operation.ts +0 -972
- package/dist/operations/makers/capability.d.ts +0 -1
- package/dist/operations/makers/capability.js +0 -117
- package/dist/operations/makers/capability.ts +0 -166
- package/dist/operations/makers/trial.d.ts +0 -1
- package/dist/operations/makers/trial.js +0 -413
- package/dist/operations/makers/trial.ts +0 -520
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import type { Threaded } from '../core/thread.ts'
|
|
2
2
|
import { named } from '../core/scope.ts'
|
|
3
3
|
|
|
4
|
+
export interface Sourced {
|
|
5
|
+
readonly at: string
|
|
6
|
+
readonly source: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type Proof = string | { readonly files: readonly Sourced[]; readonly at: string }
|
|
10
|
+
|
|
11
|
+
export function shown(proof: Proof): string {
|
|
12
|
+
if (typeof proof === 'string') return proof
|
|
13
|
+
const target = proof.files.find((one) => one.at === proof.at) ?? proof.files[0]
|
|
14
|
+
const beside = proof.files.filter((one) => one.at !== proof.at).map((one) => one.at)
|
|
15
|
+
return `${target?.source ?? ''}${beside.length > 0 ? ` (in a tree holding ${beside.join(', ')})` : ''}`
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
export interface Constraint extends Threaded {
|
|
5
19
|
readonly version: number
|
|
6
20
|
readonly where: { readonly paths: readonly string[]; readonly except?: readonly string[] }
|
|
7
21
|
readonly bans: string
|
|
8
22
|
readonly because: string
|
|
9
23
|
readonly instead: string
|
|
10
|
-
readonly wrong:
|
|
11
|
-
readonly right:
|
|
24
|
+
readonly wrong: Proof
|
|
25
|
+
readonly right: Proof
|
|
12
26
|
readonly governs?: string
|
|
13
27
|
readonly severity?: 'forbidden' | 'warning'
|
|
14
28
|
}
|
|
@@ -214,7 +228,14 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
214
228
|
'`declares` is exactly one parent ever, and with two the child\'s owner, its shape and its origin each have two answers while the tree gives none. Measured 2026-08-14: the two rows may sit in different files, so no slopnator that sees one file can find this — check-threads found it only by comparing parents across the whole descent, which is why it was a check rather than a rule. Measured 2026-08-15: this rule read `threads/` and nothing else, so a file outside the tree that declared a child the tree already declares was invisible to it — and a file outside the tree may name children, which is the whole premise `broken-lineage` rests on. It now counts parents from every file the tree reaches, not from one directory',
|
|
215
229
|
instead:
|
|
216
230
|
'keep the row that owns it and delete the other. If both are true, one of them is a `derives` or a `seeds`, which may have many parents because neither of them owns the child',
|
|
217
|
-
wrong:
|
|
231
|
+
wrong: {
|
|
232
|
+
files: [
|
|
233
|
+
{ 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" },
|
|
234
|
+
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_parented_row.ts',\n },\n ],\n}\n" },
|
|
235
|
+
{ at: 'threads/_probe_parented_row.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n at: 'threads/one.ts',\n },\n {\n at: 'threads/one.ts',\n },\n ],\n}\n" },
|
|
236
|
+
],
|
|
237
|
+
at: 'threads/_probe_parented_row.ts',
|
|
238
|
+
},
|
|
218
239
|
right: "const M: Threaded = {\n declares: [\n {\n at: 'threads/one.ts',\n },\n {\n at: 'threads/two.ts',\n },\n ],\n}",
|
|
219
240
|
cites: [{ on: '2026-08-14', said: 'Cause we need unthreaded, redthread, broken lineage, and the request/produce errors....' }],
|
|
220
241
|
seeds: [
|
|
@@ -275,13 +296,19 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
275
296
|
at: './constraint.ts',
|
|
276
297
|
describes: 'The refusal of a written address that is not on disk — a file moves and every sentence naming it becomes a confident lie that still reads as correct, and counting those without judging them is worse than never scanning, because the number reads as a result.',
|
|
277
298
|
version: 1,
|
|
278
|
-
where: { paths: ['.'], except: ['slopnators/', '_generated/'
|
|
299
|
+
where: { paths: ['.'], except: ['slopnators/', '_generated/'] },
|
|
279
300
|
bans: 'a string naming a path in this repository that is not on disk',
|
|
280
301
|
because:
|
|
281
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',
|
|
282
303
|
instead:
|
|
283
304
|
'fix the path, or delete the sentence naming it. A path that carries a `{variable}` states the directory it writes into, and that directory must exist',
|
|
284
|
-
wrong:
|
|
305
|
+
wrong: {
|
|
306
|
+
files: [
|
|
307
|
+
{ at: 'threads/_probe_there.ts', source: 'export const THERE = 1\n' },
|
|
308
|
+
{ at: 'probe.tsx', source: "const at = 'threads/nope.ts'\n" },
|
|
309
|
+
],
|
|
310
|
+
at: 'probe.tsx',
|
|
311
|
+
},
|
|
285
312
|
right: "const at = 'packages/core/integrity/src/constraint.ts'",
|
|
286
313
|
cites: [{ on: '2026-08-14', said: 'I want you to transform every single thing that currently is a check into a law, so we can retire checks and we can have SSOT for laws, and slopnators only for execution.' }],
|
|
287
314
|
seeds: [
|
|
@@ -347,7 +374,13 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
347
374
|
because:
|
|
348
375
|
'the gate is the commands these name, so one that package.json lacks fails as `Command "nope:check" not found` at the moment it is reached — after every step before it has already run, and only when somebody runs the gate. Measured 2026-08-14: sixteen rows name sixteen commands and nothing has ever compared them against package.json; they are correct today by nobody having mistyped one',
|
|
349
376
|
instead: 'add the script to package.json, or fix the name. A row naming a command is a promise that the command exists',
|
|
350
|
-
wrong:
|
|
377
|
+
wrong: {
|
|
378
|
+
files: [
|
|
379
|
+
{ at: 'package.json', source: '{\n "scripts": {\n "typecheck": "tsc"\n }\n}\n' },
|
|
380
|
+
{ at: 'threads/_probe_command.ts', source: "const M = {\n runs: 'pnpm nope:check',\n}\n" },
|
|
381
|
+
],
|
|
382
|
+
at: 'threads/_probe_command.ts',
|
|
383
|
+
},
|
|
351
384
|
right: "const M = {\n runs: 'pnpm typecheck',\n}",
|
|
352
385
|
cites: [{ on: '2026-08-14', said: 'I think you need to study how a file gets parented and make proper error handling for everything. No at is just one.' }],
|
|
353
386
|
seeds: [
|
|
@@ -379,7 +412,13 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
379
412
|
'a rule is a row and a slopnator is a function, and nothing but their names holds the two together — so either half can go missing and the other reads as working. A rule whose slopnator is absent registers, loads, reports nothing, and is indistinguishable from a rule being obeyed. A slopnator no rule names is code that runs in no gate and governs nothing, while looking like enforcement to anybody reading the directory. Measured 2026-08-15: this rule was real and enforced, but it lived as a gate step in ci.ts rather than as a rule — one more place where a guarantee could be turned off by editing a list, which is the thing the rules exist to stop. Moving it here deletes the step rather than adding a check',
|
|
380
413
|
instead: 'write the rule that needs the slopnator, or delete the slopnator. `pnpm slopnator:seed` writes the scaffold for a rule that has none, and a rename is a `git mv` rather than a second file',
|
|
381
414
|
wrong: "const CONSTRAINTS = [\n {\n id: 'nothing-names-this-slopnator',\n },\n]",
|
|
382
|
-
right:
|
|
415
|
+
right: {
|
|
416
|
+
files: [
|
|
417
|
+
{ at: 'threads/_probe_paired.ts', source: "const CONSTRAINTS = [\n {\n id: 'no-comment',\n },\n]\n" },
|
|
418
|
+
{ at: 'threads/slopnators/sl_no_comment.ts', source: 'export const slopnator = { on: [], holds: () => false }\n' },
|
|
419
|
+
],
|
|
420
|
+
at: 'threads/_probe_paired.ts',
|
|
421
|
+
},
|
|
383
422
|
cites: [
|
|
384
423
|
{ on: '2026-08-14', said: 'A law is written, names a slopnator. Slopnator is seeded magically. Someone write the function. An enforcer picks it up and it just works.' },
|
|
385
424
|
{ on: '2026-08-15', said: 'Does a slopnator without a law or a law without a slopnator exist? This should be an error....' },
|
|
@@ -413,7 +452,14 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
413
452
|
'the glossary is derived: a word\'s meaning is the `describes` on the row that coins it, so a word coined twice with two descriptions has two meanings and the file can hold one. Repetition is not the defect — measured 2026-08-15, the tree coins `slopnator` on twelve rows and `trial` on twelve, deliberately, because the founder asked for the category in the glossary rather than each instance under its own name. Disagreement is the defect: those same twelve rows each stated a different sentence, so the tree asserted twelve meanings for one word and nothing looked. Five words were split this way — `slopnator`, `trial`, `ci`, `build` and `glossary` — and three of them were the same sentence written twice, which is the SSOT failure wearing a second coat',
|
|
414
453
|
instead:
|
|
415
454
|
'state the category meaning on every row that coins the word, because the word is what the row names. Where two rows describe the same file from different heights, the row that declares it wins and the node repeats it. Where a row needs to say something only true of its own instance, it needs its own id — and if you do not want that word in the glossary, the sentence does not belong on the row',
|
|
416
|
-
wrong:
|
|
455
|
+
wrong: {
|
|
456
|
+
files: [
|
|
457
|
+
{ 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" },
|
|
458
|
+
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_meant_row.ts',\n },\n ],\n}\n" },
|
|
459
|
+
{ at: 'threads/_probe_meant_row.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'one',\n describes: 'A different thing entirely.',\n },\n ],\n}\n" },
|
|
460
|
+
],
|
|
461
|
+
at: 'threads/_probe_meant_row.ts',
|
|
462
|
+
},
|
|
417
463
|
right: "const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'one',\n describes: 'The first thing.',\n },\n ],\n}",
|
|
418
464
|
cites: [
|
|
419
465
|
{ on: '2026-08-15', said: 'the ids thing. We can\'t have that, the id\'s being the same.' },
|
|
@@ -592,8 +638,24 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
592
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',
|
|
593
639
|
instead:
|
|
594
640
|
'write the name where it can be read, and declare the operation it names — `dispatch(\'wf_send_invoice\')` against a row whose `at` is `operations/wf_send_invoice.ts`. Where the name genuinely varies, the choosing belongs in the declaration, not in a template string',
|
|
595
|
-
wrong:
|
|
596
|
-
|
|
641
|
+
wrong: {
|
|
642
|
+
files: [
|
|
643
|
+
{ 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" },
|
|
644
|
+
{ 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" },
|
|
645
|
+
{ at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n" },
|
|
646
|
+
{ at: 'app/_probe_wrong.tsx', source: "dispatch('wf_not_declared')" },
|
|
647
|
+
],
|
|
648
|
+
at: 'app/_probe_wrong.tsx',
|
|
649
|
+
},
|
|
650
|
+
right: {
|
|
651
|
+
files: [
|
|
652
|
+
{ 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" },
|
|
653
|
+
{ 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" },
|
|
654
|
+
{ at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n" },
|
|
655
|
+
{ at: 'app/_probe_right.tsx', source: "dispatch('wf_send_invoice')" },
|
|
656
|
+
],
|
|
657
|
+
at: 'app/_probe_right.tsx',
|
|
658
|
+
},
|
|
597
659
|
cites: [
|
|
598
660
|
{ on: '2026-08-25', said: 'And make sure the composition can only dispatch existing operations.' },
|
|
599
661
|
{ on: '2026-08-25', said: 'Yes — reading a view is dispatching too' },
|
|
@@ -629,8 +691,24 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
629
691
|
'measured 2026-08-25: the founder wrote `<Button.primary text="translation.key" />` as the shape a composition takes, and the fourteen variants in packages/ui/src/variants.ts hold no row `primary` — the one that fits is `button-primary`. `element()` in packages/ui/src/wrappers.tsx resolves any member through a proxy, so that call renders a button carrying the class `thread-primary`, which the stylesheet generator never writes because no variant of that name exists to write it from. It renders, it is unstyled, and nothing in the repository says a word. The pairing half is the same hole from the other side: `card` declares fifteen elements that wear it and `Button` is not among them, and nothing stops `<Button.card />` either. Both are the silent-pass shape — a check reporting success because it measured nothing',
|
|
630
692
|
instead:
|
|
631
693
|
'declare the variant, and name the element in that row\'s `wears` — the row is what makes the pairing legal, which is why `<Button.buttonPrimary />` holds and `<Button.primary />` does not',
|
|
632
|
-
wrong:
|
|
633
|
-
|
|
694
|
+
wrong: {
|
|
695
|
+
files: [
|
|
696
|
+
{ 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" },
|
|
697
|
+
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_worn.ts',\n },\n ],\n}\n" },
|
|
698
|
+
{ at: 'threads/_probe_worn.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const ELEMENTS: Threaded = {\n id: 'element',\n declares: [\n {\n id: 'button',\n at: 'app/_probe_button.tsx',\n },\n ],\n}\n\nexport const VARIANTS: Threaded = {\n id: 'variant',\n declares: [\n {\n id: 'button-primary',\n at: 'app/_probe_variant.ts',\n wears: ['Button'],\n },\n ],\n}\n" },
|
|
699
|
+
{ at: 'app/_probe_wrong.tsx', source: '<Button.primary />' },
|
|
700
|
+
],
|
|
701
|
+
at: 'app/_probe_wrong.tsx',
|
|
702
|
+
},
|
|
703
|
+
right: {
|
|
704
|
+
files: [
|
|
705
|
+
{ 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" },
|
|
706
|
+
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_worn.ts',\n },\n ],\n}\n" },
|
|
707
|
+
{ at: 'threads/_probe_worn.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const ELEMENTS: Threaded = {\n id: 'element',\n declares: [\n {\n id: 'button',\n at: 'app/_probe_button.tsx',\n },\n ],\n}\n\nexport const VARIANTS: Threaded = {\n id: 'variant',\n declares: [\n {\n id: 'button-primary',\n at: 'app/_probe_variant.ts',\n wears: ['Button'],\n },\n ],\n}\n" },
|
|
708
|
+
{ at: 'app/_probe_right.tsx', source: '<Button.buttonPrimary />' },
|
|
709
|
+
],
|
|
710
|
+
at: 'app/_probe_right.tsx',
|
|
711
|
+
},
|
|
634
712
|
cites: [
|
|
635
713
|
{ on: '2026-08-25', said: 'Variants themselves declare what elements they relate to, in their declarations. Then the composition says button.primary and if that matches the combination of variant and element it\'s acceptable.' },
|
|
636
714
|
{ on: '2026-08-25', said: 'If there\'s no primary variant.....that\' an error moron. Didn\'t I said that it can\'t name a variant that doesn\'t exist?' },
|
|
@@ -702,8 +780,22 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
702
780
|
'a member is added by declaring it and then written, never the other way round — the same order a rule follows to its slopnator, and for the same reason: the row is what everything downstream reads, so a file that arrived without one is a thing the runtime, the API, the agent tools and the trials have all never heard of, while it sits there able to act. The two halves are held together by nothing but a name, so either can go missing and the other reads as working. Absence is the finding in both directions because there is no third place to look: the rows are what exists, the folder is what was written, and any gap between them is one somebody has to close on purpose. The rule reaches by shape and not by name — it reads every island that seeds its members from the tree, so an island added tomorrow is governed without editing this row',
|
|
703
781
|
instead:
|
|
704
782
|
'add the row to the island\'s tree file first and run `pnpm member:seed`, which writes the file that row brings into existence — then write the member into the template it left',
|
|
705
|
-
wrong:
|
|
706
|
-
|
|
783
|
+
wrong: {
|
|
784
|
+
files: [
|
|
785
|
+
{ at: 'threads/thread.ts', source: "export const THREAD: Threaded = {\n declares: [\n { id: 'operations', at: 'src/islands/operations.ts', governs: ['operations/'] },\n { id: 'operation', at: 'threads/operations.ts' },\n ],\n}\n" },
|
|
786
|
+
{ at: 'src/islands/operations.ts', source: 'export const OperationsIslandSchema = {}\n' },
|
|
787
|
+
{ at: 'threads/operations.ts', source: "export const OPERATIONS: readonly Threaded[] = [\n {\n id: 'wf_nothing_seeded_this',\n },\n]\n" },
|
|
788
|
+
],
|
|
789
|
+
at: 'threads/operations.ts',
|
|
790
|
+
},
|
|
791
|
+
right: {
|
|
792
|
+
files: [
|
|
793
|
+
{ at: 'threads/thread.ts', source: "export const THREAD: Threaded = {\n declares: [\n { id: 'operations', at: 'src/islands/operations.ts', governs: ['operations/'] },\n { id: 'operation', at: 'threads/operations.ts' },\n ],\n}\n" },
|
|
794
|
+
{ at: 'src/islands/operations.ts', source: 'export const OperationsIslandSchema = {}\n' },
|
|
795
|
+
{ at: 'threads/operations.ts', source: 'export const OPERATIONS: readonly Threaded[] = []\n' },
|
|
796
|
+
],
|
|
797
|
+
at: 'threads/operations.ts',
|
|
798
|
+
},
|
|
707
799
|
cites: [
|
|
708
800
|
{ on: '2026-08-17', said: 'FIRST we add the new row in workflows.ts, which seeds a new wf_{id} file in a workflows folder.' },
|
|
709
801
|
{ on: '2026-08-17', said: 'The file is not authored but seeded. We can write the operation after, but it must have a template with instructions maybe of how to build. Like slopnators.' },
|
|
@@ -771,7 +863,14 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
771
863
|
because:
|
|
772
864
|
'the loader opens the paths an island governs and nothing else, so a declaration written beside the code that needs it is not a smaller declaration — it is no declaration, and every surface downstream reports success over it: it typechecks, the editor completes it, and the generator emits nothing. This is one rule rather than one per island because the rule never differed — measured 2026-08-17: the same sentence was written eleven times, and the island-to-path fact it depends on appeared 130 times across 56 authored files, so eleven copies of a rule drifted against a fact stated everywhere. Here the island states what it governs once, and the slopnator reads it. The call it looks for is read out of the island itself rather than derived from its name, because no convention survives contact with the real set: `sql` exposes `defineSqlFunctions` and `navigation` exposes `defineMap`. An island that governs only folders is one whose kind is derived and authored nowhere, so the call is refused everywhere — which is what `AUTHORED_MAP` did for the map at build.ts:150, now stated on the island instead of in the CLI',
|
|
773
865
|
instead: 'declare it in the file the island governs, and import it where it is needed',
|
|
774
|
-
wrong:
|
|
866
|
+
wrong: {
|
|
867
|
+
files: [
|
|
868
|
+
{ at: 'threads/thread.ts', source: "export const THREAD: Threaded = {\n declares: [\n { id: 'permissions', at: 'threads/_island_permissions.ts', governs: ['app/_declare/permissions.ts'] },\n ],\n}\n" },
|
|
869
|
+
{ at: 'threads/_island_permissions.ts', source: 'export const PermissionsIslandSchema = {}\nexport function definePermissions() {}\n' },
|
|
870
|
+
{ at: 'probe.tsx', source: "export default definePermissions({ specVersion: 1, entities: [{ entity: 'ent_order' }] })\n" },
|
|
871
|
+
],
|
|
872
|
+
at: 'probe.tsx',
|
|
873
|
+
},
|
|
775
874
|
right: "import permissions from 'app/_declare/permissions'",
|
|
776
875
|
cites: [
|
|
777
876
|
{ on: '2026-08-17', said: 'If there\'s something that is govern by an island but it\'s propelry done through island, error.' },
|
|
@@ -805,8 +904,20 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
805
904
|
because:
|
|
806
905
|
'what an island governs is the one fact about it that nothing else can supply. It is not derivable from the island\'s name — measured 2026-08-17: `navigation` governs `app/screens/` and its schema lives in `map.ts`, and `sql` exposes `defineSqlFunctions`, so two of thirteen break any convention a generator could apply. Stated as nothing, the rule that refuses a declaration elsewhere has no elsewhere to refuse: no file is the place its kind belongs, which makes every file the place. The island still parses, still typechecks, still appears in the tree, and governs nobody — inert rather than absent, which is the harder kind to notice',
|
|
807
906
|
instead: 'state what the island governs — a file where its kind is declared, or a folder whose implementation it governs',
|
|
808
|
-
wrong:
|
|
809
|
-
|
|
907
|
+
wrong: {
|
|
908
|
+
files: [
|
|
909
|
+
{ at: 'threads/_inert_probe.ts', source: "export const M: Threaded = {\n declares: [\n { id: 'events', at: 'packages/core/spec/src/islands/events.ts' },\n ],\n}\n" },
|
|
910
|
+
{ at: 'packages/core/spec/src/islands/events.ts', source: 'export const EventsIslandSchema = {}\n' },
|
|
911
|
+
],
|
|
912
|
+
at: 'threads/_inert_probe.ts',
|
|
913
|
+
},
|
|
914
|
+
right: {
|
|
915
|
+
files: [
|
|
916
|
+
{ at: 'threads/_inert_probe.ts', source: "export 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
|
+
{ at: 'packages/core/spec/src/islands/events.ts', source: 'export const EventsIslandSchema = {}\n' },
|
|
918
|
+
],
|
|
919
|
+
at: 'threads/_inert_probe.ts',
|
|
920
|
+
},
|
|
810
921
|
cites: [
|
|
811
922
|
{ on: '2026-08-17', said: 'An island extends threader. It declares what it governs.' },
|
|
812
923
|
{ on: '2026-08-17', said: 'If there\'s an island that doesn\'t state what it governs, error.' },
|
|
@@ -875,7 +986,14 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
875
986
|
instead:
|
|
876
987
|
'name an island the tree declares, or drop the `governs` — a rule about a kind nobody can declare is a rule about nothing',
|
|
877
988
|
wrong: "export const L: Constraint = {\n id: 'a-rule',\n governs: 'no-island-by-this-name',\n}",
|
|
878
|
-
right:
|
|
989
|
+
right: {
|
|
990
|
+
files: [
|
|
991
|
+
{ at: 'threads/thread.ts', source: "export const THREAD: Threaded = {\n declares: [\n { id: 'entities', at: 'threads/_island_entities.ts', governs: ['app/_declare/entities.ts'] },\n ],\n}\n" },
|
|
992
|
+
{ at: 'threads/_island_entities.ts', source: 'export const EntitiesIslandSchema = {}\nexport function defineEntities() {}\n' },
|
|
993
|
+
{ at: 'threads/_probe_governed.ts', source: "export const L: Constraint = {\n id: 'a-rule',\n governs: 'entities',\n}\n" },
|
|
994
|
+
],
|
|
995
|
+
at: 'threads/_probe_governed.ts',
|
|
996
|
+
},
|
|
879
997
|
cites: [
|
|
880
998
|
{ on: '2026-08-16', said: 'Here\'s a new law I need you to add: No island without law.' },
|
|
881
999
|
{ on: '2026-08-16', said: 'So also we need a "law doesn\'t match island" if this is to be done entirely by hand so we catch drifts.' },
|
|
@@ -1045,8 +1163,24 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
1045
1163
|
because:
|
|
1046
1164
|
'measured 2026-08-22: `OperationNode` stated `node` as a bare string, so a body naming `nd_teleport` compiled, loaded and dispatched, and nothing anywhere compared that name against a declared node. The first reading of this rule matched that string. Measured again 2026-08-26, after a node began building its own span: an operation writes no such string any more — it imports a node and calls it — so the reading matched nothing on any real file, and its own proofs were written in the same retired shape, so it passed its trial while measuring nothing. Both halves drifted together, which is the only way a rule goes quiet without anybody noticing. Proved by planting a node body no row declares and running a real app: it ran, answered `ok`, and the sweep named the file for being unthreaded and said nothing about the operation built from it. What an operation says today is the import it reaches its node through, so that is what is read — resolved to a path and checked against the descent, which also catches an import that resolves to nothing at all, where the only other answer was a module-not-found at run time. Measured a third time the same day, an hour after that reading shipped: it matched `import … from` and nothing else, so `await import(...)`, `require(...)` and `export … from` each walked through it, each tried against a real tree and each unremarked. Twice now this rule has enumerated a form instead of stating its rule, which is how a refusal ends up being a list of whatever somebody happened to try. The rule is about the PATH: a string that resolves under `nodes/` names a node, and a node the descent never reaches is a step nobody agreed to — whether it arrives by import, by require, by re-export, or sitting in a const waiting to be used',
|
|
1047
1165
|
instead: 'declare the node in the tree and then build from it. What an app can be asked to do grows by declaring a node, never by importing a file nothing brought into existence',
|
|
1048
|
-
wrong:
|
|
1049
|
-
|
|
1166
|
+
wrong: {
|
|
1167
|
+
files: [
|
|
1168
|
+
{ 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" },
|
|
1169
|
+
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_nodes.ts',\n },\n ],\n}\n" },
|
|
1170
|
+
{ at: 'threads/_probe_nodes.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const NODES: Threaded = {\n id: 'nodes',\n declares: [\n {\n id: 'read',\n at: 'nodes/nd_read.ts',\n },\n ],\n}\n" },
|
|
1171
|
+
{ at: 'operations/_probe_wrong.ts', source: "import { node as ghost } from '../nodes/nd_ghost.ts'" },
|
|
1172
|
+
],
|
|
1173
|
+
at: 'operations/_probe_wrong.ts',
|
|
1174
|
+
},
|
|
1175
|
+
right: {
|
|
1176
|
+
files: [
|
|
1177
|
+
{ 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" },
|
|
1178
|
+
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_nodes.ts',\n },\n ],\n}\n" },
|
|
1179
|
+
{ at: 'threads/_probe_nodes.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const NODES: Threaded = {\n id: 'nodes',\n declares: [\n {\n id: 'read',\n at: 'nodes/nd_read.ts',\n },\n ],\n}\n" },
|
|
1180
|
+
{ at: 'operations/_probe_right.ts', source: "import { node as read } from '../nodes/nd_read.ts'" },
|
|
1181
|
+
],
|
|
1182
|
+
at: 'operations/_probe_right.ts',
|
|
1183
|
+
},
|
|
1050
1184
|
cites: [
|
|
1051
1185
|
{ on: '2026-08-22', said: 'Operations are bilt of nodes, only nodes that are declared.' },
|
|
1052
1186
|
{ on: '2026-08-22', said: 'That\'s why it grows. We declare, operations can do more.' },
|
|
@@ -1080,8 +1214,26 @@ export const CONSTRAINTS: readonly Constraint[] = [
|
|
|
1080
1214
|
because:
|
|
1081
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',
|
|
1082
1216
|
instead: 'name the row for what it is. An operation is `wf_`, `vw_` or `agt_` and belongs in the operations tree; a node is `nd_` and belongs in the nodes tree. A row in the wrong list is not a naming slip, it is a declaration of the wrong thing',
|
|
1083
|
-
wrong:
|
|
1084
|
-
|
|
1217
|
+
wrong: {
|
|
1218
|
+
files: [
|
|
1219
|
+
{
|
|
1220
|
+
at: 'threads/operations.ts',
|
|
1221
|
+
source:
|
|
1222
|
+
"export const MEMBERS: readonly Member[] = [\n {\n id: 'nd_teleport',\n at: '_threads/operations.ts',\n describes: 'a node wearing an operation list',\n version: 1,\n },\n]\n",
|
|
1223
|
+
},
|
|
1224
|
+
],
|
|
1225
|
+
at: 'threads/operations.ts',
|
|
1226
|
+
},
|
|
1227
|
+
right: {
|
|
1228
|
+
files: [
|
|
1229
|
+
{
|
|
1230
|
+
at: 'threads/operations.ts',
|
|
1231
|
+
source:
|
|
1232
|
+
"export const MEMBERS: readonly Member[] = [\n {\n id: 'wf_send_invoice',\n at: '_threads/operations.ts',\n describes: 'sends an invoice',\n version: 1,\n },\n]\n",
|
|
1233
|
+
},
|
|
1234
|
+
],
|
|
1235
|
+
at: 'threads/operations.ts',
|
|
1236
|
+
},
|
|
1085
1237
|
cites: [
|
|
1086
1238
|
{ on: '2026-08-24', said: 'operations.ts, where it declares nodes.ts and dispatch.ts, and member rows in operations are wf_,vw_ and agt_ files.' },
|
|
1087
1239
|
{ on: '2026-08-25', said: '3 fix it please.' },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { readdirSync, readFileSync, writeFileSync } from
|
|
2
|
+
import { readdirSync, readFileSync, writeFileSync } from "../../core/files.js";
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { argv, execPath, stderr, stdout } from 'node:process';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process'
|
|
2
|
-
import { readdirSync, readFileSync, writeFileSync } from '
|
|
2
|
+
import { readdirSync, readFileSync, writeFileSync } from '../../core/files.ts'
|
|
3
3
|
import { createRequire } from 'node:module'
|
|
4
4
|
import { dirname, join, resolve } from 'node:path'
|
|
5
5
|
import { argv, execPath, stderr, stdout } from 'node:process'
|
|
@@ -1,26 +1,19 @@
|
|
|
1
|
-
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
return path;
|
|
8
|
-
};
|
|
9
1
|
import { spawnSync } from 'node:child_process';
|
|
10
2
|
import { createHash } from 'node:crypto';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
3
|
+
import { readFileSync as bytesAt } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
13
5
|
import { argv, execPath, stderr, stdout } from 'node:process';
|
|
14
|
-
import { pathToFileURL } from 'node:url';
|
|
15
6
|
import { descend } from "../../core/descend.js";
|
|
7
|
+
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "../../core/files.js";
|
|
16
8
|
import { LOADER, exit } from "../../core/invoked.js";
|
|
9
|
+
import { valuesIn } from "../../core/rows.js";
|
|
17
10
|
import { ORGANISATION, ours } from "../../core/scope.js";
|
|
18
11
|
import { findTree, inTree } from "../../core/tree.js";
|
|
19
12
|
const TREE = findTree();
|
|
20
13
|
const ROOT = TREE.at;
|
|
21
14
|
const STEP_TIMEOUT = 600_000;
|
|
22
|
-
const { GATE } = (
|
|
23
|
-
const { GENERATORS } = (
|
|
15
|
+
const { GATE } = valuesIn(readFileSync(inTree(TREE, 'gate.ts')));
|
|
16
|
+
const { GENERATORS } = valuesIn(readFileSync(inTree(TREE, 'derivation.ts')));
|
|
24
17
|
function refuse(why) {
|
|
25
18
|
stderr.write(`gate: ${why}\n`);
|
|
26
19
|
exit(1);
|
|
@@ -51,7 +44,7 @@ function steps() {
|
|
|
51
44
|
const writers = [
|
|
52
45
|
...rowsOf(GATE.declares),
|
|
53
46
|
...rowsOf(GENERATORS),
|
|
54
|
-
...[...walked.threaded.values()].flat().filter((one) => one.list === 'declares').map((one) => one.row),
|
|
47
|
+
...[...walked.threaded.values()].flat().filter((one) => one.list === 'declares' && one.row.by === undefined).map((one) => one.row),
|
|
55
48
|
...[...walked.stated.values()].map((one) => one),
|
|
56
49
|
];
|
|
57
50
|
const written = new Map();
|
|
@@ -127,7 +120,7 @@ function fingerprint() {
|
|
|
127
120
|
const hash = (at) => {
|
|
128
121
|
if (!existsSync(join(ROOT, at)))
|
|
129
122
|
return;
|
|
130
|
-
of[at] = createHash('sha256').update(
|
|
123
|
+
of[at] = createHash('sha256').update(bytesAt(join(ROOT, at))).digest('hex').slice(0, 16);
|
|
131
124
|
};
|
|
132
125
|
const sheets = join(ROOT, '_threads');
|
|
133
126
|
if (existsSync(sheets)) {
|
|
@@ -155,7 +148,6 @@ function fingerprint() {
|
|
|
155
148
|
const RECEIPT_AT = join('.thread', 'gate.json');
|
|
156
149
|
const RECEIPT = join(ROOT, RECEIPT_AT);
|
|
157
150
|
if (failed.length === 0 && dropped.length === 0) {
|
|
158
|
-
mkdirSync(dirname(RECEIPT), { recursive: true });
|
|
159
151
|
writeFileSync(RECEIPT, `${JSON.stringify({ passed: chosen.length, ...fingerprint() }, null, 2)}\n`, 'utf8');
|
|
160
152
|
stdout.write(`gate: wrote ${RECEIPT_AT} — what compiles is checked against this, so a tree that skipped the gate does not build.\n`);
|
|
161
153
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process'
|
|
2
2
|
import { createHash } from 'node:crypto'
|
|
3
|
-
import {
|
|
3
|
+
import { readFileSync as bytesAt } from 'node:fs'
|
|
4
4
|
import { dirname, join } from 'node:path'
|
|
5
5
|
import { argv, execPath, stderr, stdout } from 'node:process'
|
|
6
|
-
import { fileURLToPath
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
7
|
import { descend } from '../../core/descend.ts'
|
|
8
|
+
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from '../../core/files.ts'
|
|
8
9
|
import { LOADER, exit } from '../../core/invoked.ts'
|
|
10
|
+
import { valuesIn } from '../../core/rows.ts'
|
|
9
11
|
import { ORGANISATION, ours } from '../../core/scope.ts'
|
|
10
12
|
import { findTree, inTree } from '../../core/tree.ts'
|
|
11
13
|
|
|
@@ -14,8 +16,8 @@ const ROOT = TREE.at
|
|
|
14
16
|
|
|
15
17
|
const STEP_TIMEOUT = 600_000
|
|
16
18
|
|
|
17
|
-
const { GATE } = (
|
|
18
|
-
const { GENERATORS } = (
|
|
19
|
+
const { GATE } = valuesIn(readFileSync(inTree(TREE, 'gate.ts'))) as { GATE: { derives?: unknown; declares?: unknown } }
|
|
20
|
+
const { GENERATORS } = valuesIn(readFileSync(inTree(TREE, 'derivation.ts'))) as { GENERATORS: readonly Record<string, unknown>[] }
|
|
19
21
|
|
|
20
22
|
function refuse(why: string): never {
|
|
21
23
|
stderr.write(`gate: ${why}\n`)
|
|
@@ -66,7 +68,7 @@ function steps(): readonly Step[] {
|
|
|
66
68
|
const writers: readonly Row[] = [
|
|
67
69
|
...rowsOf(GATE.declares),
|
|
68
70
|
...rowsOf(GENERATORS),
|
|
69
|
-
...[...walked.threaded.values()].flat().filter((one) => one.list === 'declares').map((one) => one.row as Row),
|
|
71
|
+
...[...walked.threaded.values()].flat().filter((one) => one.list === 'declares' && (one.row as Row).by === undefined).map((one) => one.row as Row),
|
|
70
72
|
...[...walked.stated.values()].map((one) => one as Row),
|
|
71
73
|
]
|
|
72
74
|
|
|
@@ -156,7 +158,7 @@ function fingerprint(): { readonly of: Record<string, string>; readonly versions
|
|
|
156
158
|
const of: Record<string, string> = {}
|
|
157
159
|
const hash = (at: string): void => {
|
|
158
160
|
if (!existsSync(join(ROOT, at))) return
|
|
159
|
-
of[at] = createHash('sha256').update(
|
|
161
|
+
of[at] = createHash('sha256').update(bytesAt(join(ROOT, at))).digest('hex').slice(0, 16)
|
|
160
162
|
}
|
|
161
163
|
const sheets = join(ROOT, '_threads')
|
|
162
164
|
if (existsSync(sheets)) {
|
|
@@ -182,7 +184,6 @@ const RECEIPT_AT = join('.thread', 'gate.json')
|
|
|
182
184
|
const RECEIPT = join(ROOT, RECEIPT_AT)
|
|
183
185
|
|
|
184
186
|
if (failed.length === 0 && dropped.length === 0) {
|
|
185
|
-
mkdirSync(dirname(RECEIPT), { recursive: true })
|
|
186
187
|
writeFileSync(RECEIPT, `${JSON.stringify({ passed: chosen.length, ...fingerprint() }, null, 2)}\n`, 'utf8')
|
|
187
188
|
stdout.write(`gate: wrote ${RECEIPT_AT} — what compiles is checked against this, so a tree that skipped the gate does not build.\n`)
|
|
188
189
|
} else if (existsSync(RECEIPT)) {
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
return path;
|
|
8
|
-
};
|
|
9
1
|
import { spawnSync } from 'node:child_process';
|
|
10
|
-
import { existsSync, readFileSync } from
|
|
2
|
+
import { existsSync, readFileSync } from "../../core/files.js";
|
|
3
|
+
import { valuesIn } from "../../core/rows.js";
|
|
11
4
|
import { join } from 'node:path';
|
|
12
5
|
import { argv, env, execPath, stderr, stdout } from 'node:process';
|
|
13
|
-
import {
|
|
6
|
+
import { TYPED } from "../../core/answers.js";
|
|
14
7
|
import { CLI, exit } from "../../core/invoked.js";
|
|
15
8
|
import { named } from "../../core/scope.js";
|
|
16
9
|
import { findTree, inTree } from "../../core/tree.js";
|
|
@@ -31,11 +24,6 @@ const CHECKS = [
|
|
|
31
24
|
runs: 'pnpm exec eslint .',
|
|
32
25
|
because: 'the rules that live on the lint surface, which is the only one carrying a fixer the sweep can report but not repair',
|
|
33
26
|
},
|
|
34
|
-
{
|
|
35
|
-
id: 'seal',
|
|
36
|
-
runs: 'pnpm seal:check',
|
|
37
|
-
because: 'the package must be the bytes it says it is. CHECKED and never regenerated, because a release that rewrites its own seal can never fail it',
|
|
38
|
-
},
|
|
39
27
|
];
|
|
40
28
|
const MINE = /^(?:npx |pnpm )?thread /;
|
|
41
29
|
function run(what, extra) {
|
|
@@ -79,7 +67,7 @@ const TOKEN = 'sec_registry_token';
|
|
|
79
67
|
const PRODUCTION = 'production';
|
|
80
68
|
const secrets = inTree(TREE, 'secrets.ts');
|
|
81
69
|
const declared = existsSync(secrets)
|
|
82
|
-
? ((
|
|
70
|
+
? (valuesIn(readFileSync(secrets)).SECRETS ?? [])
|
|
83
71
|
: [];
|
|
84
72
|
const vaulted = declared.some((one) => one.id === TOKEN);
|
|
85
73
|
if (vaulted && !sealedIn(ROOT, PRODUCTION).includes(TOKEN)) {
|
|
@@ -108,10 +96,10 @@ stdout.write(vaulted
|
|
|
108
96
|
? `publish: ${TOKEN} is declared and sealed for ${PRODUCTION}. It is opened only after every check passes, and handed to pnpm as its auth setting for ${registry}.\n`
|
|
109
97
|
: `publish: no row declares ${TOKEN}, so this publish uses ${signedAs}. Declare it with \`thread create secret\` to publish with a token sealed in this repository instead.\n`);
|
|
110
98
|
stdout.write('publish: deriving and building what the packaging rows state first, so every check below reads the files this release ships rather than the source they were written from.\n');
|
|
111
|
-
const made = run(
|
|
99
|
+
const made = run(`${TYPED} packaged --build`);
|
|
112
100
|
stdout.write(`publish: packaged exited ${String(made)}\n`);
|
|
113
101
|
if (made !== 0) {
|
|
114
|
-
refuse('packaged', made, 'a package whose manifest and release note are not what its rows state would go out describing itself as something nobody declared, and one that did not compile would go out promising files it does not have',
|
|
102
|
+
refuse('packaged', made, 'a package whose manifest and release note are not what its rows state would go out describing itself as something nobody declared, and one that did not compile would go out promising files it does not have', `${TYPED} packaged --build`);
|
|
115
103
|
}
|
|
116
104
|
stdout.write(`\npublish: ${String(CHECKS.length)} check(s) stand between this tree and ${registry}. Any one of them stops it.\n`);
|
|
117
105
|
for (const check of CHECKS)
|
|
@@ -124,32 +112,32 @@ for (const [index, check] of CHECKS.entries()) {
|
|
|
124
112
|
if (code !== 0)
|
|
125
113
|
refuse(check.id, code, check.because, check.runs);
|
|
126
114
|
}
|
|
127
|
-
const numbered = run(
|
|
115
|
+
const numbered = run(`${TYPED} packaged --release`);
|
|
128
116
|
stdout.write(`publish: packaged --release exited ${String(numbered)}\n`);
|
|
129
117
|
if (numbered !== 0) {
|
|
130
|
-
refuse('packaged', numbered, 'the next release number is written onto the manifests only once every check has passed, and writing it refused, so nothing here carries a number to go out under',
|
|
118
|
+
refuse('packaged', numbered, 'the next release number is written onto the manifests only once every check has passed, and writing it refused, so nothing here carries a number to go out under', `${TYPED} packaged --release`);
|
|
131
119
|
}
|
|
132
120
|
const handed = vaulted ? { [setting()]: await tokened() } : undefined;
|
|
133
121
|
stdout.write(`\npublish: every check passed. Publishing to ${registry} with ${vaulted ? `${TOKEN} from this repository's vault` : signedAs}.\n`);
|
|
134
122
|
const sent = run(`pnpm -r publish --registry ${registry} --no-git-checks --access public`, handed);
|
|
135
123
|
if (sent !== 0) {
|
|
136
124
|
stderr.write(`\npublish: the checks passed and the publish itself exited ${String(sent)}.\n`);
|
|
137
|
-
const back = run(
|
|
125
|
+
const back = run(`${TYPED} packaged`);
|
|
138
126
|
stderr.write(back === 0
|
|
139
127
|
? 'publish: the manifests carry the last recorded release again and no note was written, so the next `thread publish` numbers this release once more.\n'
|
|
140
|
-
:
|
|
128
|
+
: `publish: the manifests still carry the number that did not go out. Run \`${TYPED} packaged\` to put the last recorded release back.\n`);
|
|
141
129
|
exit(1);
|
|
142
130
|
}
|
|
143
|
-
const recorded = run(
|
|
131
|
+
const recorded = run(`${TYPED} packaged --record`);
|
|
144
132
|
stdout.write(`publish: packaged --record exited ${String(recorded)}\n`);
|
|
145
133
|
if (recorded !== 0) {
|
|
146
|
-
stderr.write(
|
|
134
|
+
stderr.write(`\npublish: the packages went out and the note recording them refused to be written, so the next publish would number this release again. Run \`${TYPED} packaged --record\` and read what it says.\n`);
|
|
147
135
|
exit(1);
|
|
148
136
|
}
|
|
149
137
|
const SHEET = 'packaging.ts';
|
|
150
138
|
const sheet = inTree(TREE, SHEET);
|
|
151
139
|
const stated = existsSync(sheet)
|
|
152
|
-
? (
|
|
140
|
+
? valuesIn(readFileSync(sheet))
|
|
153
141
|
: {};
|
|
154
142
|
const shipped = stated.PACKAGES ?? [];
|
|
155
143
|
function versionAt(folder) {
|
|
@@ -191,5 +179,5 @@ else {
|
|
|
191
179
|
stdout.write(`publish: committed the derived artifacts to ${branch} — ${going}.\n`);
|
|
192
180
|
}
|
|
193
181
|
}
|
|
194
|
-
stdout.write(`\npublish: done. What went out was derived, then built, then passed the gate
|
|
182
|
+
stdout.write(`\npublish: done. What went out was derived, then built, then passed the gate and the lint, in that order. The compile inside \`thread packaged --build\` is the typecheck.\n`);
|
|
195
183
|
exit(0);
|