dsh-tower 2.5.0 → 2.7.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 (142) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/COMPATIBILITY.md +2 -0
  3. package/CONTRIBUTING.md +1 -1
  4. package/GLOSSARY.md +6 -0
  5. package/PRINCIPLES.md +15 -7
  6. package/README.en.md +18 -16
  7. package/README.md +18 -11
  8. package/adapters/leases.js +20 -6
  9. package/adapters/legacy.js +149 -37
  10. package/adapters/stateFile.js +11 -1
  11. package/app/archive.js +30 -5
  12. package/app/briefing.js +95 -12
  13. package/app/checkCompletion.js +107 -18
  14. package/app/enrichedMissions.js +1 -1
  15. package/app/freshness.js +77 -0
  16. package/app/gate.js +26 -12
  17. package/app/merge.js +7 -4
  18. package/app/mission.js +24 -1
  19. package/app/missionContext.js +2 -1
  20. package/app/plan.js +64 -1
  21. package/app/provision.js +133 -22
  22. package/app/read.js +38 -5
  23. package/app/rebase.js +10 -8
  24. package/app/review.js +104 -61
  25. package/app/reviewCheckout.js +133 -0
  26. package/app/sandbox.js +121 -0
  27. package/app/sharedCheckout.js +90 -0
  28. package/app/spawn-prechecks.js +31 -20
  29. package/app/spawn-support.js +59 -8
  30. package/app/spawn.js +3 -0
  31. package/app/teardown.js +119 -2
  32. package/app/tower.js +34 -6
  33. package/client.js +321 -0
  34. package/cordis.patch.yml +55 -3
  35. package/docs/adr/0005-component-surface.md +58 -13
  36. package/docs/adr/0006-mission-shapes.md +306 -0
  37. package/docs/architecture/overview.md +18 -10
  38. package/docs/examples/first-mission.md +3 -2
  39. package/docs/faq.md +9 -7
  40. package/docs/guide/install-upgrade-migration.md +58 -7
  41. package/docs/guide/quickstart.md +20 -10
  42. package/docs/guide/scenarios-and-philosophy.md +35 -28
  43. package/docs/operations/runbook.md +1 -1
  44. package/docs/operations/security.md +11 -10
  45. package/docs/reference/blockers.md +20 -11
  46. package/docs/reference/component-surface.md +20 -8
  47. package/docs/reference/config.generated.md +8 -5
  48. package/docs/reference/config.md +30 -5
  49. package/docs/reference/doctor.md +7 -7
  50. package/docs/reference/errors.generated.md +1 -0
  51. package/docs/reference/errors.md +2 -1
  52. package/docs/reference/operations.md +1 -1
  53. package/docs/reference/shapes.md +123 -0
  54. package/docs/reference/surface-budget.md +53 -4
  55. package/docs/reference/testing.md +5 -3
  56. package/docs/reference/tools.generated.md +7 -7
  57. package/docs/reference/tools.md +5 -5
  58. package/docs/zh/faq.md +7 -5
  59. package/docs/zh/guide/install-upgrade-migration.md +25 -6
  60. package/docs/zh/guide/quickstart.md +17 -10
  61. package/docs/zh/guide/scenarios-and-philosophy.md +23 -15
  62. package/docs/zh/operations/security.md +9 -8
  63. package/docs/zh/reference/blockers.md +13 -9
  64. package/docs/zh/reference/config.md +22 -5
  65. package/docs/zh/reference/errors.md +2 -1
  66. package/docs/zh/reference/testing.md +5 -4
  67. package/docs/zh/reference/tools.md +3 -3
  68. package/domain/batch.js +75 -105
  69. package/domain/collab/batch.js +30 -10
  70. package/domain/collab/spawn.js +53 -16
  71. package/domain/components.js +12 -4
  72. package/domain/config/schema.js +87 -22
  73. package/domain/contracts/outputs.js +2 -1
  74. package/domain/contracts/tool-inputs/missions.js +10 -2
  75. package/domain/errors.js +6 -0
  76. package/domain/evidence.js +283 -0
  77. package/domain/gate.js +17 -34
  78. package/domain/integration/gate.js +17 -43
  79. package/domain/integration/init.js +3 -2
  80. package/domain/integration/provision.js +51 -0
  81. package/domain/integration/rebase.js +16 -1
  82. package/domain/integration/review.js +203 -9
  83. package/domain/mission.js +16 -33
  84. package/domain/missions/completion.js +46 -8
  85. package/domain/missions/findings.js +2 -1
  86. package/domain/missions/lifecycle.js +131 -35
  87. package/domain/missions/plan.js +30 -6
  88. package/domain/missions/review.js +2 -18
  89. package/domain/planning.js +5 -5
  90. package/domain/read/missionContext.js +17 -3
  91. package/domain/read/views.js +5 -1
  92. package/domain/roles.js +222 -0
  93. package/domain/roster.js +9 -3
  94. package/domain/scope.js +51 -0
  95. package/domain/shapes/build.js +254 -0
  96. package/domain/shapes/index.js +171 -0
  97. package/domain/shapes/survey.js +445 -0
  98. package/domain/state/archive.js +28 -4
  99. package/domain/state/migrations.js +111 -4
  100. package/domain/state/schema.js +69 -7
  101. package/domain/state/semantics.js +93 -13
  102. package/domain/state.js +5 -1
  103. package/domain/summary.js +5 -11
  104. package/domain/vocabulary.js +15 -33
  105. package/host/capability-registry.js +25 -9
  106. package/host/config.js +40 -3
  107. package/host/doctor.js +20 -8
  108. package/host/preset-survey-sandbox.js +19 -0
  109. package/host/preset-survey.js +18 -0
  110. package/host/presets.js +17 -11
  111. package/host/tool-registry.js +23 -13
  112. package/locale/en.json +16 -1
  113. package/locale/preset-survey/en.json +6 -0
  114. package/locale/preset-survey/zh.json +6 -0
  115. package/locale/preset-survey-sandbox/en.json +6 -0
  116. package/locale/preset-survey-sandbox/zh.json +6 -0
  117. package/locale/zh.json +16 -1
  118. package/package.json +19 -2
  119. package/ports/state.js +8 -1
  120. package/scripts/architecture-waivers.json +3 -3
  121. package/scripts/check-components.mjs +120 -6
  122. package/scripts/check-docs.mjs +1 -1
  123. package/scripts/check-pack.mjs +5 -0
  124. package/scripts/component-surface.json +83 -29
  125. package/scripts/generate-capability-locale.mjs +5 -0
  126. package/scripts/generate-shapes-reference.mjs +171 -0
  127. package/scripts/mount-smoke/surface.mjs +3 -0
  128. package/scripts/pack-manifest.txt +22 -0
  129. package/scripts/shape-audit.mjs +361 -0
  130. package/scripts/surface-baseline.json +75 -4
  131. package/scripts/types-coverage.mjs +7 -0
  132. package/survey-sandbox.js +48 -0
  133. package/survey.js +40 -0
  134. package/tools/complete.js +5 -5
  135. package/tools/diff.js +5 -3
  136. package/tools/plan.js +1 -1
  137. package/tools/preflight.js +3 -2
  138. package/tools/review.js +1 -1
  139. package/tools/spawn.js +48 -50
  140. package/tools/status.js +8 -1
  141. package/tools/test.js +2 -1
  142. package/types/index.d.ts +76 -5
@@ -10,8 +10,8 @@ a better fit.
10
10
 
11
11
  If you only remember one sentence: **use Tower when the output is an integrated repository change that
12
12
  must land through a controlled merge; use the other patterns when the output is exploration, repeated
13
- attempts, or programmatic fan-out.** Enable `review.requireReview` when the change also needs an
14
- independent reviewer.
13
+ attempts, or programmatic fan-out.** Set the review policy to `required` when the change also needs
14
+ an independent reviewer; the default policy is `optional`.
15
15
 
16
16
  ## 1. The design philosophy
17
17
 
@@ -45,11 +45,13 @@ When review is enabled, a reviewer also inspects a specific branch tip and the g
45
45
  binding. Separation of duties is the point when review is on: the agent that wrote the code is not the
46
46
  agent that approves it, and neither is the agent that merges.
47
47
 
48
- Independent review is optional by default: `review.requireReview:true` enforces it (and then a complete
49
- mission must carry a clean, merge-approved review from a reviewer bound to the branch tip). Set the flag
50
- to `true` for sensitive repositories; `reviewPolicy:"none"` + a reason + `gates.allowReviewExemption:true`
51
- remains available as a per-mission exemption when the global gate is on. Reviews can be submitted and
52
- recorded either way.
48
+ The review policy is one tri-state control: `off`, `optional` (the default) or `required`. `required`
49
+ enforces independent review (a complete mission must carry a clean, merge-approved review from a
50
+ reviewer bound to the branch tip). Choose it for sensitive repositories; `reviewPolicy:"none"` + a
51
+ reason + `gates.allowReviewExemption:true` remains available as a per-mission exemption when the
52
+ policy is required. `optional` records reviews but never blocks a merge, and `off` is a supported
53
+ mode that removes the review tool. The policy is configuration, not a component row: there is one
54
+ control on the plugin page, not three mutually exclusive rows.
53
55
 
54
56
  ### 1.5 Evidence over assertion
55
57
 
@@ -184,7 +186,7 @@ integration, and what "done" means.**
184
186
  | Isolation | Git worktree + branch per mission; one writer per mission | Shared workspace; coordination by task ownership | Fresh child per round; no built-in branch/merge contract | Per-step child; no Git isolation | Child sessions; may share workspace |
185
187
  | Durable state | Git metadata + JSON state + activity + board | Team task board + sessions | Minimal; each round starts fresh | Run record/results; not a merge ledger | Session/subagent records |
186
188
  | Integration authority | Tower only, after preflight/gate (plus review when enabled) | Humans/teammates merge or edit manually | Usually the parent/worker applies results | Script applies results | Parent applies results |
187
- | Review / gate | Optional independent review (enable `review.requireReview`) + preflight/merge gate | Review is a collaboration convention, not a hard gate | No built-in review gate | No built-in review gate | No built-in review gate |
189
+ | Review / gate | Optional independent review (one `review.mode` control: off/optional/required) + preflight/merge gate | Review is a collaboration convention, not a hard gate | No built-in review gate | No built-in review gate | No built-in review gate |
188
190
  | Parallelism | Multiple missions, disjoint scopes, serialized merges | Many teammates, shared board | Sequential rounds (or external fan-out) | High fan-out, script-controlled | High fan-out |
189
191
  | Human role | Owns the mode; can inspect evidence and release | Collaborator/coordinator | Author of the repeated objective | Author of the orchestration script | Delegator |
190
192
  | Best for | Feature delivery, migrations, release-bound changes | Design, research, cross-cutting collaboration | Iterative search/repair with fresh context | Large audits, batch analysis, deterministic pipelines | Quick exploration or independent subtasks |
@@ -195,8 +197,8 @@ integration, and what "done" means.**
195
197
  Ordinary `subagent`/`subagent_fork` calls are cheap and flexible. They are the right tool for "read
196
198
  these five files in parallel" or "try three approaches". They do not provide a write scope, a
197
199
  dedicated worktree, a durable mission, independent review, or a merge gate. Tower wraps the same
198
- subagent primitive in those contracts; it also costs more per child. (Review is optional: enable
199
- `review.requireReview` when you need the reviewer role.)
200
+ subagent primitive in those contracts; it also costs more per child. (Review is optional: set the
201
+ policy to `required` when you need the reviewer role to gate a merge.)
200
202
 
201
203
  Choose subagents when the work is read-mostly, short-lived, or you will integrate the result
202
204
  yourself. Choose Tower when the result must land on a branch under a controlled merge; enable review
@@ -245,14 +247,19 @@ Tower entry clears an active plan (or refuses entry under `mode.planModePolicy:"
245
247
  Tower session cannot use the goal/todo/team/workflow tools. Plan first, then execute; do not expect
246
248
  Tower to discover the plan for you.
247
249
 
248
- ### 4.6 Review is optional by default
250
+ ### 4.6 The review policy is one tri-state control
249
251
 
250
- `review.requireReview:false` is the default, so a completed mission can merge without a reviewer for
251
- low-risk repositories, scratch work, or human-supervised sessions. Set it to `true` to enforce the
252
+ The review policy is one control — `off`, `optional` (the default) or `required` — in the plugin-page
253
+ `dsh-tower` configuration section, mirrored by `review.mode` in config. It is not a component row and
254
+ not three mutually exclusive rows. `optional` means a completed mission can merge without a reviewer
255
+ for low-risk repositories, scratch work, or human-supervised sessions. `required` enforces the
252
256
  independent review gate; the gate then re-checks `no-review`, `not-clean`, `review-hold`,
253
- `review-not-approved`, `tip-moved`, and `review-mission-mismatch`. `tower_review` is always available
254
- and records verdicts even when the gate is off. For one mission only, with the global gate on, set
255
- `reviewPolicy:"none"` + a reason and `gates.allowReviewExemption:true`.
257
+ `review-not-approved`, `tip-moved`, and `review-mission-mismatch`. `off` is a supported mode that
258
+ does not register `tower_review`; `optional` records verdicts but never blocks a merge. For one
259
+ mission only, with the policy `required`, set `reviewPolicy:"none"` + a reason and
260
+ `gates.allowReviewExemption:true`. The deprecated one-release boolean alias maps `true` → `required`
261
+ and `false` → `optional`; see
262
+ [install, upgrade and migration](install-upgrade-migration.md).
256
263
 
257
264
  ### 4.7 Concrete examples by workflow
258
265
 
@@ -309,8 +316,8 @@ A team wants "organization invitations": token model, email delivery, accept/dec
309
316
  `src/ui/invites/**`; make the API depend on tokens and email.
310
317
  2. Spawn workers for the independent missions; the API waits for its deps.
311
318
  3. Each worker commits on its branch, completes with checks.
312
- 4. With `review.requireReview:true`, spawn reviewers and require a clean+merge verdict; otherwise
313
- review is optional.
319
+ 4. With `review.mode:required`, spawn reviewers and require a clean+merge verdict; otherwise review
320
+ is optional or off.
314
321
  5. `tower_preflight` and `tower_merge` each mission; resolve scope/base/review blockers.
315
322
  6. `tower_teardown`; `MISSIONS.md` is the human-readable record.
316
323
 
@@ -344,9 +351,9 @@ Tower missions from those findings.
344
351
  ### Scenario E: sensitive release
345
352
 
346
353
  A library release with external consumers. Plan missions for the change, migration guide, and
347
- release notes; enable `review.requireReview:true` if the release requires independent review, require
348
- tests, and use `tower_batch` to close the release evidence only when all required checks are present;
349
- merge and tag outside Tower as your release process requires.
354
+ release notes; set `review.mode:required` if the release requires independent review, require tests,
355
+ and use `tower_batch` to close the release evidence only when all required checks are present; merge
356
+ and tag outside Tower as your release process requires.
350
357
 
351
358
  ## 6. Choosing in practice: a short decision path
352
359
 
@@ -354,9 +361,9 @@ merge and tag outside Tower as your release process requires.
354
361
  2. **Would you open a PR for it?** No → Tower is too heavy; use a normal agent.
355
362
  3. **Do the slices have stable scopes and a known definition of done?** No → plan/explore first; then
356
363
  return to Tower.
357
- 4. **Do you need a merge gate?** Repository changes with integration risk → Tower. Enable
358
- `review.requireReview:true` if you also need independent review. If neither isolation nor evidence
359
- is needed, plain subagents may be enough.
364
+ 4. **Do you need a merge gate?** Repository changes with integration risk → Tower. Set the review
365
+ policy to `required` if you also need independent review. If neither isolation nor evidence is
366
+ needed, plain subagents may be enough.
360
367
  5. **Do you need many parallel analyses or repeated fresh attempts?** Use Workflow/Ralph for that
361
368
  phase; encode the resulting changes as Tower missions.
362
369
  6. **Do you need cross-cutting human collaboration?** Use Agent Teams to decide; execute the decided
@@ -368,9 +375,9 @@ merge and tag outside Tower as your release process requires.
368
375
  round, gate, merge. Keep missions meaningful (roughly a PR-sized slice), not one task per function.
369
376
  - **Merges serialize**: overlapping scopes auto-add dependencies; independent missions still merge
370
377
  one at a time onto the base. Do not expect linear speedup for merge-heavy work.
371
- - **Review is not free when enabled**: each mission then needs a reviewer and a clean verdict.
372
- `review.requireReview:false` (the default) makes review optional; when the gate is on, exemptions
373
- are opt-in and require a reason.
378
+ - **Review is not free when required**: each mission then needs a reviewer and a clean verdict.
379
+ `review.mode:optional` (the default) makes review non-blocking, and `off` removes the review tool;
380
+ when the policy is `required`, exemptions are opt-in and require a reason.
374
381
  - **Too-fine missions** increase coordination and merge cost. **Too-coarse missions** lose the benefit
375
382
  of parallel review and make scope conflicts likely.
376
383
  - **Tower is not a sandbox**: it is a coordination protocol. Host permissions, repository hooks, and
@@ -385,7 +392,7 @@ merge and tag outside Tower as your release process requires.
385
392
  - [ ] The work produces a repository change that deserves a PR-style review.
386
393
  - [ ] I can name 2–8 missions with non-overlapping scopes and a rough dependency order.
387
394
  - [ ] Each mission has a testable definition of done (tasks, checks/tests, optional review).
388
- - [ ] If this change needs independent review, `review.requireReview` is on (default is off).
395
+ - [ ] If this change needs independent review, the review policy is `required` (default is `optional`).
389
396
  - [ ] I know which missions can run in parallel and which must wait.
390
397
  - [ ] I accept the merge-gate overhead (and review overhead if enabled) and will inspect
391
398
  `MISSIONS.md`/the branch.
@@ -21,7 +21,7 @@
21
21
 
22
22
  `tower_rebase` first. If the branch is already contained in the base it returns `alreadyMerged` and
23
23
  leaves the branch untouched — then call `tower_merge` to record it. A conflict marks the mission
24
- `blocked`; resolve in the worktree, set the mission back to `active` (or `completed`), retry.
24
+ `blocked`; resolve in the worktree, then `tower_mission({clear_blockers:true})` resumes it to `active`, retry.
25
25
 
26
26
  ## Interrupt, stale roster and takeover
27
27
 
@@ -4,15 +4,16 @@ Tower's guarantees are **coordination guarantees**, not a sandbox. Understand wh
4
4
 
5
5
  ## Enforced by the plugin
6
6
 
7
- - **Review binding (when review is enabled).** `tower_review` records the branch tip it inspected;
8
- with `review.requireReview:true`, the gate rejects a moved tip (`tip-moved`) unless a clean
9
- conflict-free rebase waived it. A reviewer that modifies the branch after spawning is rejected
7
+ - **Review binding (when required).** `tower_review` records the branch tip it inspected; with
8
+ `review.mode:required`, the gate rejects a moved tip (`tip-moved`) unless a clean conflict-free
9
+ rebase waived it. A reviewer that modifies the branch after spawning is rejected
10
10
  (`reviewer must not modify the mission branch`).
11
- - **Review is optional by default, enforceable by config.** `review.requireReview:false` is the
12
- default; set it to `true` to enforce the independent-review gate. Mission-level
13
- `reviewPolicy:"none"` + reason is the narrower exemption when the global gate is on. With the gate
14
- off, separation of duties is not enforced by the plugin; use it only for trusted or human-supervised
15
- repositories.
11
+ - **Review is optional by default, enforceable by the tri-state policy.** `review.mode` is `off`,
12
+ `optional` (the default) or `required`. Set it to `required` to enforce the independent-review gate.
13
+ Mission-level `reviewPolicy:"none"` + reason is the narrower exemption when the policy is required.
14
+ With `off`, review is intentionally unavailable; with `optional`, separation of duties is not
15
+ enforced by the plugin, so use it only for trusted or human-supervised repositories. The policy is
16
+ one control on the plugin page, not a component row.
16
17
  - **Role enforcement.** The caller is resolved against the roster. Worker mission updates are limited to
17
18
  task/blocker/note; `completed`, `abandoned`, scope widening and blocker clearing are tower-only, and
18
19
  completion (with its unread-inbox/commit gates) must go through `tower_complete`. Tower-only tools reject
@@ -84,8 +85,8 @@ intent behind that entry. What it does provide:
84
85
  It does **not** defend against: a model that legitimately passes every gate while acting on malicious
85
86
  instructions; repository hooks or test commands the repository itself declares; or filesystem/shell
86
87
  access outside the worktree by a worker that ignores its instructions. For a higher-assurance team,
87
- set `mode.allowAgentControl:false`, keep `review.requireReview:true`, review the repository before
88
- Tower work, and run the host under OS-level isolation.
88
+ set `mode.allowAgentControl:false`, keep the review policy at `review.mode:required`, review the
89
+ repository before Tower work, and run the host under OS-level isolation.
89
90
 
90
91
  ## Not enforced by the plugin (deployment concerns)
91
92
 
@@ -1,9 +1,18 @@
1
1
  # Blockers and failure codes
2
2
 
3
- `tower_preflight` returns `{ok:false, blockers:[{code, detail}]}`. `tower_merge` and `tower_rebase`
3
+ `tower_preflight` returns `{ok:false, blockers:[{code, detail}], warnings:[...]}`. `tower_merge` and `tower_rebase`
4
4
  return the same shape for operation failures. Codes are stable; the detail is human-facing. The
5
5
  tables below define 32 stable blocker/failure codes.
6
6
 
7
+ `survey-stale` is special: it is reported as a stable warning in `plan`/`preflight` by default and
8
+ becomes a merge blocker only when `gates.requireFreshSurvey` is `true`. Its warning object carries
9
+ `{code, missionId, depId, readCommit, baseCommit, message}` so a caller can act without parsing prose.
10
+
11
+ The review blockers below (`no-review`, `review-mission-mismatch`, `not-clean`, `review-hold`,
12
+ `review-not-approved`, `tip-moved`, `review-exemption-invalid`) exist only when `review.mode` is
13
+ `required`. With `off` or `optional` no review blocker is produced; `off` additionally removes
14
+ `tower_review` and refuses reviewer spawn.
15
+
7
16
  ## Gate blockers (26)
8
17
 
9
18
  | Code | Meaning / next action |
@@ -15,16 +24,16 @@ tables below define 32 stable blocker/failure codes.
15
24
  | `no-check-evidence` | `gates.requireChecks` is enabled and the completion report has no `checks`. Return the completed mission to `active`, re-run `tower_complete` with checks, then review again. |
16
25
  | `branch-missing` | No worktree/branch yet: spawn a worker first. |
17
26
  | `stale-base` | The branch does not contain the base tip: run `tower_rebase`. |
18
- | `read-only-survey` | Survey mission changed files; survey missions must be read-only. |
19
27
  | `out-of-scope` | Changed files outside the mission scope; widen scope deliberately or revert. On a case-sensitive checkout a case-only mismatch is still rejected and the detail names the scope entry it would match if case were folded. |
20
28
  | `base-dirty` | The main checkout has uncommitted changes the merge would overwrite. |
21
29
  | `deps-unmerged` | A dependency mission is not satisfied: a build dependency must be `merged`/`abandoned`, while a survey dependency is satisfied at `completed` (archived entries included). |
22
- | `no-review` | `review.requireReview:true` and no review exists on the branch: spawn a reviewer (or disable the review gate). |
23
- | `review-mission-mismatch` | `review.requireReview:true` and the latest review belongs to another mission. |
24
- | `not-clean` | `review.requireReview:true` and latest review is `p1-Nitems`/`p2-Nitems`; another round is required. Verdicts from later routes of the same round are appended to it instead of flipping the mission again. |
25
- | `review-hold` | `review.requireReview:true` and latest review recommends hold; a later clean review must supersede it. |
26
- | `review-not-approved` | `review.requireReview:true` and latest clean review says `fix-then-merge` or `hold`; only `merge` approves. |
27
- | `tip-moved` | `review.requireReview:true` and the branch changed after review; review again (or use a clean rebase waiver). |
30
+ | `survey-stale` | A build dependency on a survey whose `readCommit` is not ancestor-or-equal of the build base. Default: a `warnings[]` entry, plan and preflight only. With `gates.requireFreshSurvey:true` the same code blocks the merge; re-plan the survey against the current base, or record why the read is still valid. |
31
+ | `no-review` | `review.mode:required` and no review exists on the branch: spawn a reviewer (or disable the review gate). |
32
+ | `review-mission-mismatch` | `review.mode:required` and the latest review belongs to another mission. |
33
+ | `not-clean` | `review.mode:required` and latest review is `p1-Nitems`/`p2-Nitems`; another round is required. Verdicts from later routes of the same round are appended to it instead of flipping the mission again. |
34
+ | `review-hold` | `review.mode:required` and latest review recommends hold; a later clean review must supersede it. |
35
+ | `review-not-approved` | `review.mode:required` and latest clean review says `fix-then-merge` or `hold`; only `merge` approves. |
36
+ | `tip-moved` | `review.mode:required` and the branch changed after review; review again (or use a clean rebase waiver). |
28
37
  | `worker-busy` | A roster agent for this mission is running; wait or retire it. |
29
38
  | `worker-status-unknown` | The parent catalog is unavailable, so a live roster agent cannot be proven stopped; retry after the catalog recovers or adopt it. |
30
39
  | `base-wrong-branch` | The main checkout is not on the recorded base branch. |
@@ -32,14 +41,14 @@ tables below define 32 stable blocker/failure codes.
32
41
  | `worker-stale` | A roster worker for this mission has no catalog activity beyond `spawn.staleMs`; adopt it (or interrupt it only if it is actually running) before merge. |
33
42
  | `tests-missing` | `gates.requireTests` is enabled and there is no passing discovery receipt for the current branch tip; run `tower_test` or `tower_complete`. |
34
43
  | `tests-failed` | The latest discovery receipt failed or did not meet minCount/required checks. Fix the tests and re-run. |
35
- | `review-exemption-invalid` | `review.requireReview:true` and the mission requests `reviewPolicy:"none"` while `gates.allowReviewExemption` is off (or the reason is missing). |
44
+ | `review-exemption-invalid` | `review.mode:required` and the mission requests `reviewPolicy:"none"` while `gates.allowReviewExemption` is off (or the reason is missing). |
36
45
  | `merge-intent-pending` | A merge intent was recorded before a crash/failure; run `tower_merge` to reconcile before another gate. |
37
46
 
38
47
  ## Merge failures (4)
39
48
 
40
49
  | Code | Meaning / next action |
41
50
  |---|---|
42
- | `merge-conflict` | Real index conflict; the mission is `blocked`. Resolve, restore the mission to `active`/`completed`, retry. |
51
+ | `merge-conflict` | Real index conflict; the mission is `blocked`. Resolve the conflict, then `tower_mission({clear_blockers:true})` resumes the mission to `active`; retry. |
43
52
  | `merge-hook-failed` | A hook/process failed before a commit existed; repository restored, mission stays `completed`. Retry; `timeout: true` means the git process timed out. Raise `timeoutMs` for slow hooks. |
44
53
  | `merge-shape` | Git produced unexpected merge parents; inspect the repository, do not force. |
45
54
  | `batch-invalid` | A batch id/member/receipt/check reference is invalid or close evidence is incomplete. |
@@ -52,7 +61,7 @@ A merge that already landed is **not** a blocker: `tower_merge` records it (`rec
52
61
  | Code | Meaning / next action |
53
62
  |---|---|
54
63
  | `worktree-dirty` | Commit or discard worktree changes, then retry. |
55
- | `rebase-conflict` | A real conflict; the mission is `blocked`. Resolve manually, return it to `active`, retry. |
64
+ | `rebase-conflict` | A real conflict; the mission is `blocked`. Resolve manually, then `clear_blockers` resumes it to `active`; retry. |
56
65
  | `worker-busy` | An agent is running for this mission; wait or retire it first. |
57
66
 
58
67
  `tower_rebase` returns `alreadyMerged: true` when the branch is already contained in the base; call
@@ -3,7 +3,7 @@
3
3
  > Generated by `npm run docs:generate:components` from `scripts/component-surface.json` and
4
4
  > `scripts/surface-baseline.json`. Do not edit by hand.
5
5
 
6
- Decision: [ADR 0005](../adr/0005-component-surface.md) · decision date: 2026-10-23 · baseline 2026-10-23 · visible components: 11 (`tower`, `tower-skills`, `tower-batch`, `tower-import`, `tower-leases`, `tower-tests`, `tower-board`, `tower-archive`, `tower-hmr`, `preset-tower-reviewer`, `tower-ui`)
6
+ Decision: [ADR 0005](../adr/0005-component-surface.md) · decision date: 2026-11-01 · baseline 2026-10-23 · visible components: 12 (`tower`, `tower-skills`, `tower-batch`, `tower-import`, `tower-leases`, `tower-tests`, `tower-board`, `tower-archive`, `tower-hmr`, `preset-tower-reviewer`, `tower-survey`, `tower-ui`)
7
7
 
8
8
  The component axis answers one question for every config key: may a user turn it
9
9
  off, and if so, must that switch be a component? Four classes cover every key, so
@@ -14,7 +14,7 @@ off, and if so, must that switch be a component? Four classes cover every key, s
14
14
  | `core` | Removing it breaks the Tower contract. It is never a switch. |
15
15
  | `optional-component` | A capability whose off state leaves the rest of Tower intact. It must be a user-visible switch with a documented fallback. |
16
16
  | `optional-config` | A tuning knob, not a capability. It stays in the config form and never hides a component. |
17
- | `forbidden-to-disable` | The host, the health contract, the recovery escape, the review contract and the merge gates. No group, row or boolean key may turn it off. |
17
+ | `forbidden-to-disable` | The host, the health contract, the recovery escape and the merge gates. No group, row or boolean key may turn it off. |
18
18
 
19
19
  ## Components
20
20
 
@@ -35,13 +35,21 @@ row is a section of its parent and is not independently switchable.
35
35
  | `tower-archive` | visible | row | Terminal missions are not moved out of state.json, so the hot state keeps growing and status/doctor keep loading their full history. Merges, gates and every other capability keep working; nothing is deleted. afterDays/maxMissions keep their tuning meaning. |
36
36
  | `tower-hmr` | visible | row | Development-only tooling: tower_reload and /tower reload are unavailable, so a linked-profile reload has to be triggered outside Tower. A normal profile behaves exactly as before; the row ships disabled on purpose. |
37
37
  | `preset-tower-worker` | internal | row | Declared by dsh-tower/preset-worker: tower_spawn fails without the worker preset. A mandatory dependency, never a switch. |
38
- | `preset-tower-reviewer` | visible | row | Declared by dsh-tower/preset-reviewer: the reviewer cannot be dispatched. Supported while review.requireReview is false (merge is unaffected); with the gate on planning/mode entry is locked with an explanatory error. |
38
+ | `preset-tower-reviewer` | visible | row | Declared by dsh-tower/preset-reviewer: the reviewer cannot be dispatched. Supported while review.mode is off or optional (merge is unaffected; off does not use the preset at all); with review.mode required, planning/mode entry is locked with an explanatory error. |
39
+ | `tower-survey` | visible | group | Declared by the tower-survey group: surveys cannot be planned (planning one is refused with an actionable code) and no survey agent is dispatched, because both the read-only and the sandbox preset are nested dependencies of this row. Build missions and every existing capability are unaffected. survey.sandbox stays tuning: it chooses shared vs detached placement, never whether surveys exist. |
40
+ | `preset-tower-survey` | internal | row | Declared by dsh-tower/preset-survey: the shared-checkout survey preset is a dependency of the tower-survey group, never its own switch. With the group off, a shared survey cannot be dispatched. |
41
+ | `preset-tower-survey-sandbox` | internal | row | Declared by dsh-tower/preset-survey-sandbox: the detached-sandbox survey preset is a dependency of the tower-survey group, never its own switch. It is selected only when survey.sandbox is detached; with the group off, a detached survey cannot be dispatched. |
39
42
  | `tower-ui` | visible | row | The Tower web UI plugin does not load in this profile: no Tower tool cards, composer-dock summary or status strip. Host tools, missions, state and the MISSIONS.md board are unaffected. |
40
43
 
41
44
  ## Forbidden to disable
42
45
 
43
- The host, the health contract, the recovery escape, the review contract and the
44
- merge gates are configuration or code, never switches. `npm run
46
+ The host, the health contract, the recovery escape and the merge gates are
47
+ configuration or code, never switches. The review gate in `required` mode is
48
+ also non-disableable: a completed mission still needs the review evidence bound
49
+ to the branch tip, and the exemption path stays explicit. The policy itself is
50
+ not a never-switch — `review.mode` is the one tri-state control on the
51
+ plugin-page config form (`off`, `optional` or `required`) — and only the gate it
52
+ turns on in `required` mode is non-disableable. `npm run
45
53
  docs:check:components` derives the live disablers from `host/tool-registry.js`
46
54
  and `cordis.patch.yml`; a disabler that is not recorded here fails the gate.
47
55
 
@@ -50,7 +58,7 @@ and `cordis.patch.yml`; a disabler that is not recorded here fails the gate.
50
58
  | the Tower host | Without the host the plugin registers nothing; there is no off state that leaves the rest working, and no config boolean may remove it. The visible `tower` anchor is the all-off boundary, and the host section nested under it may never become its own switch. | none | none may exist |
51
59
  | tower_doctor (read-only health contract) | Diagnosis must always exist so a failure message can point at it. #122 removed exposeDoctor and the exposeOperational master switch; the tool is registered for every configuration. | none | none may exist |
52
60
  | tower_recover (stale state-lock escape) | The escape hatch must exist whenever the lock it repairs can be left behind. #122 removed exposeRecovery and the exposeOperational master switch; the tool is registered for every configuration. | none | none may exist |
53
- | the review contract | review.requireReview is a merge blocker, not a component; a row may not switch it off. The reviewer row is conditional, not a way to weaken the gate. | none | none may exist |
61
+ | the review gate in required mode | The policy is one tri-state configuration control on the plugin-page form, not a component row: review.mode is off, optional (default) or required, and off is a documented mode that removes tower_review and makes the merge gate ignore review. What no group, row or boolean key may switch off is the gate once the mode is required: a completed mission still needs a clean review bound to the branch tip, and the exemption path stays explicit. review.requireReview is a deprecated one-release alias, not a switch. The reviewer preset row remains a capability the policy may leave unused, never a way to weaken required mode. | none | none may exist |
54
62
  | the merge gates | gates.* decides delivery; a component may not bypass it. | none | none may exist |
55
63
 
56
64
  ## Gateable tools
@@ -92,7 +100,8 @@ All keys from `domain/config/schema.js`, classified on the component axis.
92
100
  | `mode.storePath` | `optional-config` | Custom mode-store location; no capability is removed. |
93
101
  | `mode.maxEntries` | `optional-config` | Bounds the persisted mode store. |
94
102
  | `mode.lockTimeoutMs` | `optional-config` | Bounded wait for the cross-process mode lock. |
95
- | `review.requireReview` | `forbidden-to-disable` | Turns independent review into a merge blocker; no component may switch it off. With it on, the preset-tower-reviewer row may not be off (planning/mode entry refuse the combination). |
103
+ | `review.mode` | `core` | The review policy: off, optional (default) or required, selected by one tri-state control on the plugin-page configuration form. It is configuration, not a component row and not three mutually exclusive rows. off intentionally removes tower_review, refuses reviewer spawn and makes the merge gate ignore review; optional never blocks a merge; required makes a clean tip-bound review a merge blocker. The reviewer preset row stays a capability the mode may leave unused. |
104
+ | `review.requireReview` | `core` | Deprecated one-release boolean alias for review.mode (true -> required, false -> optional; default optional). It is policy configuration and a deprecated alias, not a switch a row may own; false never overrides an explicit mode. Kept so 2.5 configurations keep working; remove after the 2.6 alias window. |
96
105
  | `review.maxRoundsPerReviewer` | `forbidden-to-disable` | Caps review rounds; part of the review contract. |
97
106
  | `merge.timeoutMs` | `optional-config` | Default merge timeout; a per-call value wins. |
98
107
  | `merge.maxBufferBytes` | `optional-config` | Captured merge output buffer. |
@@ -104,6 +113,8 @@ All keys from `domain/config/schema.js`, classified on the component axis.
104
113
  | `spawn.provider` | `optional-config` | Provider route for spawned agents. |
105
114
  | `spawn.workerModel` | `optional-config` | Optional model route for workers. |
106
115
  | `spawn.reviewerModel` | `optional-config` | Optional independent model route for reviewers. |
116
+ | `spawn.surveyModel` | `optional-config` | Optional model route for survey workers. |
117
+ | `survey.sandbox` | `optional-config` | Tuning, not a capability: shared (read-only in the shared checkout) or detached (a disposable detached sandbox that may write and run commands). The tower-survey row is still the switch. |
107
118
  | `spawn.maxLiveWorkers` | `core` | Resource-safety cap on live workers. |
108
119
  | `spawn.staleMs` | `optional-config` | Roster staleness window before merge is blocked. |
109
120
  | `spawn.maxWaitMs` | `optional-config` | Upper bound for a blocking spawn wait. |
@@ -113,11 +124,11 @@ All keys from `domain/config/schema.js`, classified on the component axis.
113
124
  | `tests.admission` | `optional-config` | Chooses when discovery runs inside the optional tests capability. Tuning exemption: Tuning when the admission check runs inside the optional tower-tests row, which still owns whether test tooling exists. (2026-11-03 · #138 · [0005-component-surface.md](../adr/0005-component-surface.md)) |
114
125
  | `tests.timeoutMs` | `optional-config` | Per-command discovery timeout. |
115
126
  | `tests.maxBytes` | `optional-config` | Captured discovery output cap. |
116
- | `tests.autoChecks` | `optional-config` | Attaches a tests check to completion when discovery runs. Tuning exemption: Tuning the default receipt policy inside the optional tower-tests row, which still owns whether any test tooling exists. (2026-11-03 · #138 · [0005-component-surface.md](../adr/0005-component-surface.md)) |
117
127
  | `tests.allowPackageDiscovery` | `core` | Allows repository code to execute; a security contract, not a component. |
118
128
  | `gates.requireChecks` | `forbidden-to-disable` | Requires completion evidence before merge; a merge gate. |
119
129
  | `gates.requireTests` | `forbidden-to-disable` | Requires a test receipt before merge; a merge gate. With the tower-tests row off, planning/mode entry refuse the combination. |
120
130
  | `gates.allowReviewExemption` | `forbidden-to-disable` | The review-exemption path is part of the review and merge contract. |
131
+ | `gates.requireFreshSurvey` | `forbidden-to-disable` | Promotes the survey-stale dependency warning to a merge blocker; the gate itself is part of the merge contract and defaults to advisory. |
121
132
  | `gates.maxReportBytes` | `forbidden-to-disable` | The report cap is part of the completion contract; the refusal itself is never configurable away. |
122
133
  | `docs.boardLocation` | `optional-config` | Chooses where the board is written; whether it is written is the tower-board component row (#133). |
123
134
  | `archive.afterDays` | `optional-config` | Archive age threshold inside the optional tower-archive component (#135). |
@@ -135,6 +146,7 @@ hides a capability cannot use the tuning class without that record.
135
146
 
136
147
  | Key |
137
148
  |---|
149
+ | `review.mode` |
138
150
  | `spawn.requireLease` |
139
151
  | `tests.admission` |
140
152
 
@@ -1,7 +1,7 @@
1
1
  # Config reference (generated)
2
2
 
3
3
  > Generated by `npm run docs:generate:config` from `domain/config/schema.js`. Values are defaults before repo config.
4
- > Surface: config keys: 53 · sections: 14 · tools: 29 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
4
+ > Surface: config keys: 56 · sections: 15 · tools: 29 · baseline 2026-10-23. Generated from `scripts/surface-baseline.json`; do not edit by hand.
5
5
 
6
6
  | Key | Default | Description |
7
7
  |---|---|---|
@@ -25,32 +25,35 @@
25
25
  | `mode.storePath` | `` | Directory for modes.json; defaults to $DSH_HOME/tower-mode. |
26
26
  | `mode.maxEntries` | `200` | Maximum persisted mode entries. |
27
27
  | `mode.lockTimeoutMs` | `5000` | How long saveModes waits for the cross-process modes.lock before giving up. |
28
- | `review.requireReview` | `false` | Require an independent clean review before merge. Default false: review is optional; set true to enforce the review gate globally. |
28
+ | `review.mode` | `"optional"` | Review policy. off = review is intentionally unavailable (recording a review is refused, reviewer spawn is refused, and the merge gate ignores review; a profile-level off also does not register tower_review); optional = review is available but never blocks a merge (default); required = a clean review bound to the branch tip is a merge blocker. |
29
+ | `review.requireReview` | `false` | Deprecated alias for review.mode (true -> required, false -> optional); prefer review.mode. true next to a non-required mode is rejected; false never overrides an explicit mode (off, optional or required). The Plugins-page control shows the alias when it is the only key and always writes review.mode with review.requireReview:false. |
29
30
  | `review.maxRoundsPerReviewer` | `5` | Review-round cap per reviewer per mission branch. |
30
31
  | `merge.timeoutMs` | `900000` | Default tower_merge timeout, including pre-merge hooks. |
31
32
  | `merge.maxBufferBytes` | `67108864` | Captured merge output buffer. |
32
33
  | `git.timeoutMs` | `15000` | Default timeout for Git commands other than merge; merge uses merge.timeoutMs. |
33
34
  | `worktrees.root` | `` | Default parent directory for mission worktrees; empty derives <main-checkout-parent>/.dsh-tower-worktrees/<hash>. |
34
- | `spawn.workerPreset` | `"tower-worker"` | Agent preset for worker missions. |
35
+ | `spawn.workerPreset` | `"tower-worker"` | Agent preset for build workers. |
35
36
  | `spawn.reviewerPreset` | `"tower-reviewer"` | Agent preset for reviewers. |
36
37
  | `spawn.maxDepth` | `1` | Subagent depth requested when spawning tower agents. |
37
38
  | `spawn.provider` | `` | Subagent provider id; empty picks spawn, then the first registered provider. |
38
39
  | `spawn.workerModel` | `{}` | Model route for workers; omitted inherits the parent session. |
39
40
  | `spawn.reviewerModel` | `{}` | Model route for reviewers; omitted inherits the parent session (recommended). |
41
+ | `spawn.surveyModel` | `{}` | Model route for survey workers; omitted inherits the parent session. |
40
42
  | `spawn.maxLiveWorkers` | `4` | Maximum live worker entries allowed per repository at once. |
41
43
  | `spawn.staleMs` | `15000` | Roster entries with no catalog activity after this age are marked stale and block merge (0 disables). |
42
44
  | `spawn.maxWaitMs` | `90000` | Upper bound for tower_spawn waitMs (default 90s, Kimi-compatible). |
43
45
  | `spawn.requireLease` | `` | Optional lease name tower_spawn must hold before starting a worker. |
44
46
  | `send.maxPerMinute` | `120` | Per-repository tower_send rate limit per minute (0 disables). |
45
47
  | `send.interruptDefault` | `false` | Default value of tower_send.interrupt when the caller omits it. |
48
+ | `survey.sandbox` | `"none"` | Where a survey reads. none = the shared checkout with a read-only preset (writes and shell commands are impossible); detached = a disposable detached git worktree created at the recorded commit, with a sandbox preset that may write and run commands inside it. A detached sandbox is removed when the survey completes or is abandoned; it has no branch, never participates in merge/stale-base, and cannot commit its evidence. |
46
49
  | `tests.admission` | `"off"` | Run discovery before spawning a worker, when completing, or only on explicit tower_test (default off: repository code runs only on an explicit tower_test). |
47
50
  | `tests.timeoutMs` | `60000` | Per discovery command timeout. |
48
51
  | `tests.maxBytes` | `16777216` | Maximum captured stdout bytes per command. |
49
- | `tests.autoChecks` | `true` | Attach a tests check to the completion report when discovery runs. |
50
52
  | `tests.allowPackageDiscovery` | `true` | Allow package.json tower.testDiscovery commands from the mission worktree (executes repository code; see security docs). |
51
- | `gates.requireChecks` | `false` | Require at least one completion check (tower_complete.checks) before a mission can merge. |
53
+ | `gates.requireChecks` | `false` | Require at least one recorded check (tower_complete.checks) or test-discovery receipt before a mission can merge. |
52
54
  | `gates.requireTests` | `false` | Require a passing test-discovery receipt for the reviewed worktree tip before merge. |
53
55
  | `gates.allowReviewExemption` | `false` | Allow reviewPolicy:none missions with a non-empty reviewExemptionReason to skip review blockers. |
56
+ | `gates.requireFreshSurvey` | `false` | Promote the advisory survey-stale dependency warning to a merge blocker: a build may only merge when every survey dependency read a commit that is an ancestor-or-equal of the build base. Default false (warning only). |
54
57
  | `gates.maxReportBytes` | `32768` | Hard cap, in UTF-8 bytes, for tower_complete.report; a larger report is rejected with an actionable nextAction, never truncated. |
55
58
  | `docs.boardLocation` | `"common"` | common = <git-common-dir>/dsh-tower/MISSIONS.md; repo = <repoRoot>/MISSIONS.md. Whether the board is written at all is the tower-board component row (#133). |
56
59
  | `archive.afterDays` | `30` | Terminal missions untouched for at least this many days are archived on the next mutation (0 archives them immediately). Archival itself is the tower-archive component row (#135). |
@@ -27,13 +27,38 @@ win, profile keys survive when the repo does not set them. `tower_status` report
27
27
 
28
28
  ```json
29
29
  {
30
- "review": { "requireReview": true, "maxRoundsPerReviewer": 3 },
30
+ "review": { "mode": "required", "maxRoundsPerReviewer": 3 },
31
31
  "merge": { "timeoutMs": 1800000 },
32
32
  "worktrees": { "root": "D:/tower-worktrees" },
33
33
  "spawn": { "reviewerPreset": "tower-reviewer", "maxDepth": 2 }
34
34
  }
35
35
  ```
36
36
 
37
+ `review.mode` is the review policy: `off`, `optional` or `required`. `review.requireReview` is a
38
+ deprecated alias kept for one release (`true` → `required`, `false` → `optional`); prefer
39
+ `review.mode`. When both are set in the same layer, `requireReview:true` next to any mode other than
40
+ `required` is a contradiction and is rejected with `config-invalid` naming both keys.
41
+ `requireReview:false` never contradicts an explicit mode: `off`, `optional` and `required` all stay
42
+ accepted and the mode wins (`required` still enforces the gate; the resolved alias is mirrored back as
43
+ `true` only for that mode). A repository layer is resolved on its own: it may relax review, but a
44
+ profile with `review.mode:off` never registered `tower_review`, so a repository cannot raise its own
45
+ mode.
46
+
47
+ The Plugins-page control shows the policy exactly as the runtime resolves it: `review.mode` decides
48
+ when present, and a profile that still sets only `review.requireReview` displays Required (`true`) or
49
+ Optional (`false`). Saving a choice always writes `review.mode` and sets `review.requireReview` to
50
+ `false` in the same profile patch, so the alias can never contradict the selected mode.
51
+
52
+ The same three choices are available on the Plugins page, in the `dsh-tower` configuration
53
+ section: **Off**, **Optional** (the default) and **Required**, each with a one-line description of
54
+ its effect. That control writes the profile config through the Host settings form, so the accepted
55
+ value is reflected on the page without a manual file edit.
56
+
57
+ One live change cannot rebuild the mounted tool surface: a profile that started with review off did
58
+ not register `tower_review`. Switching to Optional or Required through the page immediately changes
59
+ the merge gate, but the review tool itself appears only after a restart (or a profile reload); the
60
+ control states that after such a save.
61
+
37
62
  The `mode.*` group is session-scoped, not repository-scoped: it is accepted in the repository file
38
63
  for schema compatibility but only the profile config controls the Tower-mode prompt and the
39
64
  model-controlled toggle. A malformed file, a non-object value, unknown top-level keys, or a value that
@@ -61,7 +86,8 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
61
86
  | `mode.denyTools` | goal/todo/team/workflow/subagent tools | Tools refused while the mode is active (12 defaults). |
62
87
  | `mode.storePath` | `$DSH_HOME/tower-mode` | Directory for `modes.json`. |
63
88
  | `mode.maxEntries` | `200` | Maximum persisted mode entries (1–2000). |
64
- | `review.requireReview` | `false` | Enforce an independent clean review before merge. Default `false` (review optional); set `true` to require `no-review`/`not-clean`/`review-hold`/`review-not-approved`/`tip-moved`/`review-mission-mismatch` checks. `tower_review` remains available either way. |
89
+ | `review.mode` | `optional` | Review policy. `off` = review is intentionally unavailable: recording a review is refused, `tower_spawn` refuses the reviewer role, the merge gate ignores review, and a profile-level `off` does not register `tower_review` (a repository-level `off` refuses the submission instead); `optional` = review is available but never blocks a merge (default); `required` = a clean review bound to the branch tip is a merge blocker (`no-review`/`not-clean`/`review-hold`/`review-not-approved`/`tip-moved`/`review-mission-mismatch`). |
90
+ | `review.requireReview` | `false` | Deprecated alias for `review.mode`: `true` → `required`, `false` → `optional`. Prefer `review.mode`. `true` next to a non-`required` `review.mode` is rejected with `config-invalid` naming both keys; `false` is accepted next to any explicit `review.mode` (`off`/`optional`/`required`) and never overrides it. The Plugins-page control shows the alias when it is the only key and always writes `review.mode` with `review.requireReview:false`. |
65
91
  | `review.maxRoundsPerReviewer` | `5` | Review-round cap per reviewer per branch. |
66
92
  | `merge.timeoutMs` | `900000` (15 min) | Default `tower_merge` timeout, including hooks. A per-call `timeoutMs` wins. |
67
93
  | `merge.maxBufferBytes` | `67108864` (64 MB) | Merge output buffer. |
@@ -82,11 +108,10 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
82
108
  | `tests.admission` | `off` | `spawn` runs discovery before starting a worker, `complete` at `tower_complete`, `off` only on explicit `tower_test` (default; repository code is not executed automatically). |
83
109
  | `tests.timeoutMs` | `60000` | Per discovery command timeout. |
84
110
  | `tests.maxBytes` | `16777216` (16 MiB) | Captured stdout cap per command. |
85
- | `tests.autoChecks` | `true` | Attach an automatic `tests` check to the completion report. |
86
111
  | `tests.allowPackageDiscovery` | `true` | Allow repo-controlled `tower.testDiscovery` commands (executes worktree code; see security). |
87
- | `gates.requireChecks` | `false` | Require at least one `tower_complete.checks` entry before merge (`no-check-evidence`). |
112
+ | `gates.requireChecks` | `false` | Require at least one recorded check (`tower_complete.checks`) or a test-discovery receipt before merge (`no-check-evidence`). |
88
113
  | `gates.requireTests` | `false` | Require a passing discovery receipt for the current mission branch tip (`tests-missing`); independent of the review gate. With the `tower-tests` component row off, planning/mode entry refuse this combination with an explanation. |
89
- | `gates.allowReviewExemption` | `false` | With `review.requireReview:true`, allow `reviewPolicy:"none"` + a non-empty reason to skip review blockers. Off deliberately; irrelevant while the global review gate is off. |
114
+ | `gates.allowReviewExemption` | `false` | With `review.mode:required`, allow `reviewPolicy:"none"` + a non-empty reason to skip review blockers. Off deliberately; irrelevant when `review.mode` is `off` or `optional` (no review blocker is produced). |
90
115
  | `gates.maxReportBytes` | `32768` (32 KiB) | Hard cap in UTF-8 bytes for `tower_complete.report`. A larger report is rejected with `invalid-argument` and an actionable `nextAction` (file the full text in the mission worktree and reference its path); it is never truncated. Existing larger reports still load. |
91
116
  | `docs.boardLocation` | `common` | `common` = `<git-common-dir>/dsh-tower/MISSIONS.md`; `repo` = `<repoRoot>/MISSIONS.md`. Whether the board is written at all is the `tower-board` component row (#133); `docs.board` was removed and is rejected as an unknown key. |
92
117
  | `archive.afterDays` | `30` | Age trigger: a terminal mission untouched for at least this many days is archived on the next mutation (`0` archives immediately). Archival itself is the `tower-archive` component row (#135); `archive.enabled` was removed and is rejected as an unknown key. |
@@ -8,17 +8,17 @@ It returns:
8
8
  "ok": true,
9
9
  "checks": [
10
10
  { "id": "git", "status": "pass", "detail": "git version 2.55.0" },
11
- { "id": "surface", "status": "pass", "detail": "config keys: 53 · sections: 14 · tools: 29 (baseline 2026-10-23)" },
11
+ { "id": "surface", "status": "pass", "detail": "config keys: 57 · sections: 15 · tools: 29 (baseline 2026-10-23)" },
12
12
  { "id": "state", "status": "pass", "detail": ".../state.json revision 7" },
13
- { "id": "state-schema", "status": "pass", "detail": "schemaVersion 3 is current" },
13
+ { "id": "state-schema", "status": "pass", "detail": "schemaVersion 5 is current" },
14
14
  { "id": "case-mode", "status": "pass", "detail": "caseInsensitive: true; scope matching uses case-insensitive comparison" }
15
15
  ],
16
16
  "surface": {
17
- "configKeys": 53,
18
- "sections": 14,
17
+ "configKeys": 57,
18
+ "sections": 15,
19
19
  "tools": 29,
20
20
  "baselineDate": "2026-10-23",
21
- "line": "config keys: 53 · sections: 14 · tools: 29"
21
+ "line": "config keys: 57 · sections: 15 · tools: 29"
22
22
  },
23
23
  "stateLock": {
24
24
  "held": true,
@@ -38,8 +38,8 @@ It returns:
38
38
  "heldMs": 92000
39
39
  },
40
40
  "stateSchema": {
41
- "currentVersion": 3,
42
- "supportedVersion": 3,
41
+ "currentVersion": 5,
42
+ "supportedVersion": 5,
43
43
  "migrations": [],
44
44
  "status": "current"
45
45
  },
@@ -29,6 +29,7 @@ single recovery step appended to the message as ` — next: …` (with ` (retrya
29
29
  | `provider-cooldown` | The provider is throttled; spawn is in a short cooldown. | yes | wait for the cooldown to expire, then retry |
30
30
  | `preset-missing` | The configured agent preset is not registered. | no | install the tower-worker and tower-reviewer agent presets and restart DSH |
31
31
  | `reviewer-prerequisite` | The reviewer spawn prerequisite is not met. | no | complete the mission first, then spawn the reviewer |
32
+ | `review-disabled` | Review is disabled by `review.mode:off`; no reviewer, verdict, or review blocker exists in this configuration. | no | set review.mode to optional or required in the Tower config, then retry |
32
33
  | `not-authorized` | The calling agent role may not perform this action. | no | call the tool as the tower, or as the worker/reviewer it is assigned to |
33
34
  | `workspace-busy` | Another Tower session owns this repository workspace lock. | yes | run /tower off in the other session or wait for its lock to become stale |
34
35
  | `worker-limit` | The repository reached spawn.maxLiveWorkers live workers. | no | wait for a live worker to finish or raise spawn.maxLiveWorkers |
@@ -2,7 +2,7 @@
2
2
 
3
3
  Tool failures are thrown errors, not `{ok:false}` results (gate outcomes are the exception: see
4
4
  [blockers.md](blockers.md)). Every thrown error carries a stable code as a message prefix. This
5
- release defines 35 tool error codes.
5
+ release defines 36 tool error codes.
6
6
 
7
7
  Every `TowerToolError` carries `retryable` (boolean), `nextAction` (string or `null`) and `details`
8
8
  (structured context or `null`) for programmatic callers. The model-visible surface is the message:
@@ -43,6 +43,7 @@ plain `Error` without a structured code is reported as `tool-internal`.
43
43
  | `provider-cooldown` | The provider is throttled; spawn is in a short cooldown. | yes | wait for the cooldown to expire, then retry | repeated retryable provider failures; wait for `blockedUntil` |
44
44
  | `preset-missing` | The configured agent preset is not registered. | no | install the tower-worker and tower-reviewer agent presets and restart DSH | missing `tower-worker`/`tower-reviewer` preset rows, DSH not restarted |
45
45
  | `reviewer-prerequisite` | The reviewer spawn prerequisite is not met. | no | complete the mission first, then spawn the reviewer | reviewer spawned before the mission is `completed`, or before a worktree exists |
46
+ | `review-disabled` | Review is disabled by `review.mode:off`; no reviewer, verdict, or review blocker exists in this configuration. | no | set review.mode to optional or required in the Tower config, then retry | `tower_spawn` with `role:"reviewer"`, or a review submission, while `review.mode` is `off` |
46
47
  | `not-authorized` | The calling agent role may not perform this action. | no | call the tool as the tower, or as the worker/reviewer it is assigned to | a worker calling a tower-only tool or changing mission status/scope; a reviewer mutating findings |
47
48
  | `workspace-busy` | Another Tower session owns this repository workspace lock. | yes | run /tower off in the other session or wait for its lock to become stale | `mode.workspaceLock:true` and a second session calls a repo-scoped tool |
48
49
  | `worker-limit` | The repository reached spawn.maxLiveWorkers live workers. | no | wait for a live worker to finish or raise spawn.maxLiveWorkers | spawning a fifth worker with the default limit |
@@ -31,7 +31,7 @@ tower_resource({action:"release", name:"heavy-ci", token:"<token from acquire>"}
31
31
 
32
32
  - acquire is tower-only; the caller gets a token, pid, holder, created/expiry timestamps;
33
33
  - the same token renews; another holder gets `[tower:lease-busy]`;
34
- - release requires the token; an **expired** lease still requires the token and explicit `force:true`
34
+ - release requires the holder token; an **expired** lease is recovered by an explicit `force:true` release (a live lease still refuses a foreign token)
35
35
  (no silent takeover);
36
36
  - `spawn.requireLease` makes a worker spawn fail unless that lease is held.
37
37