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
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schema": 1,
3
- "date": "2026-10-23",
3
+ "date": "2026-11-01",
4
4
  "adr": "docs/adr/0005-component-surface.md",
5
5
  "classes": {
6
6
  "core": "Removing it breaks the Tower contract. It is never a switch.",
7
7
  "optional-component": "A capability whose off state leaves the rest of Tower intact. It must be a user-visible switch with a documented fallback.",
8
8
  "optional-config": "A tuning knob, not a capability. It stays in the config form and never hides a component.",
9
- "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."
9
+ "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."
10
10
  },
11
11
  "components": [
12
12
  {
@@ -88,7 +88,25 @@
88
88
  "id": "preset-tower-reviewer",
89
89
  "scope": "visible",
90
90
  "kind": "row",
91
- "fallback": "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."
91
+ "fallback": "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."
92
+ },
93
+ {
94
+ "id": "tower-survey",
95
+ "scope": "visible",
96
+ "kind": "group",
97
+ "fallback": "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."
98
+ },
99
+ {
100
+ "id": "preset-tower-survey",
101
+ "scope": "internal",
102
+ "kind": "row",
103
+ "fallback": "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."
104
+ },
105
+ {
106
+ "id": "preset-tower-survey-sandbox",
107
+ "scope": "internal",
108
+ "kind": "row",
109
+ "fallback": "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."
92
110
  },
93
111
  {
94
112
  "id": "tower-ui",
@@ -99,6 +117,7 @@
99
117
  ],
100
118
  "gateableTools": {},
101
119
  "behaviorKnobs": [
120
+ "review.mode",
102
121
  "spawn.requireLease",
103
122
  "tests.admission"
104
123
  ],
@@ -221,10 +240,15 @@
221
240
  "class": "optional-config",
222
241
  "note": "Bounded wait for the cross-process mode lock."
223
242
  },
243
+ {
244
+ "name": "review.mode",
245
+ "class": "core",
246
+ "note": "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."
247
+ },
224
248
  {
225
249
  "name": "review.requireReview",
226
- "class": "forbidden-to-disable",
227
- "note": "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)."
250
+ "class": "core",
251
+ "note": "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."
228
252
  },
229
253
  {
230
254
  "name": "review.maxRoundsPerReviewer",
@@ -281,6 +305,16 @@
281
305
  "class": "optional-config",
282
306
  "note": "Optional independent model route for reviewers."
283
307
  },
308
+ {
309
+ "name": "spawn.surveyModel",
310
+ "class": "optional-config",
311
+ "note": "Optional model route for survey workers."
312
+ },
313
+ {
314
+ "name": "survey.sandbox",
315
+ "class": "optional-config",
316
+ "note": "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."
317
+ },
284
318
  {
285
319
  "name": "spawn.maxLiveWorkers",
286
320
  "class": "core",
@@ -344,17 +378,6 @@
344
378
  "class": "optional-config",
345
379
  "note": "Captured discovery output cap."
346
380
  },
347
- {
348
- "name": "tests.autoChecks",
349
- "class": "optional-config",
350
- "note": "Attaches a tests check to completion when discovery runs.",
351
- "exemption": {
352
- "date": "2026-11-03",
353
- "ref": "docs/adr/0005-component-surface.md",
354
- "issue": "#138",
355
- "reason": "Tuning the default receipt policy inside the optional tower-tests row, which still owns whether any test tooling exists."
356
- }
357
- },
358
381
  {
359
382
  "name": "tests.allowPackageDiscovery",
360
383
  "class": "core",
@@ -375,6 +398,11 @@
375
398
  "class": "forbidden-to-disable",
376
399
  "note": "The review-exemption path is part of the review and merge contract."
377
400
  },
401
+ {
402
+ "name": "gates.requireFreshSurvey",
403
+ "class": "forbidden-to-disable",
404
+ "note": "Promotes the survey-stale dependency warning to a merge blocker; the gate itself is part of the merge contract and defaults to advisory."
405
+ },
378
406
  {
379
407
  "name": "gates.maxReportBytes",
380
408
  "class": "forbidden-to-disable",
@@ -417,37 +445,63 @@
417
445
  "capability": "the Tower host",
418
446
  "reason": "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.",
419
447
  "tools": [],
420
- "rowIds": ["tower-host"]
448
+ "rowIds": [
449
+ "tower-host"
450
+ ]
421
451
  },
422
452
  {
423
453
  "id": "health",
424
454
  "capability": "tower_doctor (read-only health contract)",
425
455
  "reason": "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.",
426
- "tools": ["tower_doctor"],
427
- "rowIds": ["tower-doctor"]
456
+ "tools": [
457
+ "tower_doctor"
458
+ ],
459
+ "rowIds": [
460
+ "tower-doctor"
461
+ ]
428
462
  },
429
463
  {
430
464
  "id": "recovery",
431
465
  "capability": "tower_recover (stale state-lock escape)",
432
466
  "reason": "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.",
433
- "tools": ["tower_recover"],
434
- "rowIds": ["tower-recover"]
467
+ "tools": [
468
+ "tower_recover"
469
+ ],
470
+ "rowIds": [
471
+ "tower-recover"
472
+ ]
435
473
  },
436
474
  {
437
475
  "id": "review",
438
- "capability": "the review contract",
439
- "reason": "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.",
440
- "tools": ["tower_diff", "tower_preflight", "tower_review"],
441
- "rowIds": ["tower-review"],
442
- "gatedBy": ["review.requireReview", "review.maxRoundsPerReviewer"]
476
+ "capability": "the review gate in required mode",
477
+ "reason": "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.",
478
+ "tools": [],
479
+ "rowIds": [],
480
+ "gatedBy": [
481
+ "review.mode",
482
+ "review.maxRoundsPerReviewer",
483
+ "gates.allowReviewExemption"
484
+ ]
443
485
  },
444
486
  {
445
487
  "id": "merge-gates",
446
488
  "capability": "the merge gates",
447
489
  "reason": "gates.* decides delivery; a component may not bypass it.",
448
- "tools": ["tower_complete", "tower_merge"],
449
- "rowIds": ["tower-gates", "tower-merge-gates"],
450
- "gatedBy": ["gates.requireChecks", "gates.requireTests", "gates.allowReviewExemption", "gates.maxReportBytes"]
490
+ "tools": [
491
+ "tower_complete",
492
+ "tower_merge"
493
+ ],
494
+ "rowIds": [
495
+ "tower-gates",
496
+ "tower-merge-gates"
497
+ ],
498
+ "gatedBy": [
499
+ "gates.requireChecks",
500
+ "gates.requireTests",
501
+ "gates.allowReviewExemption",
502
+ "gates.requireFreshSurvey",
503
+ "gates.maxReportBytes"
504
+ ]
451
505
  }
452
506
  ]
453
507
  }
@@ -63,6 +63,11 @@ export const CAPABILITY_LOCALES = [
63
63
  export const PRESET_LOCALES = [
64
64
  { specifier: 'preset-worker', component: 'preset-tower-worker', mandatory: true },
65
65
  { specifier: 'preset-reviewer', component: 'preset-tower-reviewer', mandatory: false },
66
+ // #148 W2: the survey capability row is a group; its two preset children are
67
+ // internal dependencies (never individual switches), so both are labelled with
68
+ // the mandatory-dependency marker.
69
+ { specifier: 'preset-survey', component: 'preset-tower-survey', mandatory: true },
70
+ { specifier: 'preset-survey-sandbox', component: 'preset-tower-survey-sandbox', mandatory: true },
66
71
  ]
67
72
 
68
73
  /** Every row that ships a generated subpath locale resource. */
@@ -0,0 +1,171 @@
1
+ // Generate the shape reference from the one shape registry (#149).
2
+ //
3
+ // Every fact in docs/reference/shapes.md comes from domain/shapes/index.js and
4
+ // the two shape modules, so documentation cannot drift from the policy the
5
+ // generic layers actually resolve. `--check` is wired into `npm run check`.
6
+ import fs from 'node:fs'
7
+ import path from 'node:path'
8
+ import { fileURLToPath } from 'node:url'
9
+ import { SHAPE_KINDS, shapeFor } from '../domain/shapes/index.js'
10
+ import { RULES } from './shape-audit.mjs'
11
+
12
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
13
+ const target = path.join(root, 'docs', 'reference', 'shapes.md')
14
+
15
+ /**
16
+ * Resolve an audit rule by id. A verdict that names a rule the audit does not
17
+ * run fails generation, so the reference can never describe an unenforced rule.
18
+ * @param {string} id
19
+ */
20
+ function fitness(id) {
21
+ const rule = RULES.find((entry) => entry.id === id)
22
+ if (!rule) throw new Error(`unknown shape-audit rule "${id}" named by the shapes reference generator`)
23
+ return `\`${rule.id}\` — ${rule.rule}`
24
+ }
25
+
26
+ /** Rule ids as code spans; throws if a verdict names a rule the audit does not run. */
27
+ function rules(...ids) {
28
+ for (const id of ids) {
29
+ if (!RULES.some((entry) => entry.id === id)) {
30
+ throw new Error(`unknown shape-audit rule "${id}" named by the shapes reference generator`)
31
+ }
32
+ }
33
+ return ids.map((id) => `\`${id}\``).join(', ')
34
+ }
35
+
36
+ const fitnessRules = RULES.map((rule) => `| \`${rule.id}\` | \`${rule.owner}\` | ${rule.rule} |`)
37
+
38
+ const rows = SHAPE_KINDS.map((kind) => {
39
+ const shape = shapeFor(kind)
40
+ const hasTerminal = shape.statuses.some((status) => shape.isTerminal(status))
41
+ // #148 W2: a shape with a sandbox dimension resolves its placement from
42
+ // tuning config, so the reference names both placements instead of the static
43
+ // default only. Both placements render as text: a raw `|` would split the cell.
44
+ const provisioning = shape.fields.sandbox ? '`shared` / `detached`' : `\`${shape.provisioning}\``
45
+ return `| \`${kind}\` | \`${shape.statuses.join('`, `')}\` | ${hasTerminal ? 'yes' : 'no'} | ${provisioning} | ${shape.canMerge} | ${shape.canRebase} | ${shape.reach.ownsPaths} / \`${shape.reach.fence}\` | ${shape.requiredComponent ?? '—'} | ${shape.acceptsResult} |`
46
+ })
47
+
48
+ const transitions = SHAPE_KINDS.map((kind) => {
49
+ const shape = shapeFor(kind)
50
+ const body = shape.statuses
51
+ .map((status) => {
52
+ const targets = shape.transitions[status] ?? []
53
+ return `| \`${status}\` | ${shape.isTerminal(status) ? 'terminal' : targets.map((target) => `\`${target}\``).join(', ') || '—'} |`
54
+ })
55
+ .join('\n')
56
+ return `### ${kind}\n\n| From | To |\n|---|---|\n${body}`
57
+ })
58
+
59
+ const invariants = SHAPE_KINDS.map((kind) => {
60
+ const { completion } = shapeFor(kind)
61
+ const flags = Object.entries(completion)
62
+ .filter(([key]) => key.startsWith('requires'))
63
+ .map(([key, value]) => `${key}=${value}`)
64
+ .join(', ')
65
+ return `| \`${kind}\` | ${flags} | \`${completion.check.name}\` |`
66
+ })
67
+
68
+ const expected = `# Mission shapes (generated)
69
+
70
+ > Generated by \`npm run docs:generate:shapes\` from \`domain/shapes/index.js\`. Do not edit by hand.
71
+
72
+ A mission shape is the single place its kind's contract lives (#148/#149). Generic
73
+ layers resolve \`shapeFor(mission.kind)\` and never branch on a kind literal; the
74
+ shape audit (\`npm run shape:audit\`) refuses kind and role comparisons outside
75
+ \`domain/shapes/\`, the global \`TERMINAL_STATUSES\`/\`TRANSITIONS\` vocabulary,
76
+ one-argument \`isTerminal\`/\`canTransition\` calls, and the dimension regressions
77
+ recorded in the inventory below (review tip, batch status, lease ledger, blocked
78
+ writes, evidence reads, legacy vocabulary, path permission and role/shape
79
+ conflation).
80
+
81
+ ## Kinds
82
+
83
+ | Kind | Statuses | Has terminal | Provisioning | Can merge | Can rebase | Reach (owns / fence) | Component | Accepts result |
84
+ |---|---|---|---|---|---|---|---|---|
85
+ ${rows.join('\n')}
86
+
87
+ ## Transitions
88
+
89
+ ${transitions.join('\n\n')}
90
+
91
+ ## Survey placement (#148 W2)
92
+
93
+ \`survey.sandbox\` is tuning, not a component: the \`tower-survey\` row stays the
94
+ switch. The shape resolves both the placement and the preset, so generic layers
95
+ never read the raw key or compare a kind.
96
+
97
+ The decision is made once, at provisioning time. A mission with a recorded
98
+ \`mission.sandbox\` keeps the detached placement and its completion invariants
99
+ even if \`survey.sandbox\` later changes — a config flip can neither skip the
100
+ sandbox HEAD/zero-commit checks nor strand the disposable checkout. A survey
101
+ that never got a sandbox keeps the shared invariants for the same reason.
102
+
103
+ | \`survey.sandbox\` | Provisioning | Preset | Completion invariants |
104
+ |---|---|---|---|
105
+ | \`none\` (default) | \`shared\`: no checkout, the agent reads the main checkout | \`tower-survey\` (strictly read-only: no write, edit, or shell tool) | HEAD equals \`readCommit\`, tracked-status fingerprint unchanged, zero commits |
106
+ | \`detached\` | \`detached\`: \`git worktree add --detach <worktreeRoot>/<id>-sandbox <readCommit>\` (no branch, no \`baseCommit\`, never merged) | \`tower-survey-sandbox\` (read/write/edit/pwsh plus the mission tools; spawn/merge/rebase/review/diff still absent) | sandbox HEAD equals \`readCommit\`, zero commits; disposable write/shell side effects are ignored; sandbox removed on completion, abandon or teardown with a bounded retry |
107
+
108
+ ## Completion invariants
109
+
110
+ | Kind | Invariants | Check |
111
+ |---|---|---|
112
+ ${invariants.join('\n')}
113
+
114
+ ## Role
115
+
116
+ | Contract | Owner |
117
+ |---|---|
118
+ | Status, transitions, terminality, merge/rebase, provisioning, reach, plan fields, completion, dependencies, briefing, summary | \`domain/shapes/<kind>.js\` |
119
+ | Kind resolution and the action guard | \`domain/shapes/index.js\` |
120
+ | Role vocabulary and the role contract (preset, model, placement, briefing, prerequisites) | \`domain/roles.js\` |
121
+ | Generated reference and the audit | \`scripts/generate-shapes-reference.mjs\`, \`scripts/shape-audit.mjs\` |
122
+
123
+ ## Dimension inventory (#149)
124
+
125
+ Every dimension the registry audit covers. A **shape-owned** verdict lives in
126
+ \`domain/shapes/<kind>.js\`; a **correctly generic** verdict has one owner module
127
+ outside the shapes; a **redesigned** verdict replaced an earlier ambiguous
128
+ contract. The fitness rule is the mechanical check the audit (or the generated
129
+ invariant) enforces.
130
+
131
+ | Dimension | Verdict | Owner module | Fitness rule |
132
+ |---|---|---|---|
133
+ | Mission lifecycle | **Shape-owned.** Statuses, transitions and terminality are per kind. | \`domain/shapes/<kind>.js\` | ${rules('kind-literal', 'global-status-vocabulary', 'shape-entry-point')} |
134
+ | Role vocabulary | **Correctly generic.** Roles are actors, not mission kinds. | \`domain/roles.js\` | ${rules('role-literal')} |
135
+ | Provisioning | **Shape-owned.** A build gets a branch worktree; a survey is shared or a detached sandbox. | \`domain/shapes/<kind>.js\` | Generated invariant: only a build creates a branch/worktree; a recorded \`mission.sandbox\` decides a survey's placement. |
136
+ | Read-only completion | **Shape-owned.** \`resolveCompletion\` derives invariants from the recorded placement. | \`domain/shapes/survey.js\` | Generated invariant: shared survey keeps HEAD and tracked status; detached survey keeps HEAD pinned; both make zero commits. |
137
+ | Dependency | **Shape-owned.** \`dependencySatisfied\` names the terminal statuses that satisfy a dependent. | \`domain/shapes/<kind>.js\` | Generated invariant: a completed survey satisfies a dependent build; a merged build does too. |
138
+ | Archive / reanchor / planning | **Shape-owned.** Terminality is the one archive/reanchor predicate. | \`domain/shapes/<kind>.js\` | Generated invariant: a terminal shape leaves open work and never blocks reanchor. |
139
+ | Completion result | **Shape-owned.** Only a survey may record a completion result. | \`domain/shapes/<kind>.js\` | Generated invariant: \`shape.acceptsResult\`. |
140
+ | Required component | **Shape-owned.** A survey needs the \`tower-survey\` row. | \`domain/shapes/<kind>.js\` | Generated invariant: planning refuses with an actionable code while the required component is off. |
141
+ | Review | **Redesigned.** Commit-stamped evidence: a reviewer reads a disposable checkout at the recorded review commit; only a shape with a branch is reviewable. | \`app/review.js\`, \`app/reviewCheckout.js\`, \`domain/missions/review.js\` | ${fitness('review-tip-read')} |
142
+ | Batch | **Correctly generic second lifecycle.** A batch is finite evidence over missions, not a mission kind; no shape knows about batches. | \`domain/batch.js\`, \`domain/collab/batch.js\`, \`app/batch.js\` | ${fitness('batch-status-literal')} |
143
+ | Leases | **Correctly generic.** One fail-closed admission point; expiry is recovered only by an explicit \`force\` release. | \`adapters/leases.js\`, \`app/spawn-prechecks.js\` | ${fitness('lease-ledger')} |
144
+ | Paused vs blocked | **Redesigned.** \`blocked\` is derived machine state; \`paused\` is a deliberate stop with a cause. | \`domain/missions/lifecycle.js\`, \`domain/state/semantics.js\` | ${fitness('blocked-status-write')} |
145
+ | Test receipts / evidence | **Redesigned — one reader.** Completion checks and discovery receipts are one normalized evidence contract in \`domain/evidence.js\`; the shape declares whether a receipt is tip-bound. \`tower_complete\` no longer copies a synthetic \`tests\` check into the report. | \`domain/evidence.js\`; \`domain/shapes/<kind>.js\` declares \`evidence\` | ${fitness('evidence-single-read')} |
146
+ | Legacy import | **Redesigned — strict adapter.** One payload is one declared format; status remaps, assumed kinds and non-carried fields are reported as \`remapped\`/\`unresolved\`/\`dropped\` instead of approximated. Import stays read-only and never resumes a paused mission. | \`adapters/legacy.js\`; \`domain/batch.js\` reads only the canonical \`pausedMissionIds\` | ${fitness('legacy-vocabulary')} |
147
+ | Scope ownership | **Redesigned — shape.reach.** The shape owns path permission (\`reach.ownsPaths\`, \`reach.fence\`); the gate, planner, lifecycle and auto-deps derive from it through one fence. | \`domain/scope.js\` + \`shape.reach\` in \`domain/shapes/<kind>.js\` | ${fitness('scope-reach')} |
148
+ | Role vs kind | **Redesigned — orthogonal role contract.** Spawn, preset, model, placement, briefing, roster budget and gates resolve the role through \`domain/roles.js\`; the shape only supplies facts. | \`domain/roles.js\`; callers \`tools/spawn.js\`, \`app/spawn-prechecks.js\`, \`app/spawn-support.js\`, \`domain/roster.js\`, \`host/presets.js\` | ${fitness('role-shape-conflation')} |
149
+
150
+ ## Fitness rules
151
+
152
+ Generated from \`scripts/shape-audit.mjs\` \`RULES\`; the audit is wired into \`npm run check\`.
153
+
154
+ | Rule | Owner module | Contract |
155
+ |---|---|---|
156
+ ${fitnessRules.join('\n')}
157
+ `
158
+
159
+ if (process.argv.includes('--check')) {
160
+ const actual = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
161
+ if (actual !== expected) {
162
+ console.error('shapes reference is stale; run npm run docs:generate:shapes')
163
+ process.exit(1)
164
+ }
165
+ console.log(`shapes reference: ${SHAPE_KINDS.length} shapes in sync`)
166
+ } else {
167
+ fs.mkdirSync(path.dirname(target), { recursive: true })
168
+ fs.writeFileSync(target, expected)
169
+ console.log(`wrote ${path.relative(root, target)} (${SHAPE_KINDS.length} shapes)`)
170
+ }
171
+ process.exit(0)
@@ -36,6 +36,7 @@ export const OPTIONAL_ROW_IDS = [
36
36
  'tower-import',
37
37
  'tower-leases',
38
38
  'tower-skills',
39
+ 'tower-survey',
39
40
  'tower-tests',
40
41
  ]
41
42
 
@@ -67,6 +68,8 @@ export function defaultOffToolIds() {
67
68
  export const PRESET_ROWS = Object.freeze([
68
69
  Object.freeze({ id: 'tower-worker', specifier: 'preset-worker', name: 'Tower Worker', order: 60 }),
69
70
  Object.freeze({ id: 'tower-reviewer', specifier: 'preset-reviewer', name: 'Tower Reviewer', order: 61 }),
71
+ Object.freeze({ id: 'tower-survey', specifier: 'preset-survey', name: 'Tower Survey', order: 62 }),
72
+ Object.freeze({ id: 'tower-survey-sandbox', specifier: 'preset-survey-sandbox', name: 'Tower Survey Sandbox', order: 63 }),
70
73
  ])
71
74
 
72
75
  /** Presets the bundle declares for its worker and reviewer roles. */
@@ -28,6 +28,7 @@ app/briefing.js
28
28
  app/checkCompletion.js
29
29
  app/enrichedMissions.js
30
30
  app/findings.js
31
+ app/freshness.js
31
32
  app/gate.js
32
33
  app/init.js
33
34
  app/merge.js
@@ -40,12 +41,16 @@ app/read.js
40
41
  app/rebase.js
41
42
  app/recording.js
42
43
  app/review.js
44
+ app/reviewCheckout.js
43
45
  app/roster.js
46
+ app/sandbox.js
47
+ app/sharedCheckout.js
44
48
  app/spawn-prechecks.js
45
49
  app/spawn-support.js
46
50
  app/spawn.js
47
51
  app/teardown.js
48
52
  app/tower.js
53
+ client.js
49
54
  cordis.patch.yml
50
55
  docs/README.md
51
56
  docs/adr/0001-ports-adapters-as-data.md
@@ -53,6 +58,7 @@ docs/adr/0002-single-state-json.md
53
58
  docs/adr/0003-allow-agent-control-default-true.md
54
59
  docs/adr/0004-surface-budget.md
55
60
  docs/adr/0005-component-surface.md
61
+ docs/adr/0006-mission-shapes.md
56
62
  docs/adr/README.md
57
63
  docs/agents/domain.md
58
64
  docs/agents/issue-tracker.md
@@ -79,6 +85,7 @@ docs/reference/errors.generated.md
79
85
  docs/reference/errors.md
80
86
  docs/reference/events.md
81
87
  docs/reference/operations.md
88
+ docs/reference/shapes.md
82
89
  docs/reference/surface-budget.md
83
90
  docs/reference/testing.md
84
91
  docs/reference/tools.generated.md
@@ -115,6 +122,7 @@ domain/contracts/tool-inputs/missions.js
115
122
  domain/contracts/tool-inputs/ops.js
116
123
  domain/contracts/tool-inputs/shared.js
117
124
  domain/errors.js
125
+ domain/evidence.js
118
126
  domain/events.js
119
127
  domain/gate.js
120
128
  domain/integration/gate.js
@@ -137,8 +145,12 @@ domain/read/enrichedMissions.js
137
145
  domain/read/missionContext.js
138
146
  domain/read/views.js
139
147
  domain/report.js
148
+ domain/roles.js
140
149
  domain/roster.js
141
150
  domain/scope.js
151
+ domain/shapes/build.js
152
+ domain/shapes/index.js
153
+ domain/shapes/survey.js
142
154
  domain/state.js
143
155
  domain/state/archive.js
144
156
  domain/state/migrations.js
@@ -162,6 +174,8 @@ host/hmr.js
162
174
  host/modeState.js
163
175
  host/modeStore.js
164
176
  host/preset-reviewer.js
177
+ host/preset-survey-sandbox.js
178
+ host/preset-survey.js
165
179
  host/preset-worker.js
166
180
  host/presets.js
167
181
  host/tool-registry.js
@@ -183,6 +197,10 @@ locale/leases/en.json
183
197
  locale/leases/zh.json
184
198
  locale/preset-reviewer/en.json
185
199
  locale/preset-reviewer/zh.json
200
+ locale/preset-survey-sandbox/en.json
201
+ locale/preset-survey-sandbox/zh.json
202
+ locale/preset-survey/en.json
203
+ locale/preset-survey/zh.json
186
204
  locale/preset-worker/en.json
187
205
  locale/preset-worker/zh.json
188
206
  locale/skills/en.json
@@ -218,6 +236,7 @@ scripts/generate-component-surface.mjs
218
236
  scripts/generate-config-reference.mjs
219
237
  scripts/generate-error-reference.mjs
220
238
  scripts/generate-events-reference.mjs
239
+ scripts/generate-shapes-reference.mjs
221
240
  scripts/generate-tools-reference.mjs
222
241
  scripts/generate-types.mjs
223
242
  scripts/issue-audit.mjs
@@ -234,6 +253,7 @@ scripts/pack-manifest.txt
234
253
  scripts/regression-audit.mjs
235
254
  scripts/release-check.mjs
236
255
  scripts/release-notes.mjs
256
+ scripts/shape-audit.mjs
237
257
  scripts/surface-baseline.json
238
258
  scripts/surface-baseline.mjs
239
259
  scripts/types-coverage.mjs
@@ -243,6 +263,8 @@ skills.js
243
263
  skills/tower-recovery.md
244
264
  skills/tower-review.md
245
265
  skills/tower-worker-protocol.md
266
+ survey-sandbox.js
267
+ survey.js
246
268
  tools/adopt.js
247
269
  tools/batch.js
248
270
  tools/complete.js