immune-brain 4.4.1 → 4.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 (51) hide show
  1. package/README.md +3 -2
  2. package/package.json +1 -1
  3. package/plugins/immune-brain/.claude-plugin/plugin.json +1 -1
  4. package/plugins/immune-brain/.pi-extension/imm-canary-enroll.ts +0 -1
  5. package/plugins/immune-brain/.pi-extension/imm-canary-work.ts +47 -2
  6. package/plugins/immune-brain/.pi-extension/imm-unattended-batch.ts +23 -18
  7. package/plugins/immune-brain/.pi-extension/runtime-stub.ts +0 -3
  8. package/plugins/immune-brain/dist/BASELINE.md +18 -1
  9. package/plugins/immune-brain/dist/claude/mcp-server.mjs +1401 -1101
  10. package/plugins/immune-brain/dist/docs/reference/subagent-dispatch-protocol.md +30 -0
  11. package/plugins/immune-brain/dist/imm-brainstorm.md +21 -0
  12. package/plugins/immune-brain/dist/imm-loop.md +52 -6
  13. package/plugins/immune-brain/dist/imm-planner.md +102 -13
  14. package/plugins/immune-brain/dist/role-prompts/code-review.md +19 -0
  15. package/plugins/immune-brain/dist/role-prompts/qa.md +13 -1
  16. package/plugins/immune-brain/runtime/assurance/coordinator.ts +55 -2
  17. package/plugins/immune-brain/runtime/assurance/review_evidence.ts +4 -0
  18. package/plugins/immune-brain/runtime/claude/interaction.ts +1 -2
  19. package/plugins/immune-brain/runtime/claude/kernel_ports.ts +73 -27
  20. package/plugins/immune-brain/runtime/claude/mcp_server.ts +3 -4
  21. package/plugins/immune-brain/runtime/claude/review_host.ts +15 -0
  22. package/plugins/immune-brain/runtime/github_issue_tracker.ts +956 -634
  23. package/plugins/immune-brain/runtime/kernel/application.ts +2 -5
  24. package/plugins/immune-brain/runtime/kernel/authority_port.ts +5 -14
  25. package/plugins/immune-brain/runtime/kernel/batch_authority.ts +30 -49
  26. package/plugins/immune-brain/runtime/kernel/canary_application.ts +1 -2
  27. package/plugins/immune-brain/runtime/kernel/canary_eligibility.ts +0 -1
  28. package/plugins/immune-brain/runtime/kernel/capability_registry.ts +7 -9
  29. package/plugins/immune-brain/runtime/kernel/enrollment.ts +21 -12
  30. package/plugins/immune-brain/runtime/kernel/enrollment_authority.ts +5 -11
  31. package/plugins/immune-brain/runtime/kernel/storage.ts +8 -3
  32. package/plugins/immune-brain/runtime/kernel/types.ts +2 -1
  33. package/plugins/immune-brain/runtime/kernel/validation.ts +3 -1
  34. package/plugins/immune-brain/runtime/plugin_version.ts +1 -1
  35. package/plugins/immune-brain/runtime/prompts/code-review.md +19 -0
  36. package/plugins/immune-brain/runtime/prompts/qa.md +13 -1
  37. package/plugins/immune-brain/runtime/unattended/batch_git.ts +9 -1
  38. package/plugins/immune-brain/runtime/unattended/batch_plan.ts +3 -9
  39. package/plugins/immune-brain/runtime/unattended/batch_preflight.ts +152 -46
  40. package/plugins/immune-brain/runtime/unattended/batch_reasons.ts +0 -6
  41. package/plugins/immune-brain/runtime/unattended/batch_reconfirmation.ts +193 -0
  42. package/plugins/immune-brain/runtime/unattended/batch_runner.ts +112 -122
  43. package/plugins/immune-brain/runtime/unattended/batch_state.ts +57 -16
  44. package/plugins/immune-brain/runtime/unattended/types.ts +0 -2
  45. package/plugins/immune-brain/runtime/v4_runtime.ts +54 -0
  46. package/plugins/immune-brain/runtime/workspace_scope.ts +75 -12
  47. package/plugins/immune-brain/skills/BASELINE.md +18 -1
  48. package/plugins/immune-brain/skills/imm-brainstorm/SKILL.md +5 -0
  49. package/plugins/immune-brain/skills/imm-loop/SKILL.md +4 -2
  50. package/plugins/immune-brain/skills/imm-planner/SKILL.md +10 -1
  51. package/plugins/immune-brain/runtime/unattended/confirmation_deadline.ts +0 -33
@@ -4,6 +4,18 @@
4
4
 
5
5
  ## Eligibility
6
6
 
7
+ Before any dispatch, state the bounded question and scope: the question being
8
+ investigated, the named paths the probe may read, the expected evidence, and the
9
+ stop condition that closes the probe. A probe without a stated stop condition is
10
+ an open-ended sweep, not a bounded one.
11
+
12
+ Read known paths directly first. Expand along proved callers, dependencies, and
13
+ state owners only when a concrete missing fact requires it, and stop once the
14
+ question is closed; do not continue reading because a path is nearby. Repository-wide
15
+ listings, unrelated directories, and other projects' session logs are out of
16
+ scope. Project-specific log or history searches stay inside the named project
17
+ paths; do not scan external session directories unless the user explicitly asks.
18
+
7
19
  派发前依次执行:
8
20
 
9
21
  1. 从任务摘要、changed paths 和显式请求分类为 `trivial`、`single_domain`、`multi_domain` 或 `high_risk`。
@@ -13,6 +25,23 @@
13
25
 
14
26
  常用 fallback reason:`cost_scope_mismatch`、`unavailable_environment`、`host_authorization_required`、`trigger_not_hit`、`unclear_boundary`。
15
27
 
28
+ ### Parallel Local Calls And Partial Results
29
+
30
+ Independent local calls may be issued together and their results inspected
31
+ together, `Promise.allSettled`-style, so one failure does not discard the other
32
+ results. Dependent actions stay ordered: an action that needs another's output
33
+ waits for it. Do not use a settled-all barrier for work that has a real sequence
34
+ dependency.
35
+
36
+ When output is truncated, continue in bounded pages only while the material is
37
+ still needed for the question; do not restart the read from the top or expand it
38
+ into a full-file sweep. Before retrying an operation that may have partially
39
+ succeeded, inspect what it already produced: a partial edit or partial write is
40
+ retried only for the part that is actually unfinished, never replayed wholesale.
41
+ Telemetry and tool-call counts are observational; they never justify omitting
42
+ evidence the question still requires, and they are not comparable to another
43
+ session's numbers unless the inputs and environment match.
44
+
16
45
  ## Authorization
17
46
 
18
47
  Canonical project grant:
@@ -103,6 +132,7 @@ Parent workflow role 必须:
103
132
  3. 保留 source lens 和 candidate attribution。
104
133
  4. 把 partial/error 标记为 `degraded`。
105
134
  5. 保留自身 baseline review,不把最终判断权交给 child。
135
+ 6. 报告 partial 结果时说明哪些部分已完成、哪些仍未完成,不把部分结果当作完整结果。
106
136
 
107
137
  普通 advisory/discovery 的每次启动都消耗一个 candidate budget slot;失败、取消、timeout 或 result_untrusted 均丢弃该输出且不得自动重试。Parent 仅在剩余候选仍独立有用且 evidence budget 仍需要时继续,否则转 solo/fail-closed fallback,并记录 `dispatch_failed` 或 `child_timeout`。Read-only eligibility 与 Pi 的 one-foreground-child 调度限制是两回事:只读调查可同时存在多个待派发候选,但实际执行仍逐个 foreground child 串行消费,不得把多个 foreground Agent 假定为并发 batch。该规则不改变 Kernel authority Review 的显式恢复协议。Child 永远不获得实现、Plan write、workflow mutation 或 QA closure authority。
108
138
 
@@ -65,6 +65,24 @@ delta and explain the new evidence. An explicit defer stops its subtree and is
65
65
  recorded as `BR-DEFER-*`; if the subtree still changes the current Result,
66
66
  interface, or compatibility, explain why it cannot be deferred.
67
67
 
68
+ Every sourced branch closes as resolved, explicitly excluded, explicitly
69
+ deferred, or dependent-blocked with its blocking prerequisite named. An excluded
70
+ or deferred branch is a recorded decision, not a silent omission; a
71
+ dependent-blocked branch blocks only its dependent commitment and is reported as
72
+ an open node. Coverage is complete when every sourced branch carries exactly one
73
+ of those four states, and no branch is dropped for being inconvenient, expensive,
74
+ or downstream. Exhaustive-interview mode changes interview breadth, never this
75
+ coverage. A clear zero-question request performs the same coverage check over
76
+ the request, repository evidence, and confirmed parent decisions before it hands
77
+ off.
78
+
79
+ Do not narrow a confirmed outcome into a smaller deliverable. "Do X fully" is
80
+ not satisfied by "do part of X", and a dependency ordering such as "prototype
81
+ first" is not a scope reduction such as "prototype only". A generalized
82
+ integration-to-CLI or prototype-first-to-prototype-only narrowing presented as
83
+ the complete result is a conservation failure; the handoff completeness check
84
+ rejects it instead of accepting the smaller deliverable.
85
+
68
86
  Brainstorm finishes when the material decision frontier is empty and no blocked
69
87
  fact prevents the current handoff. Independent framing may continue while a
70
88
  dependent subtree is blocked. If the user
@@ -97,6 +115,7 @@ empty, or report remaining blocked/open nodes if the user stops early.
97
115
 
98
116
  - **Trigger Shape**: Explicit Brainstorm entry permits proportionate clarification, including a zero-question handoff for a clear request. Read the exhaustive protocol only on an explicit request for thorough interrogation. Do not add a second confirmation for an unchanged final summary.
99
117
  - **Decision Provenance**: Investigate evidenced current-goal uncertainty. A concrete scenario is relevant when the request, repository, or a settled parent decision makes it material; do not invent speculative future needs.
118
+ - **Relevant Coverage**: Traverse every sourced branch — from the user request, repository evidence, or a settled parent decision — to one closing state: resolved, explicitly excluded, explicitly deferred, or dependent-blocked with the blocking node named. Ask only on the currently unblocked frontier, so a dependent-blocked branch stays open instead of being asked early or silently omitted. Coverage is a property of the traversal, not of the question count: a zero-question fast path and a bulk approval close the same branches the exhaustive traversal would.
100
119
  - **Dependency-Aware Rounds**: Ask every independent question on the complete currently unblocked frontier together. Ask fewer questions only because dependencies keep downstream branches blocked, never because of an arbitrary question budget.
101
120
  - **Read-only by default**: Inspect context and summarize the problem. do not implement inside this skill.
102
121
  - **Handoff**: Write concise design notes under `docs/brainstorms/` only if explicitly requested.
@@ -111,6 +130,8 @@ empty, or report remaining blocked/open nodes if the user stops early.
111
130
 
112
131
  Follow [`docs/reference/subagent-dispatch-protocol.md`](docs/reference/subagent-dispatch-protocol.md) for the full dispatch lifecycle. This section defines brainstorm-specific optional research dispatch.
113
132
 
133
+ Before any research path that leaves the repository — an external model or provider call, a fetch of non-project content, or sending project data off-host — check the project's existing research channel and data scope. Documentation, mocks, and local read-only reads are not real-channel evidence, and a public article does not select or authorize a provider. Consume the existing channel; a genuinely new channel or data scope is a material delta that needs its own authorization. Report documentation, mock, real-channel, and model-quality evidence as distinct claims rather than one undifferentiated "verified".
134
+
114
135
  Runtime helpers: `imm_core.buildBrainstormEnsembleRequest`, `imm_core.buildBrainstormEnsembleDispatchEnvelopes`, `imm_core.normalizePiBrainstormAgentResults`, and `imm_core.normalizeBrainstormEnsemblePacket`.
115
136
 
116
137
  ### Brainstorm Ensemble Advisory
@@ -74,8 +74,12 @@ Continue while the current projection has a valid action:
74
74
  4. Follow the returned Kernel obligation. Fresh QA suffices for routine work;
75
75
  material and critical work additionally require fresh independent Review.
76
76
  Normal completion does not require a second user confirmation.
77
- 5. For rework, follow the projected artifact state before editing. Resolve
78
- findings only after fixing and verifying their cause. Changed snapshots
77
+ 5. For rework, follow the projected artifact state before editing. Submit the
78
+ rework verdict first, then edit: staging an in-scope change while the
79
+ artifacts are frozen invalidates the snapshot and discards that verdict,
80
+ forcing a fresh QA round. After the Kernel returns the artifacts to active,
81
+ make the fix, resolve findings only after fixing and verifying their cause,
82
+ then run the newly required obligations. Changed snapshots
79
83
  invalidate old evidence; run the newly required obligations. On the second
80
84
  rework of one acceptance id or anchor, the fix response must either state why
81
85
  it covers every known trigger class of the violated invariant — the shared
@@ -126,11 +130,18 @@ The runtime owns no model invocation and adds no persisted execution lifecycle.
126
130
  After diagnostics pass, the Parent advances Kernel QA and dispatches any reserved
127
131
  foreground Review once using the exact returned `agent_params`, then submits its
128
132
  structured verdict. An open Review reservation stays with that Host invocation;
129
- batch re-entry waits for it rather than redispatching. On Kernel `done`, stage
130
- only that child's generated terminal audit evidence and call
131
- `start_unattended_batch` with the same slug. The driver reconciles settlement,
133
+ batch re-entry waits for it rather than redispatching. When a batch child's
134
+ foreground terminal mutation reaches Kernel `done`, the same tool call stages
135
+ only that child's generated terminal audit evidence and re-enters the batch under
136
+ the still-valid authorization, opening no gate. The driver reconciles settlement,
132
137
  adopts or creates the one scope-bound commit, then enrolls the next ready child
133
- under the still-valid authorization. Ordinary Executor handoff and below-limit
138
+ or settles the batch as `completed`; the result carries the batch report as
139
+ `batch`. A reserved Review, a parked child and a stopped child end where they do
140
+ today and are never committed automatically. If the re-entry fails, the Kernel
141
+ result is unchanged and `batch` carries one recovery action: call
142
+ `start_unattended_batch` with the same slug, which is also how to continue by
143
+ hand. A commit that fast-forwards the batch branch outside the runner is adopted,
144
+ not attributed to a child; any other HEAD movement still fails closed. Ordinary Executor handoff and below-limit
134
145
  QA repair do not park the batch; genuine budget/authority stops remain fail-closed.
135
146
 
136
147
  Batch execution never pushes a ref, opens or updates a pull request, resolves a
@@ -140,6 +151,27 @@ per completed child.
140
151
 
141
152
  ## Decisions and Recovery
142
153
 
154
+ - Resume from authority facts, not prose. After an uncertain interruption, bind
155
+ the exact `task_id`, `run_id`, record revision, lifecycle, artifact state,
156
+ obligation, and claim from the Kernel projection before acting. A compaction
157
+ heading, a Handoff summary, an Issue state, or any other prose summary is not
158
+ authority and can be stale or wrong: when it conflicts with the projection,
159
+ correct the prose and resume the existing owner instead of trusting the summary.
160
+ - The workflow stages are distinct and never inferred from one another:
161
+ authored, validated, published, enrolled, assured, and completed each require
162
+ their own evidence. An open or closed Issue is not Enrollment or completion; a
163
+ published Issue is not Enrollment; a plan-only delivery is not execution
164
+ readiness.
165
+ - Consume a successful operation's fresh projection directly. Do not add a
166
+ status read after an operation that already returned the current projection, and
167
+ never repeat a mutation merely to re-observe its result. Kernel CAS and
168
+ freshness checks remain mandatory, and reducing Parent reads does not bypass
169
+ them.
170
+ - A falsely claimed later-Slice enrollment is a summary defect, not authority.
171
+ Retain the actual owner, and do not create or replay a mutation, gate, or
172
+ completion to match the prose. Summary production outside this repository is not
173
+ locally fixed; the consumer-side protection is these rules, and a producer bug
174
+ is recorded as a separate follow-up rather than resolved here.
143
175
  - Scope expansion returns to Planner's Enrolled Intent Revision route. Planner
144
176
  prepares the complete proposed revision without replacing the active owner;
145
177
  the current Loop submits it through Kernel revision authority. Collect all
@@ -147,6 +179,14 @@ per completed child.
147
179
  reasons in one request. Do not edit outside scope while waiting or widen it
148
180
  piecemeal without new evidence. Bounded test or PR repair stays inside the same
149
181
  TaskIntent.
182
+ - Effects are separate grants. A stage-only recovery restores staging and grants
183
+ no commit, push, or publication; an existing exact approval, including a valid
184
+ batch capability bounded to one child, stays usable without another chat gate.
185
+ Before a new outbound research call, check the project's existing channel and
186
+ data scope; a public article, a mock, or a local read-only database authorizes
187
+ no new provider or data effect.
188
+ - Text instructions are contracts, not a hard bash sandbox. They guide and are
189
+ reviewed; the Kernel authority gates remain the real boundary.
150
190
  - Invoke `approve_breaking_intent_revision` with the complete next intent
151
191
  directly; the native Host gate is the single user decision. Do not overwrite
152
192
  enrolled intent sidecars or ask for chat pre-confirmation.
@@ -240,6 +280,12 @@ Stop reason:
240
280
  Next action:
241
281
  ```
242
282
 
283
+ Every line reports authority evidence, not prose inference: name the exact
284
+ task/run identity and the observed lifecycle, artifact, obligation, and claim
285
+ facts that decided the exit. If a summary claimed more than the projection
286
+ proves — for example a later Slice as enrolled or the task as completed — report
287
+ the projection instead and correct the prose.
288
+
243
289
  ## Failure Output
244
290
 
245
291
  For `settlement_unknown`, call `advance_assurance` once to reconcile the Kernel
@@ -49,6 +49,25 @@ decisions. Resolve a local delta here. If the answer reopens multiple product
49
49
  branches or changes the overall goal or Scope, stop and return to
50
50
  `imm-brainstorm`.
51
51
 
52
+ Before handoff, run one handoff completeness check over the closed-world
53
+ manifest: map every upstream item and every required phase exactly once, report
54
+ each Slice's closable result plus the remaining gap, and confirm the complete
55
+ confirmed outcome and its phases are actually delivered. Upstream items close as
56
+ covered, decisioned, deferred, or out with a reason; a required phase is the
57
+ confirmed outcome itself, so it must be delivered rather than closed out with a
58
+ reason. A reason excuses an upstream item, never a still-required phase: dropping
59
+ one is exactly the silent narrowing this check exists to catch, and the genuine
60
+ reduction is a user decision that returns to `imm-brainstorm` and changes what
61
+ was required. The check fails, and the candidate is not handoff-ready, when a
62
+ required phase or upstream item has no mapping, or when the plan conserves a
63
+ smaller deliverable than the confirmed outcome. An outcome-narrowing
64
+ substitution is such a failure: a confirmed full integration presented as one
65
+ pairwise CLI, or a `prototype first` dependency presented as `prototype only`.
66
+ Both are conservation failures, not scope decisions, and the checker rejects
67
+ them instead of accepting the reduced deliverable. Coverage is independent of
68
+ question count, so a clear request and an already-confirmed decision still pass
69
+ the same check without acquiring an extra confirmation.
70
+
52
71
  Direct Planner entry remains valid for a clear request and does not require a
53
72
  Brainstorm pass. Resolve facts and derive technical design; if an unresolved
54
73
  user-owned product decision appears, return to `imm-brainstorm` rather than
@@ -254,8 +273,21 @@ goal prose and never truncates a title. The tracker rereads every canonical
254
273
  TaskIntent for identity, risk, and acceptance IDs; projection fields and public
255
274
  summaries never widen TaskIntent scope or authority. It validates the complete dependency graph before
256
275
  remote writes, creates the Parent once, creates all Children, attaches every
257
- Child as a native Sub-issue, creates native `blocked_by` relations, and rereads
258
- the complete topology. Every Child carries `ready-for-agent`, blocked Children
276
+ Child as a native Sub-issue, creates native `blocked_by` relations, and takes
277
+ every Issue number from its own create response. The write flow is direct: one
278
+ start-of-run read of the repository identity, the Issue listing, and the label
279
+ listing, then one create per absent Issue in dependency order, one attach per
280
+ Child, and one relation write per edge. No repository listing, attachment,
281
+ dependency or ownership confirmation follows a write; one Issue-scoped read
282
+ follows each create to resolve the database id the relation endpoints need,
283
+ because `gh issue create` returns only the Issue URL. No cumulative deadline
284
+ exists either; each call keeps its own per-call timeout and
285
+ the caller's cancellation. Deduplication is start-only: a rerun of the same
286
+ approved batch adopts whatever the start listing already carries and writes only
287
+ the missing relations, so it never creates a duplicate Issue and never repeats a
288
+ completed write. Concurrent edits made during a run are not detected in that
289
+ run; they surface as drift at the start of the next run. Every Child carries
290
+ `ready-for-agent`, blocked Children
259
291
  additionally carry `blocked`, and the Parent carries neither; the tracker never
260
292
  creates labels, so a repository missing a required label fails the batch closed
261
293
  before any remote write. The Child Agent Brief includes a direct Parent Issue link.
@@ -279,27 +311,39 @@ complete-decomposition decision; or `tracker_projection_failed` with the returne
279
311
  failure and exact retry action. A candidate Initiative or partial Issue set
280
312
  recorded only in the Spec or final summary is neither user confirmation nor a
281
313
  completed carrier outcome. Report `retryable_failure`, `permanent_failure`, or
282
- `ambiguous_remote_state` and the exact batch retry action. This does not invalidate
314
+ `ambiguous_remote_state` and the exact batch retry action. A failure after the
315
+ request may have landed is reported as uncertain with its confirmed steps, its
316
+ pending steps, and one recovery action: rerun the same approved batch, whose
317
+ start listing adopts whatever landed. This does not invalidate
283
318
  already-authored planning files, but it blocks `tracker_associated` and every
284
319
  Enrollment or execution handoff for that Initiative until the same complete
285
- batch succeeds. Do not infer opt-in from tracker output or Issue state, auto-close the Parent,
286
- import Issue state, create a TaskIntent from an Issue, or store Issue identity in
320
+ batch succeeds. Do not infer opt-in from tracker output or Issue state, import Issue state, create a TaskIntent from an Issue, or store Issue identity in
287
321
  TaskIntent or TaskRecord. Existing Issue markers grant permission only for
288
322
  idempotent retry of that same approved Initiative; they never grant execution
289
323
  authority.
290
324
 
325
+ The Planner never closes the Parent. The terminal projection of a Child does: when
326
+ `mark-terminal` closes a Child as completed and every Slice Child of that Initiative
327
+ is closed as completed, the tracker closes the Parent as completed; an open, stopped
328
+ or parked Child keeps it open.
329
+
291
330
  For amending an already-published Initiative, the same
292
331
  `imm-tracker publish-initiative --stdin --json` operation accepts an optional
293
332
  `amendment` input after the literal user approves the changed pending frontier.
294
333
  Provide the complete approved pending Tasks plus the complete read-only
295
334
  historical Child list, each binding the observed `issue_number`, title, body,
296
- and state at approval. The tracker verifies every bound Issue against that
297
- baseline before any write, updates only approved pending briefs, converges the
298
- exact approved pending `blocked_by` set, creates newly added pending Children,
299
- and preserves historical Children byte-for-byte with their native Sub-issue
300
- links and dependency relations. Omitted membership, baseline drift, or a
301
- stopped historical prerequisite fails closed with zero or bounded mutations
302
- and an exact retry action; the strict no-amendment default is unchanged.
335
+ and state at approval. The amendment uses the same direct write flow: one start
336
+ listing (plus Issue-scoped Sub-issue and `blocked_by` reads) verifies every bound
337
+ Issue against its baseline, then the Parent is edited at most once, each approved
338
+ pending brief is edited at most once, newly added pending Children are created as
339
+ in ordinary publication, and each pending Child's `blocked_by` set is converged
340
+ to the approved set. Historical Children are never written and keep their
341
+ byte-exact content, native Sub-issue links and dependency relations. No read
342
+ follows a write and no closing recheck runs, so baseline drift that appears after
343
+ the start listing is not detected in that run; it surfaces at the start of the
344
+ next run. Omitted membership, baseline drift present at start, and a stopped
345
+ historical prerequisite fail closed before any write with an exact retry action;
346
+ the strict no-amendment default is unchanged.
303
347
 
304
348
  ### Verification Descriptor Discipline
305
349
 
@@ -327,9 +371,36 @@ before execution. Report `valid` and `enrollment_ready` only as structural and
327
371
  Enrollment readiness; only a completed deterministic QA result proves that a
328
372
  descriptor executed and passed.
329
373
 
374
+ **Acceptance-to-invariant mapping.** Trace each acceptance assertion to an
375
+ observable invariant at a real seam, then name the positive, negative, and bound
376
+ controls that exercise it. Name the controls explicitly instead of relying on
377
+ the happy path: a happy path alone never proves a guard. The six generalized
378
+ motivating classes to check for are: normalization precedes retrieval (an input
379
+ is normalized before it is used to retrieve); validation precedes merge (a
380
+ rejected input never enters the merged state); the deadline covers the whole
381
+ lifecycle including cleanup; ranking tolerates missing embeddings (an absent
382
+ signal is ranked, not silently skipped); the budget covers the complete
383
+ serialized request; and each success, failure, and timeout outcome carries its
384
+ own duration evidence. These are workflow-quality input patterns, not requests
385
+ to edit the original applications that motivated the audit.
386
+
387
+ **Environment and prerequisite discipline.** A descriptor that needs a
388
+ prerequisite states its provenance and how it is obtained. Preparation must not
389
+ hide a download, credential use, production write, or system change: authorize
390
+ it explicitly, or redesign the check to avoid it. Require cleanup on failure and
391
+ interruption, and restoration of any explicitly authorized setting. An absent
392
+ dependency, a missing simulator, or a preparation failure is a failed check, not
393
+ a pass. Zero matched checks and skipped required checks are failures: a required
394
+ check that selects no test, or that is skipped, must fail the acceptance rather
395
+ than silently succeed. Separate automated behavioral or geometry evidence from
396
+ human judgment of motion quality; a descriptor can assert only the automated
397
+ half. Textual presence in a contract string is not proof that a provider obeyed
398
+ it, and structural Enrollment readiness is not proof that a descriptor executed.
399
+
330
400
  ## Core Responsibilities
331
401
 
332
402
  - **Decomposition**: Convert requirements into one or more TaskIntents. Add a Spec under `docs/specs/` only for complex work. Treat Technical Design as one TaskIntent decomposition dimension alongside outcome, Verification, dependency, risk, rollback, compatibility, and authority.
403
+ - **Outcome Conservation**: The plan conserves the complete confirmed outcome and its required phases. A required phase is delivered, not closed out with a reason; a genuine reduction changes what the user confirmed and returns to `imm-brainstorm`. A generalized substitution such as full integration becoming a pairwise CLI, or `prototype first` becoming `prototype only`, is a completeness-check failure rather than a valid plan. Each Slice reports its closable result and the remaining gap so an omitted phase is visible instead of implied.
333
404
  - **Outcome Focus**: Each TaskIntent owns one independently verifiable outcome. Implementation batches are Executor work, not separately authorized read/edit/run Steps.
334
405
  - **Planning granularity**: Keep a coherent outcome together when acceptance, risk, rollback, and authority can settle together. Use the TaskIntent decomposition rules below for independent outcomes. File count, tokens, compactions, elapsed time, and review rounds are evidence for judgment, not universal gates.
335
406
  - **Historical artifacts**: v3 prose Plan mutation is retired. `imm-plan` is a read-only validator for archived Plans; create no new Roadmap, Phase, successor Plan, or State Ledger.
@@ -340,7 +411,7 @@ descriptor executed and passed.
340
411
  - **Technical Design Authority**: The Spec is the single Technical Design baseline. TaskIntent acceptance and scope reference the applicable design decisions or invariants without copying Technical Design prose. If discovery invalidates the baseline, stop execution and return to Planner to update the Spec and decide whether `replan` is required. TaskIntent and Initiative text do not duplicate Technical Design prose or become a prose Plan substitute.
341
412
  - **TaskIntent decomposition**: Use the selected design boundaries as one retain/split criterion for TaskIntent slices. Keep work in one TaskIntent when the selected views describe one coherent executable slice with shared acceptance, risk treatment, rollback, and authority. Split a successor TaskIntent when a service boundary, state-machine owner, migration/compatibility boundary, independently promotable layer, or sequence dependency needs independent verification, rollback, authorization, or settlement. Do not split merely because the design names several layers, files, or services. Treat trust-boundary changes as the same kind of decomposition evidence: a TaskIntent should normally change one primary trust-boundary invariant, while merely traversing several boundaries or updating both sides of one end-to-end authority chain does not require a split. Split separate trust invariants when they can be independently verified, rolled back, authorized, migrated, or settled. Keep multiple trust-boundary changes together only when they form one atomic security outcome and splitting would create an unsafe or unusable intermediate state; record that reason in the Spec. This is Planner judgment, not a TaskIntent schema field or an Enrollment counting rule. This does not revive prose Plan, Roadmap, or Phase authority.
342
413
  - **Mermaid Use**: Mermaid is required only when a medium/high-risk design contains structure, sequence, data flow, or state transition relationships that a diagram materially clarifies. Mermaid is not a universal gate; a diagram supplements adjacent prose and never becomes a second design authority. Medium/High risk Specs record `**Diagram decision**: required|not_required` and a non-empty `**Diagram reason**:`. A `required` decision must have a Mermaid block; `not_required` explains why prose is sufficient. Low-risk Specs omit the empty ceremony and record neither field.
343
- - **Verification**: Every acceptance assertion has a concrete focused descriptor that can fail on the intended regression. Hypothetical evidence is not execution-ready.
414
+ - **Verification**: Every acceptance assertion has a concrete focused descriptor that can fail on the intended regression, and every named invariant has positive, negative, and bound controls at an observable seam. Hypothetical evidence is not execution-ready; a check that matches zero tests, is skipped, or lacks its prerequisite is a failure rather than a pass.
344
415
  - **Executable Scope**: `scope_hint` is the mutation envelope, not discovery context. Close references across callers, tests, generated mirrors, and state-machine owners before authoring. Simple tasks are TaskIntent-only. A complex task binds at most one active Spec by content identity; do not add archive paths for freeze, and do not relocate artifacts. Collect all known scope gaps in one revision request; ask again only when new evidence changes the boundary.
345
416
  - **Devil's Advocate Preplan Audit**: Medium/High risk work records a `Devil's Advocate Audit` in its Spec covering rollback resilience, verification vanity, and spec dilution detection. Explain recovery from partial implementation, why verification detects the regression, and how accepted requirements remain covered. Simple TaskIntent-only work records outcome, boundary, and concrete verification on the Intent. Low-risk work omits the empty template.
346
417
  - **Execution posture**: Record `test-first` or `characterization-first` on the Spec when the work is complex, otherwise on the TaskIntent, when explicitly requested or justified by fragile untested behavior. The Executor owns the local choreography; do not create prototype or RED/GREEN/REFACTOR authority Steps. Throwaway probes must have a cleanup condition and a durable decision output.
@@ -433,11 +504,23 @@ preparation does not apply the revision or authorize expanded execution.
433
504
  verification descriptors or add a mandatory user confirmation.
434
505
  - **Review Mapping**: In-scope rework stays with the enrolled TaskIntent and explicit `imm-loop` entry. Cross-scope findings become a Planner decision delta with concrete missing paths and verification evidence; do not create a successor prose Plan.
435
506
  - **Brainstorm Manifest Mapping**: Record every upstream `BR-*` item in a Spec `Brainstorm Trace` when the work is complex, otherwise on the TaskIntent, mapped to acceptance, a captured decision, or an explicit reason for deferral or exclusion. Resolve every `BR-Q-*` item before handoff. Do not silently narrow confirmed framing.
507
+ - **Handoff Completeness Check**: Before reporting a candidate as handoff-ready, confirm every upstream item and required phase is mapped exactly once, that required phases are delivered rather than excused by a reason, and that no confirmed outcome was conserved as a smaller deliverable. Reject a narrowed deliverable as a completeness failure; ask again only for a new material delta, never to reconfirm an unchanged clear request or an already-confirmed decision.
436
508
  - **Session Lifecycle Ownership**: The user chooses the current or a new session. Tokens, compactions, tool counts, elapsed time, and review rounds never trigger automatic session creation or termination. Recovery uses TaskRecord and the fresh Kernel projection.
437
509
  - **Subagents**: Only when optional research is needed, read Research Dispatch and its shared dispatch reference. Default to inline evidence gathering. Plan conditional reviewers such as `security-reviewer` only if their trigger surfaces are explicit; do not manufacture them.
438
510
  - **Enrolled Intent**: Follow Enrolled Intent Revision for a Loop-requested scope or acceptance change; candidate preparation never changes the current owner or grants execution authority.
439
511
  - **CONTEXT.md Vocabulary**: Consult the relevant `CONTEXT.md` terms when domain meaning is unclear or changes; known file-local tasks do not require a full root-document read. `CONTEXT.md` is vocabulary and architecture navigation, not execution state.
440
512
  - **Discovery Protocol**: Read `CONTEXT.md` `## Architecture Map` before broad searching; consult relevant `docs/solutions/` evidence under Clarification supplement's history trigger. Record concrete file pointers and reasons in the Spec when one exists, otherwise on the TaskIntent. Do not read or write a legacy Step discovery cache.
513
+
514
+ **Bounded discovery.** State the question, the named paths, the expected evidence,
515
+ and the stop condition before an investigation expands. Read known paths directly,
516
+ expand only along proved callers and state owners, and stop once the question is
517
+ closed instead of continuing into nearby unrelated paths. Keep project-specific
518
+ log or history searches inside the named project paths. Reads or investigation
519
+ output that is truncated continue in bounded pages only while still needed, and an
520
+ operation that may have partially succeeded is inspected before it is retried for
521
+ only its unfinished part. Telemetry and tool-call counts are observational: they
522
+ never justify dropping evidence the question requires, and they are not comparable
523
+ to another session's numbers unless the inputs and environment match.
441
524
  - **Planning Quality Gate**: For elevated-risk complex work, verify contract surfaces, compatibility, interruption recovery, rollback, verification strength, and Brainstorm traceability in the Spec. Simple TaskIntent-only work verifies those properties on the Intent. Do not invoke retired Plan mutation or State Ledger synchronization.
442
525
  - **Parallel Probes**: Optional read-only probes must have bounded non-overlapping scopes, expected evidence, and no file or authority writes. They are advisory discovery, not persisted Step annotations. Probe failure falls back to inline investigation with a recorded reason.
443
526
 
@@ -445,6 +528,8 @@ preparation does not apply the revision or authorize expanded execution.
445
528
 
446
529
  Follow [`docs/reference/subagent-dispatch-protocol.md`](docs/reference/subagent-dispatch-protocol.md) for the full dispatch lifecycle. This section defines planner-specific optional research dispatch.
447
530
 
531
+ A discovery path that leaves the repository — an external model/provider call or sending project data off-host — first checks the project's existing research channel and data scope. Architecture exploration and local reads stay on the existing channel; a new channel or data scope is a material delta with its own authorization, and documentation, mock, or local read-only evidence never substitutes for real-channel evidence.
532
+
448
533
  Use the invoking Host's read-only role-boundary route for bounded
449
534
  `arch-explorer` and explicit-lens `advisory-reviewer` routing. Invoke the returned foreground Agent envelope
450
535
  exactly. The Parent owns Spec/TaskIntent synthesis, Brainstorm traceability,
@@ -492,14 +577,18 @@ a State Ledger. Keep historical Plan validation strictly read-only.
492
577
  | Split one outcome into read/edit/run micro-steps | One TaskIntent owns one closable outcome; Executor owns implementation batches. |
493
578
  | Append repair outside scope | Return the complete known scope delta for Kernel revision; do not widen execution. |
494
579
  | Drop a brainstorm-confirmed item as "out of scope" without saying so | Closed-world handoff: every `BR-*` ID must be covered, decisioned, deferred, scoped out with reason, or resolved as an assumption. |
580
+ | Conserve a narrower deliverable than the confirmed outcome | **Conservation failure**: a full integration is not a pairwise CLI and `prototype first` is not `prototype only`; the handoff completeness check rejects the reduced deliverable instead of accepting a reason for it. |
495
581
  | Finalize dependent planning while brainstorm questions are open | **Clarification Barrier**: Finalization and dependent commitments are blocked until required `BR-Q-*` items are answered. Independent investigation and explicitly unapproved alternative drafts may continue; silence is not consent. |
496
582
  | Skip adversarial self-review on Medium/High risk because the plan is small | **Devil's Advocate** audit still checks rollback resilience, verification vanity, and spec dilution before the plan is treated as execution-ready. |
497
583
 
498
584
  ## Red Flags
499
585
 
500
586
  - Acceptance verification names only hypothetical evidence with no runnable descriptor.
587
+ - An acceptance descriptor names no negative or bound control for its invariant, or its required prerequisite (dependency, simulator, preparation) is absent and still counted as a pass.
588
+ - A required check matches zero tests or is skipped and the run is reported as passing.
501
589
  - New work depends on a prose Plan validator, Step activation, or State Ledger.
502
590
  - A Brainstorm manifest lacks complete `BR-*` coverage on the Spec (complex) or TaskIntent (simple).
591
+ - The handoff completeness check passed while a required phase or upstream item is unmapped, or while the plan delivers only part of the confirmed outcome.
503
592
  - A Medium/High risk Spec lacks a `Devil's Advocate Audit` covering rollback resilience, verification vanity, and spec dilution detection.
504
593
  - New Spec prose ignores the document-language policy.
505
594
  - Candidate artifacts escape the approved planning scope.
@@ -34,6 +34,25 @@ about the fix's scope, not a new bug. A trigger the accepted contract explicitly
34
34
  excludes is reported as an advisory note against that boundary, never as blocking
35
35
  rework.
36
36
 
37
+ ## Invariant and Evidence Coverage
38
+
39
+ Judge whether the change actually closes the invariant its acceptance names,
40
+ not only the path the report mentions. A fix that covers one trigger but leaves
41
+ another trigger of the same invariant is a coverage gap in the same class, not a
42
+ new independent bug: say so and name the class. Check the negative and bound
43
+ behavior, not only the happy path. The six generalized classes worth checking
44
+ are: normalization precedes retrieval, validation precedes merge, the deadline
45
+ covers the whole lifecycle including cleanup, ranking tolerates missing
46
+ embeddings, the budget covers the complete serialized request, and each success,
47
+ failure, and timeout outcome carries its own duration evidence.
48
+
49
+ A required check that is skipped, that matches zero tests, or whose prerequisite
50
+ is absent does not prove the acceptance; do not accept it as evidence. Full
51
+ suite coverage reported at a coarse pass count is not per-invariant evidence.
52
+ Environment, preparation, and cleanup breakdowns are environment findings, not
53
+ assertion findings. Keep automated behavioral or geometry evidence separated
54
+ from a human quality judgment.
55
+
37
56
  Return exactly one JSON object with the fields required by the Loop review
38
57
  contract: `contract`, `role`, `task_id`, `snapshot_digest`, `decision` (`pass`
39
58
  or `rework`), and for `pass` include `approval` (`kind`, `authority_role`,
@@ -20,4 +20,16 @@ Plan remains a literal-user decision.
20
20
  For elevated-risk work, check the latest referenced Spec's Design Conformance
21
21
  against implementation evidence. A local implementation mismatch is `rework`;
22
22
  return `rework` with bounded repair evidence. A structural or intended design
23
- change is `replan`; return `replan` with the missing design fact. QA must not approve a changed design or silently accept a deviation. If the checkpoint is `awaiting_user_successor_decision`, stop without dispatch; only a literal user may invoke `--approve-successor`.
23
+ change is `replan`; return `replan` with the missing design fact. QA must not approve a changed design or silently accept a deviation.
24
+
25
+ Judge the recorded execution evidence, not the descriptor text. A required check
26
+ proves an acceptance invariant only when it actually executed and passed: an
27
+ absent dependency, a prerequisite preparation failure, a skipped required check,
28
+ or a check that matched zero tests is a failure, not a pass. Structural
29
+ "Enrollment ready" or "descriptor valid" is not execution evidence, and a
30
+ contract string proves nothing about provider behavior. Automated behavioral or
31
+ geometry evidence cannot stand in for a human motion-quality judgment, and vice
32
+ versa; keep the two claims separate. Environment, preparation, and cleanup
33
+ breakdowns are environment findings, not assertion findings: report them with
34
+ the descriptor reference and observed outcome rather than as a code defect.
35
+ If the checkpoint is `awaiting_user_successor_decision`, stop without dispatch; only a literal user may invoke `--approve-successor`.
@@ -316,7 +316,10 @@ export interface ForegroundToolUpdate {
316
316
  details: Record<string, unknown>;
317
317
  }
318
318
 
319
+ /** Wall-clock milliseconds spent per advance stage; the stage names are the progress stages. */
320
+ export interface AssuranceTimings { total_ms: number; stage_ms: Record<string, number> }
319
321
  type AssuranceRecoveryFields = {
322
+ timings?: AssuranceTimings;
320
323
  diagnostics?: QaCheckDiagnostic[];
321
324
  environment_failure?: boolean;
322
325
  recovery?: AssuranceRecovery;
@@ -404,12 +407,23 @@ export interface AssuranceCoordinatorPorts {
404
407
  qaOnAuthorityCommit?: () => void;
405
408
  qaAfterAuthorityCommit?: () => Promise<void>;
406
409
  qaJobTimeoutMs?: number;
410
+ /** Attach per-stage `timings` to advance results. Defaults to IMM_ASSURANCE_TIMINGS=1. */
411
+ reportTimings?: boolean;
412
+ /** Overrides QA_MAX_IDENTICAL_FAILURES; tests only. */
413
+ qaMaxIdenticalFailures?: number;
407
414
  }
408
415
 
409
416
  export const QA_MIN_JOB_TIMEOUT_SECONDS = 15 * 60;
410
417
  export const QA_MAX_JOB_TIMEOUT_SECONDS = 60 * 60;
411
418
  export const QA_JOB_OVERHEAD_SECONDS = 2 * 60;
412
419
  export const QA_JOB_TIMEOUT_SECONDS = QA_MIN_JOB_TIMEOUT_SECONDS;
420
+ /**
421
+ * A QA run that fails before attestation writes no authority, so the Kernel
422
+ * would happily rerun the same descriptors forever. After this many identical
423
+ * failures on the same snapshot (record revision, intent hash, diff hash) the
424
+ * coordinator refuses to rerun until the snapshot changes.
425
+ */
426
+ export const QA_MAX_IDENTICAL_FAILURES = 2;
413
427
  export const REVIEW_PREPARATION_TIMEOUT_MS = 30_000;
414
428
  export const REVIEW_DISPATCH_TIMEOUT_MS = 120_000;
415
429
  export const REVIEW_VERDICT_VALIDATION_TIMEOUT_MS = 30_000;
@@ -705,6 +719,8 @@ export class AssuranceCoordinator {
705
719
  private readonly operationControllers = new Map<string, { operationId: string; controller: AbortController }>();
706
720
  private readonly reviewReservations = new Map<string, ReviewReservation>();
707
721
  private readonly rejectedReviewOperations = new Map<string, { operationId: string; reason: string }>();
722
+ private readonly qaFailureStreaks = new Map<string, { key: string; count: number }>();
723
+ private readonly qaAttemptKeys = new Map<string, string>();
708
724
  private readonly unknownOperations = new Map<string, { operation: "qa" | "review"; operationId: string; reason: string }>();
709
725
  private readonly sessionInvocations = new Set<InvocationToken>();
710
726
  private sessionActive = true;
@@ -731,6 +747,8 @@ export class AssuranceCoordinator {
731
747
  this.removeEvidence(reservation);
732
748
  }
733
749
  this.reviewReservations.clear();
750
+ this.qaFailureStreaks.clear();
751
+ this.qaAttemptKeys.clear();
734
752
  for (const invocation of [...this.sessionInvocations]) this.closeSessionInvocation(invocation);
735
753
  }
736
754
 
@@ -762,7 +780,19 @@ export class AssuranceCoordinator {
762
780
 
763
781
  async advance(taskId: string, ctx: HostContext, signal?: AbortSignal, onUpdate?: (update: ForegroundToolUpdate) => void): Promise<AssuranceAdvanceResult> {
764
782
  const checks = new Map<string, QaCheckDiagnostic>();
783
+ const started = performance.now();
784
+ const stageMs: Record<string, number> = {};
785
+ let stage = "preparing";
786
+ let stageStarted = started;
787
+ const closeStage = (now: number) => { stageMs[stage] = (stageMs[stage] ?? 0) + Math.round(now - stageStarted); };
765
788
  const result = await this.advanceOnce(taskId, ctx, signal, update => {
789
+ const next = update.details.stage;
790
+ if (typeof next === "string" && next !== stage) {
791
+ const now = performance.now();
792
+ closeStage(now);
793
+ stage = next;
794
+ stageStarted = now;
795
+ }
766
796
  const diagnostic = update.details.diagnostic as QaCheckDiagnostic | undefined;
767
797
  if (diagnostic) {
768
798
  checks.set(diagnostic.descriptor_ref, diagnostic);
@@ -772,8 +802,25 @@ export class AssuranceCoordinator {
772
802
  }
773
803
  onUpdate?.(update);
774
804
  });
805
+ this.recordQaAttempt(taskId, result);
775
806
  const diagnostics = result.diagnostics ?? [...checks.values()];
776
- return this.withRecovery(taskId, ctx, diagnostics.length ? { ...result, diagnostics } : result);
807
+ const enriched = diagnostics.length ? { ...result, diagnostics } : result;
808
+ if (!(this.ports.reportTimings ?? process.env.IMM_ASSURANCE_TIMINGS === "1")) return this.withRecovery(taskId, ctx, enriched);
809
+ const finished = performance.now();
810
+ closeStage(finished);
811
+ return this.withRecovery(taskId, ctx, { ...enriched, timings: { total_ms: Math.round(finished - started), stage_ms: stageMs } });
812
+ }
813
+
814
+ private recordQaAttempt(taskId: string, result: AssuranceAdvanceResult): void {
815
+ const key = this.qaAttemptKeys.get(taskId);
816
+ this.qaAttemptKeys.delete(taskId);
817
+ if (!key || result.state === "cancelled") return;
818
+ if (result.state !== "failed" || result.operation !== "qa") {
819
+ this.qaFailureStreaks.delete(taskId);
820
+ return;
821
+ }
822
+ const streak = this.qaFailureStreaks.get(taskId);
823
+ this.qaFailureStreaks.set(taskId, { key, count: streak?.key === key ? streak.count + 1 : 1 });
777
824
  }
778
825
 
779
826
  private async withRecovery<T extends AssuranceAdvanceResult | AssuranceSubmitReviewResult>(taskId: string, ctx: HostContext, enriched: T): Promise<T> {
@@ -947,7 +994,13 @@ export class AssuranceCoordinator {
947
994
  // Preparation counts against the same ceiling as the checks, so the
948
995
  // aggregate clock starts before the first prepared group runs. A host
949
996
  // may only tighten the derived budget, never raise it past the ceiling.
950
- const declaredQaJobMs = deriveQaJobTimeoutMs(assurance.descriptors.values());
997
+ const qaKey = `${assurance.snapshot.record_revision}|${assurance.snapshot.intent_content_hash}|${assurance.snapshot.diff_hash}`;
998
+ const streak = this.qaFailureStreaks.get(taskId);
999
+ const maxFailures = this.ports.qaMaxIdenticalFailures ?? QA_MAX_IDENTICAL_FAILURES;
1000
+ if (streak?.key === qaKey && streak.count >= maxFailures)
1001
+ return { state: "blocked", reason: `deterministic QA already failed ${streak.count} times on this exact snapshot; change the workspace diff or intent, or restart the session after fixing the environment, before retrying` };
1002
+ this.qaAttemptKeys.set(taskId, qaKey);
1003
+ const declaredQaJobMs = deriveQaJobTimeoutMs(assurance.descriptors.values());
951
1004
  const qaJobTimeoutMs = Math.min(declaredQaJobMs, this.ports.qaJobTimeoutMs ?? declaredQaJobMs);
952
1005
  const qaJobDeadline = setTimeout(() => {
953
1006
  qaJobBudgetExceeded = true;
@@ -463,6 +463,10 @@ function publishInput(
463
463
  const recomputed = `sha256:${createHash("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
464
464
  if (recomputed !== input.expectedDiffHash)
465
465
  throw new Error("review task revision does not match assurance snapshot");
466
+ // A revision whose tree equals the base carries nothing to review; reserving
467
+ // Review for it would settle QA outcomes against bytes it does not contain.
468
+ if (Object.keys(snapshot.changed_paths).length === 0)
469
+ throw new Error("review revision carries no task change; stage the task's in-scope work before Review");
466
470
  return { snapshot, revision: publishReviewRevision(snapshot.repository_root, snapshot, recomputed, input.taskId) };
467
471
  }
468
472
 
@@ -81,8 +81,7 @@ export interface NativeConfirmationInput {
81
81
  batch_branch: string;
82
82
  children: Array<{ task_id: string; slice_id: string; risk?: string }>;
83
83
  excluded: Array<{ task_id: string; slice_id: string; reason: string }>;
84
- budget: { max_children: number; deadline_at: string; qa_failure_limit: number };
85
- expires_at: string;
84
+ budget: { max_children: number; qa_failure_limit: number };
86
85
  };
87
86
  signal?: AbortSignal;
88
87
  }