@bluewombat/runtime 0.9.0 → 0.11.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 (113) hide show
  1. package/README.md +200 -99
  2. package/SPECS.md +121 -19
  3. package/dist/cli.js +35 -4
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/agent-shorthand.d.ts +25 -5
  6. package/dist/config/agent-shorthand.d.ts.map +1 -1
  7. package/dist/config/agent-shorthand.js +65 -21
  8. package/dist/config/agent-shorthand.js.map +1 -1
  9. package/dist/config/fixture-yaml.d.ts +8 -0
  10. package/dist/config/fixture-yaml.d.ts.map +1 -0
  11. package/dist/config/fixture-yaml.js +30 -0
  12. package/dist/config/fixture-yaml.js.map +1 -0
  13. package/dist/config/home.d.ts +2 -0
  14. package/dist/config/home.d.ts.map +1 -1
  15. package/dist/config/home.js +2 -0
  16. package/dist/config/home.js.map +1 -1
  17. package/dist/config/load-config.d.ts.map +1 -1
  18. package/dist/config/load-config.js +301 -62
  19. package/dist/config/load-config.js.map +1 -1
  20. package/dist/config/parse-args.d.ts +1 -1
  21. package/dist/config/parse-args.d.ts.map +1 -1
  22. package/dist/config/parse-args.js +84 -58
  23. package/dist/config/parse-args.js.map +1 -1
  24. package/dist/config/types.d.ts +96 -16
  25. package/dist/config/types.d.ts.map +1 -1
  26. package/dist/config/types.js +13 -1
  27. package/dist/config/types.js.map +1 -1
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/loop/authority.d.ts +38 -1
  33. package/dist/loop/authority.d.ts.map +1 -1
  34. package/dist/loop/authority.js +41 -2
  35. package/dist/loop/authority.js.map +1 -1
  36. package/dist/loop/context.d.ts +16 -1
  37. package/dist/loop/context.d.ts.map +1 -1
  38. package/dist/loop/drive.d.ts +9 -3
  39. package/dist/loop/drive.d.ts.map +1 -1
  40. package/dist/loop/drive.js +17 -7
  41. package/dist/loop/drive.js.map +1 -1
  42. package/dist/loop/features.d.ts +16 -0
  43. package/dist/loop/features.d.ts.map +1 -1
  44. package/dist/loop/features.js +404 -62
  45. package/dist/loop/features.js.map +1 -1
  46. package/dist/loop/open-host.d.ts +10 -0
  47. package/dist/loop/open-host.d.ts.map +1 -1
  48. package/dist/loop/open-host.js +111 -9
  49. package/dist/loop/open-host.js.map +1 -1
  50. package/dist/loop/report.d.ts.map +1 -1
  51. package/dist/loop/report.js +21 -9
  52. package/dist/loop/report.js.map +1 -1
  53. package/dist/loop/streams.d.ts +3 -0
  54. package/dist/loop/streams.d.ts.map +1 -1
  55. package/dist/loop/streams.js +2 -1
  56. package/dist/loop/streams.js.map +1 -1
  57. package/dist/loop/tick.d.ts +3 -0
  58. package/dist/loop/tick.d.ts.map +1 -1
  59. package/dist/loop/tick.js.map +1 -1
  60. package/dist/loop/trace.d.ts +1 -0
  61. package/dist/loop/trace.d.ts.map +1 -1
  62. package/dist/loop/trace.js +2 -2
  63. package/dist/loop/trace.js.map +1 -1
  64. package/dist/loop/transformers.d.ts +16 -2
  65. package/dist/loop/transformers.d.ts.map +1 -1
  66. package/dist/loop/transformers.js +126 -22
  67. package/dist/loop/transformers.js.map +1 -1
  68. package/dist/loop/warm.js +1 -1
  69. package/dist/loop/warm.js.map +1 -1
  70. package/dist/operator/agent-plugins.d.ts.map +1 -1
  71. package/dist/operator/agent-plugins.js +14 -8
  72. package/dist/operator/agent-plugins.js.map +1 -1
  73. package/dist/operator/board.d.ts +43 -4
  74. package/dist/operator/board.d.ts.map +1 -1
  75. package/dist/operator/board.js +153 -10
  76. package/dist/operator/board.js.map +1 -1
  77. package/dist/operator/doctor.d.ts +1 -1
  78. package/dist/operator/doctor.d.ts.map +1 -1
  79. package/dist/operator/doctor.js +76 -29
  80. package/dist/operator/doctor.js.map +1 -1
  81. package/dist/operator/explain.d.ts.map +1 -1
  82. package/dist/operator/explain.js +36 -12
  83. package/dist/operator/explain.js.map +1 -1
  84. package/dist/operator/film.js +0 -3
  85. package/dist/operator/film.js.map +1 -1
  86. package/dist/operator/incident-dossier.d.ts +154 -0
  87. package/dist/operator/incident-dossier.d.ts.map +1 -0
  88. package/dist/operator/incident-dossier.js +696 -0
  89. package/dist/operator/incident-dossier.js.map +1 -0
  90. package/dist/operator/incident-system.d.ts +48 -0
  91. package/dist/operator/incident-system.d.ts.map +1 -0
  92. package/dist/operator/incident-system.js +152 -0
  93. package/dist/operator/incident-system.js.map +1 -0
  94. package/dist/operator/incident.d.ts +27 -0
  95. package/dist/operator/incident.d.ts.map +1 -0
  96. package/dist/operator/incident.js +220 -0
  97. package/dist/operator/incident.js.map +1 -0
  98. package/dist/operator/init.js +27 -22
  99. package/dist/operator/init.js.map +1 -1
  100. package/dist/operator/live.js +4 -4
  101. package/dist/operator/live.js.map +1 -1
  102. package/dist/operator/log.d.ts +2 -0
  103. package/dist/operator/log.d.ts.map +1 -1
  104. package/dist/operator/log.js +7 -3
  105. package/dist/operator/log.js.map +1 -1
  106. package/dist/operator/restart.d.ts +20 -0
  107. package/dist/operator/restart.d.ts.map +1 -0
  108. package/dist/operator/restart.js +63 -0
  109. package/dist/operator/restart.js.map +1 -0
  110. package/dist/operator/setup.d.ts.map +1 -1
  111. package/dist/operator/setup.js +2 -0
  112. package/dist/operator/setup.js.map +1 -1
  113. package/package.json +14 -14
package/README.md CHANGED
@@ -53,19 +53,21 @@ package's README — Host does not grow a `--repo` flag for it.
53
53
  | --------------- | -------------------------------------------------------------------------------------- |
54
54
  | `mason run` | Drain the TicketTracker, run Conductor, report |
55
55
  | `mason watch` | Snapshot the ledger and follow the journal. Ctrl-C leaves the worker running |
56
- | `mason status` | Snapshot the ledger, then exit. `--json` for scripts |
56
+ | `mason status` | Snapshot the ledger as a Feature / Ticket tree, then exit. `--json` for scripts |
57
57
  | `mason log` | Replay the journal, with the reason a phase stopped. `--problems` for just those |
58
58
  | `mason init` | Set this directory up: asks, writes the config and the Builder stub, then runs `setup` |
59
59
  | `mason setup` | Prepare the tracker. A plan by default; `--apply` writes. Exit 1 on a blocked step |
60
60
  | `mason doctor` | Check the config, the slots, and the manager. Exit 1 on any failure |
61
+ | `mason incident` | Gather what is known about one stop (ledger, journal, streams, transcripts) into one page, for a person to read or hand on. Without a key, list what waits. `--json`, `--out DIR`, `--with-config`. Read-only |
61
62
  | `mason explain` | Say what an agent role starts with: prompt, plugins, skills, the project's profile. Read-only, offline. Exit 1 on a setup every attempt would fail on |
62
63
  | `mason cancel` | Abandon a Ticket by key, when the tracker cannot say it is gone |
64
+ | `mason restart` | Resume an escalated Feature with a fresh workspace, made again from its line. Offline: the tracker is not told |
63
65
  | `mason release` | Drop a held unit without waiting for the bail clock |
64
66
 
65
67
  ### What an agent role starts with
66
68
 
67
69
  ```bash
68
- mason explain builder.producer # one role; no role names all of them
70
+ mason explain ticket.build.producer # one role; no role names all of them
69
71
  mason explain --json # the same facts for a script
70
72
  ```
71
73
 
@@ -128,7 +130,9 @@ mason watch
128
130
  That is the operator surface: the durable state of every Ticket, then the film
129
131
  of the current step (`submitted` included). It does not talk to the tracker, and
130
132
  it cannot `resume` or claim. `status` is the same snapshot without the
131
- follow. `--json` prints that snapshot as JSON.
133
+ follow: a header with the counts, each Feature with its Tickets under it
134
+ (escalated Features first, finished ones last, keys in numeric order), then the
135
+ Tickets of no Feature. `--json` prints that tree as JSON.
132
136
 
133
137
  When a run only printed `unavailable:<key>`, the reason is already in the
134
138
  journal and `log` is how you read it:
@@ -144,6 +148,24 @@ distinct failure. If a slot was started with `--transcript-dir`, the newest
144
148
  transcript files are named underneath, with the agent exit and duration read
145
149
  from the start of each file.
146
150
 
151
+ When a Ticket or a Feature is waiting for a person, `incident` gathers what the
152
+ run knows of it:
153
+
154
+ ```bash
155
+ mason incident # what waits for a person
156
+ mason incident 106 # one stop: the key, or the tracker's number
157
+ mason incident 106 --out ./inc # incident.md and incident.json, to hand on
158
+ mason incident 106 --with-config # also the config file, secret-looking values blanked
159
+ ```
160
+
161
+ The page says what the run was made of (versions, what the configuration switched
162
+ on), who refused the work and what they said, the Tickets of the
163
+ Feature with their pull requests, the Gate's configuration, the journal's events
164
+ of the last round and where the streams and transcripts are. What it could not
165
+ find is listed under "Not found". Streams exist only if the Project turned them
166
+ on (`--streams-dir`); without them the page has the Gate's report but not what
167
+ the Gate printed.
168
+
147
169
  To abandon a Ticket the tracker can no longer mention (a deleted issue, a
148
170
  manager with no `probe`), stop `run` if it holds the ledger lock, then:
149
171
 
@@ -151,13 +173,29 @@ manager with no `probe`), stop `run` if it holds the ledger lock, then:
151
173
  mason cancel github:owner/name#19
152
174
  ```
153
175
 
154
- After a crash left a unit `running` with nobody on it, stop `run` if it
155
- holds the lock, then:
176
+ A unit that was stopped (Ctrl-C, a crash) is picked up where it was by the next
177
+ `mason run`: its workspace is kept, the Attempts it finished still count and the
178
+ numbering goes on. To start a unit over instead (the line moved on, the work took
179
+ a wrong turn), stop `run` if it holds the lock, then:
156
180
 
157
181
  ```bash
158
182
  mason release github:owner/name#19
159
183
  ```
160
184
 
185
+ `release` destroys the workspace and forgets it; the next `run` isolates a fresh one.
186
+
187
+ A Feature that escalated keeps its work. When a person then changes its line and resumes it,
188
+ the work takes in what the line gained, merged into it, before it is judged again; a conflict
189
+ escalates the Feature again, naming it. To throw the work away instead, stop `run` if it holds
190
+ the lock, then:
191
+
192
+ ```bash
193
+ mason restart github:owner/name#106 # or just 106
194
+ ```
195
+
196
+ The Feature must be escalated. Its workspace is removed and it is resumed; the next `run`
197
+ cuts the work again from the line. The tracker is not told.
198
+
161
199
  The film is `<ledger>/events.jsonl`, next to `cursor`. It is not truth after a
162
200
  crash — the ledger is. The file can grow; there is no rotation yet.
163
201
 
@@ -174,45 +212,50 @@ value that starts with `@`, like every `@bluewombat/...` package name, needs
174
212
  quotes: `@` is a reserved YAML indicator at the start of a plain scalar.
175
213
 
176
214
  ```yaml
177
- featureTracker:
178
- package: "@bluewombat/manager-fake"
179
- options:
180
- source: ./.mason/source
181
- target: ./.mason/threads
182
- ticketTracker:
183
- package: "@bluewombat/manager-fake"
184
- options:
185
- source: ./.mason/source
186
- target: ./.mason/threads
187
215
  workLine:
188
216
  stable: ./work-line-stable
189
217
  isolation: "@bluewombat/isolation-copy"
190
218
  warm:
191
219
  cmd: [pnpm, install, --frozen-lockfile]
192
220
  timeoutMs: 600000
193
- builder:
194
- producer:
195
- cmd: [node, ./mason-builder.mjs]
196
- timeoutMs: 600000
197
- gates: { defaultTimeoutMs: 120000, gates: [] }
198
- repair:
199
- cmd: [node, ./mason-repair.mjs]
200
- timeoutMs: 600000
221
+ ticket:
222
+ tracker:
223
+ package: "@bluewombat/manager-fake"
224
+ options:
225
+ source: ./.mason/source
226
+ target: ./.mason/threads
227
+ build:
228
+ producer:
229
+ cmd: [node, ./mason-builder.mjs]
230
+ timeoutMs: 600000
231
+ repair:
232
+ cmd: [node, ./mason-repair.mjs]
233
+ timeoutMs: 600000
201
234
  gates: { defaultTimeoutMs: 120000, gates: [] }
202
- maxAttempts: 3
203
- assembly:
235
+ maxAttempts: 3
236
+ validate: { agent: false }
204
237
  gates: { defaultTimeoutMs: 900000, gates: [] }
238
+ feature:
239
+ tracker:
240
+ package: "@bluewombat/manager-fake"
241
+ options:
242
+ source: ./.mason/source
243
+ target: ./.mason/threads
205
244
  authority:
206
245
  enabled: false
207
246
  timeoutMs: 600000
208
247
  pollIntervalMs: 10000
209
248
  ```
210
249
 
250
+ The file reads by level, and each level in the order it is used: `ticket` and `feature` both hold
251
+ `tracker`, then what makes the work (`build` for a Ticket, `test` for a Feature), `validate`, `gates`, `fix`.
252
+
211
253
  | Field | Meaning |
212
254
  | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
213
- | `featureTracker` | `package` (a name or path that exports `createManager`) and `options`: where Features are written and signalled |
214
- | `ticketTracker` | Same shape: where the Tickets of a Feature are, and the Authority that holds the lines. May name the same package as `featureTracker`, or another |
255
+ | `feature.tracker` | `package` (a name or path that exports `createManager`) and `options`: where Features are written and signalled |
256
+ | `ticket.tracker` | Same shape: where the Tickets of a Feature are, and the Authority that holds the lines. May name the same package as `feature.tracker`, or another |
215
257
  | `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` |
258
+ | `workLine.targetStable` | A second copy of the work line, on the branch a Feature is folded into: what is merged into the Feature when it is brought up to date. Host keeps its own at `<home>/target-line` when the manager names a reference; required with an Authority otherwise |
216
259
  | `workLine.isolation` | Package name or path exporting `strategy` — required. Host loads it like a manager; Isolator/Integrator do not sniff |
217
260
  | `workLine.isolationOptions` | What that strategy is told, unread by Host — like a manager's `options`. `isolation-git` reads `exclude`, the paths a Child never gets (default `.env`, `.env.*`). A strategy without `createStrategy` refuses any |
218
261
  | `workLine.warm` | Optional `cmd` / `timeoutMs`. Runs in the workspace after Isolation, before the Builder — typically `pnpm install --frozen-lockfile`. Absent: the Builder gets a cold tree |
@@ -223,26 +266,30 @@ pollIntervalMs: 10000
223
266
  | `ledger` | WorkLedger root (created). The Cursor is a file inside it. Default `<home>/ledger` |
224
267
  | `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 |
225
268
  | `agents` | Agents a pass names instead of writing `cmd` (§ Agent passes written short) |
226
- | `builder.producer` | `cmd` / `timeoutMs` / `gates`. Spawned in the workspace. Exit 0 runs its own `gates` |
227
- | `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 |
228
- | `builder.maxAttempts` | Attempts one Task may start. Default 3 |
229
- | `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` |
230
- | `assembly.validate` | `cmd` / `timeoutMs` / `gates`. Optional. A local, read-only judge of the assembled ticket, 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 |
231
- | `assembly.maxAttempts` | Attempts that one fix may start. Default 3 |
232
- | `assembly.gates` | `defaultTimeoutMs` and `gates`: the produce:false judgement-only pass — no producer of its own, re-checking the assembled ticket. Empty: the Authority judges alone. Independent of `assembly.fix.gates` / `assembly.validate.gates` — never shared with either |
269
+ | `feature.test` | Optional. Declares the feature-test acceptance check (see § The feature-test acceptance check): the pass that makes the Ticket holding the suite, `{ agent, prompt, homePlugins, appDeckProfile, timeoutMs, gates }` or `{ cmd, timeoutMs, gates }`. One command for the first pass and for a pass a Gate refused; its own `gates` (they check the form: compiles, one test per manifest id, no application file touched). Nothing is borrowed from `ticket.build.producer`, `ticket.build.repair` or `ticket.build.gates`. Needs a Feature Gate with `skipWhen: skip-feature-test`, and the other way round |
270
+ | `ticket.build.producer` | `cmd` / `timeoutMs`. Spawned in the workspace. Exit 0 runs `ticket.build.gates` |
271
+ | `ticket.build.repair` | The same, for a pass a Gate refused. **Required**, and never inherited: it names its own command. It is judged by the same `ticket.build.gates` as the producer |
272
+ | `ticket.build.gates` | `defaultTimeoutMs` and `gates`: the one Gate list for `ticket.build.producer` and `ticket.build.repair`. `gates` written on either pass is refused, naming this key. Empty: the unit runs ungated |
273
+ | `ticket.build.maxAttempts` | Attempts one Task may start. Default 3 |
274
+ | `ticket.fix` | `cmd` / `timeoutMs` / `gates`. Spawned when a judgement of the whole refused it — by an Authority, by `ticket.validate`, or by both. Judged by its own `gates` |
275
+ | `ticket.validate` | `cmd` / `timeoutMs` / `gates`. A local, read-only judge of the assembled ticket, run after align, with or without an Authority. A refusal is repaired by `ticket.fix` the same as an Authority's. Judged by its own `gates`, never `ticket.build`'s or `ticket.fix`'s. `validate` is required when a `ticket` block is written, and so is `feature.validate`: `{ agent: false, gates }` turns the agent off and keeps the gates, and `{ agent: false }` with no gates turns the review off — a bare `gates:` counts as none. |
276
+ | `ticket.maxAttempts` | Attempts that one fix may start. Default 3 |
277
+ | `ticket.gates` | `defaultTimeoutMs` and `gates`: the produce:false judgement-only pass — no producer of its own, re-checking the assembled ticket. Empty: the Authority judges alone. Independent of `ticket.fix.gates` / `ticket.validate.gates` — never shared with either |
233
278
  | `timeoutMs` | How long a child **outside** a Task may run: manager, isolations |
234
- | `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 |
279
+ | `ticket.maxRefusals` | Times the work may be sent back before it escalates, by an Authority, by `ticket.validate`, or by both — one shared budget. Default 3 |
280
+ | `ticket.maxParallel` | How many Tickets of a Project are worked on at once. Default 1: one after the other. Above 1 needs `authority.enabled`; a Ticket waiting on a check or a person holds a place, the others go on. Tickets that wait on another (*Blocked by*) still start only once it is `done` |
281
+ | `feature` | Required with `authority.enabled`. How a Feature's line is delivered to the line it is folded into, by the same steps as a Ticket: the final line is merged into the Feature's work (`workLine.targetStable`, or Host's own copy of the reference), `validate` (same shape as `ticket.validate`) reviews the whole against the Feature's request and its `gates` (at least one) are where the project's checks on the whole line run, a refusal goes to `fix`, the line is published, and `gates` (at least one, the CI) judge it. `maxAttempts` and `maxRefusals` (default 3, same rule as a Ticket's) are its own. Nothing falls back to `ticket`. The merge itself is automatic only with `merge-when-ready`; the delivery to the Authority is for every Feature |
235
282
  | `pollIntervalMs` | Set it and the process keeps draining until SIGINT. Absent: one tick. Pick it with § Choosing `pollIntervalMs` — 10000 is the floor worth having |
236
283
  | `observability.streams` | `enabled`, `dir`, `keep`. Films what every child says, as it says it — one file per child under `<dir>/<ticket>/`, 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 |
237
284
 
238
285
  Every `cmd` is named where it is used, and nothing falls back to a neighbour: a
239
286
  `repair` with no command of its own is refused rather than quietly running the
240
- first-pass agent again. `builder` makes the unit of work. `assembly` judges the whole
241
- and, when that judgement refuses it, runs `assembly.fix` — not a second first-pass.
242
- `assembly.validate`, when declared, is a second, local judge of the whole: it
287
+ first-pass agent again. `ticket.build` makes the unit of work. `ticket` judges the whole
288
+ and, when that judgement refuses it, runs `ticket.fix` — not a second first-pass.
289
+ `ticket.validate`, when declared, is a second, local judge of the whole: it
243
290
  runs after align, before the Authority sees the work (or before the final
244
- fold, with none). Its refusal is repaired by `assembly.fix` exactly as an
245
- Authority's is, and shares the same `maxRefusals` budget.
291
+ fold, with none). Its refusal is repaired by `ticket.fix` exactly as an
292
+ Authority's is, and shares the same `ticket.maxRefusals` budget.
246
293
 
247
294
  Paths a slot **writes** — `--transcript-dir`, `--agent-home` and
248
295
  `--plugins-out` — always resolve against the config file's directory, even
@@ -266,36 +313,39 @@ agents:
266
313
  denyShell: [] # commands refused to the agent, e.g. [gh, vercel]; ssh and scp always are
267
314
  transcripts: ./.mason/transcripts
268
315
  pluginsOut: ./.mason/plugins # where deck profiles are built
269
- builder:
270
- producer:
271
- agent: cursor
272
- prompt: ./prompts/builder.md
273
- plugins: [./ai-plugins/core/builder]
274
- profile: builder
275
- args: [--mode, agent] # the vendor CLI's own flags
276
- timeoutMs: 1200000
277
- gates: { defaultTimeoutMs: 120000, gates: [] }
316
+ ticket:
317
+ build:
318
+ producer:
319
+ agent: cursor
320
+ prompt: ./prompts/builder.md
321
+ homePlugins: [./ai-plugins/core/builder]
322
+ appDeckProfile: .ai/profiles/builder
323
+ args: [--mode, agent] # the vendor CLI's own flags
324
+ timeoutMs: 1200000
278
325
  ```
279
326
 
280
327
  Host writes the `cmd` it stands for — `node <slots>/<role>.mjs --prompt-file …
281
- -- node <slots>/agents/cursor.mjs --transcript-dir … --plugin-dir … --profile
328
+ -- node <slots>/agents/cursor.mjs --transcript-dir … --plugin-dir … --app-deck-profile
282
329
  … --plugins-out … --agent-arg …` — with `<slots>` the
283
330
  `node_modules/@bluewombat/slots` next to the config. Nothing past the config
284
- knows a pass was written short. The roles: `builder.producer`,
285
- `builder.repair`, `assembly.fix`, `assembly.validate`, and `authority.describe`
286
- (an argv, or `{ agent, prompt?, plugins?, profile?, args? }`).
331
+ knows a pass was written short. The roles: `ticket.build.producer`,
332
+ `ticket.build.repair`, `ticket.fix`, `ticket.validate`, and `authority.describe`
333
+ (an argv, or `{ agent, prompt?, homePlugins?, appDeckProfile?, args? }`).
287
334
 
288
335
  | Key | Meaning |
289
336
  | --------- | --------------------------------------------------------------------------------------------------------- |
290
337
  | `agent` | A name `agents` declares. With `cmd` beside it, the pass is refused |
291
338
  | `prompt` | `--prompt-file` of the role slot. Absent: the role's built-in template |
292
339
  | `rules` | `--rules-file` of the role slot |
293
- | `plugins` | Plugin folders of the Mason home, each a `--plugin-dir` |
294
- | `profile` | The project's deck profile for this role. Needs the agent's `pluginsOut`. See the slots' § Agents/Plugins |
340
+ | `homePlugins` | Plugin folders of the Mason home, each a `--plugin-dir` |
341
+ | `appDeckProfile` | The app's deck profile for this role, as a path: `[<app dir>/].ai/profiles/<name>`. Needs the agent's `pluginsOut`. See the slots' § Agents/Plugins |
295
342
  | `args` | Appended to the agent's own `args`, each an `--agent-arg` |
296
343
 
297
- A `profile` is read in the project, not in the Mason home: the role gets the
298
- base plugins and that profile, as the project's committed deck locks pin them.
344
+ An `appDeckProfile` is read in the app, not in the Mason home: the role gets the
345
+ app's base plugins, that profile's plugins and its `prompt.md`, as the app's
346
+ committed deck locks pin them. deck keeps profiles in `<app>/.ai/profiles/<name>`,
347
+ so the path has that shape; `<app dir>` is where the app's `.ai/` is in the
348
+ checkout (`.` by default, `apps/web` in a monorepo). It stays inside the checkout.
299
349
  `mason doctor` checks those locks in the work line without the network.
300
350
 
301
351
  Every tracker field lives in the `options` of its side. Host does not read them.
@@ -375,20 +425,20 @@ Examples ship in [`@bluewombat/slots`](../../plugins/slots/README.md), installed
375
425
  the shipped roles and agents (Cursor CLI, Claude Code), the shipped Gates, and
376
426
  their options. What follows is what Host guarantees them.
377
427
 
378
- ```json
379
- "builder": {
380
- "producer": {
381
- "cmd": [
382
- "node", "./node_modules/@bluewombat/slots/builders/producer.mjs",
383
- "--",
384
- "node", "./node_modules/@bluewombat/slots/agents/claude.mjs"
385
- ],
386
- "timeoutMs": 600000
387
- }
388
- }
428
+ ```yaml
429
+ ticket:
430
+ build:
431
+ producer:
432
+ cmd:
433
+ - node
434
+ - ./node_modules/@bluewombat/slots/builders/producer.mjs
435
+ - --
436
+ - node
437
+ - ./node_modules/@bluewombat/slots/agents/claude.mjs
438
+ timeoutMs: 600000
389
439
  ```
390
440
 
391
- `builder.repair` is required too, with its own `cmd` and `timeoutMs`. The
441
+ `ticket.build.repair` is required too, with its own `cmd` and `timeoutMs`. The
392
442
  composition with `--` is the same.
393
443
 
394
444
  ### Builder
@@ -406,15 +456,15 @@ The config always names a producer (and a repair). Implementer can still run a
406
456
  Task as its Gate sequence alone — that is the first judgement of the assembled
407
457
  ticket, which makes nothing.
408
458
 
409
- ### Assembly fix
459
+ ### Ticket fix
410
460
 
411
461
  Spawned in the ticket workspace when a judgement of the whole refused it
412
462
  (`--report`, `--report-from`). Same argv as a Builder otherwise. Host hands
413
- Implementer `assembly.fix` as that pass's only agent: there is no first-pass
463
+ Implementer `ticket.fix` as that pass's only agent: there is no first-pass
414
464
  producer at this moment. The refusal it repairs may have come from an
415
- Authority or from `assembly.validate`.
465
+ Authority or from `ticket.validate`.
416
466
 
417
- ### Assembly validate
467
+ ### Ticket validate
418
468
 
419
469
  Spawned in the ticket workspace after align, with or without an Authority —
420
470
  `--intention`, `--id`, `--attempt`, `--context`. No `--report`: it is a
@@ -425,23 +475,24 @@ that completes cleanly but finds a problem is still a refusal.
425
475
 
426
476
  ### Gates
427
477
 
428
- Four independent sequences, one per role — `builder.producer`,
429
- `builder.repair`, `assembly.fix`, `assembly.validate` — plus `assembly.gates`
430
- for the one judgement-only pass with no producer of its own (re-checking the
431
- assembled ticket; `ci-green` reads the checks the work line ran on what the
432
- Publisher placed, so it belongs there). No sharing, no dispatch choosing
433
- between two buckets: a check right for one pass is not necessarily right for
434
- another, even within the same stage. `builder.repair` is the sharpest edge —
435
- it does not inherit `builder.producer.gates`, so a repair Attempt with no
436
- Gate of its own runs ungated.
437
-
438
- Implementer's Gates are told `--stage unit` or `--stage assembly` alongside
478
+ Four sequences: `ticket.build.gates`, shared by `ticket.build.producer` and
479
+ `ticket.build.repair` (a repair is judged by exactly what judged the first try),
480
+ then one per assembly role — `ticket.fix`, `ticket.validate` — plus
481
+ `ticket.gates` for the one judgement-only pass with no producer of its own
482
+ (re-checking the assembled ticket; `ci-green` reads the checks the work line
483
+ ran on what the Publisher placed, so it belongs there). The assembly lists are
484
+ not shared with each other or with the builder, and no dispatch chooses between
485
+ two buckets: a check right for one pass is not necessarily right for another,
486
+ even within the same stage. `gates` written on `ticket.build.producer` or
487
+ `ticket.build.repair` is refused, and the message names `ticket.build.gates`.
488
+
489
+ Implementer's Gates are told `--stage unit` or `--stage ticket` alongside
439
490
  `--id`, `--attempt`, `--gate-id` and `--intention`.
440
491
  Most have no use for it. `workspace-changed` does: "nothing changed" means
441
492
  nothing was done in a unit, and is the correct outcome for an assembly that
442
493
  needed nothing.
443
494
 
444
- `init` writes an empty list on `builder.producer` and `builder.repair`; add a
495
+ `init` writes an empty list on `ticket.build.producer` and `ticket.build.repair`; add a
445
496
  Gate when the Project has a check.
446
497
 
447
498
  Every child carries its own ceiling and no other. A Gate entry may name a
@@ -451,11 +502,11 @@ from what another child left behind, so a slow producer cannot leave its
451
502
  Gates a millisecond to answer in.
452
503
 
453
504
  What bounds the unit is therefore what you wrote:
454
- `builder.maxAttempts × (builder.producer.timeoutMs + Σ builder.producer.gates timeouts)`
505
+ `ticket.build.maxAttempts × (ticket.build.producer.timeoutMs + Σ ticket.build.gates timeouts)`
455
506
  for a first pass, and the same shape with `repair` in place of `producer` once
456
507
  a Gate has refused one.
457
508
  A repair and an assembly fix each carry their own `timeoutMs`. An assembly fix
458
- is bounded the same way from `assembly.maxAttempts` and `assembly.fix`.
509
+ is bounded the same way from `ticket.maxAttempts` and `ticket.fix`.
459
510
 
460
511
  ```json
461
512
  {
@@ -471,6 +522,53 @@ is bounded the same way from `assembly.maxAttempts` and `assembly.fix`.
471
522
  }
472
523
  ```
473
524
 
525
+ ### The feature-test acceptance check
526
+
527
+ A Project proves each Feature by its own tests, run once on the assembled Feature
528
+ line, when its config says so, by a pair:
529
+
530
+ ```yaml
531
+ feature:
532
+ test: # makes the Ticket that holds the suite (label `feature-test` on the tracker)
533
+ agent: claude
534
+ prompt: ./prompts/feature-test.md # {{task}} is the scenarios; {{report}}, {{refused_by}}, {{refusal}} on a refused pass
535
+ timeoutMs: 1800000
536
+ gates: # form only: it compiles, one test per manifest id, no application file touched
537
+ gates:
538
+ - { id: feature-tests-form, argv: [...], timeoutMs: 120000 }
539
+ validate:
540
+ agent: false
541
+ gates:
542
+ defaultTimeoutMs: 120000
543
+ gates:
544
+ - id: feature-tests
545
+ skipWhen: skip-feature-test # Host does not run this Gate for a Feature that carries the label
546
+ timeoutMs: 720000
547
+ argv:
548
+ - node
549
+ - ./node_modules/@bluewombat/slots/gates/feature-tests.mjs
550
+ - --run
551
+ - pnpm test --grep "{ids}" --reporter=junit --outputFile={junit}
552
+ ```
553
+
554
+ `skipWhen` is the one condition Host reads for a Gate, on a Feature's Gates only
555
+ (`feature.validate`, `feature.gates`, `feature.fix`); its one value today is
556
+ `skip-feature-test`, whatever the tracker calls that label. A Gate left out is not a Gate that
557
+ passed: the journal gets `gate-skipped` and the trace says "`feature-test` not run: the Feature carries
558
+ skip-feature-test". The Gate knows nothing of the skip and no environment variable carries it.
559
+
560
+ The tracker side: a Ticket carrying the feature-test label (`featureTestLabel` of the TicketTracker manager) is
561
+ made by `feature.test`; a Feature carrying the skip label
562
+ (`skipFeatureTestLabel` of the FeatureTracker manager) has that Ticket cancelled, with a line in
563
+ the trace. The label is read again until the Feature is submitted. A Project that declares the
564
+ check refuses a Feature that holds neither a feature-test Ticket nor the skip label, or whose feature-test
565
+ Ticket is not blocked by every other Ticket, as `invalid`, with the reason as a comment.
566
+
567
+ What the Gate proves is a red/green verdict; what the operator reads is its text in the
568
+ journal (`gate-finished`) — a red suite is also in the reason of the refusal the fix is
569
+ given and in the escalation. A passing suite's text is not shown on the pull request: see
570
+ RESERVATIONS I17.
571
+
474
572
  ### Describer
475
573
 
476
574
  How a delivered ticket describes itself — the words the manager puts on the
@@ -543,31 +641,34 @@ it each stop `mason run` at boot, and `mason doctor` reports the same offline
543
641
  and is the operator's directory.
544
642
 
545
643
  ```
546
- assembled → align → assembly.validate (optional, local)
547
- refused → assembly.fix, then align again, then validate again
644
+ assembled → align → recheck (ticket.build.gates again, on the aligned work)
645
+ conflict or failed → ticket.fix, then align again
646
+ → ticket.validate (optional, local)
647
+ refused → ticket.fix, then align again, recheck, validate again
548
648
  accepted → Publisher places the work → manager opens the Submission
549
649
  → submitted, and the run comes back to it on each poll
550
- assembly.gates judge what was published
551
- refused → assembly.fix, then the same Submission is offered again
552
- accepted → the Authority folds it → done
650
+ ticket.gates judge what was published
651
+ refused → ticket.fix, then the same Submission is offered again
652
+ accepted → has the line moved on since the align? yes: align again
653
+ no: the Authority folds it → done (a refused fold also aligns again)
553
654
  ```
554
655
 
555
- Neither half judges: that is what `assembly.gates` are for. The Authority places
556
- and folds; a Gate says whether the fold may happen. `assembly.validate`, when
656
+ Neither half judges: that is what `ticket.gates` are for. The Authority places
657
+ and folds; a Gate says whether the fold may happen. `ticket.validate`, when
557
658
  declared, judges before either half — the same verdict has the same fate
558
659
  whether or not there is an Authority to offer the work to afterwards. After the
559
660
  Authority folds, Host brings its copy of the work line up to date with a
560
661
  fast-forward — the work line moved where the Authority is, and the next
561
662
  Isolation must start from that version, not from a copy left behind.
562
663
 
563
- A refusal is not a second failure path: its report reaches `assembly.fix`
564
- exactly as a Gate's report reaches `builder.repair`. After `maxRefusals`
664
+ A refusal is not a second failure path: its report reaches `ticket.fix`
665
+ exactly as a Gate's report reaches `ticket.build.repair`. After `ticket.maxRefusals`
565
666
  refusals the ticket escalates with that report as its Trace, and `done` names
566
667
  the Submission rather than a directory on the machine that ran.
567
668
 
568
669
  ## Write another manager
569
670
 
570
- Host loads whatever `featureTracker.package` and `ticketTracker.package` name, as long as it exports `createManager`. The
671
+ Host loads whatever `feature.tracker.package` and `ticket.tracker.package` name, as long as it exports `createManager`. The
571
672
  contract and a worked example are in
572
673
  [`@bluewombat/manager-kit`](../manager-kit/README.md). Nothing in Host changes.
573
674
 
@@ -636,7 +737,7 @@ change that breaks it does not merge.
636
737
  | `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 |
637
738
  | `plugins/` | Load a package by name and check its shape: a manager, an isolation strategy | What to do with the loaded thing | `config` |
638
739
  | `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 |
639
- | `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 |
740
+ | `operator/` | Tools for a human: `init`, `setup`, `doctor`, `watch` / `status`, `log`, `incident`, `cancel`, `restart`, `release`, `board`, the terminal prompt | Anything the loop needs — the loop cannot see this layer | everything below |
640
741
  | `index.ts`, `cli.ts` | The package surface and the dispatch. Nothing else sits at the top of `src/` | Logic | everything |
641
742
 
642
743
  Host's code names no plugin. A manager, an isolation strategy, a persistence
@@ -691,6 +792,6 @@ src/
691
792
  config/ argv, JSON config, config discovery → HostOptions; defaults: the plugin names Host ships with
692
793
  plugins/ load: the one loader; manager, isolation, persist: one shape each; discover
693
794
  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
694
- operator/ run, init, setup, doctor, explain, live (watch / status), log, film, cancel, release, board, prompt, git-remote
795
+ operator/ run, init, setup, doctor, explain, live (watch / status), log, incident, film, cancel, release, board, prompt, git-remote
695
796
  fixtures/ stand-in slots for the loop's tests
696
797
  ```