@orkestrel/scaffold 0.0.66 → 0.0.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/bin/main.js +67 -44
  2. package/dist/bin/main.js.map +1 -1
  3. package/dist/host/agents/templates/brief.md +9 -0
  4. package/dist/host/claude/agents/orkestrel.md +8 -8
  5. package/dist/host/claude/rules/names.md +15 -0
  6. package/dist/host/claude/rules/tests.md +33 -4
  7. package/dist/host/claude/rules/workspace.md +14 -2
  8. package/dist/host/dotfiles/prettierignore +3 -0
  9. package/dist/host/guides/README.md +65 -0
  10. package/dist/host/guides/abort.md +169 -0
  11. package/dist/host/guides/agent.md +1509 -0
  12. package/dist/host/guides/brief.md +1266 -0
  13. package/dist/host/guides/browser.md +2200 -0
  14. package/dist/host/guides/budget.md +196 -0
  15. package/dist/host/guides/codec.md +519 -0
  16. package/dist/host/guides/console.md +785 -0
  17. package/dist/host/guides/contract.md +1193 -0
  18. package/dist/host/guides/csv.md +541 -0
  19. package/dist/host/guides/database.md +2518 -0
  20. package/dist/host/guides/emitter.md +233 -0
  21. package/dist/host/guides/form.md +1791 -0
  22. package/dist/host/guides/html.md +717 -0
  23. package/dist/host/guides/indexeddb.md +505 -0
  24. package/dist/host/guides/interpret.md +1029 -0
  25. package/dist/host/guides/lsp.md +515 -0
  26. package/dist/host/guides/markdown.md +964 -0
  27. package/dist/host/guides/mcp.md +5554 -0
  28. package/dist/host/guides/middleware.md +927 -0
  29. package/dist/host/guides/msg.md +440 -0
  30. package/dist/host/guides/ndjson.md +120 -0
  31. package/dist/host/guides/ollama.md +380 -0
  32. package/dist/host/guides/pool.md +280 -0
  33. package/dist/host/guides/probe.md +1210 -0
  34. package/dist/host/guides/process.md +1620 -0
  35. package/dist/host/guides/program.md +1110 -0
  36. package/dist/host/guides/qualifier.md +854 -0
  37. package/dist/host/guides/queue.md +370 -0
  38. package/dist/host/guides/rater.md +330 -0
  39. package/dist/host/guides/reason.md +1122 -0
  40. package/dist/host/guides/relation.md +373 -0
  41. package/dist/host/guides/router.md +753 -0
  42. package/dist/host/guides/scaffold.md +192 -31
  43. package/dist/host/guides/sea.md +383 -0
  44. package/dist/host/guides/server.md +752 -0
  45. package/dist/host/guides/sqlite.md +330 -0
  46. package/dist/host/guides/sse.md +187 -0
  47. package/dist/host/guides/supervisor.md +4890 -0
  48. package/dist/host/guides/table.md +1556 -0
  49. package/dist/host/guides/template.md +280 -0
  50. package/dist/host/guides/terminal.md +1145 -0
  51. package/dist/host/guides/test.md +2969 -0
  52. package/dist/host/guides/timeout.md +252 -0
  53. package/dist/host/guides/tool.md +311 -0
  54. package/dist/host/guides/toolbox.md +1038 -0
  55. package/dist/host/guides/websocket.md +282 -0
  56. package/dist/host/guides/worker.md +615 -0
  57. package/dist/host/guides/workflow.md +1507 -0
  58. package/dist/host/guides/workspace.md +595 -0
  59. package/dist/host/manifest.json +1218 -10
  60. package/dist/host/tests/policy.test.ts +279 -2
  61. package/dist/host/tests/setupPolicy.ts +437 -6
  62. package/dist/src/core/index.cjs +44 -22
  63. package/dist/src/core/index.cjs.map +1 -1
  64. package/dist/src/core/index.d.cts +33 -9
  65. package/dist/src/core/index.d.ts +33 -9
  66. package/dist/src/core/index.js +43 -23
  67. package/dist/src/core/index.js.map +1 -1
  68. package/dist/src/server/index.cjs +1750 -1567
  69. package/dist/src/server/index.cjs.map +1 -1
  70. package/dist/src/server/index.d.cts +106 -24
  71. package/dist/src/server/index.d.ts +106 -24
  72. package/dist/src/server/index.js +1751 -1570
  73. package/dist/src/server/index.js.map +1 -1
  74. package/package.json +9 -9
@@ -41,6 +41,15 @@ _Name each applicable rule file, the dispatch-named skill and its required refer
41
41
  governing guide or spec. Write `none` in a slot that is genuinely empty rather than dropping the
42
42
  slot._
43
43
 
44
+ **Installed primitives.** ORKESTREL_PACKAGES_WITH_SURFACE_POINTERS.
45
+
46
+ _Name every installed `@orkestrel/*` package whose exports the unit's owned files may overlap —
47
+ `@orkestrel/test` and `@orkestrel/contract` for any unit that owns a `tests/**` or `src/**` file —
48
+ with the pointer the unit reads first: the package's guide `## Surface` section in the scaffold
49
+ checkout, or its declaration under `node_modules`. State that a helper, guard, wait, recorder, or
50
+ deferred whose job an installed export does is a defect, and give the audit's checker the
51
+ export-name probe over the diff._
52
+
44
53
  **Host.** SHELL, WORKING_PATH, NETWORK_AND_SANDBOX_LIMITS.
45
54
 
46
55
  _Name the shell, the working path, and the sandbox, network, and approval limits the unit's commands
@@ -46,7 +46,7 @@ so network-controlled descriptions never enter agent instruction context.
46
46
  | Package | Version | Layer | Runtime dependencies | Peer dependencies |
47
47
  | ----------------------- | -------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
48
48
  | `@orkestrel/abort` | `0.0.10` | L1 | `@orkestrel/contract` `^0.0.17` | |
49
- | `@orkestrel/agent` | `0.0.21` | L5 | `@orkestrel/abort` `^0.0.10`, `@orkestrel/budget` `^0.0.10`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/database` `^0.0.14`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/queue` `^0.0.13`, `@orkestrel/timeout` `^0.0.10`, `@orkestrel/tool` `^0.0.14`, `@orkestrel/workflow` `^0.0.18`, `@orkestrel/workspace` `^0.0.8` | |
49
+ | `@orkestrel/agent` | `0.0.22` | L5 | `@orkestrel/abort` `^0.0.10`, `@orkestrel/budget` `^0.0.10`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/database` `^0.0.14`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/queue` `^0.0.13`, `@orkestrel/timeout` `^0.0.10`, `@orkestrel/tool` `^0.0.14`, `@orkestrel/workflow` `^0.0.18`, `@orkestrel/workspace` `^0.0.8` | |
50
50
  | `@orkestrel/brief` | `0.0.8` | L4 | `@orkestrel/reason` `^0.0.10`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/interpret` `^0.0.13` | |
51
51
  | `@orkestrel/browser` | `0.0.16` | L3 | `@orkestrel/html` `^0.0.9`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/websocket` `^0.0.12` | |
52
52
  | `@orkestrel/budget` | `0.0.10` | L1 | `@orkestrel/contract` `^0.0.17` | |
@@ -57,19 +57,19 @@ so network-controlled descriptions never enter agent instruction context.
57
57
  | `@orkestrel/database` | `0.0.14` | L2 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/indexeddb` `^0.0.11`, `@orkestrel/sqlite` `^0.0.11` | |
58
58
  | `@orkestrel/emitter` | `0.0.10` | L1 | `@orkestrel/contract` `^0.0.17` | |
59
59
  | `@orkestrel/form` | `0.0.6` | L2 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10` | |
60
- | `@orkestrel/guide` | `0.0.18` | L3 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/markdown` `^0.0.14` | |
60
+ | `@orkestrel/guide` | `0.0.19` | L3 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/markdown` `^0.0.14` | |
61
61
  | `@orkestrel/html` | `0.0.9` | L1 | `@orkestrel/contract` `^0.0.17` | |
62
62
  | `@orkestrel/indexeddb` | `0.0.11` | L1 | `@orkestrel/contract` `^0.0.17` | |
63
63
  | `@orkestrel/interpret` | `0.0.13` | L3 | `@orkestrel/reason` `^0.0.10`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/template` `^0.0.7` | |
64
- | `@orkestrel/lsp` | `0.0.7` | L3 | `@orkestrel/emitter` `^0.0.10`, `@orkestrel/process` `^0.0.11`, `@orkestrel/contract` `^0.0.17` | |
64
+ | `@orkestrel/lsp` | `0.0.8` | L3 | `@orkestrel/emitter` `^0.0.10`, `@orkestrel/process` `^0.0.12`, `@orkestrel/contract` `^0.0.17` | |
65
65
  | `@orkestrel/markdown` | `0.0.14` | L2 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/html` `^0.0.9` | |
66
- | `@orkestrel/mcp` | `0.0.29` | L4 | `@orkestrel/sse` `^0.0.7`, `@orkestrel/tool` `^0.0.14`, `@orkestrel/codec` `^0.0.3`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/process` `^0.0.11`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/websocket` `^0.0.12` | `@orkestrel/router` `^0.0.14`, `@orkestrel/server` `^0.0.19` |
66
+ | `@orkestrel/mcp` | `0.0.30` | L4 | `@orkestrel/sse` `^0.0.7`, `@orkestrel/tool` `^0.0.14`, `@orkestrel/codec` `^0.0.3`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/process` `^0.0.12`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/websocket` `^0.0.12` | `@orkestrel/router` `^0.0.14`, `@orkestrel/server` `^0.0.19` |
67
67
  | `@orkestrel/middleware` | `0.0.20` | L4 | `@orkestrel/abort` `^0.0.10`, `@orkestrel/budget` `^0.0.10`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/timeout` `^0.0.10` | `@orkestrel/database` `^0.0.14`, `@orkestrel/server` `^0.0.19` |
68
68
  | `@orkestrel/msg` | `0.0.10` | L0 | | |
69
69
  | `@orkestrel/ndjson` | `0.0.10` | L1 | `@orkestrel/contract` `^0.0.17` | |
70
- | `@orkestrel/ollama` | `0.0.15` | L6 | `@orkestrel/agent` `^0.0.21`, `@orkestrel/budget` `^0.0.10`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/ndjson` `^0.0.10`, `@orkestrel/timeout` `^0.0.10`, `@orkestrel/tool` `^0.0.14` | |
70
+ | `@orkestrel/ollama` | `0.0.16` | L6 | `@orkestrel/tool` `^0.0.14`, `@orkestrel/agent` `^0.0.22`, `@orkestrel/budget` `^0.0.10`, `@orkestrel/ndjson` `^0.0.10`, `@orkestrel/contract` `^0.0.17` | |
71
71
  | `@orkestrel/pool` | `0.0.11` | L2 | `@orkestrel/emitter` `^0.0.10` | |
72
- | `@orkestrel/probe` | `0.0.13` | L5 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/lsp` `^0.0.7`, `@orkestrel/mcp` `^0.0.29`, `@orkestrel/queue` `^0.0.13`, `@orkestrel/timeout` `^0.0.10`, `@orkestrel/tool` `^0.0.14` | `oxlint` `^1.82.0`, `typescript` `^6.0.3`, `vitest` `^4.1.11` |
72
+ | `@orkestrel/probe` | `0.0.14` | L5 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/lsp` `^0.0.8`, `@orkestrel/mcp` `^0.0.30`, `@orkestrel/queue` `^0.0.13`, `@orkestrel/timeout` `^0.0.10`, `@orkestrel/tool` `^0.0.14` | `oxlint` `^1.82.0`, `typescript` `^6.0.3`, `vitest` `^4.1.11` |
73
73
  | `@orkestrel/process` | `0.0.12` | L2 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10` | |
74
74
  | `@orkestrel/program` | `0.0.13` | L4 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/qualifier` `^0.0.14`, `@orkestrel/rater` `^0.0.14`, `@orkestrel/reason` `^0.0.10` | |
75
75
  | `@orkestrel/qualifier` | `0.0.14` | L3 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/reason` `^0.0.10` | |
@@ -78,8 +78,8 @@ so network-controlled descriptions never enter agent instruction context.
78
78
  | `@orkestrel/reason` | `0.0.10` | L2 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10` | |
79
79
  | `@orkestrel/relation` | `0.0.12` | L3 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/database` `^0.0.14`, `@orkestrel/emitter` `^0.0.10` | |
80
80
  | `@orkestrel/router` | `0.0.14` | L2 | `@orkestrel/abort` `^0.0.10`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/contract` `^0.0.17` | |
81
- | `@orkestrel/scaffold` | `0.0.65` | L3 | `@orkestrel/console` `^0.0.13`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/markdown` `^0.0.14`, `@orkestrel/process` `^0.0.11`, `@orkestrel/template` `^0.0.7` | |
82
- | `@orkestrel/sea` | `0.0.15` | L3 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/process` `^0.0.11` | |
81
+ | `@orkestrel/scaffold` | `0.0.67` | L3 | `@orkestrel/console` `^0.0.13`, `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/markdown` `^0.0.14`, `@orkestrel/process` `^0.0.12`, `@orkestrel/template` `^0.0.7` | |
82
+ | `@orkestrel/sea` | `0.0.16` | L3 | `@orkestrel/contract` `^0.0.17`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/process` `^0.0.12` | |
83
83
  | `@orkestrel/server` | `0.0.19` | L3 | `@orkestrel/abort` `^0.0.10`, `@orkestrel/codec` `^0.0.3`, `@orkestrel/router` `^0.0.14`, `@orkestrel/emitter` `^0.0.10`, `@orkestrel/timeout` `^0.0.10`, `@orkestrel/contract` `^0.0.17` | |
84
84
  | `@orkestrel/sqlite` | `0.0.11` | L1 | `@orkestrel/contract` `^0.0.17` | |
85
85
  | `@orkestrel/sse` | `0.0.7` | L0 | | |
@@ -121,6 +121,21 @@ The root design laws in `AGENTS.md` — one term per concept, boolean behavior s
121
121
  - Outside a declared wire body, a mirrored name never uses `kind` or `type` as a member name, and never uses a word § Rejected naming lists. A Compound File Binary (CFB) directory entry's object-type byte takes a named discriminant.
122
122
  - A declared wire body — a type whose members transliterate an external wire format field for field — keeps the external field names, `type` and `kind` included, and its TSDoc names the format it transliterates. The package's own domain type carries neither word, and the package owns the projection between the wire body and the domain type.
123
123
 
124
+ ## Fleet name ownership
125
+
126
+ Give every bare exported name one owning package across the `@orkestrel` fleet. A bare name is the identifier alone, case included: the same identifier in another environment, or on another kind of declaration, is the same name, and the same word in another case is a different name.
127
+
128
+ Check a public name against the fleet's published guides before adding it. The `surface` policy rule reports a name another package's guide already claims, and `.claude/rules/workspace.md` § Policy instruments fixes the instrument that reports it.
129
+
130
+ The rule grandfathers a source name the target's own hosted guide claims, because that guide is the target's own claim to it. It grandfathers no root `tests/setup*.ts` export, so clear a colliding setup export before adopting the scaffold release that carries the rule. A vendored `tests/setup*.ts` module a scaffold release stages sits outside the inspected population.
131
+
132
+ Resolve a reported collision with these rules, in order.
133
+
134
+ 1. **Reuse before renaming.** Where the colliding declarations carry the same contract, delete this one and import the owner's export. Declare the dependency that import needs, or stop and report that the dependency is not authorized.
135
+ 2. **The subject keeps the name.** Where the contracts differ, leave the name with the package whose domain the name describes, and rename the other declaration for the thing it is. Refuse a rename that makes the renamed declaration vaguer; name its own contract instead.
136
+ 3. **Qualify rather than extract.** Extract a shared package only where a proof shows the colliding declarations interchangeable and the dependency change is authorized. Otherwise qualify the name in place for the domain it serves, and route the extraction through a design round rather than renaming by fiat.
137
+ 4. **Close the collision in the code.** Change a declaration; the rule reads no allowlist a target can add a name to, and it carries no suppression. The committed `host.json` inventory in the `@orkestrel/scaffold` checkout records the collisions that predate this rule, and a release build refuses a staged collision that record does not already hold. Shrink that record by closing a collision, and never widen it to admit one. Never suppress the violation, and never edit a vendored policy instrument to clear it.
138
+
124
139
  ## Acronyms
125
140
 
126
141
  Keep canonical case:
@@ -172,7 +172,7 @@ A test that spawns a process, packs, installs, or drives a real build is a proof
172
172
 
173
173
  Test helpers are shared infrastructure, not local test-file clutter.
174
174
 
175
- `@orkestrel/test` owns the helpers every workspace repeats: the call recorder, the real delay, the JSON and async collectors, and the owned scratch directory. Import them from `@orkestrel/test`, and its Node-only helpers from `@orkestrel/test/server`. Write a helper of your own only where the package exports none for the job. The following shapes are the contract a workspace codes against, not source to copy.
175
+ `@orkestrel/test` owns the helpers every workspace repeats: the call recorder, the real delay, the JSON and async collectors, and the owned scratch directory. Import them from `@orkestrel/test`, and its Node-only helpers from `@orkestrel/test/server`. Before declaring a helper in a `tests/setup*.ts` module, read the installed surface — `guides/test.md` § Surface in the scaffold checkout, or the package's own declaration under `node_modules` — and declare only what no export does. A setup-module export whose name or job matches an installed export is a defect, whichever file declared it first. The following shapes are the contract a workspace codes against, not source to copy.
176
176
 
177
177
  - For the vendored test set (`tests/setupPolicy.ts`, `tests/policy.test.ts`, and
178
178
  `tests/config.test.ts`), keep shared helpers within that set instead of importing them from
@@ -219,9 +219,38 @@ function waitForDelay(ms?: number): Promise<void>
219
219
  Use it to yield, never to wait for something another process produces. A fixed delay chosen to
220
220
  outlast a child's startup is a race whose loss looks like a product defect: the test measures
221
221
  interpreter bootstrap rather than the behaviour it names, and it fails on a loaded host and passes on
222
- an idle one. Wait until a named condition holds instead, polling with `waitForDelay` inside a budget
223
- measured by `performance.now()`, and fail with the condition's own description when the budget
224
- expires.
222
+ an idle one. Wait for a named condition, an event, or an abort with the helpers under
223
+ § Condition instead.
224
+
225
+ ### Condition
226
+
227
+ Import `waitForCondition`, `waitForEvent`, `waitForAbort`, and `retryUntil` from
228
+ `@orkestrel/test`. A polling loop, a deadline read, or a deferred that observes an abort or an
229
+ event in a test or a setup module is a defect: `waitForCondition` owns the poll, `waitForEvent`
230
+ the deferred on an event, and `waitForAbort` the deferred on a signal. A guide fence transcribed
231
+ byte for byte into `tests/guides.test.ts` is the consumer's code and stays as the guide shows it.
232
+ Each timed wait takes a `description` the timeout error names and `WaitOptions` (`budget`,
233
+ `interval`, `signal`):
234
+
235
+ ```ts
236
+ function waitForCondition(
237
+ description: string,
238
+ condition: () => boolean | Promise<boolean>,
239
+ options?: WaitOptions,
240
+ ): Promise<void>
241
+ function waitForEvent<TArgs extends readonly unknown[]>(
242
+ subscribe: EventSubscriber<TArgs>,
243
+ description: string,
244
+ options?: WaitOptions,
245
+ ): Promise<TArgs>
246
+ function waitForAbort(signal: AbortSignal): Promise<void>
247
+ function retryUntil<T>(
248
+ description: string,
249
+ produce: () => T | Promise<T>,
250
+ satisfied: (value: T) => boolean,
251
+ options?: RetryOptions,
252
+ ): Promise<T>
253
+ ```
225
254
 
226
255
  ### Scratch
227
256
 
@@ -264,8 +264,20 @@ Policy instruments:
264
264
  a named module-scope `report{Noun}` function. Never write rule logic inline in the table. That
265
265
  arrow is the sanctioned exception to the in-body function-expression limits in
266
266
  `.claude/rules/architecture.md` for exactly that table.
267
- - Name no individual rule id here. This section fixes the instruments and how work is assigned
268
- between them; each rule's substance stays with the law it enforces.
267
+ - Name an individual rule id here only where the rule reads its evidence from outside the workspace
268
+ its instrument runs in. This section fixes the instruments and how work is assigned between them;
269
+ each rule's substance stays with the law it enforces.
270
+ - `surface` is that rule, and the sweep owns it. It reads the fleet's published guides — a target
271
+ reads `node_modules/@orkestrel/scaffold/dist/host/guides/`, and a scaffold checkout reads its own
272
+ `guides/` — and compares every bare name their `## Surface` tables claim against the target's live
273
+ barrel exports and its own root `tests/setup*.ts` exports. `.claude/rules/names.md` § Fleet name
274
+ ownership defines the bare name that comparison matches and decides which declaration gives a
275
+ claimed name up.
276
+ - Keep every barrel statement in the form `.claude/rules/architecture.md` § Barrel exports fixes.
277
+ The sweep refuses a barrel population it cannot read whole and reports that refusal as a `surface`
278
+ violation rather than passing over the statements it did read. A catalog row with no hosted guide,
279
+ and a missing hosted guide root, refuse the same way.
280
+ - Change the code when an instrument reports a violation.
269
281
 
270
282
  ## Text integrity
271
283
 
@@ -12,3 +12,6 @@ demo/showcase.html
12
12
 
13
13
  # Fetched-bytes mirrors keep their upstream bytes and stay out of the formatter.
14
14
  tests/mirrors/
15
+
16
+ # The vendored host inventory: `npm run build` regenerates it (`stageInventory`), so its bytes are the generator's, not the formatter's.
17
+ host.json
@@ -0,0 +1,65 @@
1
+ # Guides
2
+
3
+ An index into this repository's guides, by concept and by directory, following the
4
+ documentation contract in [`.claude/rules/documentation.md`](../.claude/rules/documentation.md).
5
+
6
+ ## By concept
7
+
8
+ | Concept | Spec | Source | Tests |
9
+ | -------- | ---------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------- |
10
+ | Scaffold | [`scaffold.md`](scaffold.md) | [`src/core`](../src/core), [`src/server`](../src/server) | [`tests/src/core`](../tests/src/core), [`tests/src/server`](../tests/src/server) |
11
+
12
+ `scaffold.md` documents the union of the package's library faces: the pure core
13
+ ([`src/core`](../src/core)) and the server face ([`src/server`](../src/server)). The server face
14
+ carries the `Materializer` that writes a target, the `Upstream` reader that fetches releases and
15
+ guide mirrors, and the `WriteTransaction` those writes stage through. The `scaffold` executable
16
+ ([`src/bin`](../src/bin)) publishes no barrel, so it is documented in prose and sits outside the
17
+ surface bijection.
18
+
19
+ That bijection is the row's contract, and [`tests/guides.test.ts`](../tests/guides.test.ts)
20
+ enforces it: every symbol the guide documents exists in the core barrel or the server barrel, and
21
+ every symbol either barrel exports is documented.
22
+
23
+ ## By directory
24
+
25
+ | Directory | Guide |
26
+ | ------------ | ---------------------------- |
27
+ | `src/core` | [`scaffold.md`](scaffold.md) |
28
+ | `src/server` | [`scaffold.md`](scaffold.md) |
29
+ | `src/bin` | [`scaffold.md`](scaffold.md) |
30
+
31
+ ## Line reference
32
+
33
+ This repository vendors a byte-identical guide mirror for **every published `@orkestrel/*`
34
+ package**, not only its own dependencies. Scaffold is the line's blueprint compiler: `new` seeds a
35
+ workspace with the mirrors named by `SEED_GUIDE_PATHS`. The `catalog` command refreshes the
36
+ mirrors — the declared set by default, or the complete published line under `--all`. Each mirror is
37
+ fetched from its own repository's `main` at `guides/<name>.md`.
38
+
39
+ Every file in this directory ships inside the published package as well, at
40
+ `dist/host/guides/<name>.md`, so an installed `@orkestrel/scaffold` carries the whole line as
41
+ readable data. `catalog` writes that staged copy into a target whose mirror is absent and whose
42
+ upstream read returned no bytes, and the `surface` policy rule reads the same set to decide which
43
+ package owns an exported name.
44
+
45
+ These subsets carry extra weight:
46
+
47
+ - **Runtime dependencies** — `@orkestrel/console` ([`console.md`](console.md)),
48
+ `@orkestrel/contract` ([`contract.md`](contract.md)), `@orkestrel/emitter`
49
+ ([`emitter.md`](emitter.md)), `@orkestrel/markdown` ([`markdown.md`](markdown.md)),
50
+ `@orkestrel/process` ([`process.md`](process.md)), and `@orkestrel/template`
51
+ ([`template.md`](template.md)). The library faces reach contract, emitter, markdown, and
52
+ template; the `scaffold` executable reaches console, contract, markdown, and process.
53
+ - **Development** — `@orkestrel/guide` ([`guide.md`](guide.md)) backs this repository's
54
+ guides-parity suite, [`tests/guides.test.ts`](../tests/guides.test.ts); the
55
+ `readSurfaceCollisions` helper in the server face, which loads it at call time; and the `surface`
56
+ rule in [`tests/setupPolicy.ts`](../tests/setupPolicy.ts). A consumer calling
57
+ `readSurfaceCollisions` supplies the package itself, because scaffold declares it for development
58
+ alone.
59
+
60
+ Every mirror documents **that package's** surface, not anything sourced in this repository. A mirror
61
+ that drifts from its upstream `main` is a defect: refresh it rather than editing it here.
62
+
63
+ ## See also
64
+
65
+ - [`AGENTS.md`](../AGENTS.md) and [`.claude/rules/documentation.md`](../.claude/rules/documentation.md) — the repository rules and documentation contract.
@@ -0,0 +1,169 @@
1
+ # Abort
2
+
3
+ > The cancellation primitive: a thin, traceable wrapper over a native `AbortController` that
4
+ > carries a trace `id`, exposes a standard `AbortSignal`, and links to a parent signal so one
5
+ > cancellation cascades through a tree of handles.
6
+
7
+ The native signal is the complete observation contract: hand it to any cancellable API, inspect
8
+ `aborted` and `reason`, or subscribe to its standard `abort` event. Where a parent was given, that
9
+ signal fires when either the handle's own `abort()` is called or the parent aborts, with no
10
+ listener bookkeeping to write. Async layers bound their work against a `signal`. Deliberately
11
+ thin. It does **not** re-implement cancellation machinery — the native `AbortController` is the
12
+ engine; `Abort` only adds a traceable `id` and parent-linking on top. It does **not** wrap the
13
+ signal in a bespoke interface, so it stays interoperable with `fetch`, streams, and every Web API
14
+ that already speaks `AbortSignal`. Source: [`src/core`](../src/core). Surfaced through the
15
+ `@src/core` barrel.
16
+
17
+ ## Surface
18
+
19
+ Create a cancellation handle, hand its `signal` to cancellable work, and `abort()` to cancel:
20
+
21
+ ```ts
22
+ import { createAbort } from '@orkestrel/abort'
23
+
24
+ const abort = createAbort({ id: 'fetch-user' })
25
+ const response = await fetch(url, { signal: abort.signal })
26
+ abort.abort() // cancels the in-flight fetch through the native signal; `aborted` flips true
27
+ ```
28
+
29
+ The `signal` is a plain `AbortSignal`, so it drops into anything that takes one. Call `abort()` to cancel and `abort(reason)` to attach a value the cancelled work can read off `signal.reason`. Aborting is idempotent — the first call wins, and a second is a safe no-op. Pass a parent `signal` to link cancellation: the child's `signal` then fires when the parent aborts, so one cancellation cascades to every linked handle (through `AbortSignal.any`, no listener bookkeeping). Pass `id` to label a handle for tracing, or let it default to a random UUID.
30
+
31
+ Construction is a strict JavaScript boundary. `validateAbortOptions` normalizes omitted options to a fresh empty object; otherwise it requires a plain record, reads `id` and `signal` exactly once inside a contained boundary, and returns a fresh copy omitting absent keys. A provided `id` must be a string and a provided parent `signal` must be native. Invalid values fail immediately with a coded `ContractError`: malformed or unreadable options use `bound`, a nonstring id uses `literal`, and a nonnative option signal uses `placement`, with exact `options`, `options.id`, or `options.signal` context, limits, safe previews, and the originating cause for unreadable records. Direct `linkSignal` calls independently validate `own` and `parent` as native signals with `placement` context. An `undefined` `id` still generates a UUID, while an empty string remains a valid explicit id.
32
+
33
+ ### Factories
34
+
35
+ | API | Kind | Summary |
36
+ | ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
37
+ | `createAbort` | function | Creates a cancellation handle from validated options and returns it as an `AbortInterface` — a resolved trace `id` and a `signal` already linked to any parent given, so a caller holds the published contract rather than the `Abort` class. |
38
+
39
+ ### Helpers
40
+
41
+ | API | Kind | Summary |
42
+ | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
43
+ | `validateAbortOptions` | function | Validates once-read abort construction options and returns a fresh normalized copy omitting absent optional keys. |
44
+ | `linkSignal` | function | Links an own `AbortSignal` to an optional parent signal, returning `AbortSignal.any([own, parent])` when a parent is given. |
45
+
46
+ ### Validators
47
+
48
+ In a guard table a `Shape` cell holds the type the guard narrows to.
49
+
50
+ | API | Kind | Shape | Summary |
51
+ | --------------- | -------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
52
+ | `isAbortSignal` | function | `AbortSignal` | Determines whether a value is a native `AbortSignal`, staying total for structural spoofs and for hostile or revoked proxies. |
53
+
54
+ ### Classes
55
+
56
+ | API | Kind | Summary |
57
+ | ------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
58
+ | `Abort` | class | Implements `AbortInterface` over a private `AbortController` the instance owns, resolving the trace `id` at construction and exposing either that controller's own `signal` or one linked to a parent. |
59
+
60
+ ### Types
61
+
62
+ A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional member and `plus` introducing its call-signature members, and a type alias's own type literal with a union's arms escaped as `\|`.
63
+
64
+ | Type | Kind | Shape | Summary |
65
+ | ---------------- | --------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
66
+ | `AbortOptions` | interface | `{ id?, signal? }` | Represents the options for `createAbort` and `Abort` construction. |
67
+ | `AbortInterface` | interface | `{ id, signal, aborted } plus abort` | Represents the cancellation contract a consumer holds — a traceable `id`, the exposed `AbortSignal`, an `aborted` reading of it, and an idempotent `abort` that cancels the work bound to that signal. |
68
+
69
+ The `id`, `signal`, and `aborted` members of `AbortInterface` are `readonly` data members (Surface rows, earlier) — its call-signature method is documented under [Methods](#methods).
70
+
71
+ ## Methods
72
+
73
+ The public methods of `AbortInterface` — every call-signature member listed (its `readonly` data members `id` / `signal` / `aborted` stay Surface rows). `Abort` implements the interface exactly, so this doubles as the class's instance-method surface (see `.claude/rules/documentation.md` § Parity).
74
+
75
+ #### `AbortInterface`
76
+
77
+ `abort` is the lifecycle verb `.claude/rules/names.md` § Fixed lifecycle vocabulary fixes as "Cancel with signal propagation" — it aborts the underlying controller, flipping `aborted` and firing `signal`.
78
+
79
+ | Method | Returns | Summary |
80
+ | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
81
+ | `abort` | `void` | Aborts the underlying controller, flipping `aborted` and firing `signal`. Aborting is idempotent — the first reason sticks and every later call is a no-op. |
82
+
83
+ ## Contract
84
+
85
+ These invariants hold across `src/core` ↔ `abort.md`:
86
+
87
+ 1. **DOC ↔ SOURCE bijection.** Every `function` / `class` / `interface` / `type` row in the `## Surface` tables is a real export of the aborts module, and every export appears as a Surface row — exhaustive, both directions (see `.claude/rules/documentation.md` § Parity).
88
+ 2. **Native wrapper.** `Abort` owns a private `AbortController`; `abort(reason?)` delegates to `controller.abort(reason)`, and `aborted` reads the exposed `signal.aborted`. No re-implemented cancellation machinery.
89
+ 3. **Parent linking through `AbortSignal.any`.** With a parent `signal`, the exposed `signal` is `AbortSignal.any([own, parent])`, so it fires on EITHER the own `abort()` or the parent aborting — and the own `abort()` does NOT abort the parent. Without a parent, `signal` is the own controller's signal directly.
90
+ 4. **Reason propagates.** `abort(reason)` forwards the reason to the controller, so `signal.reason` is the value passed in — when the own `abort()` is what fired the signal. Any DEFINED reason is preserved by identity, including falsy ones (`null`, `0`, `''`, `false`, `NaN`) — never `if (reason)`-gated. When the reason is `undefined` (or omitted), the host substitutes a default `AbortError` `DOMException`, so cancelled work always has a reason to inspect rather than `undefined`. On a linked handle whose PARENT aborted first, `signal.reason` is the parent's reason instead (correct `AbortSignal.any` semantics: the signal carries the reason of whichever source fired it).
91
+ 5. **Idempotent — first reason sticks.** A handle aborts at most once: a second (or third) `abort(reason2)` neither re-fires `signal` nor overwrites `reason`, even when the first reason was a falsy `0` / `''`. A handle linked to a parent that has ALREADY aborted at construction is born aborted (`aborted === true`, carrying the parent's reason) — its own later `abort()` is then a safe no-op.
92
+ 6. **Traceable identity.** `id` is a stable string for the handle's lifetime — caller-supplied through `options.id`, or a `crypto.randomUUID()` default that is unique across instances (verified across a large batch).
93
+ 7. **Native observation contract.** The exposed `AbortSignal` is the complete observation surface: consumers read `aborted` / `reason` and subscribe to the standard `abort` event. The package adds no parallel event system.
94
+ 8. **Strict JavaScript boundaries.** `validateAbortOptions` normalizes omission to a fresh object; otherwise it requires a plain readable record, reads each declared option exactly once, validates a defined string `id` and native parent `signal`, and returns a fresh normalized copy without absent keys before controller allocation or link composition. Invalid options use `bound`, invalid ids use `literal`, and invalid option signals use `placement`, with exact paths, limits, safe previews, and preserved causes for unreadable records. `linkSignal` separately guards its direct `own` and `parent` inputs with `placement` errors and native-signal limits. Structural spoofs and hostile or revoked proxies are rejected without leaking native errors.
95
+ 9. **DOC ↔ SOURCE method bijection.** The `## Methods` table lists exactly `AbortInterface`'s public methods — exhaustive, both directions — and `Abort` exposes the same public methods, no more (see `.claude/rules/documentation.md` § Parity).
96
+
97
+ ## Patterns
98
+
99
+ ### Create and abort
100
+
101
+ Create a handle, hand its `signal` to cancellable work, and call `abort(reason)` to cancel it:
102
+
103
+ ```ts
104
+ import { createAbort } from '@orkestrel/abort'
105
+
106
+ const abort = createAbort()
107
+ const stream = openStream({ signal: abort.signal })
108
+ // later, to cancel:
109
+ abort.abort('user navigated away') // signal.reason carries the value
110
+ ```
111
+
112
+ ### Link to a parent (cascading cancellation)
113
+
114
+ A child handle linked to a parent `signal` fires when the parent aborts — one cancellation cascades to every linked handle, without manual listener wiring.
115
+
116
+ ```ts
117
+ import { createAbort } from '@orkestrel/abort'
118
+
119
+ const parent = createAbort({ id: 'request' })
120
+ const child = createAbort({ id: 'sub-task', signal: parent.signal })
121
+
122
+ parent.abort() // child.aborted is now true; child.signal has fired
123
+ // the child can still be aborted on its own without touching the parent
124
+ ```
125
+
126
+ ### Race work against the signal
127
+
128
+ The cleanest bound is to thread `signal` straight into work that already accepts one — then a single `abort()` cancels the whole chain natively, no race to write:
129
+
130
+ ```ts
131
+ import { createAbort } from '@orkestrel/abort'
132
+
133
+ const abort = createAbort()
134
+ const rows = await query(sql, { signal: abort.signal }) // cancels at the source on abort
135
+ ```
136
+
137
+ When the work does NOT take a signal, race it against the abort. The native `signal.reason` is what `abort(reason)` stored (or the default `AbortError`), so reject with it directly:
138
+
139
+ ```ts
140
+ import { createAbort } from '@orkestrel/abort'
141
+
142
+ async function run<T>(work: Promise<T>, abort = createAbort()): Promise<T> {
143
+ const cancelled = new Promise<never>((_, reject) =>
144
+ abort.signal.addEventListener('abort', () => reject(abort.signal.reason), { once: true }),
145
+ )
146
+ return Promise.race([work, cancelled])
147
+ }
148
+ ```
149
+
150
+ ### Practices
151
+
152
+ - **Hand off the `signal`, keep the handle** — pass `abort.signal` into cancellable APIs (`fetch`, a stream, a worker); keep the `Abort` to call `abort()`. Prefer threading the signal into work that accepts one over racing it yourself.
153
+ - **Link, don't re-wire** — pass a parent `signal` to cascade cancellation; `AbortSignal.any` does the bookkeeping, so never hand-roll `addEventListener` chains between handles.
154
+ - **Carry a reason** — pass a value to `abort(reason)`; it surfaces on `signal.reason` for the cancelled work to inspect. Any defined value is kept as-is (even a falsy `0` / `''`); only `undefined` is replaced with a default `AbortError`.
155
+ - **Label for tracing** — set `id` on long-lived or correlated handles; let it default to a UUID otherwise.
156
+ - **Observe the native signal** — inspect `aborted` / `reason` or subscribe to the standard `abort` event; no parallel event abstraction is needed.
157
+
158
+ ## Tests
159
+
160
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/core` bijection (value + type exports), the `AbortInterface` ↔ `Abort` method bijection, and the equality gate: every `Summary` cell against its declaration's description paragraph, the titled `Create and abort` fence against the `@example` block of that title (pinned so the titled pair cannot be retired silently), and the README pitch against this guide's tagline. It also runs the flagship fences and asserts the values their comments claim.
161
+ - [`tests/src/core/Abort.test.ts`](../tests/src/core/Abort.test.ts) — `abort()` flips `aborted` and fires `signal`, `abort(reason)` propagates the reason, a second/third `abort(reason2)` is an idempotent no-op (`signal.reason` stays the first reason), a fresh handle is not aborted, parent linking (the signal fires on the parent's abort and on its own; a parent that aborts first propagates the parent's reason), and `id` is honored / stable / unique (across a 1,000-instance batch). Edge cases: every reason type (`undefined`/omitted → a default `AbortError` `DOMException`; string / object / the falsy-but-defined `null` / `0` / `''` / `false` / `NaN` preserved by identity; first falsy reason still sticks), a parent already aborted at construction (born aborted, carrying the parent's reason, own `abort()` then inert), chained Aborts (an `Abort` parented to another's `signal`, 2–3 levels: a root abort fans down with its reason, a mid/leaf abort never flows up), `new Abort` ↔ `createAbort` parity, and proportionate public-constructor boundary integration.
162
+ - [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — `createAbort` returns a working `AbortInterface` and honors `id` / a parent `signal`.
163
+ - [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — `validateAbortOptions` fresh normalization, optional-key omission, exactly-once reads, hostile getter containment, and exact taxonomy/context; plus `linkSignal` own/parent composition and exact direct-call placement errors.
164
+ - [`tests/src/core/validators.test.ts`](../tests/src/core/validators.test.ts) — `isAbortSignal` accepts a native signal and remains total for structural spoofs and revoked proxies.
165
+
166
+ ## See also
167
+
168
+ - [`AGENTS.md`](../AGENTS.md) — the pointer to the `@orkestrel/scaffold` coding contract, whose § Fixed lifecycle vocabulary, § Entity-scoped names, and § Parity rules govern this guide.
169
+ - [`README.md`](README.md) — the guides index.