@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.
- package/dist/bin/main.js +67 -44
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/templates/brief.md +9 -0
- package/dist/host/claude/agents/orkestrel.md +8 -8
- package/dist/host/claude/rules/names.md +15 -0
- package/dist/host/claude/rules/tests.md +33 -4
- package/dist/host/claude/rules/workspace.md +14 -2
- package/dist/host/dotfiles/prettierignore +3 -0
- package/dist/host/guides/README.md +65 -0
- package/dist/host/guides/abort.md +169 -0
- package/dist/host/guides/agent.md +1509 -0
- package/dist/host/guides/brief.md +1266 -0
- package/dist/host/guides/browser.md +2200 -0
- package/dist/host/guides/budget.md +196 -0
- package/dist/host/guides/codec.md +519 -0
- package/dist/host/guides/console.md +785 -0
- package/dist/host/guides/contract.md +1193 -0
- package/dist/host/guides/csv.md +541 -0
- package/dist/host/guides/database.md +2518 -0
- package/dist/host/guides/emitter.md +233 -0
- package/dist/host/guides/form.md +1791 -0
- package/dist/host/guides/html.md +717 -0
- package/dist/host/guides/indexeddb.md +505 -0
- package/dist/host/guides/interpret.md +1029 -0
- package/dist/host/guides/lsp.md +515 -0
- package/dist/host/guides/markdown.md +964 -0
- package/dist/host/guides/mcp.md +5554 -0
- package/dist/host/guides/middleware.md +927 -0
- package/dist/host/guides/msg.md +440 -0
- package/dist/host/guides/ndjson.md +120 -0
- package/dist/host/guides/ollama.md +380 -0
- package/dist/host/guides/pool.md +280 -0
- package/dist/host/guides/probe.md +1210 -0
- package/dist/host/guides/process.md +1620 -0
- package/dist/host/guides/program.md +1110 -0
- package/dist/host/guides/qualifier.md +854 -0
- package/dist/host/guides/queue.md +370 -0
- package/dist/host/guides/rater.md +330 -0
- package/dist/host/guides/reason.md +1122 -0
- package/dist/host/guides/relation.md +373 -0
- package/dist/host/guides/router.md +753 -0
- package/dist/host/guides/scaffold.md +192 -31
- package/dist/host/guides/sea.md +383 -0
- package/dist/host/guides/server.md +752 -0
- package/dist/host/guides/sqlite.md +330 -0
- package/dist/host/guides/sse.md +187 -0
- package/dist/host/guides/supervisor.md +4890 -0
- package/dist/host/guides/table.md +1556 -0
- package/dist/host/guides/template.md +280 -0
- package/dist/host/guides/terminal.md +1145 -0
- package/dist/host/guides/test.md +2969 -0
- package/dist/host/guides/timeout.md +252 -0
- package/dist/host/guides/tool.md +311 -0
- package/dist/host/guides/toolbox.md +1038 -0
- package/dist/host/guides/websocket.md +282 -0
- package/dist/host/guides/worker.md +615 -0
- package/dist/host/guides/workflow.md +1507 -0
- package/dist/host/guides/workspace.md +595 -0
- package/dist/host/manifest.json +1218 -10
- package/dist/host/tests/policy.test.ts +279 -2
- package/dist/host/tests/setupPolicy.ts +437 -6
- package/dist/src/core/index.cjs +44 -22
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +33 -9
- package/dist/src/core/index.d.ts +33 -9
- package/dist/src/core/index.js +43 -23
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +1750 -1567
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +106 -24
- package/dist/src/server/index.d.ts +106 -24
- package/dist/src/server/index.js +1751 -1570
- package/dist/src/server/index.js.map +1 -1
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
82
|
-
| `@orkestrel/sea` | `0.0.
|
|
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`.
|
|
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
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
268
|
-
|
|
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.
|