@bluewombat/runtime 0.4.0 → 0.6.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.
Files changed (55) hide show
  1. package/README.md +85 -57
  2. package/SPECS.md +35 -18
  3. package/dist/cli.js +18 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/load-config.d.ts.map +1 -1
  6. package/dist/config/load-config.js +68 -16
  7. package/dist/config/load-config.js.map +1 -1
  8. package/dist/config/parse-args.d.ts.map +1 -1
  9. package/dist/config/parse-args.js +84 -24
  10. package/dist/config/parse-args.js.map +1 -1
  11. package/dist/config/types.d.ts +32 -12
  12. package/dist/config/types.d.ts.map +1 -1
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/loop/deliveries.d.ts +16 -0
  18. package/dist/loop/deliveries.d.ts.map +1 -1
  19. package/dist/loop/deliveries.js +25 -0
  20. package/dist/loop/deliveries.js.map +1 -1
  21. package/dist/loop/drive.d.ts.map +1 -1
  22. package/dist/loop/drive.js +15 -1
  23. package/dist/loop/drive.js.map +1 -1
  24. package/dist/loop/open-host.d.ts +1 -1
  25. package/dist/loop/open-host.d.ts.map +1 -1
  26. package/dist/loop/open-host.js +13 -1
  27. package/dist/loop/open-host.js.map +1 -1
  28. package/dist/loop/report.d.ts.map +1 -1
  29. package/dist/loop/report.js +20 -0
  30. package/dist/loop/report.js.map +1 -1
  31. package/dist/loop/tick.d.ts +3 -1
  32. package/dist/loop/tick.d.ts.map +1 -1
  33. package/dist/loop/tick.js.map +1 -1
  34. package/dist/loop/transformers.d.ts +9 -2
  35. package/dist/loop/transformers.d.ts.map +1 -1
  36. package/dist/loop/transformers.js +31 -29
  37. package/dist/loop/transformers.js.map +1 -1
  38. package/dist/loop/warm.d.ts +21 -0
  39. package/dist/loop/warm.d.ts.map +1 -0
  40. package/dist/loop/warm.js +59 -0
  41. package/dist/loop/warm.js.map +1 -0
  42. package/dist/operator/doctor.d.ts.map +1 -1
  43. package/dist/operator/doctor.js +19 -20
  44. package/dist/operator/doctor.js.map +1 -1
  45. package/dist/operator/film.js +5 -0
  46. package/dist/operator/film.js.map +1 -1
  47. package/dist/operator/init.js +14 -5
  48. package/dist/operator/init.js.map +1 -1
  49. package/dist/operator/live.js +9 -7
  50. package/dist/operator/live.js.map +1 -1
  51. package/dist/operator/release.d.ts +18 -0
  52. package/dist/operator/release.d.ts.map +1 -0
  53. package/dist/operator/release.js +65 -0
  54. package/dist/operator/release.js.map +1 -0
  55. package/package.json +15 -15
package/README.md CHANGED
@@ -50,16 +50,17 @@ package's README — Host does not grow a `--repo` flag for it.
50
50
 
51
51
  ## Commands
52
52
 
53
- | Command | Job |
54
- | -------------- | -------------------------------------------------------------------------------------- |
55
- | `mason run` | Drain the FeatureManager, run Conductor, report |
56
- | `mason watch` | Snapshot the ledger and follow the journal. Ctrl-C leaves the worker running |
57
- | `mason status` | Snapshot the ledger, then exit. `--json` for scripts |
58
- | `mason log` | Replay the journal, with the reason a phase stopped. `--problems` for just those |
59
- | `mason init` | Set this directory up: asks, writes the config and the Builder stub, then runs `setup` |
60
- | `mason setup` | Prepare the tracker. A plan by default; `--apply` writes. Exit 1 on a blocked step |
61
- | `mason doctor` | Check the config, the slots, and the manager. Exit 1 on any failure |
62
- | `mason cancel` | Abandon a Feature by key, when the tracker cannot say it is gone |
53
+ | Command | Job |
54
+ | --------------- | -------------------------------------------------------------------------------------- |
55
+ | `mason run` | Drain the FeatureManager, run Conductor, report |
56
+ | `mason watch` | Snapshot the ledger and follow the journal. Ctrl-C leaves the worker running |
57
+ | `mason status` | Snapshot the ledger, then exit. `--json` for scripts |
58
+ | `mason log` | Replay the journal, with the reason a phase stopped. `--problems` for just those |
59
+ | `mason init` | Set this directory up: asks, writes the config and the Builder stub, then runs `setup` |
60
+ | `mason setup` | Prepare the tracker. A plan by default; `--apply` writes. Exit 1 on a blocked step |
61
+ | `mason doctor` | Check the config, the slots, and the manager. Exit 1 on any failure |
62
+ | `mason cancel` | Abandon a Feature by key, when the tracker cannot say it is gone |
63
+ | `mason release` | Drop a held Subtask without waiting for the bail clock |
63
64
 
64
65
  One `run` per ledger: `run` writes `<ledger>/lock` with its pid and a second
65
66
  `run` on the same home refuses to start while that process lives (two runs
@@ -106,6 +107,13 @@ manager with no `probe`), stop `run` if it holds the ledger lock, then:
106
107
  mason cancel github:owner/name#19
107
108
  ```
108
109
 
110
+ After a crash left a Subtask `running` with nobody on it, stop `run` if it
111
+ holds the lock, then:
112
+
113
+ ```bash
114
+ mason release github:owner/name#19
115
+ ```
116
+
109
117
  The film is `<ledger>/events.jsonl`, next to `cursor`. It is not truth after a
110
118
  crash — the ledger is. The file can grow; there is no rotation yet.
111
119
 
@@ -129,20 +137,25 @@ managerOptions:
129
137
  workLine:
130
138
  stable: ./work-line-stable
131
139
  isolation: "@bluewombat/isolation-copy"
140
+ warm:
141
+ cmd: [pnpm, install, --frozen-lockfile]
142
+ timeoutMs: 600000
132
143
  planner:
133
144
  cmd:
134
145
  - node
135
146
  - ./node_modules/@bluewombat/slots/planners/one-subtask.mjs
136
147
  timeoutMs: 600000
148
+ gates: { defaultTimeoutMs: 120000, gates: [] }
137
149
  builder:
138
150
  producer:
139
151
  cmd: [node, ./mason-builder.mjs]
140
152
  timeoutMs: 600000
153
+ gates: { defaultTimeoutMs: 120000, gates: [] }
141
154
  repair:
142
155
  cmd: [node, ./mason-repair.mjs]
143
156
  timeoutMs: 600000
157
+ gates: { defaultTimeoutMs: 120000, gates: [] }
144
158
  maxAttempts: 3
145
- gates: { defaultTimeoutMs: 120000, gates: [] }
146
159
  assembly:
147
160
  gates: { defaultTimeoutMs: 900000, gates: [] }
148
161
  authority:
@@ -151,31 +164,31 @@ timeoutMs: 600000
151
164
  pollIntervalMs: 10000
152
165
  ```
153
166
 
154
- | Field | Meaning |
155
- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
156
- | `manager` | Package name or path that exports `createManager` |
157
- | `managerOptions` | Opaque to Host. What it holds is the manager's own documentation |
158
- | `workLine.stable` | Existing directory the work folds into. Optional when the manager names a reference work line: Host then keeps its own copy at `<home>/work-line` |
159
- | `workLine.isolation` | Package name or path exporting `strategy` — required. Host loads it like a manager; Isolator/Integrator do not sniff |
160
- | `workLine.isolationOptions` | What that strategy is told, unread by Host — like `managerOptions`. `isolation-git` reads `exclude`, the paths a Child never gets (default `.env`, `.env.*`). A strategy without `createStrategy` refuses any |
161
- | `workLine.branch` | The work line a fold lands on. Without it, what the reference names, or else whatever is checked out |
162
- | `authority` | `enabled`, the `publish` / `refresh` slots, and the optional `describe` slot (§ Describer). Absent: the fold is local |
163
- | `home` | Where Host keeps what is its own — default `./.mason` |
164
- | `workspaceRoot` | Isolated feature / Subtask directories (created). Default `<home>/workspaces` |
165
- | `ledger` | WorkLedger root (created). The Cursor is a file inside it. Default `<home>/ledger` |
166
- | `persist` | Package name or path exporting `openPersist({ ledgerRoot })`: how the ledger is stored under that root. Default `@bluewombat/persist-fs`; `@bluewombat/persist-sqlite` ships too. `watch` reads through the same backend |
167
- | `planner` | `cmd` / `timeoutMs`. FeatureStandard in, a Plan out |
168
- | `builder.producer` | `cmd` / `timeoutMs`. Spawned in the Subtask workspace. Exit 0 lets Gates run |
169
- | `builder.repair` | The same, for a pass a Gate refused. **Required**, and never inherited |
170
- | `builder.maxAttempts` | Attempts one Task may start. Default 3 |
171
- | `builder.gates` | `defaultTimeoutMs` and `gates`: ordered checks on each Subtask |
172
- | `assembly.fix` | `cmd` / `timeoutMs`. Spawned when a judgement of the whole refused it — by an Authority, by `assembly.validate`, or by both |
173
- | `assembly.validate` | `cmd` / `timeoutMs`. Optional. A local, read-only judge of the assembled feature, run after align, with or without an Authority. A refusal is repaired by `assembly.fix` the same as an Authority's |
174
- | `assembly.maxAttempts` | Attempts that one fix may start. Default 3 |
175
- | `assembly.gates` | Ordered checks on the assembled feature. Empty: the Authority judges alone |
176
- | `timeoutMs` | How long a child **outside** a Task may run: manager, isolations |
177
- | `maxRefusals` | Times the work may be sent back before it escalates, by an Authority, by `assembly.validate`, or by both — one shared budget. Default 3 |
178
- | `pollIntervalMs` | Set it and the process keeps draining until SIGINT. Absent: one tick. Pick it with § Choosing `pollIntervalMs` — 10000 is the floor worth having |
167
+ | Field | Meaning |
168
+ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
169
+ | `manager` | Package name or path that exports `createManager` |
170
+ | `managerOptions` | Opaque to Host. What it holds is the manager's own documentation |
171
+ | `workLine.stable` | Existing directory the work folds into. Optional when the manager names a reference work line: Host then keeps its own copy at `<home>/work-line` |
172
+ | `workLine.isolation` | Package name or path exporting `strategy` — required. Host loads it like a manager; Isolator/Integrator do not sniff |
173
+ | `workLine.isolationOptions` | What that strategy is told, unread by Host — like `managerOptions`. `isolation-git` reads `exclude`, the paths a Child never gets (default `.env`, `.env.*`). A strategy without `createStrategy` refuses any |
174
+ | `workLine.warm` | Optional `cmd` / `timeoutMs`. Runs once in the Feature workspace after Isolation, before any Subtask is copied from it — typically `pnpm install --frozen-lockfile`. Absent: Subtasks inherit a cold Feature |
175
+ | `workLine.branch` | The work line a fold lands on. Without it, what the reference names, or else whatever is checked out |
176
+ | `authority` | `enabled`, the `publish` / `refresh` slots, and the optional `describe` slot (§ Describer). Absent: the fold is local |
177
+ | `home` | Where Host keeps what is its own — default `./.mason` |
178
+ | `workspaceRoot` | Isolated feature / Subtask directories (created). Default `<home>/workspaces` |
179
+ | `ledger` | WorkLedger root (created). The Cursor is a file inside it. Default `<home>/ledger` |
180
+ | `persist` | Package name or path exporting `openPersist({ ledgerRoot })`: how the ledger is stored under that root. Default `@bluewombat/persist-fs`; `@bluewombat/persist-sqlite` ships too. `watch` reads through the same backend |
181
+ | `planner` | `cmd` / `timeoutMs` / `gates`. FeatureStandard in, a Plan out. Its own `gates` judges `workLineStable` once a Plan is otherwise accepted — not where the Planner agent itself reads from |
182
+ | `builder.producer` | `cmd` / `timeoutMs` / `gates`. Spawned in the Subtask workspace. Exit 0 runs its own `gates` |
183
+ | `builder.repair` | The same, for a pass a Gate refused. **Required**, and never inherited — including its `gates`: a repair with none of its own runs ungated, even when `builder.producer.gates` is not empty |
184
+ | `builder.maxAttempts` | Attempts one Task may start. Default 3 |
185
+ | `assembly.fix` | `cmd` / `timeoutMs` / `gates`. Spawned when a judgement of the whole refused it — by an Authority, by `assembly.validate`, or by both. Judged by its own `gates` |
186
+ | `assembly.validate` | `cmd` / `timeoutMs` / `gates`. Optional. A local, read-only judge of the assembled feature, run after align, with or without an Authority. A refusal is repaired by `assembly.fix` the same as an Authority's. Judged by its own `gates`, never `builder`'s or `assembly.fix`'s |
187
+ | `assembly.maxAttempts` | Attempts that one fix may start. Default 3 |
188
+ | `assembly.gates` | `defaultTimeoutMs` and `gates`: the produce:false judgement-only pass — no producer of its own, re-checking the assembled feature. Empty: the Authority judges alone. Independent of `assembly.fix.gates` / `assembly.validate.gates` — never shared with either |
189
+ | `timeoutMs` | How long a child **outside** a Task may run: manager, isolations |
190
+ | `maxRefusals` | Times the work may be sent back before it escalates, by an Authority, by `assembly.validate`, or by both — one shared budget. Default 3 |
191
+ | `pollIntervalMs` | Set it and the process keeps draining until SIGINT. Absent: one tick. Pick it with § Choosing `pollIntervalMs` — 10000 is the floor worth having |
179
192
  | `observability.streams` | `enabled`, `dir`, `keep`. Films what every child says, as it says it — one file per child under `<dir>/<feature>/`, named in the journal by a `stream-opened` line. `dir` resolves against the config file; default `<home>/streams`. `keep` is `stdout` / `stderr`, default both. Off unless `enabled` is true: a stream is the Project's own code and prompts in the clear |
180
193
 
181
194
  Every `cmd` is named where it is used, and nothing falls back to a neighbour: a
@@ -187,6 +200,10 @@ runs after align, before the Authority sees the work (or before the final
187
200
  fold, with none). Its refusal is repaired by `assembly.fix` exactly as an
188
201
  Authority's is, and shares the same `maxRefusals` budget.
189
202
 
203
+ Paths a slot **writes** — `--transcript-dir` and `--agent-home` — always resolve
204
+ against the config file's directory, even before that directory exists. Left
205
+ relative they would land inside the Task worktree (published, then destroyed).
206
+
190
207
  Every tracker field lives in `managerOptions`. Host does not read them.
191
208
 
192
209
  ### Choosing `pollIntervalMs`
@@ -208,11 +225,11 @@ touch it. It only sets how long `mason run` takes to notice:
208
225
  a read whose answer did not change comes back 304, which does not count
209
226
  against the token's budget.
210
227
 
211
- | The tick | GitHub REST calls | Counted when nothing moved | Besides |
212
- | ---------------------------- | ------------------------------------------------------------------- | -------------------------- | --------------------------- |
213
- | Nothing to do | 1: the issues updated since the Cursor, 100 per page | 0 (304) | none: no fetch |
214
- | A Feature in flight | + 1 per Feature that can still be abandoned: is its issue still there | 0 (304) | a `git fetch` of the work line copy (not REST) |
215
- | A Feature `submitted` | + the judgement: `ci-green` asks for the pull request, its check runs and its statuses (about 3) | about 3: `ci-green` is its own process and keeps no `etag` | |
228
+ | The tick | GitHub REST calls | Counted when nothing moved | Besides |
229
+ | --------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ---------------------------------------------- |
230
+ | Nothing to do | 1: the issues updated since the Cursor, 100 per page | 0 (304) | none: no fetch |
231
+ | A Feature in flight | + 1 per Feature that can still be abandoned: is its issue still there | 0 (304) | a `git fetch` of the work line copy (not REST) |
232
+ | A Feature `submitted` | + the judgement: `ci-green` asks for the pull request, its check runs and its statuses (about 3) | about 3: `ci-green` is its own process and keeps no `etag` | |
216
233
 
217
234
  Reports to the tracker (a comment, a label move: 2 to 4 calls each) happen
218
235
  when the work moves, not per tick, and each changes the listing: the read
@@ -318,18 +335,27 @@ that completes cleanly but finds a problem is still a refusal.
318
335
 
319
336
  ### Gates
320
337
 
321
- Two sequences, because a Subtask and the feature it assembles into are not the
322
- same situation. `builder.gates` runs on each Subtask; `assembly.gates` on the
323
- assembled feature. Put the Project's own checks in `builder.gates`, and fill
324
- `assembly.gates` with what only makes sense on the whole — `ci-green` reads the
325
- checks the work line ran on what the Publisher placed, so it belongs there.
326
-
327
- Every Gate is told `--stage unit` or `--stage assembly` alongside `--id`,
328
- `--attempt`, `--gate-id`, `--intention` and `--definition-of-done`. Most have no
329
- use for it. `workspace-changed` does: "nothing changed" means nothing was done
330
- in a unit, and is the correct outcome for an assembly that needed nothing.
331
-
332
- `init` writes an empty list; add a Gate when the Project has a check.
338
+ Five independent sequences, one per role — `planner`, `builder.producer`,
339
+ `builder.repair`, `assembly.fix`, `assembly.validate` — plus `assembly.gates`
340
+ for the one judgement-only pass with no producer of its own (re-checking the
341
+ assembled feature; `ci-green` reads the checks the work line ran on what the
342
+ Publisher placed, so it belongs there). No sharing, no dispatch choosing
343
+ between two buckets: a check right for one pass is not necessarily right for
344
+ another, even within the same stage. `builder.repair` is the sharpest edge —
345
+ it does not inherit `builder.producer.gates`, so a repair Attempt with no
346
+ Gate of its own runs ungated.
347
+
348
+ Implementer's Gates are told `--stage unit` or `--stage assembly` alongside
349
+ `--id`, `--attempt`, `--gate-id`, `--intention` and `--definition-of-done`.
350
+ Most have no use for it. `workspace-changed` does: "nothing changed" means
351
+ nothing was done in a unit, and is the correct outcome for an assembly that
352
+ needed nothing. FeatureBreakdown's Gates (`planner.gates`) are told only
353
+ `--key` and `--gate-id` — no Attempt counter, no `--stage`, no
354
+ `--definition-of-done` — so a Gate written for Implementer's shape is not a
355
+ drop-in for `planner.gates`.
356
+
357
+ `init` writes an empty list on `builder.producer` and `builder.repair`; add a
358
+ Gate when the Project has a check.
333
359
 
334
360
  Every child carries its own ceiling and no other. A Gate entry may name a
335
361
  `timeoutMs`; without one it takes its sequence's `defaultTimeoutMs`, and a Gate
@@ -338,7 +364,9 @@ from what another child left behind, so a slow producer can no longer leave its
338
364
  Gates a millisecond to answer in.
339
365
 
340
366
  What bounds a Subtask is therefore what you wrote:
341
- `builder.maxAttempts × (builder.producer.timeoutMs + Σ builder.gates timeouts)`.
367
+ `builder.maxAttempts × (builder.producer.timeoutMs + Σ builder.producer.gates timeouts)`
368
+ for a first pass, and the same shape with `repair` in place of `producer` once
369
+ a Gate has refused one.
342
370
  A repair and an assembly fix each carry their own `timeoutMs`. An assembly fix
343
371
  is bounded the same way from `assembly.maxAttempts` and `assembly.fix`.
344
372
 
@@ -525,7 +553,7 @@ change that breaks it does not merge.
525
553
  | `config/` | What a Project wrote: argv, `mason.config.yaml`, discovery, `HostOptions`. Pure: YAML in, options out | A runtime value (a Journal, a Port, a process handle) | Node, `manager-kit` types |
526
554
  | `plugins/` | Load a package by name and check its shape: a manager, an isolation strategy | What to do with the loaded thing | `config` |
527
555
  | `loop/` | The process: listen → probe → adapt → admit → run → report; composition (`open-host`, `transformers`, Authority, work line, lock, Cursor, journal, trace) | A command a human types; a string a human reads about the config | `plugins`, `config`, the kernel, Transformers |
528
- | `operator/` | Tools for a human: `init`, `setup`, `doctor`, `watch` / `status`, `log`, `cancel`, `board`, the terminal prompt | Anything the loop needs — the loop cannot see this layer | everything below |
556
+ | `operator/` | Tools for a human: `init`, `setup`, `doctor`, `watch` / `status`, `log`, `cancel`, `release`, `board`, the terminal prompt | Anything the loop needs — the loop cannot see this layer | everything below |
529
557
  | `index.ts`, `cli.ts` | The package surface and the dispatch. Nothing else sits at the top of `src/` | Logic | everything |
530
558
 
531
559
  Host's code names no plugin. A manager, an isolation strategy, a persistence
@@ -576,10 +604,10 @@ SPECS.md behavioural contract
576
604
  README.md how to install, configure and run; the layers above
577
605
  src/
578
606
  index.ts the package surface
579
- cli.ts mason run | watch | status | log | cancel | init | setup | doctor
607
+ cli.ts mason run | watch | status | log | cancel | release | init | setup | doctor
580
608
  config/ argv, JSON config, config discovery → HostOptions; defaults: the plugin names Host ships with
581
609
  plugins/ load: the one loader; manager, isolation, persist: one shape each; discover
582
610
  loop/ open-host (composition), tick (the loop), deliveries, drive, probe, report, authority, work-line, cursor, lock, journal (the film), streams (what a child said), trace, transformers
583
- operator/ init, setup, doctor, live (watch / status), log, film, cancel, board, prompt, git-remote
611
+ operator/ init, setup, doctor, live (watch / status), log, film, cancel, release, board, prompt, git-remote
584
612
  fixtures/ stand-in slots for the loop's tests
585
613
  ```
package/SPECS.md CHANGED
@@ -34,10 +34,10 @@ Planner / Builder / Gate argv.
34
34
 
35
35
  ## 2. Faces
36
36
 
37
- | Face | Entry | Must not |
38
- | ------ | -------------------------------------------------------------------------- | --------------------------------------------- |
39
- | Import | `openHost(options)` then `runOnce` / `run` | Live inside Conductor `src/run/` |
40
- | CLI | `mason run \| watch \| status \| log \| cancel \| init \| setup \| doctor` | Interpret a FeatureStandard; pick Child paths |
37
+ | Face | Entry | Must not |
38
+ | ------ | ------------------------------------------------------------------------------------- | --------------------------------------------- |
39
+ | Import | `openHost(options)` then `runOnce` / `run` | Live inside Conductor `src/run/` |
40
+ | CLI | `mason run \| watch \| status \| log \| cancel \| release \| init \| setup \| doctor` | Interpret a FeatureStandard; pick Child paths |
41
41
 
42
42
  `src/loop/` is the process: `open-host.ts` opens packages (composition),
43
43
  `tick.ts` runs the listen / probe / Cursor / sweep loop, `deliveries.ts` handles one
@@ -147,9 +147,11 @@ than waited for; the Transformer then answers `interrupted`. `mason run` exits
147
147
 
148
148
  Each pass appends JSON lines to `<ledgerRoot>/events.jsonl` (listen, probed,
149
149
  Transformer progress, admitted, reported, queued, idle, a heartbeat while
150
- `submitted`). Every report to the tracker is filmed with how long it took
151
- (`ms`): `reported` when the manager took it, `report-declined` when it did not
152
- (already said, or it failed). That file is a film, not truth: a crash may drop the last lines;
150
+ `submitted`, one `held` while a Subtask waits on a bail that has not expired).
151
+ Every report to the tracker is filmed with how long it took (`ms`): `reported`
152
+ when the manager took it (including a Thread that already carried that eventId
153
+ — a duplicate is already said), `report-declined` when it failed. That file is
154
+ a film, not truth: a crash may drop the last lines;
153
155
  the ledger is still right. `mason run` still writes a human Trace on stdout.
154
156
  The journal is how another process attaches.
155
157
 
@@ -177,7 +179,7 @@ and left; the next sweep that finds the Project free claims it. The same set
177
179
  copy: starting a queued Feature on a copy that was not fast-forwarded this
178
180
  tick would build on a version that no longer exists.
179
181
 
180
- ## 8. init, setup, doctor, watch, status, log and cancel
182
+ ## 8. init, setup, doctor, watch, status, log, cancel and release
181
183
 
182
184
  `init` writes a config that already runs: the Planner is the bootstrap one from
183
185
  `@bluewombat/slots/planners/`, the Builder is a stub that says what to replace
@@ -186,16 +188,23 @@ and the Gate sequence starts empty. Example Gates live in
186
188
  `@bluewombat/slots/gates/` (`parent-clean`, `sensitive-path`,
187
189
  `workspace-changed`, `ci-green`).
188
190
 
189
- Two Gate sequences: `builder.gates` is judged on each Subtask, `assembly.gates`
190
- on the feature they assemble into. A check that is right for one can be wrong for the
191
- other — `workspace-changed` says an Attempt that changed nothing did nothing,
192
- which is true of a Subtask and false of an assembly whose correct outcome is that
193
- nothing was left to do. The assembly produces nothing unless something refused it
194
- first: it is a judgement, and a refusal is `assembly.fix`, not a second first-pass.
195
- Two things may judge it: the Gate sequence (`assembly.gates`, and an Authority's
196
- own checks on what was published) and, optionally, `assembly.validate` — a local,
197
- read-only pass that runs before either. Both send a refusal to the same `fix`
198
- and spend the same `maxRefusals` budget.
191
+ Five independent Gate sequences, one per role — `planner.gates`,
192
+ `builder.producer.gates`, `builder.repair.gates`, `assembly.fix.gates`,
193
+ `assembly.validate.gates` — plus `assembly.gates` for the one judgement-only
194
+ pass with no producer of its own. No sequence is shared and nothing is
195
+ dispatched between buckets: a check that is right for one pass can be wrong
196
+ for another, even within the same stage — `workspace-changed` says an Attempt
197
+ that changed nothing did nothing, which is true of `builder.producer`/`repair`
198
+ and false of `assembly.gates`'s pass, whose correct outcome is that nothing
199
+ was left to do. `builder.repair` does not inherit `builder.producer.gates`: a
200
+ repair Attempt with no Gate of its own runs ungated. The assembly's
201
+ judgement-only pass produces nothing unless something refused it first: it is
202
+ a judgement, and a refusal is `assembly.fix`, not a second first-pass. Two
203
+ things may judge it: `assembly.gates` (and an Authority's own checks on what
204
+ was published) and, optionally, `assembly.validate` — a local, read-only pass
205
+ that runs before either, judged by its own `assembly.validate.gates`, not
206
+ `assembly.gates`. Both send a refusal to the same `fix` and spend the same
207
+ `maxRefusals` budget.
199
208
 
200
209
  Every child of a Task carries its own ceiling and no other. Every `cmd` names
201
210
  its own `timeoutMs`, and none is inherited from a neighbour — nothing else
@@ -334,6 +343,14 @@ already cancelled, 1 when abandon is refused (`done`, point of no return), 2
334
343
  when the key is missing or the ledger does not know it. The key is the first
335
344
  positional: `cancel github:owner/name#19`.
336
345
 
346
+ `release <key>` drops a held Subtask (or a Feature still in `planning`) without
347
+ waiting for the bail clock — the hatch after a crash left nobody on the work.
348
+ A clean stop releases itself. It takes the ledger lock — stop `run` first if
349
+ it holds it. Exit 0 when the Subtask was released or the Feature was already
350
+ free, 1 when the Feature is past `planning` / `running` (the state is named),
351
+ 2 when the key is missing or the ledger does not know it. The key is the first
352
+ positional: `release github:owner/name#19`.
353
+
337
354
  ## 9. Acceptance
338
355
 
339
356
  1. Convertible upsert in an empty Source → WorkLineStable carries the Builder
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { runInit } from "./operator/init.js";
10
10
  import { runLive } from "./operator/live.js";
11
11
  import { runLog } from "./operator/log.js";
12
12
  import { isInteractive, terminalAsk } from "./operator/prompt.js";
13
+ import { runRelease } from "./operator/release.js";
13
14
  import { runSetup } from "./operator/setup.js";
14
15
  const USAGE = `Usage: ${PRODUCT} <command>
15
16
 
@@ -18,6 +19,7 @@ const USAGE = `Usage: ${PRODUCT} <command>
18
19
  status snapshot the ledger, then exit
19
20
  log replay the journal, including why a phase stopped
20
21
  cancel abandon a Feature by key (when the tracker cannot say it is gone)
22
+ release drop a held Subtask without waiting for the bail clock
21
23
  init set this directory up — asks, writes, then runs setup
22
24
  doctor check the config, the slots, and the manager
23
25
  setup prepare the tracker (--apply to write; a plan without it)
@@ -184,6 +186,22 @@ async function main(argv) {
184
186
  detachSignals();
185
187
  }
186
188
  }
189
+ if (command === "release") {
190
+ const interruptFlag = { interrupted: false };
191
+ const detachSignals = installSignalHandlers(interruptFlag);
192
+ try {
193
+ return await runRelease({
194
+ cwd: process.cwd(),
195
+ argv: rest,
196
+ env: process.env,
197
+ write: (line) => process.stdout.write(line),
198
+ interruptFlag,
199
+ });
200
+ }
201
+ finally {
202
+ detachSignals();
203
+ }
204
+ }
187
205
  process.stderr.write(USAGE);
188
206
  return 2;
189
207
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,MAAM,KAAK,GAAG,UAAU,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAiCC,eAAe;+BAChB,YAAY;;;;;;;CAO1C,CAAC;AAEF,SAAS,qBAAqB,CAAC,KAA+B;IAC5D,MAAM,QAAQ,GAAG,GAAS,EAAE;QAC1B,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;IAC3B,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAChC,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,GAAG,CAAC,IAAc;IAC/B,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACvD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;QAC3C,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,aAAa,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAgB,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,aAAa,EAAE,CAAC;IACrE,MAAM,aAAa,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;IAC3D,IAAI,IAAsD,CAAC;IAC3D,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC/B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QAChC,qFAAqF;QACrF,IAAI,aAAa,CAAC,WAAW,EAAE,CAAC;YAC9B,OAAO,GAAG,CAAC;QACb,CAAC;QACD,OAAO,MAAM,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpF,OAAO,CAAC,CAAC;IACX,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,EAAE,KAAK,EAAE,CAAC;QACpB,aAAa,EAAE,CAAC;IAClB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;QACtB,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;QACtB,OAAO,MAAM,MAAM,CAAC;YAClB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;SAC5C,CAAC,CAAC;IACL,CAAC;IACD,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,aAAa,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QAC7C,MAAM,aAAa,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC;gBACnB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;gBAClB,IAAI,EAAE,IAAI;gBACV,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;gBAC3C,aAAa;gBACb,MAAM,EAAE,OAAO,KAAK,OAAO;aAC5B,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,aAAa,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IACD,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACvB,4EAA4E;QAC5E,6DAA6D;QAC7D,MAAM,MAAM,GACV,CAAC,aAAa,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACrF,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACpD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;gBAC3B,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;gBAClB,IAAI,EAAE,IAAI;gBACV,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;gBAC3C,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC;aACzD,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;gBAC3C,OAAO,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;YAC9B,CAAC;YACD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;gBAAS,CAAC;YACT,QAAQ,EAAE,KAAK,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IACD,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,aAAa,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QAC7C,MAAM,aAAa,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC;gBACpB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;gBAClB,IAAI,EAAE,IAAI;gBACV,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;gBAC3C,aAAa;aACd,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,aAAa,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IACD,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,MAAM,SAAS,CAAC;YACrB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;SAC5C,CAAC,CAAC;IACL,CAAC;IACD,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,MAAM,aAAa,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QAC7C,MAAM,aAAa,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,OAAO,MAAM,SAAS,CAAC;gBACrB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;gBAClB,IAAI,EAAE,IAAI;gBACV,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;gBAC3C,aAAa;aACd,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,aAAa,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACnD,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/config/load-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAGV,cAAc,EAIf,MAAM,YAAY,CAAC;AA8BpB,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAmKrD"}
1
+ {"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/config/load-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAIV,cAAc,EAIf,MAAM,YAAY,CAAC;AA+BpB,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAsKrD"}
@@ -18,9 +18,10 @@ const KNOWN_KEYS = new Set([
18
18
  "maxRefusals",
19
19
  "pollIntervalMs",
20
20
  ]);
21
- const WORK_LINE_KEYS = new Set(["stable", "branch", "isolation", "isolationOptions"]);
21
+ const WORK_LINE_KEYS = new Set(["stable", "branch", "isolation", "isolationOptions", "warm"]);
22
22
  const PASS_KEYS = new Set(["cmd", "timeoutMs"]);
23
- const STAGE_KEYS = new Set(["producer", "repair", "maxAttempts", "gates"]);
23
+ const GATED_PASS_KEYS = new Set(["cmd", "timeoutMs", "gates"]);
24
+ const STAGE_KEYS = new Set(["producer", "repair", "maxAttempts"]);
24
25
  const ASSEMBLY_KEYS = new Set(["fix", "validate", "maxAttempts", "gates"]);
25
26
  const GATES_KEYS = new Set(["defaultTimeoutMs", "gates"]);
26
27
  const AUTHORITY_KEYS = new Set(["enabled", "publish", "refresh", "describe"]);
@@ -97,6 +98,9 @@ export function loadConfig(path) {
97
98
  if (workLine.isolationOptions !== undefined) {
98
99
  invocation.workLineIsolationOptions = workLine.isolationOptions;
99
100
  }
101
+ if (workLine.warm !== undefined) {
102
+ invocation.workLineWarm = workLine.warm;
103
+ }
100
104
  }
101
105
  const workspaceRoot = stringField(object, "workspaceRoot");
102
106
  if (workspaceRoot.ok === false) {
@@ -120,7 +124,7 @@ export function loadConfig(path) {
120
124
  invocation.persist = persist.value;
121
125
  }
122
126
  if (object.planner !== undefined) {
123
- const planner = readPass(object.planner, configDir, "planner");
127
+ const planner = readGatedPass(object.planner, configDir, "planner");
124
128
  if (planner.ok === false) {
125
129
  return planner;
126
130
  }
@@ -243,6 +247,14 @@ function readWorkLine(value, configDir) {
243
247
  Array.isArray(isolationOptions))) {
244
248
  return { ok: false, reason: 'Config "workLine.isolationOptions" must be a YAML mapping.' };
245
249
  }
250
+ let warm;
251
+ if (object.warm !== undefined) {
252
+ const read = readPass(object.warm, configDir, "workLine.warm");
253
+ if (read.ok === false) {
254
+ return read;
255
+ }
256
+ warm = read.value;
257
+ }
246
258
  return {
247
259
  ok: true,
248
260
  ...(stable.value === undefined ? {} : { stable: resolve(configDir, stable.value) }),
@@ -251,6 +263,7 @@ function readWorkLine(value, configDir) {
251
263
  ...(isolationOptions === undefined
252
264
  ? {}
253
265
  : { isolationOptions: isolationOptions }),
266
+ ...(warm === undefined ? {} : { warm }),
254
267
  };
255
268
  }
256
269
  /**
@@ -483,6 +496,49 @@ function readPass(value, configDir, key) {
483
496
  value: { cmd: resolveArgv(cmd.value, configDir), timeoutMs: timeoutMs.value },
484
497
  };
485
498
  }
499
+ /**
500
+ * A command, and its own Gate sequence — `planner`, `builder.producer`,
501
+ * `builder.repair`, `assembly.fix`, `assembly.validate`. Deliberately its own
502
+ * ~10 lines rather than a parameterized `readPass`: each reader's error
503
+ * messages and key set stay independently legible.
504
+ */
505
+ function readGatedPass(value, configDir, key) {
506
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
507
+ return {
508
+ ok: false,
509
+ reason: `Config "${key}" must be a YAML mapping with "cmd" and "timeoutMs".`,
510
+ };
511
+ }
512
+ const object = value;
513
+ for (const name of Object.keys(object)) {
514
+ if (!GATED_PASS_KEYS.has(name)) {
515
+ return { ok: false, reason: `Config "${key}" has unknown key "${name}".` };
516
+ }
517
+ }
518
+ const cmd = stringArrayField(object, "cmd");
519
+ if (cmd.ok === false || cmd.value === undefined) {
520
+ return { ok: false, reason: `Config "${key}.cmd" must be a non-empty array of strings.` };
521
+ }
522
+ const timeoutMs = positiveIntField(object, "timeoutMs");
523
+ if (timeoutMs.ok === false || timeoutMs.value === undefined) {
524
+ return {
525
+ ok: false,
526
+ reason: `Config "${key}" needs a positive "timeoutMs": nothing else bounds a command.`,
527
+ };
528
+ }
529
+ let gates = [];
530
+ if (object.gates !== undefined) {
531
+ const read = readGates(object.gates, configDir, `${key}.gates`);
532
+ if (read.ok === false) {
533
+ return read;
534
+ }
535
+ gates = read.value;
536
+ }
537
+ return {
538
+ ok: true,
539
+ value: { cmd: resolveArgv(cmd.value, configDir), timeoutMs: timeoutMs.value, gates },
540
+ };
541
+ }
486
542
  /**
487
543
  * Making a Subtask: both passes named, neither borrowing the other's command.
488
544
  */
@@ -502,18 +558,17 @@ function readBuilder(value, configDir) {
502
558
  if (object.repair === undefined) {
503
559
  return { ok: false, reason: 'Config "builder" needs a "repair".' };
504
560
  }
505
- const readProducer = readPass(object.producer, configDir, "builder.producer");
561
+ const readProducer = readGatedPass(object.producer, configDir, "builder.producer");
506
562
  if (readProducer.ok === false) {
507
563
  return readProducer;
508
564
  }
509
- const readRepair = readPass(object.repair, configDir, "builder.repair");
565
+ const readRepair = readGatedPass(object.repair, configDir, "builder.repair");
510
566
  if (readRepair.ok === false) {
511
567
  return readRepair;
512
568
  }
513
569
  const spec = {
514
570
  producer: readProducer.value,
515
571
  repair: readRepair.value,
516
- gates: [],
517
572
  };
518
573
  const maxAttempts = positiveIntField(object, "maxAttempts");
519
574
  if (maxAttempts.ok === false) {
@@ -522,13 +577,6 @@ function readBuilder(value, configDir) {
522
577
  if (maxAttempts.value !== undefined) {
523
578
  spec.maxAttempts = maxAttempts.value;
524
579
  }
525
- if (object.gates !== undefined) {
526
- const gates = readGates(object.gates, configDir, "builder.gates");
527
- if (gates.ok === false) {
528
- return gates;
529
- }
530
- spec.gates = gates.value;
531
- }
532
580
  return { ok: true, value: spec };
533
581
  }
534
582
  /**
@@ -554,14 +602,14 @@ function readAssembly(value, configDir) {
554
602
  }
555
603
  const spec = { gates: [] };
556
604
  if (object.fix !== undefined) {
557
- const read = readPass(object.fix, configDir, "assembly.fix");
605
+ const read = readGatedPass(object.fix, configDir, "assembly.fix");
558
606
  if (read.ok === false) {
559
607
  return read;
560
608
  }
561
609
  spec.fix = read.value;
562
610
  }
563
611
  if (object.validate !== undefined) {
564
- const read = readPass(object.validate, configDir, "assembly.validate");
612
+ const read = readGatedPass(object.validate, configDir, "assembly.validate");
565
613
  if (read.ok === false) {
566
614
  return read;
567
615
  }
@@ -592,8 +640,12 @@ function readAssembly(value, configDir) {
592
640
  * resolved against the slot's working directory, which is the Task workspace.
593
641
  * A workspace is a git worktree that is published and then destroyed: output
594
642
  * left there is committed into the work first, and lost after.
643
+ *
644
+ * `--transcript-dir` and `--agent-home` are Host-owned write paths (films, and
645
+ * the vendor's isolated HOME). Both must resolve against the config directory
646
+ * even before the directory exists.
595
647
  */
596
- const WRITTEN_PATH_FLAGS = new Set(["--transcript-dir"]);
648
+ const WRITTEN_PATH_FLAGS = new Set(["--transcript-dir", "--agent-home"]);
597
649
  function resolveArgv(argv, configDir) {
598
650
  return argv.map((token, at) => WRITTEN_PATH_FLAGS.has(argv[at - 1] ?? "")
599
651
  ? resolve(configDir, token)