dsh-tower 2.6.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.
- package/CHANGELOG.md +82 -0
- package/COMPATIBILITY.md +1 -0
- package/CONTRIBUTING.md +1 -1
- package/README.en.md +2 -1
- package/README.md +6 -1
- package/adapters/leases.js +20 -6
- package/adapters/legacy.js +149 -37
- package/adapters/stateFile.js +11 -1
- package/app/archive.js +30 -5
- package/app/briefing.js +95 -12
- package/app/checkCompletion.js +107 -18
- package/app/enrichedMissions.js +1 -1
- package/app/freshness.js +77 -0
- package/app/gate.js +26 -12
- package/app/merge.js +7 -4
- package/app/mission.js +24 -1
- package/app/missionContext.js +2 -1
- package/app/plan.js +64 -1
- package/app/provision.js +133 -22
- package/app/read.js +38 -5
- package/app/rebase.js +10 -8
- package/app/review.js +97 -61
- package/app/reviewCheckout.js +133 -0
- package/app/sandbox.js +121 -0
- package/app/sharedCheckout.js +90 -0
- package/app/spawn-prechecks.js +25 -24
- package/app/spawn-support.js +59 -8
- package/app/spawn.js +3 -0
- package/app/teardown.js +119 -2
- package/app/tower.js +29 -1
- package/cordis.patch.yml +50 -0
- package/docs/adr/0006-mission-shapes.md +306 -0
- package/docs/architecture/overview.md +6 -4
- package/docs/guide/install-upgrade-migration.md +26 -0
- package/docs/operations/runbook.md +1 -1
- package/docs/reference/blockers.md +8 -4
- package/docs/reference/component-surface.md +7 -2
- package/docs/reference/config.generated.md +6 -4
- package/docs/reference/config.md +1 -2
- package/docs/reference/doctor.md +7 -7
- package/docs/reference/operations.md +1 -1
- package/docs/reference/shapes.md +123 -0
- package/docs/reference/surface-budget.md +33 -1
- package/docs/reference/testing.md +4 -2
- package/docs/reference/tools.generated.md +5 -5
- package/docs/reference/tools.md +2 -2
- package/docs/zh/reference/blockers.md +2 -2
- package/docs/zh/reference/config.md +1 -2
- package/docs/zh/reference/testing.md +3 -2
- package/domain/batch.js +75 -105
- package/domain/collab/batch.js +30 -10
- package/domain/collab/spawn.js +53 -16
- package/domain/config/schema.js +47 -17
- package/domain/contracts/outputs.js +0 -1
- package/domain/contracts/tool-inputs/missions.js +9 -1
- package/domain/evidence.js +283 -0
- package/domain/gate.js +11 -31
- package/domain/integration/gate.js +17 -43
- package/domain/integration/init.js +3 -2
- package/domain/integration/provision.js +51 -0
- package/domain/integration/rebase.js +16 -1
- package/domain/mission.js +16 -33
- package/domain/missions/completion.js +46 -8
- package/domain/missions/findings.js +2 -1
- package/domain/missions/lifecycle.js +131 -35
- package/domain/missions/plan.js +30 -6
- package/domain/missions/review.js +2 -18
- package/domain/planning.js +5 -5
- package/domain/read/missionContext.js +17 -3
- package/domain/read/views.js +5 -1
- package/domain/roles.js +222 -0
- package/domain/roster.js +9 -3
- package/domain/scope.js +51 -0
- package/domain/shapes/build.js +254 -0
- package/domain/shapes/index.js +171 -0
- package/domain/shapes/survey.js +445 -0
- package/domain/state/archive.js +28 -4
- package/domain/state/migrations.js +111 -4
- package/domain/state/schema.js +69 -7
- package/domain/state/semantics.js +93 -13
- package/domain/state.js +5 -1
- package/domain/summary.js +5 -11
- package/domain/vocabulary.js +15 -33
- package/host/capability-registry.js +25 -9
- package/host/doctor.js +2 -1
- package/host/preset-survey-sandbox.js +19 -0
- package/host/preset-survey.js +18 -0
- package/host/presets.js +8 -3
- package/host/tool-registry.js +14 -12
- package/locale/en.json +16 -1
- package/locale/preset-survey/en.json +6 -0
- package/locale/preset-survey/zh.json +6 -0
- package/locale/preset-survey-sandbox/en.json +6 -0
- package/locale/preset-survey-sandbox/zh.json +6 -0
- package/locale/zh.json +16 -1
- package/package.json +13 -2
- package/ports/state.js +8 -1
- package/scripts/architecture-waivers.json +3 -3
- package/scripts/check-docs.mjs +1 -1
- package/scripts/check-pack.mjs +2 -0
- package/scripts/component-surface.json +68 -20
- package/scripts/generate-capability-locale.mjs +5 -0
- package/scripts/generate-shapes-reference.mjs +171 -0
- package/scripts/mount-smoke/surface.mjs +3 -0
- package/scripts/pack-manifest.txt +21 -0
- package/scripts/shape-audit.mjs +361 -0
- package/scripts/surface-baseline.json +68 -4
- package/survey-sandbox.js +48 -0
- package/survey.js +40 -0
- package/tools/complete.js +5 -5
- package/tools/diff.js +5 -3
- package/tools/plan.js +1 -1
- package/tools/preflight.js +3 -2
- package/tools/spawn.js +47 -49
- package/tools/test.js +2 -1
- package/types/index.d.ts +66 -5
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# ADR 0006: Mission shapes
|
|
2
|
+
|
|
3
|
+
- Status: accepted
|
|
4
|
+
- Date: 2026-11-10
|
|
5
|
+
- Ref: #148, #149
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
`kind` was a column on one mission record, but it carried three contracts: a build
|
|
10
|
+
owns a branch and a worktree, is reviewed at its tip, and merges; a survey only
|
|
11
|
+
reads one commit. The state document could describe both shapes at once, so the
|
|
12
|
+
generic layers kept asking `kind === 'survey'` and the answer leaked into
|
|
13
|
+
completion (`commit >= 1`), the merge gate (`read-only-survey`), archive and
|
|
14
|
+
reanchor terminality, planning's open-work count, and provisioning. A survey
|
|
15
|
+
that completed could not be archived, blocked reanchor forever, counted as open
|
|
16
|
+
work, and paid for a full checkout while consuming the live-worker budget.
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
`domain/shapes/` is the one registry. Each shape module owns its statuses,
|
|
21
|
+
transitions, terminality, merge/rebase permission, provisioning, plan fields,
|
|
22
|
+
completion invariants, dependency satisfaction, briefing and summary projection.
|
|
23
|
+
`domain/shapes/index.js` is the only way a generic layer resolves a shape:
|
|
24
|
+
`shapeFor(mission.kind)`, `isTerminal(kind, status)`, `canTransition(kind, from,
|
|
25
|
+
to)`, and `assertActionSupported(mission, action)` for merge/rebase/preflight/diff.
|
|
26
|
+
The global `TERMINAL_STATUSES`/`TRANSITIONS` vocabulary and the one-argument
|
|
27
|
+
`isTerminal`/`canTransition` entry points are deleted.
|
|
28
|
+
|
|
29
|
+
Schema version 4 is a TypeBox union: a build mission keeps its worktree/branch and
|
|
30
|
+
merge fields; a survey carries `readCommit` and `readDirty` and may not carry
|
|
31
|
+
`worktree`, `branch`, `baseCommit`, `mergedCommit` or `mergedAt`. `merged` is not
|
|
32
|
+
a survey status. Migration v3→v4 is pure, one-way and idempotent: it stamps every
|
|
33
|
+
mission with a shape, reads a legacy survey's commit from
|
|
34
|
+
`worktree.baseCommit ?? baseCommit`, drops its branch fields, and maps a legacy
|
|
35
|
+
`merged` survey to `completed`.
|
|
36
|
+
|
|
37
|
+
Read-only is structural, not a gate: `tower_plan({kind:'survey'})` refuses while
|
|
38
|
+
the `tower-survey` component row is off; a survey spawn uses the shared checkout
|
|
39
|
+
and the `tower-survey` preset allow-list; `tower_complete` checks that HEAD still
|
|
40
|
+
equals `readCommit`, that the tracked-status set matches the set captured at plan
|
|
41
|
+
time, and that zero commits were made.
|
|
42
|
+
|
|
43
|
+
`scripts/shape-audit.mjs` enforces the decision: no kind or role literal
|
|
44
|
+
comparison outside `domain/shapes/` and `domain/roles.js`, no global status
|
|
45
|
+
vocabulary, no one-argument entry points. Waivers are justified data in
|
|
46
|
+
`scripts/architecture-waivers.json` under `shapeWaivers`; an unjustified or stale
|
|
47
|
+
waiver fails. `docs/reference/shapes.md` is generated from the registry.
|
|
48
|
+
|
|
49
|
+
## Surface budget
|
|
50
|
+
|
|
51
|
+
The shape redesign grows the config surface by exactly one key, recorded in
|
|
52
|
+
`scripts/surface-baseline.json`:
|
|
53
|
+
|
|
54
|
+
- `spawn.surveyModel` (tuning): an optional independent model route, mirroring
|
|
55
|
+
`spawn.workerModel`/`spawn.reviewerModel`.
|
|
56
|
+
|
|
57
|
+
An earlier cut of this work also added `spawn.surveyPreset` (core) and
|
|
58
|
+
`survey.sandbox` (tuning) in a new `survey` section. Review contracted the
|
|
59
|
+
surface back before release (config 57 → 55, sections 15 → 14): a shape owns its
|
|
60
|
+
preset (`SHAPES.survey.spawn.defaultPreset`), so a `spawn.surveyPreset` key could
|
|
61
|
+
only disagree with the registry; and `survey.sandbox` advertised a detached
|
|
62
|
+
read-only checkout that W1 does not implement. Neither is a tuning knob a user
|
|
63
|
+
should see; the completion invariants (HEAD, tracked-status fingerprint, zero
|
|
64
|
+
commits) are the read-only guarantee, not a placement option.
|
|
65
|
+
|
|
66
|
+
The `tower-survey` row is a visible optional component (components 11 → 12): off
|
|
67
|
+
means surveys cannot be planned (an actionable code) and no survey agent can be
|
|
68
|
+
dispatched; build missions are unaffected. It is a declarative preset row: the
|
|
69
|
+
host resolves its availability through the async `agentPresets` probe, not the
|
|
70
|
+
process-local code registry.
|
|
71
|
+
|
|
72
|
+
## Rejected alternatives
|
|
73
|
+
|
|
74
|
+
- Keep `kind` on one record and guard each leak with another `kind === 'survey'`
|
|
75
|
+
check. Rejected: that is exactly the census in #149; the state document would
|
|
76
|
+
still be able to describe both shapes and every new layer would re-learn the
|
|
77
|
+
rule.
|
|
78
|
+
- Add a `shape: 'build'|'survey'` flag selecting an old and a new code path during
|
|
79
|
+
a transition period. Rejected: a dual-shape path lets a document carry an
|
|
80
|
+
impossible combination (a survey with a worktree, a merged survey) and makes the
|
|
81
|
+
migration untestable.
|
|
82
|
+
- Keep `commit >= 1` and make a survey commit an empty commit. Rejected: a survey
|
|
83
|
+
is a read, so the empty commit records nothing and hides that HEAD moved.
|
|
84
|
+
- Enforce read-only at merge time (`read-only-survey` in the gate). Rejected: the
|
|
85
|
+
write already happened by then; read-only must be structural (preset allow-list)
|
|
86
|
+
and checked as a completion invariant.
|
|
87
|
+
- Give a survey its own branch so the existing lifecycle "just works". Rejected:
|
|
88
|
+
it reintroduces provisioning cost, branch ownership, stale-base and merge
|
|
89
|
+
vocabulary for a kind that never merges.
|
|
90
|
+
|
|
91
|
+
## Consequences
|
|
92
|
+
|
|
93
|
+
- `kind` is now a closed vocabulary with one registry; adding a kind means adding
|
|
94
|
+
a shape module and regenerating `docs/reference/shapes.md`, not editing generic
|
|
95
|
+
layers.
|
|
96
|
+
- Older v3 documents migrate on load; a legacy survey's `worktree` is removed by
|
|
97
|
+
a bounded-retry teardown and its completion with commits is accepted as history.
|
|
98
|
+
- A completed survey is terminal, archivable, no longer open work, and no longer
|
|
99
|
+
blocks reanchor or the live-worker budget.
|
|
100
|
+
- Surveys can be planned only while the `tower-survey` row is on, and they cannot
|
|
101
|
+
be merged, rebased, preflighted or diffed: each refuses with `invalid-transition`
|
|
102
|
+
and an actionable next step.
|
|
103
|
+
- The shape audit is part of `npm run check`; a new kind or role comparison in a
|
|
104
|
+
generic layer fails CI unless a justified waiver is recorded.
|
|
105
|
+
|
|
106
|
+
## Addendum: W2 detached sandbox and dependency semantics (#148)
|
|
107
|
+
|
|
108
|
+
W1 implemented the shared-checkout survey only. W2 adds the placement it left
|
|
109
|
+
out, without changing the default or the shared path:
|
|
110
|
+
|
|
111
|
+
- `survey.sandbox` (`none`|`detached`, default `none`) is tuning, recorded in
|
|
112
|
+
`scripts/surface-baseline.json` (config keys 55 → 56, sections 14 → 15). The
|
|
113
|
+
shape resolves the placement (`resolveProvisioning`) and the preset
|
|
114
|
+
(`resolvePreset`); no generic layer reads the key or compares a kind.
|
|
115
|
+
`spawn.surveyPreset` stays removed because the preset follows the registry and
|
|
116
|
+
the resolved placement.
|
|
117
|
+
- `detached` provisions `git worktree add --detach <worktreeRoot>/<id>-sandbox
|
|
118
|
+
<readCommit>` with no branch and records `mission.sandbox`. It never records
|
|
119
|
+
`baseCommit`, never participates in stale-base, review or merge, and the
|
|
120
|
+
completion invariant is sandbox HEAD === `readCommit` plus zero commits.
|
|
121
|
+
Expected write/shell side effects in the disposable checkout are ignored;
|
|
122
|
+
the shared placement keeps the tracked-status fingerprint invariant.
|
|
123
|
+
- The sandbox preset (`tower-survey-sandbox`) allows read/write/edit/pwsh plus
|
|
124
|
+
the mission tools and still denies spawn/merge/rebase/review/diff. Both survey
|
|
125
|
+
presets are internal children of the one visible `tower-survey` group row, so
|
|
126
|
+
the component count does not move (12) and turning the capability off removes
|
|
127
|
+
both.
|
|
128
|
+
- Cleanup is advisory: completion, abandon and `tower_teardown` remove the
|
|
129
|
+
sandbox with a bounded retry. A failure keeps `mission.sandbox`, records
|
|
130
|
+
`sandboxCleanupError` and names the `git worktree remove --force <path>`
|
|
131
|
+
remedy in the activity log; the transition itself is not rolled back. Archival
|
|
132
|
+
defers a mission whose sandbox is still present instead of moving the only
|
|
133
|
+
record of the path out of hot state.
|
|
134
|
+
- `gates.requireFreshSurvey` (default `false`) controls dependency freshness. A
|
|
135
|
+
build depending on a survey whose `readCommit` is not ancestor-or-equal of the
|
|
136
|
+
build base gets a `survey-stale` warning in plan/preflight with
|
|
137
|
+
`{code,missionId,depId,readCommit,baseCommit,message}`; only when the gate is
|
|
138
|
+
`true` does the same code block the merge. The ancestor check is the existing
|
|
139
|
+
`isAncestor` (`git merge-base --is-ancestor`, which is true for equal commits).
|
|
140
|
+
- A build briefing carries each survey dependency's recorded `result`
|
|
141
|
+
(`paths`/`symbols`/`plan`/`notes`) with bounded formatting, so the dependent
|
|
142
|
+
worker sees the evidence rather than only the terminal status. `result`
|
|
143
|
+
remains survey-only at completion.
|
|
144
|
+
- Placement is decided at provisioning time, not at completion time: a recorded
|
|
145
|
+
`mission.sandbox` wins over current `survey.sandbox` config for provisioning,
|
|
146
|
+
preset and completion invariants. Flipping the key after a sandbox was created
|
|
147
|
+
therefore cannot skip the sandbox HEAD/zero-commit checks or strand the
|
|
148
|
+
disposable checkout, and a survey that never got a sandbox keeps the shared
|
|
149
|
+
invariants.
|
|
150
|
+
- The archive index keeps a survey's `readCommit` and bounded `result`, so an
|
|
151
|
+
archived survey dependency still carries its evidence into a build briefing
|
|
152
|
+
and `gates.requireFreshSurvey` cannot fail open after archiving (#73).
|
|
153
|
+
|
|
154
|
+
## Addendum: W3 review, batch, leases and paused-vs-blocked (#149)
|
|
155
|
+
|
|
156
|
+
W3 audited four state dimensions that are not mission kinds. None became a
|
|
157
|
+
shape: each has one owning module, and each verdict names a fitness rule that
|
|
158
|
+
`npm run shape:audit` enforces (generated into `docs/reference/shapes.md`).
|
|
159
|
+
|
|
160
|
+
- **Review — commit-stamped evidence (`app/review.js`, `app/reviewCheckout.js`,
|
|
161
|
+
`domain/missions/review.js`).** A verdict is evidence about a commit, not about
|
|
162
|
+
a mutable worktree. `tower_spawn` provisions a disposable detached checkout of
|
|
163
|
+
the mission branch tip for the reviewer and records `reviewCommit` /
|
|
164
|
+
`reviewCheckout` on the roster row. `tower_review` stamps `reviewedCommit` with
|
|
165
|
+
that recorded commit, refuses if the checkout HEAD moved, and the merge gate
|
|
166
|
+
compares the stamp to the current branch tip with the existing `tip-moved`
|
|
167
|
+
blocker. `tower_diff` shows a reviewer the same recorded commit. The old
|
|
168
|
+
after-the-fact "reviewer modified the branch" check (`assertReviewerSpawnTip`)
|
|
169
|
+
is deleted: a stale worker write can no longer affect what a review decided.
|
|
170
|
+
The checkout is removed when the verdict is recorded and retried by
|
|
171
|
+
`tower_teardown`. A tower break-glass verdict stamps the current branch tip
|
|
172
|
+
because there is no checkout. Fitness rule: `review-tip-read`.
|
|
173
|
+
- **Batch — correctly generic second lifecycle (`domain/batch.js`,
|
|
174
|
+
`domain/collab/batch.js`, `app/batch.js`).** A batch is finite evidence over
|
|
175
|
+
missions, not a mission kind, so it stays generic — but its second state
|
|
176
|
+
machine is now owned by one module: `BATCH_STATUS`, `BATCH_TRANSITIONS`
|
|
177
|
+
(`open → closed | abandoned`), `canTransitionBatch`, `isBatchTerminal` and
|
|
178
|
+
`normalizeBatchStatus` (legacy `created`/`active`/`pending` read as `open`).
|
|
179
|
+
`createBatch`/`closeBatch`/`abandonBatch` share one guarded transition and
|
|
180
|
+
state semantics validates the stored status, so an unknown or invented batch
|
|
181
|
+
status fails as state corruption. Receipt verification is unchanged. Fitness
|
|
182
|
+
rule: `batch-status-literal`.
|
|
183
|
+
- **Leases — advisory coordination, never authority (`adapters/leases.js`,
|
|
184
|
+
`app/spawn-prechecks.js`).** Lease acquisition is not OS-enforced exclusivity,
|
|
185
|
+
and the docs say so. There is exactly one enforcement point: the fail-closed
|
|
186
|
+
spawn admission when `spawn.requireLease` names a lease, which refuses a
|
|
187
|
+
missing lease and a lease held by another actor with an actionable remedy. The
|
|
188
|
+
prior contract deadlocked: an expired lease held by a crashed process could
|
|
189
|
+
never be released because a token mismatch was refused even with `force`. Now
|
|
190
|
+
`force: true` releases an **expired** lease (explicit crash recovery) while a
|
|
191
|
+
live lease still refuses a foreign token, and `acquire` never takes over
|
|
192
|
+
automatically. Fitness rule: `lease-ledger`.
|
|
193
|
+
- **Paused vs blocked — two distinct stopped semantics
|
|
194
|
+
(`domain/missions/lifecycle.js`, `domain/state/semantics.js`).** `blocked` is
|
|
195
|
+
derived machine state: it means "open blockers exist". `paused` is a deliberate
|
|
196
|
+
stop with a `pauseCause` (`user`|`failure`|`resource`|`dependency`). They never
|
|
197
|
+
coexist: a recorded blocker clears a pause and moves the mission to `blocked`;
|
|
198
|
+
`clear_blockers` clears them and resumes the mission (to `active`, or to
|
|
199
|
+
`completed` in the same patch); pausing or completing is refused while blockers
|
|
200
|
+
are open; `status: "blocked"` is refused and machine writers go through
|
|
201
|
+
`markBlocked(mission, text)` (merge/rebase conflicts use it). The state
|
|
202
|
+
invariant `status === "blocked" ⇔ open blockers exist` (for non-terminal
|
|
203
|
+
missions) is checked on every load/validation, and migration v4→v5 normalizes
|
|
204
|
+
recorded states: a blocked mission without a blocker gets an explicit legacy
|
|
205
|
+
blocker, blockers under another non-terminal status become `blocked`, and a
|
|
206
|
+
superseded `pauseCause` is removed. It is not a per-kind rule: both shapes
|
|
207
|
+
declare `blocked`/`paused` in their own transition tables. Fitness rule:
|
|
208
|
+
`blocked-status-write`.
|
|
209
|
+
- Because the stopped-status invariant is new, the persisted schema version is
|
|
210
|
+
now **5** (`SCHEMA_VERSION = 5` in `domain/mission.js`); the v4→v5 migration is
|
|
211
|
+
pure and idempotent like its predecessors.
|
|
212
|
+
- The audit grew four rules (`review-tip-read`, `batch-status-literal`,
|
|
213
|
+
`lease-ledger`, `blocked-status-write`) with mutation tests proving each rule
|
|
214
|
+
fails on a seeded violation. `scripts/shape-audit.mjs` exports `RULES`, and
|
|
215
|
+
`scripts/generate-shapes-reference.mjs` refuses to generate if a verdict names
|
|
216
|
+
a rule the audit does not run.
|
|
217
|
+
|
|
218
|
+
## Addendum: W4 evidence, legacy, scope and role-vs-kind (#149)
|
|
219
|
+
|
|
220
|
+
W4 audited the four remaining dimensions. Each had no single owner; each got a
|
|
221
|
+
written verdict, one owning module, and a fitness rule that `npm run shape:audit`
|
|
222
|
+
enforces (generated into `docs/reference/shapes.md`). No dimension became a
|
|
223
|
+
per-kind conditional and no second shape table was added.
|
|
224
|
+
|
|
225
|
+
- **Test receipts / evidence — redesigned to one reader (`domain/evidence.js`).**
|
|
226
|
+
The same question ("did tests run?") previously had four answers:
|
|
227
|
+
`mission.completionReport.checks`, a synthetic `tests` check that
|
|
228
|
+
`tower_complete` copied from discovery via `tests.autoChecks`,
|
|
229
|
+
`mission.testRuns`, and a separate `gates.requireTests` filter that re-read
|
|
230
|
+
receipts. `domain/evidence.js` now owns the normalized record
|
|
231
|
+
`{names, name, source, ok, command, exitCode, evidence, treeTip, entry}` with
|
|
232
|
+
sources `completionReport` | `testRuns` | `input`, aliases `tests`/`tower_test`
|
|
233
|
+
carried in `names`, and one ordering (completion checks first, then newest
|
|
234
|
+
receipts, then caller checks scoped to the mission) applied by `findEvidence` —
|
|
235
|
+
precedence and ordering are reader decisions, not fields on the record.
|
|
236
|
+
`evidenceEntries`, `findEvidence`, `passingDiscovery`,
|
|
237
|
+
`hasCheckEvidence` and `hasTestReceipts` are the only readers; the shapes
|
|
238
|
+
declare the contract (`build.evidence = {completionChecks: true, testReceipts:
|
|
239
|
+
"tip-bound"}`, `survey.evidence = {completionChecks: true, testReceipts:
|
|
240
|
+
false}`) because only a shape with a branch has a tip to bind a receipt to.
|
|
241
|
+
`gates.requireTests` uses `passingDiscovery`; completion blockers use
|
|
242
|
+
`hasCheckEvidence`; `tower_batch` validation/summary use `findEvidence`.
|
|
243
|
+
`tower_complete` no longer copies a `tests` check into the report and
|
|
244
|
+
`tower_test` returns the receipt only, so the config key `tests.autoChecks`
|
|
245
|
+
disappears (config surface 57 → 56, recorded in `scripts/surface-baseline.json`
|
|
246
|
+
and `docs/reference/surface-budget.md`). Fitness rule:
|
|
247
|
+
`evidence-single-read`, owned by `domain/evidence.js`; mutation tests seed a
|
|
248
|
+
completion-check read, a `mission.testRuns` read and prove the owner module is
|
|
249
|
+
exempt.
|
|
250
|
+
- **Legacy import — redesigned to a strict adapter contract
|
|
251
|
+
(`adapters/legacy.js`).** Import was approximate in four ways: the format was
|
|
252
|
+
inferred from the first mission id (mixed namespaces were only caught
|
|
253
|
+
incidentally), an absent `kind` was silently stamped `build`, a Tower Codex
|
|
254
|
+
`retired` status leaked into the mission summary as if it were a DSH status,
|
|
255
|
+
and batch exclusion read several hand-written snapshot shapes
|
|
256
|
+
(`missions`/`snapshot.missions`/`payload.missions`/`pausedIds`). The adapter now
|
|
257
|
+
exports `LEGACY_FORMATS`: one declared entry per accepted format (id pattern,
|
|
258
|
+
id hint, input statuses, `statusRemap`, `baseBranchField`,
|
|
259
|
+
`requiresBaseBranch`). Detection requires exactly one id namespace (a declared
|
|
260
|
+
`payload.format` is authoritative and validated against the ids); a Codex
|
|
261
|
+
`retired` status is remapped to `abandoned` and reported in `remapped`; an
|
|
262
|
+
absent kind is reported in `unresolved` instead of silently invented; mission
|
|
263
|
+
fields the summary does not carry are reported in `dropped` with the reason
|
|
264
|
+
that the exact bytes remain in the stored snapshot. The persisted snapshot
|
|
265
|
+
record gains `unresolved`, `droppedFields` and `remapped`, so the honesty
|
|
266
|
+
survives a restart. `domain/batch.js` reads only the canonical
|
|
267
|
+
`pausedMissionIds`. The import remains read-only, byte-exact,
|
|
268
|
+
hash-addressed and idempotent: it never writes `state.json`, never activates,
|
|
269
|
+
adopts or resumes a mission, and a paused mission stays paused. Fitness rule:
|
|
270
|
+
`legacy-vocabulary`, owned by `adapters/legacy.js`; mutation tests prove a
|
|
271
|
+
second `tower-codex`/`pausedIds` reader fails outside the adapter.
|
|
272
|
+
- **Scope ownership — redesigned to `shape.reach` (`domain/scope.js` +
|
|
273
|
+
shapes).** The shape flag `ownsScope` lived beside three enforcers (the
|
|
274
|
+
integration gate, the planner's auto-dependencies, and lifecycle scope
|
|
275
|
+
widening), each of which read `mission.scope` itself. The shape now declares
|
|
276
|
+
`reach = { ownsPaths, fence: "scope" | "read-only" }`: a build owns paths and
|
|
277
|
+
fences changed files against its scope; a survey reaches nothing and is fenced
|
|
278
|
+
read-only. `domain/scope.js` exports one `scopeFenceBlocker({ mission, reach,
|
|
279
|
+
changedFiles, caseInsensitive })`; `domain/integration/gate.js` derives the
|
|
280
|
+
reach from `shapeFor(mission.kind)` and re-exports the existing
|
|
281
|
+
`outOfScopeBlocker` shape; `domain/missions/plan.js`,
|
|
282
|
+
`domain/planning.js` and `domain/missions/lifecycle.js` derive
|
|
283
|
+
`reach.ownsPaths`. `ownsScope` is deleted. Fitness rule: `scope-reach`, owned
|
|
284
|
+
by `domain/scope.js`; mutation tests seed an `ownsScope` reader and a direct
|
|
285
|
+
`fileInScope` call.
|
|
286
|
+
- **Role vs kind — redesigned to an orthogonal role contract
|
|
287
|
+
(`domain/roles.js`).** Spawn resolved a reviewer by combining
|
|
288
|
+
`isReviewerRole(role)` with `shape.fields.readCommit`/`shape.canMerge`, and
|
|
289
|
+
presets, prechecks, placement, briefings and the roster each re-derived role
|
|
290
|
+
facts independently. `domain/roles.js` now owns `ROLE_CONTRACT`, one entry per
|
|
291
|
+
actor role with `preset`, `model`, `placement`, `briefing`, `tracksMission`,
|
|
292
|
+
`countsTowardBudget`, `spawnDiscovery` and `requires` (review, canMerge,
|
|
293
|
+
worktree, status set). `resolveRoleSpawn` returns the preset/model route,
|
|
294
|
+
`roleMissionBlockers` returns ordered prerequisites, and
|
|
295
|
+
`roleCountsTowardBudget`, `roleTracksMission`, `rolePlacement`,
|
|
296
|
+
`roleBriefingKind` and `rolePresetConfig` replace the scattered checks. The
|
|
297
|
+
shape supplies only facts (`canMerge`, `fields.worktree`, `spawn.model`);
|
|
298
|
+
`tools/spawn.js`, `app/spawn-prechecks.js`, `app/spawn-support.js`,
|
|
299
|
+
`domain/roster.js` and `host/presets.js` call the role API and never combine a
|
|
300
|
+
role predicate with a shape decision. Fitness rule: `role-shape-conflation`,
|
|
301
|
+
owned by `domain/roles.js`; mutation tests seed a role∧shape conditional and
|
|
302
|
+
prove the owner module is exempt.
|
|
303
|
+
- The audit now runs 12 rules. The generated reference's `Dimension inventory
|
|
304
|
+
(#149)` lists every audited dimension with its verdict, owner module and
|
|
305
|
+
fitness rule; `scripts/generate-shapes-reference.mjs` still refuses to emit a
|
|
306
|
+
rule id the audit does not run.
|
|
@@ -226,12 +226,14 @@ planned ──spawn worker──▶ active ──tower_complete──▶ complet
|
|
|
226
226
|
│ │ ▲ │
|
|
227
227
|
│ │ └──── review round not clean ────┐
|
|
228
228
|
└── abandon ──▶ abandoned│ │
|
|
229
|
-
├── blocker ──▶ blocked ──
|
|
229
|
+
├── blocker ──▶ blocked ──clear_blockers──▶ active
|
|
230
230
|
└── pause ─────▶ paused ──active──▶ ...
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
`merged` and `abandoned` are terminal. `
|
|
234
|
-
set
|
|
233
|
+
`merged` and `abandoned` are terminal. `blocked` is derived: it holds exactly while the mission has an open
|
|
234
|
+
blocker, `clear_blockers` resumes it to `active`, and `tower_mission` refuses to set `status: "blocked"` directly.
|
|
235
|
+
`paused` is a deliberate stop (`pause_cause`); a blocker supersedes a pause. `merged` is only set by
|
|
236
|
+
`tower_merge`/reconciliation.
|
|
235
237
|
|
|
236
238
|
## Gate pipeline (`tower_preflight`)
|
|
237
239
|
|
|
@@ -261,7 +263,7 @@ After the command, regardless of exit status, it inspects `HEAD`:
|
|
|
261
263
|
| Path | Contents |
|
|
262
264
|
|---|---|
|
|
263
265
|
| `<git-common-dir>/dsh-tower/state.json` | active missions, roster, reviews, findings, messages, reviews sequence |
|
|
264
|
-
| `<git-common-dir>/dsh-tower/archive/index.json` | compact terminal-mission index (ids, status, branch, timestamps) |
|
|
266
|
+
| `<git-common-dir>/dsh-tower/archive/index.json` | compact terminal-mission index (ids, status, branch, timestamps, survey `readCommit` and bounded `result`) |
|
|
265
267
|
| `<git-common-dir>/dsh-tower/archive/<year>.json` | full archived missions plus their linked reviews/findings |
|
|
266
268
|
| `<git-common-dir>/dsh-tower/activity.jsonl` | append-only activity |
|
|
267
269
|
| `<git-common-dir>/dsh-tower/state.lock` | single-writer state lock (heartbeat reclaim; a recycled PID cannot pin it) |
|
|
@@ -29,6 +29,32 @@ The Host plugin loads at DSH startup: **restart DSH after install or update**.
|
|
|
29
29
|
|
|
30
30
|
## Migration notes
|
|
31
31
|
|
|
32
|
+
- **2.6.0 → 2.7.0 (#148/#149) — mission shapes, survey tasks, and state schema v5**: a completed
|
|
33
|
+
survey is a read of a commit, not a write of a branch. `tower_plan({kind:"survey"})` records
|
|
34
|
+
`readCommit` and `readDirty`, creates no branch or worktree, and completes only while HEAD and the
|
|
35
|
+
tracked-file status still match that read. The optional `survey.sandbox: "detached"` mode gives a
|
|
36
|
+
writable inspection a disposable worktree pinned to the same commit. `tower-survey` is an optional
|
|
37
|
+
component row; survey presets are read-only by default, and surveys do not count against
|
|
38
|
+
`spawn.maxLiveWorkers`.
|
|
39
|
+
- **State migration is automatic and one-way.** The first write after upgrading migrates
|
|
40
|
+
`state.json` from v3 to v5 in one step, writes `state.json.v3.bak` first, advances `revision`
|
|
41
|
+
once, and logs `state.migrated` with steps v3→v4 and v4→v5. Legacy survey rows gain
|
|
42
|
+
`readCommit`/`readDirty` (anchored at `state.base` when the old row never started), and build
|
|
43
|
+
rows are stamped `kind:"build"`. Read-only loads migrate in memory without rewriting the file.
|
|
44
|
+
A newer document is still refused with `state-version-unsupported` and never downgraded.
|
|
45
|
+
- **Config key count 57 → 56.** `tests.autoChecks` is gone; test evidence is now the
|
|
46
|
+
`mission.testRuns` receipt read by `domain/evidence.js`. A config that still writes the key is
|
|
47
|
+
rejected as `config-unknown-key` with this note; there is no replacement switch.
|
|
48
|
+
- **`blocked` is derived.** Write `status:"blocked"` through `tower_mission` is refused; use
|
|
49
|
+
`blocker` to report and `clear_blockers` to resolve. Batch statuses are canonical
|
|
50
|
+
`open → closed|abandoned`; an expired foreign lease requires explicit `force` to recover.
|
|
51
|
+
- **Survey actions are explicit.** `tower_merge`, `tower_rebase`, `tower_preflight`, and
|
|
52
|
+
`tower_diff` refuse surveys with `invalid-transition`; a completed survey is terminal for
|
|
53
|
+
planning, archive, dependency satisfaction, and reanchor. `tower_complete` accepts an optional
|
|
54
|
+
structured `result` (`paths`, `symbols`, `notes`, `plan`) for surveys only.
|
|
55
|
+
- Reviewers are now spawned into a detached checkout bound to the recorded commit; a verdict is
|
|
56
|
+
tied to that commit, and teardown cleans the checkout. Existing build workflows are unchanged.
|
|
57
|
+
- No npm dist-tag or review-policy change is required. Restart DSH after upgrading.
|
|
32
58
|
- **2.5.0 → 2.6.0 (#M39/#M40) — review modes replace the boolean**: the review policy is one
|
|
33
59
|
tri-state control on the Plugins page in the `dsh-tower` configuration section, and the config key
|
|
34
60
|
is `review.mode`: `off`, `optional` (the default) or `required`.
|
|
@@ -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,
|
|
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
|
|
|
@@ -1,9 +1,13 @@
|
|
|
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
|
+
|
|
7
11
|
The review blockers below (`no-review`, `review-mission-mismatch`, `not-clean`, `review-hold`,
|
|
8
12
|
`review-not-approved`, `tip-moved`, `review-exemption-invalid`) exist only when `review.mode` is
|
|
9
13
|
`required`. With `off` or `optional` no review blocker is produced; `off` additionally removes
|
|
@@ -20,10 +24,10 @@ The review blockers below (`no-review`, `review-mission-mismatch`, `not-clean`,
|
|
|
20
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. |
|
|
21
25
|
| `branch-missing` | No worktree/branch yet: spawn a worker first. |
|
|
22
26
|
| `stale-base` | The branch does not contain the base tip: run `tower_rebase`. |
|
|
23
|
-
| `read-only-survey` | Survey mission changed files; survey missions must be read-only. |
|
|
24
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. |
|
|
25
28
|
| `base-dirty` | The main checkout has uncommitted changes the merge would overwrite. |
|
|
26
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). |
|
|
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. |
|
|
27
31
|
| `no-review` | `review.mode:required` and no review exists on the branch: spawn a reviewer (or disable the review gate). |
|
|
28
32
|
| `review-mission-mismatch` | `review.mode:required` and the latest review belongs to another mission. |
|
|
29
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. |
|
|
@@ -44,7 +48,7 @@ The review blockers below (`no-review`, `review-mission-mismatch`, `not-clean`,
|
|
|
44
48
|
|
|
45
49
|
| Code | Meaning / next action |
|
|
46
50
|
|---|---|
|
|
47
|
-
| `merge-conflict` | Real index conflict; the mission is `blocked`. Resolve,
|
|
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. |
|
|
48
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. |
|
|
49
53
|
| `merge-shape` | Git produced unexpected merge parents; inspect the repository, do not force. |
|
|
50
54
|
| `batch-invalid` | A batch id/member/receipt/check reference is invalid or close evidence is incomplete. |
|
|
@@ -57,7 +61,7 @@ A merge that already landed is **not** a blocker: `tower_merge` records it (`rec
|
|
|
57
61
|
| Code | Meaning / next action |
|
|
58
62
|
|---|---|
|
|
59
63
|
| `worktree-dirty` | Commit or discard worktree changes, then retry. |
|
|
60
|
-
| `rebase-conflict` | A real conflict; the mission is `blocked`. Resolve manually,
|
|
64
|
+
| `rebase-conflict` | A real conflict; the mission is `blocked`. Resolve manually, then `clear_blockers` resumes it to `active`; retry. |
|
|
61
65
|
| `worker-busy` | An agent is running for this mission; wait or retire it first. |
|
|
62
66
|
|
|
63
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-11-01 · baseline 2026-10-23 · visible components:
|
|
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
|
|
@@ -36,6 +36,9 @@ row is a section of its parent and is not independently switchable.
|
|
|
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
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
|
|
@@ -110,6 +113,8 @@ All keys from `domain/config/schema.js`, classified on the component axis.
|
|
|
110
113
|
| `spawn.provider` | `optional-config` | Provider route for spawned agents. |
|
|
111
114
|
| `spawn.workerModel` | `optional-config` | Optional model route for workers. |
|
|
112
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. |
|
|
113
118
|
| `spawn.maxLiveWorkers` | `core` | Resource-safety cap on live workers. |
|
|
114
119
|
| `spawn.staleMs` | `optional-config` | Roster staleness window before merge is blocked. |
|
|
115
120
|
| `spawn.maxWaitMs` | `optional-config` | Upper bound for a blocking spawn wait. |
|
|
@@ -119,11 +124,11 @@ All keys from `domain/config/schema.js`, classified on the component axis.
|
|
|
119
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)) |
|
|
120
125
|
| `tests.timeoutMs` | `optional-config` | Per-command discovery timeout. |
|
|
121
126
|
| `tests.maxBytes` | `optional-config` | Captured discovery output cap. |
|
|
122
|
-
| `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)) |
|
|
123
127
|
| `tests.allowPackageDiscovery` | `core` | Allows repository code to execute; a security contract, not a component. |
|
|
124
128
|
| `gates.requireChecks` | `forbidden-to-disable` | Requires completion evidence before merge; a merge gate. |
|
|
125
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. |
|
|
126
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. |
|
|
127
132
|
| `gates.maxReportBytes` | `forbidden-to-disable` | The report cap is part of the completion contract; the refusal itself is never configurable away. |
|
|
128
133
|
| `docs.boardLocation` | `optional-config` | Chooses where the board is written; whether it is written is the tower-board component row (#133). |
|
|
129
134
|
| `archive.afterDays` | `optional-config` | Archive age threshold inside the optional tower-archive component (#135). |
|
|
@@ -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:
|
|
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
|
|---|---|---|
|
|
@@ -32,26 +32,28 @@
|
|
|
32
32
|
| `merge.maxBufferBytes` | `67108864` | Captured merge output buffer. |
|
|
33
33
|
| `git.timeoutMs` | `15000` | Default timeout for Git commands other than merge; merge uses merge.timeoutMs. |
|
|
34
34
|
| `worktrees.root` | `` | Default parent directory for mission worktrees; empty derives <main-checkout-parent>/.dsh-tower-worktrees/<hash>. |
|
|
35
|
-
| `spawn.workerPreset` | `"tower-worker"` | Agent preset for
|
|
35
|
+
| `spawn.workerPreset` | `"tower-worker"` | Agent preset for build workers. |
|
|
36
36
|
| `spawn.reviewerPreset` | `"tower-reviewer"` | Agent preset for reviewers. |
|
|
37
37
|
| `spawn.maxDepth` | `1` | Subagent depth requested when spawning tower agents. |
|
|
38
38
|
| `spawn.provider` | `` | Subagent provider id; empty picks spawn, then the first registered provider. |
|
|
39
39
|
| `spawn.workerModel` | `{}` | Model route for workers; omitted inherits the parent session. |
|
|
40
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. |
|
|
41
42
|
| `spawn.maxLiveWorkers` | `4` | Maximum live worker entries allowed per repository at once. |
|
|
42
43
|
| `spawn.staleMs` | `15000` | Roster entries with no catalog activity after this age are marked stale and block merge (0 disables). |
|
|
43
44
|
| `spawn.maxWaitMs` | `90000` | Upper bound for tower_spawn waitMs (default 90s, Kimi-compatible). |
|
|
44
45
|
| `spawn.requireLease` | `` | Optional lease name tower_spawn must hold before starting a worker. |
|
|
45
46
|
| `send.maxPerMinute` | `120` | Per-repository tower_send rate limit per minute (0 disables). |
|
|
46
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. |
|
|
47
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). |
|
|
48
50
|
| `tests.timeoutMs` | `60000` | Per discovery command timeout. |
|
|
49
51
|
| `tests.maxBytes` | `16777216` | Maximum captured stdout bytes per command. |
|
|
50
|
-
| `tests.autoChecks` | `true` | Attach a tests check to the completion report when discovery runs. |
|
|
51
52
|
| `tests.allowPackageDiscovery` | `true` | Allow package.json tower.testDiscovery commands from the mission worktree (executes repository code; see security docs). |
|
|
52
|
-
| `gates.requireChecks` | `false` | Require at least one
|
|
53
|
+
| `gates.requireChecks` | `false` | Require at least one recorded check (tower_complete.checks) or test-discovery receipt before a mission can merge. |
|
|
53
54
|
| `gates.requireTests` | `false` | Require a passing test-discovery receipt for the reviewed worktree tip before merge. |
|
|
54
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). |
|
|
55
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. |
|
|
56
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). |
|
|
57
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). |
|
package/docs/reference/config.md
CHANGED
|
@@ -108,9 +108,8 @@ violates the declared schema (for example `merge.timeoutMs` below 1000) fail wit
|
|
|
108
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). |
|
|
109
109
|
| `tests.timeoutMs` | `60000` | Per discovery command timeout. |
|
|
110
110
|
| `tests.maxBytes` | `16777216` (16 MiB) | Captured stdout cap per command. |
|
|
111
|
-
| `tests.autoChecks` | `true` | Attach an automatic `tests` check to the completion report. |
|
|
112
111
|
| `tests.allowPackageDiscovery` | `true` | Allow repo-controlled `tower.testDiscovery` commands (executes worktree code; see security). |
|
|
113
|
-
| `gates.requireChecks` | `false` | Require at least one `tower_complete.checks`
|
|
112
|
+
| `gates.requireChecks` | `false` | Require at least one recorded check (`tower_complete.checks`) or a test-discovery receipt before merge (`no-check-evidence`). |
|
|
114
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. |
|
|
115
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). |
|
|
116
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. |
|
package/docs/reference/doctor.md
CHANGED
|
@@ -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:
|
|
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
|
|
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":
|
|
18
|
-
"sections":
|
|
17
|
+
"configKeys": 57,
|
|
18
|
+
"sections": 15,
|
|
19
19
|
"tools": 29,
|
|
20
20
|
"baselineDate": "2026-10-23",
|
|
21
|
-
"line": "config keys:
|
|
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":
|
|
42
|
-
"supportedVersion":
|
|
41
|
+
"currentVersion": 5,
|
|
42
|
+
"supportedVersion": 5,
|
|
43
43
|
"migrations": [],
|
|
44
44
|
"status": "current"
|
|
45
45
|
},
|
|
@@ -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
|
|
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
|
|