code-coordinator 0.5.46__py3-none-any.whl

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 (295) hide show
  1. code_coordinator-0.5.46.dist-info/METADATA +625 -0
  2. code_coordinator-0.5.46.dist-info/RECORD +295 -0
  3. code_coordinator-0.5.46.dist-info/WHEEL +5 -0
  4. code_coordinator-0.5.46.dist-info/entry_points.txt +2 -0
  5. code_coordinator-0.5.46.dist-info/licenses/LICENSE +110 -0
  6. code_coordinator-0.5.46.dist-info/top_level.txt +1 -0
  7. coord/__init__.py +176 -0
  8. coord/_board_mapping.py +229 -0
  9. coord/acceptance.py +468 -0
  10. coord/acceptance_drivers.py +632 -0
  11. coord/agent.py +7517 -0
  12. coord/agent_app.py +1555 -0
  13. coord/agent_update.py +417 -0
  14. coord/agents/opencode/.gitignore +13 -0
  15. coord/agents/opencode/agents/work.md +129 -0
  16. coord/agents/opencode/routing.jsonc +49 -0
  17. coord/audit.py +301 -0
  18. coord/auto_loop.py +1440 -0
  19. coord/board_bool_guard.py +72 -0
  20. coord/board_service.py +141 -0
  21. coord/board_wire.py +309 -0
  22. coord/brain.py +581 -0
  23. coord/branch_model.py +214 -0
  24. coord/cargo_cache.py +258 -0
  25. coord/ci_github.py +386 -0
  26. coord/ci_store.py +560 -0
  27. coord/claim.py +353 -0
  28. coord/cli.py +454 -0
  29. coord/client.py +610 -0
  30. coord/commands/__init__.py +1 -0
  31. coord/commands/_common.py +329 -0
  32. coord/commands/acceptance.py +916 -0
  33. coord/commands/agent_ops.py +1339 -0
  34. coord/commands/audit.py +131 -0
  35. coord/commands/chat.py +320 -0
  36. coord/commands/dispatch.py +1780 -0
  37. coord/commands/dispatch_workers.py +4894 -0
  38. coord/commands/drive.py +616 -0
  39. coord/commands/drive_queue.py +1203 -0
  40. coord/commands/gate_a.py +217 -0
  41. coord/commands/gates.py +89 -0
  42. coord/commands/issues.py +681 -0
  43. coord/commands/lifecycle.py +513 -0
  44. coord/commands/merge.py +1900 -0
  45. coord/commands/milestone.py +2081 -0
  46. coord/commands/plan_followup.py +1243 -0
  47. coord/commands/plans.py +156 -0
  48. coord/commands/release.py +2232 -0
  49. coord/commands/report.py +341 -0
  50. coord/commands/review.py +1523 -0
  51. coord/commands/scorecard.py +252 -0
  52. coord/commands/sessions.py +1930 -0
  53. coord/commands/setup.py +576 -0
  54. coord/commands/status.py +2089 -0
  55. coord/commands/terminal.py +385 -0
  56. coord/commands/test_gate.py +775 -0
  57. coord/commands/tui.py +288 -0
  58. coord/comments.py +718 -0
  59. coord/config.py +3032 -0
  60. coord/conflict_fix.py +633 -0
  61. coord/dao.py +483 -0
  62. coord/dashboard/__init__.py +0 -0
  63. coord/dashboard/fixture.py +376 -0
  64. coord/dashboard/index.html +658 -0
  65. coord/dashboard/server.py +1894 -0
  66. coord/dashboard/terminal.py +382 -0
  67. coord/dashboard/webapp/.gitignore +9 -0
  68. coord/dashboard/webapp/components.json +17 -0
  69. coord/dashboard/webapp/dist/assets/Gallery-da3qNiIw.js +71 -0
  70. coord/dashboard/webapp/dist/assets/Terminal-9CEnUXvW.css +32 -0
  71. coord/dashboard/webapp/dist/assets/Terminal-skVFCxPU.js +63 -0
  72. coord/dashboard/webapp/dist/assets/index-DltfZR5f.js +184 -0
  73. coord/dashboard/webapp/dist/assets/index-Dq4kwTdw.css +1 -0
  74. coord/dashboard/webapp/dist/assets/workbox-window.prod.es5-BqEJf4Xk.js +2 -0
  75. coord/dashboard/webapp/dist/icons/icon-192.png +0 -0
  76. coord/dashboard/webapp/dist/icons/icon-512.png +0 -0
  77. coord/dashboard/webapp/dist/icons/icon.svg +5 -0
  78. coord/dashboard/webapp/dist/index.html +38 -0
  79. coord/dashboard/webapp/dist/manifest.webmanifest +1 -0
  80. coord/dashboard/webapp/dist/sw.js +1 -0
  81. coord/dashboard/webapp/dist/workbox-e4022e15.js +1 -0
  82. coord/dashboard/webapp/e2e/available-gates-terminal.spec.ts +75 -0
  83. coord/dashboard/webapp/e2e/deep-link.spec.ts +172 -0
  84. coord/dashboard/webapp/e2e/fixtureServer.ts +155 -0
  85. coord/dashboard/webapp/e2e/live-update-fixture.spec.ts +113 -0
  86. coord/dashboard/webapp/e2e/realtime.spec.ts +238 -0
  87. coord/dashboard/webapp/e2e/shell.spec.ts +309 -0
  88. coord/dashboard/webapp/e2e/smoke.spec.ts +191 -0
  89. coord/dashboard/webapp/e2e/terminal.spec.ts +420 -0
  90. coord/dashboard/webapp/e2e/theme.spec.ts +138 -0
  91. coord/dashboard/webapp/eslint.config.js +20 -0
  92. coord/dashboard/webapp/index.html +37 -0
  93. coord/dashboard/webapp/node_modules/flatted/python/flatted.py +144 -0
  94. coord/dashboard/webapp/package-lock.json +10584 -0
  95. coord/dashboard/webapp/package.json +63 -0
  96. coord/dashboard/webapp/playwright.acceptance.config.ts +166 -0
  97. coord/dashboard/webapp/playwright.config.ts +93 -0
  98. coord/dashboard/webapp/postcss.config.js +6 -0
  99. coord/dashboard/webapp/public/icons/icon-192.png +0 -0
  100. coord/dashboard/webapp/public/icons/icon-512.png +0 -0
  101. coord/dashboard/webapp/public/icons/icon.svg +5 -0
  102. coord/dashboard/webapp/src/App.tsx +140 -0
  103. coord/dashboard/webapp/src/api/client.ts +199 -0
  104. coord/dashboard/webapp/src/api/generated.ts +176 -0
  105. coord/dashboard/webapp/src/components/ConnectionBadge.tsx +52 -0
  106. coord/dashboard/webapp/src/components/Detail.tsx +800 -0
  107. coord/dashboard/webapp/src/components/Gallery.tsx +341 -0
  108. coord/dashboard/webapp/src/components/Home.tsx +435 -0
  109. coord/dashboard/webapp/src/components/MobileKeyBar.tsx +280 -0
  110. coord/dashboard/webapp/src/components/PanelHeader.tsx +59 -0
  111. coord/dashboard/webapp/src/components/PipelineCard.tsx +168 -0
  112. coord/dashboard/webapp/src/components/SessionCard.tsx +99 -0
  113. coord/dashboard/webapp/src/components/SessionDetail.tsx +140 -0
  114. coord/dashboard/webapp/src/components/SessionsList.tsx +81 -0
  115. coord/dashboard/webapp/src/components/Terminal.tsx +376 -0
  116. coord/dashboard/webapp/src/components/__tests__/ConnectionBadge.test.tsx +81 -0
  117. coord/dashboard/webapp/src/components/__tests__/Detail.test.tsx +680 -0
  118. coord/dashboard/webapp/src/components/__tests__/Gallery.test.tsx +83 -0
  119. coord/dashboard/webapp/src/components/__tests__/Home.test.tsx +271 -0
  120. coord/dashboard/webapp/src/components/__tests__/MobileKeyBar.test.tsx +197 -0
  121. coord/dashboard/webapp/src/components/__tests__/PipelineCard.test.tsx +143 -0
  122. coord/dashboard/webapp/src/components/__tests__/SessionCard.test.tsx +106 -0
  123. coord/dashboard/webapp/src/components/__tests__/Terminal.test.tsx +504 -0
  124. coord/dashboard/webapp/src/components/ui/badge.tsx +41 -0
  125. coord/dashboard/webapp/src/components/ui/button.tsx +54 -0
  126. coord/dashboard/webapp/src/components/ui/card.tsx +55 -0
  127. coord/dashboard/webapp/src/components/ui/dialog.tsx +99 -0
  128. coord/dashboard/webapp/src/components/ui/dropdown-menu.tsx +189 -0
  129. coord/dashboard/webapp/src/components/ui/empty-state.tsx +35 -0
  130. coord/dashboard/webapp/src/components/ui/sheet.tsx +123 -0
  131. coord/dashboard/webapp/src/components/ui/skeleton.tsx +9 -0
  132. coord/dashboard/webapp/src/components/ui/tabs.tsx +55 -0
  133. coord/dashboard/webapp/src/components/ui/theme-provider.tsx +78 -0
  134. coord/dashboard/webapp/src/components/ui/theme-toggle.tsx +20 -0
  135. coord/dashboard/webapp/src/components/ui/toast.tsx +123 -0
  136. coord/dashboard/webapp/src/components/ui/toaster.tsx +30 -0
  137. coord/dashboard/webapp/src/components/ui/tooltip.tsx +26 -0
  138. coord/dashboard/webapp/src/components/ui/use-toast.ts +134 -0
  139. coord/dashboard/webapp/src/index.css +210 -0
  140. coord/dashboard/webapp/src/lib/pipeline.ts +29 -0
  141. coord/dashboard/webapp/src/lib/utils.ts +6 -0
  142. coord/dashboard/webapp/src/main.tsx +46 -0
  143. coord/dashboard/webapp/src/realtime/RealtimeProvider.tsx +112 -0
  144. coord/dashboard/webapp/src/realtime/__tests__/RealtimeProvider.test.tsx +189 -0
  145. coord/dashboard/webapp/src/realtime/__tests__/connection.test.ts +255 -0
  146. coord/dashboard/webapp/src/realtime/connection.ts +227 -0
  147. coord/dashboard/webapp/src/realtime/events.ts +100 -0
  148. coord/dashboard/webapp/src/routes/__tests__/paths.test.ts +92 -0
  149. coord/dashboard/webapp/src/routes/paths.ts +92 -0
  150. coord/dashboard/webapp/src/shell/ActivityRail.tsx +335 -0
  151. coord/dashboard/webapp/src/shell/AppShell.tsx +276 -0
  152. coord/dashboard/webapp/src/shell/ComingSoon.tsx +33 -0
  153. coord/dashboard/webapp/src/shell/EmptyDetail.tsx +26 -0
  154. coord/dashboard/webapp/src/shell/RouteNotFound.tsx +33 -0
  155. coord/dashboard/webapp/src/shell/ShellLayout.tsx +147 -0
  156. coord/dashboard/webapp/src/shell/StatusBar.tsx +46 -0
  157. coord/dashboard/webapp/src/shell/__tests__/ShellLayout.test.tsx +520 -0
  158. coord/dashboard/webapp/src/shell/__tests__/shellState.test.ts +95 -0
  159. coord/dashboard/webapp/src/shell/__tests__/stubViewport.ts +40 -0
  160. coord/dashboard/webapp/src/shell/breakpoints.ts +87 -0
  161. coord/dashboard/webapp/src/shell/railItems.ts +105 -0
  162. coord/dashboard/webapp/src/shell/shellState.ts +174 -0
  163. coord/dashboard/webapp/src/shell/useRegionFocus.ts +95 -0
  164. coord/dashboard/webapp/src/test-setup.ts +41 -0
  165. coord/dashboard/webapp/src/vite-env.d.ts +2 -0
  166. coord/dashboard/webapp/tailwind.config.js +140 -0
  167. coord/dashboard/webapp/tsconfig.json +25 -0
  168. coord/dashboard/webapp/tsconfig.node.json +11 -0
  169. coord/dashboard/webapp/vite.config.ts +71 -0
  170. coord/db.py +1076 -0
  171. coord/dead_end.py +332 -0
  172. coord/deploy/README.md +33 -0
  173. coord/deploy/coord-agent.service +89 -0
  174. coord/deploy/coord-db-backup.service +60 -0
  175. coord/deploy/coord-db-backup.sh +74 -0
  176. coord/deploy/coord-db-backup.timer +18 -0
  177. coord/deploy/coord-drive-queue.service +117 -0
  178. coord/deploy/coord-drive-queue.timer +39 -0
  179. coord/deploy/coord-notify.service +48 -0
  180. coord/deploy/coord-notify.timer +24 -0
  181. coord/deploy/coord-release-propagate.service +83 -0
  182. coord/deploy/coord-release-propagate.timer +38 -0
  183. coord/deploy/coord-release-window.service +119 -0
  184. coord/deploy/coord-release-window.timer +36 -0
  185. coord/deploy/coord-serve.service +82 -0
  186. coord/deploy/coord-web-dist-build.service +43 -0
  187. coord/deploy/coord-web-dist-build.timer +36 -0
  188. coord/deploy/coord-web.service +125 -0
  189. coord/deploy_manifest.py +80 -0
  190. coord/deploy_units.py +384 -0
  191. coord/deps.py +115 -0
  192. coord/diagnose.py +1623 -0
  193. coord/dispatch.py +1009 -0
  194. coord/dist_name.py +123 -0
  195. coord/drive.py +3101 -0
  196. coord/drive_queue.py +2298 -0
  197. coord/drive_state.py +870 -0
  198. coord/events.py +381 -0
  199. coord/failure_class.py +914 -0
  200. coord/filelock.py +168 -0
  201. coord/fleet_config_health.py +300 -0
  202. coord/freshness.py +206 -0
  203. coord/gate_a.py +469 -0
  204. coord/gate_b.py +411 -0
  205. coord/gate_snapshot.py +385 -0
  206. coord/gates.py +582 -0
  207. coord/github_ops.py +1954 -0
  208. coord/goal.py +125 -0
  209. coord/graph_health.py +348 -0
  210. coord/health/__init__.py +69 -0
  211. coord/health/aggregate.py +129 -0
  212. coord/health/checks/__init__.py +13 -0
  213. coord/health/checks/agent_install.py +280 -0
  214. coord/health/checks/cargo_targets.py +171 -0
  215. coord/health/checks/claude_binary.py +65 -0
  216. coord/health/checks/deploy_lane_facts.py +458 -0
  217. coord/health/checks/disk.py +99 -0
  218. coord/health/checks/fleet_board.py +89 -0
  219. coord/health/checks/fleet_deploy_lanes.py +469 -0
  220. coord/health/checks/fleet_phantom.py +69 -0
  221. coord/health/checks/fleet_unit_drift.py +151 -0
  222. coord/health/checks/graph.py +192 -0
  223. coord/health/checks/plan_usage.py +88 -0
  224. coord/health/checks/repo_state.py +161 -0
  225. coord/health/checks/spawned_coord.py +465 -0
  226. coord/health/checks/timer_active.py +254 -0
  227. coord/health/checks/toolchain.py +547 -0
  228. coord/health/checks/unit_drift.py +648 -0
  229. coord/health/checks/unit_enablement.py +171 -0
  230. coord/health/checks/worktrees.py +96 -0
  231. coord/health/cli.py +121 -0
  232. coord/health/context.py +106 -0
  233. coord/health/fleet_snapshot.py +477 -0
  234. coord/health/models.py +250 -0
  235. coord/health/pypi.py +231 -0
  236. coord/health/registry.py +240 -0
  237. coord/health/render.py +82 -0
  238. coord/health/units.py +60 -0
  239. coord/hooks.py +106 -0
  240. coord/housekeeping.py +204 -0
  241. coord/interactive.py +4286 -0
  242. coord/issue_store.py +1496 -0
  243. coord/liveness_auditor.py +293 -0
  244. coord/machine_pause.py +755 -0
  245. coord/merge_queue.py +4681 -0
  246. coord/milestone_chat.py +600 -0
  247. coord/milestone_dispatch.py +943 -0
  248. coord/milestone_gate.py +709 -0
  249. coord/milestone_order.py +840 -0
  250. coord/mock_author.py +334 -0
  251. coord/models.py +891 -0
  252. coord/network.py +269 -0
  253. coord/new_issue_chat.py +229 -0
  254. coord/notify.py +3226 -0
  255. coord/openapi.py +404 -0
  256. coord/overlap_fence.py +133 -0
  257. coord/parentage.py +200 -0
  258. coord/parentage_github.py +58 -0
  259. coord/pipeline.py +481 -0
  260. coord/plan_parser.py +266 -0
  261. coord/plans.py +543 -0
  262. coord/platform_paths.py +43 -0
  263. coord/pr_body_lint.py +67 -0
  264. coord/prereqs.py +533 -0
  265. coord/progress.py +425 -0
  266. coord/providers/__init__.py +683 -0
  267. coord/providers/base.py +218 -0
  268. coord/providers/claude.py +284 -0
  269. coord/providers/claude_pty.py +610 -0
  270. coord/providers/opencode.py +896 -0
  271. coord/reconcile.py +2233 -0
  272. coord/refine_chat.py +485 -0
  273. coord/release_cordon.py +525 -0
  274. coord/release_propagate.py +1176 -0
  275. coord/release_verify.py +777 -0
  276. coord/release_window.py +322 -0
  277. coord/reports.py +1643 -0
  278. coord/revalidate.py +1101 -0
  279. coord/review.py +3317 -0
  280. coord/scorecard.py +484 -0
  281. coord/serve_app.py +7192 -0
  282. coord/skills/update-issue/SKILL.md +93 -0
  283. coord/smoke.py +1030 -0
  284. coord/split_work.py +210 -0
  285. coord/stage_projection.py +650 -0
  286. coord/state.py +5720 -0
  287. coord/test_author.py +1064 -0
  288. coord/test_chat.py +352 -0
  289. coord/test_orchestrator.py +494 -0
  290. coord/test_report.py +178 -0
  291. coord/tui_release.py +271 -0
  292. coord/usage.py +753 -0
  293. coord/usage_limits.py +358 -0
  294. coord/usage_rollup.py +709 -0
  295. coord/worker_events.py +954 -0
@@ -0,0 +1,117 @@
1
+ # coord drive-queue tick — periodic drain of the operator-declared drive
2
+ # queue (#1750/#1754, DQ-4/#1756).
3
+ #
4
+ # A systemd *user* unit (mirrors coord-agent/coord-serve/coord-web/coord-notify),
5
+ # paired with coord-drive-queue.timer. Runs `coord drive-queue tick`, which
6
+ # reconciles the queue against live board state and launches AT MOST ONE
7
+ # `coord drive --tmux` per tick — never more, never past `--max-parallel`.
8
+ # Install ONLY on the box that can see the board (dellserver in production,
9
+ # the same host that runs coord-serve/coord-web/coord-notify): the tick
10
+ # subprocess-launches `coord drive --tmux`, which needs a local tmux server
11
+ # and the repo checkouts under SRC_ROOT, so it belongs on the daemon host, not
12
+ # a thin client.
13
+ #
14
+ # Type=oneshot + `coord drive-queue tick`'s own flock (coord.filelock,
15
+ # drive_queue_lock_path()) means a slow tick cannot stack: if a tick is still
16
+ # running when the timer fires again, the new invocation takes the lock
17
+ # non-blocking, sees it busy, prints "another drive-queue tick is running —
18
+ # skipping", and exits 0 without touching the queue. Nothing here needs to
19
+ # serialize invocations itself.
20
+ #
21
+ # COORD_BIN — the runner's own CLI must be a PINNED, NON-EDITABLE install
22
+ # (#1523): worker branch churn must never be able to rewrite the tool that is
23
+ # driving it, mid-run. On dellserver, ~/.local/bin/coord is a symlink into
24
+ # ~/.coord-venv — the SAME venv deploy/coord-agent.service runs `coord agent`
25
+ # from (see install-agent.sh) — so that already satisfies #1523, and it rides
26
+ # the ordinary agent-venv upgrade lane rather than needing its own. Before
27
+ # relying on a fix landing on this host:
28
+ #
29
+ # coord agent update --machine dellserver # or --all; the standard lane
30
+ # ~/.local/bin/coord --version # VERIFY it took — an upgrade
31
+ # # silently no-ops more often
32
+ # # than you would think
33
+ #
34
+ # Do NOT confuse this with the epic sequencer's separate, bespoke
35
+ # ~/.coord-cli-venv on elitebook (docs/AGENT_OPERATIONS.md's "fourth lane") —
36
+ # that one has no automatic upgrade path at all and was found three releases
37
+ # stale on 2026-07-29. This unit intentionally does not use that pattern.
38
+ #
39
+ # If you ever install this timer on a DIFFERENT machine, first confirm THAT
40
+ # machine's `coord` is a non-editable install before trusting it unattended:
41
+ #
42
+ # readlink -f ~/.local/bin/coord
43
+ # pip show code-coordinator | grep -i editable # must print NOTHING
44
+ #
45
+ # An editable checkout (e.g. elitebook's dev box, where ~/.local/bin/coord
46
+ # points at ~/src/claude-coordinator) lets a worker's branch churn rewrite the
47
+ # runner's own code out from under a live tick. Do not install this unit
48
+ # there. See docs/DRIVE_QUEUE.md.
49
+ #
50
+ # Install:
51
+ # mkdir -p ~/.config/systemd/user
52
+ # cp deploy/coord-drive-queue.service deploy/coord-drive-queue.timer \
53
+ # ~/.config/systemd/user/
54
+ # loginctl enable-linger "$USER" # survive logout / reboot
55
+ # systemctl --user daemon-reload
56
+ # systemctl --user enable --now coord-drive-queue.timer
57
+ #
58
+ # This is a Type=oneshot service activated BY THE TIMER — do not
59
+ # `systemctl --user enable coord-drive-queue.service` directly (it has no
60
+ # [Install] section; the timer unit is what you enable).
61
+ #
62
+ # Prereqs: same as coord-notify — coordinator.yml at ~/.coord/coordinator.yml,
63
+ # a tmux server the launched `coord drive --tmux` sessions can attach to, and
64
+ # the repo checkouts `coord drive` expects under its default SRC_ROOT.
65
+ #
66
+ # See docs/DRIVE_QUEUE.md for the full runbook (enqueue, install, stop,
67
+ # reading QUEUE: STALLED / QUEUE: BLOCKED, and the operational traps).
68
+ #
69
+ # KillMode=process (#1830) — REQUIRED, do not remove. systemd's default
70
+ # KillMode=control-group would reap this unit's entire cgroup when the tick
71
+ # exits. When the tick launches `coord drive --tmux` and NO tmux server is
72
+ # already running, `tmux new-session` has to spawn the server itself, and
73
+ # that server lands inside THIS unit's cgroup — so the drive it just started
74
+ # dies with the tick, seconds later. The failure is invisible whenever a
75
+ # tmux server already exists (e.g. an operator has a terminal open on the
76
+ # box), because then `tmux new-session` hands off to that pre-existing
77
+ # server, which lives outside this cgroup. That made the queue appear to
78
+ # work in every manual/attended test and fail only unattended — see #1830
79
+ # for the full diagnosis and the repro. KillMode=process kills only the
80
+ # tick's own process on unit stop/exit and leaves everything else it
81
+ # started (the tmux server, the drive loop) alone.
82
+
83
+ [Unit]
84
+ Description=coord drive-queue tick (#1756)
85
+ After=network-online.target
86
+ Wants=network-online.target
87
+
88
+ [Service]
89
+ Type=oneshot
90
+ KillMode=process
91
+ # --max-parallel 4 (#2012, raised from 3 in #2057) — the global ceiling,
92
+ # ONE SLOT PER REPO IN THE FLEET: claude-coordinator, quadraui, vimcode,
93
+ # coord-portal. It was 3 because the fleet had three repos when #2012 set
94
+ # it; coord-portal joined afterwards and the number was never revisited, so
95
+ # the fourth repo could never drive concurrently no matter what was queued.
96
+ #
97
+ # KEEP THIS EQUAL TO THE REPO COUNT. It is not a tuning knob for how hard
98
+ # the fleet works — that is what the per-repo ceiling and machine capacity
99
+ # decide. Set it lower and a repo silently loses its lane; set it higher
100
+ # and it does nothing, because the per-repo ceiling still admits only one
101
+ # drive per repo.
102
+ #
103
+ # #1972 added the SECOND ceiling, --max-parallel-per-repo (default 1),
104
+ # which is what keeps two drives off the same repo; without raising this
105
+ # outer one the per-repo ceiling can never bind, because the walk stops at
106
+ # the global limit first and never reaches an entry from a different repo.
107
+ #
108
+ # Raise this ONLY on a host whose coord actually enforces the per-repo
109
+ # ceiling (>= the release carrying #1972). A higher global ceiling on an
110
+ # older coord launches two same-repo drives, which stale each other's
111
+ # Test verdicts (#1479) — the exact collision #1972 exists to prevent.
112
+ ExecStart=%h/.local/bin/coord drive-queue tick --max-parallel 4 --config %h/.coord/coordinator.yml
113
+ # Generous relative to the tick's own ~120s launch-subprocess timeout
114
+ # (_LAUNCH_TIMEOUT_SECONDS in coord/commands/drive_queue.py), but well below
115
+ # the timer's 15-minute cadence (coord-drive-queue.timer) so a genuinely
116
+ # wedged tick can't run into the next fire.
117
+ TimeoutStartSec=300
@@ -0,0 +1,39 @@
1
+ # coord drive-queue tick — fires coord-drive-queue.service (Type=oneshot) on
2
+ # a cadence. See that unit's header for the full rationale and the
3
+ # COORD_BIN / #1523 pinned-runner requirement.
4
+ #
5
+ # 3 minutes (was 15). The earlier rationale — "between launches the tick is
6
+ # only ever OBSERVING ... a tighter interval buys nothing but load" — missed
7
+ # that the tick is ALSO the only thing that notices a drive has finished and
8
+ # launches the next entry. Capacity is 1, so the interval is a hard lower
9
+ # bound on the dead time between issues, not idle observation.
10
+ #
11
+ # Measured 2026-08-07 from the queue journal: #1910's PR merged at 12:47:36,
12
+ # the 12:46:42 tick still saw it alive, and the next tick launched #1834 at
13
+ # 13:02:13 — 14m37s of an idle fleet. Completions land uniformly inside the
14
+ # interval, so the expected loss is HALF of it per issue (~7.5min at 15min).
15
+ # Over a queue of 18 that was ~2.2 hours of nothing happening.
16
+ #
17
+ # Reconcile and launch happen in the SAME tick (13:02:12 reconciled #1910
18
+ # done, 13:02:13 launched #1834), so the cost is one interval, not two.
19
+ #
20
+ # A tick is a /board read plus reconciliation, and #1597 added the /board
21
+ # single-flight guard, so 20 ticks/hour is affordable. `Type=oneshot` + the
22
+ # tick's own flock means it cannot stack even if it fired faster than a tick
23
+ # takes to run. AccuracySec is tightened to 15s to match: systemd's default
24
+ # batching slop of 1min is 33% of a 3min interval.
25
+ #
26
+ # Install: see deploy/coord-drive-queue.service's header, or
27
+ # docs/DRIVE_QUEUE.md.
28
+
29
+ [Unit]
30
+ Description=Run coord drive-queue tick every 3 minutes (#1756)
31
+
32
+ [Timer]
33
+ OnBootSec=5min
34
+ OnUnitActiveSec=3min
35
+ AccuracySec=15s
36
+ Persistent=true
37
+
38
+ [Install]
39
+ WantedBy=timers.target
@@ -0,0 +1,48 @@
1
+ # coord notify — periodic driver for thin-client setups (#1311).
2
+ #
3
+ # A systemd *user* unit (mirrors coord-agent/coord-serve/coord-web), paired
4
+ # with coord-notify.timer. Runs `coord notify` on a schedule: polls each
5
+ # agent for completion, posts the GH completion/failure comments, and
6
+ # triggers the auto-loop (review-on-completion, fix-on-request-changes,
7
+ # re-review-on-fix-completion). Install ONLY on the box that owns
8
+ # ~/.coord/coord.db (dellserver in production — the same host that runs
9
+ # coord-serve/coord-web) — it must NOT run on a thin-client machine, since
10
+ # `coord notify` there would just re-route itself back over HTTP to the
11
+ # daemon anyway (#906, daemon_reroute_target), needlessly.
12
+ #
13
+ # THIS TIMER IS THE SANCTIONED SINGLE DRIVER for thin-client coord-tui/coord
14
+ # setups. Do NOT also hand-roll a `while`/`watch` loop calling `coord notify`
15
+ # alongside it, and do NOT re-enable the TUI's own 30s auto-notify for a
16
+ # thin client (it is deliberately suppressed — `is_remote_board_service()`,
17
+ # tui/src/app/data.rs). Two drivers racing each other is exactly the failure
18
+ # mode that caused the 2026-06-07 incident (duplicate fix workers from a
19
+ # request-changes verdict getting auto-bounced twice) — see #476/#477 and
20
+ # docs/ARCHITECTURE.md's "no orchestration daemon" section.
21
+ #
22
+ # Install:
23
+ # mkdir -p ~/.config/systemd/user
24
+ # cp deploy/coord-notify.service deploy/coord-notify.timer ~/.config/systemd/user/
25
+ # loginctl enable-linger "$USER" # survive logout / reboot
26
+ # systemctl --user daemon-reload
27
+ # systemctl --user enable --now coord-notify.timer
28
+ #
29
+ # This is a Type=oneshot service activated BY THE TIMER — do not
30
+ # `systemctl --user enable coord-notify.service` directly (it has no
31
+ # [Install] section; the timer unit is what you enable).
32
+ #
33
+ # Prereqs: same as coord-serve (coordinator.yml at ~/.coord/coordinator.yml,
34
+ # a `gh`-authenticated environment for the GitHub writes `coord notify` makes).
35
+ #
36
+ # See docs/AGENT_OPERATIONS.md → "Periodic coord notify (coord-notify timer)".
37
+
38
+ [Unit]
39
+ Description=coord notify — periodic pipeline driver (#1311)
40
+ After=network-online.target
41
+ Wants=network-online.target
42
+
43
+ [Service]
44
+ Type=oneshot
45
+ ExecStart=%h/.coord-venv/bin/coord notify --config %h/.coord/coordinator.yml
46
+ # Bounded below the timer's 5-minute cadence (coord-notify.timer) so a slow
47
+ # run can't overlap the next fire.
48
+ TimeoutStartSec=280
@@ -0,0 +1,24 @@
1
+ # coord notify — periodic driver for thin-client setups (#1311). Fires
2
+ # coord-notify.service (Type=oneshot) on a cadence. See that unit's header
3
+ # for the full rationale ("THIS TIMER IS THE SANCTIONED SINGLE DRIVER").
4
+ #
5
+ # 5 minutes is deliberately coarser than the in-process TUI's 30s cadence —
6
+ # this timer's job is "don't let the pipeline stall unnoticed for the whole
7
+ # session," not sub-minute responsiveness. Tighten OnUnitActiveSec if that
8
+ # proves too slow in practice; there's no correctness reason it needs to be
9
+ # this conservative, just no reason to hammer `gh` harder than necessary.
10
+ #
11
+ # Install: see deploy/coord-notify.service's header, or
12
+ # docs/AGENT_OPERATIONS.md → "Periodic coord notify (coord-notify timer)".
13
+
14
+ [Unit]
15
+ Description=Run coord notify every few minutes (#1311)
16
+
17
+ [Timer]
18
+ OnBootSec=2min
19
+ OnUnitActiveSec=5min
20
+ AccuracySec=30s
21
+ Persistent=true
22
+
23
+ [Install]
24
+ WantedBy=timers.target
@@ -0,0 +1,83 @@
1
+ # coord release propagate — the PROPAGATE half of merge-triggered release
2
+ # (#1835, PKG-7).
3
+ #
4
+ # A systemd *user* unit (mirrors coord-drive-queue), paired with
5
+ # coord-release-propagate.timer. Each fire is one attempt:
6
+ #
7
+ # 1. resolve the released version from PyPI's simple index;
8
+ # 2. ask the drive queue and the board whether the fleet is QUIESCENT;
9
+ # 3. if it is not — record a deferral and exit 0. This is the normal,
10
+ # expected outcome most of the time and it is NOT a failure;
11
+ # 4. if it is — roll each lane in the daemon-first order, run
12
+ # `coord release verify` as the final gate, roll back on red, and
13
+ # release the drive-queue deploy gates (#1757) that were waiting for
14
+ # exactly this deploy.
15
+ #
16
+ # WHY THIS IS A TIMER AND NOT A GITHUB ACTION
17
+ # -------------------------------------------
18
+ # The *publish* half IS a GitHub Action (.github/workflows/auto-release.yml)
19
+ # because publishing touches no running host. Propagation restarts every
20
+ # agent, and a restart kills every in-flight headless worker — so it must
21
+ # wait for a window where nothing is mid-flight. GitHub cannot see that;
22
+ # only the board can. Hence: publish on merge, propagate on quiescence.
23
+ #
24
+ # WHICH HOST
25
+ # ----------
26
+ # Install ONLY on the daemon host (dellserver in production — the same box
27
+ # that runs coord-serve / coord-drive-queue / coord-notify). This unit reads
28
+ # `/board` and POSTs to every agent, and it releases drive-queue holds, so it
29
+ # belongs where the queue and the board already live. Installing a second
30
+ # copy elsewhere gives the fleet two independent things deciding to restart
31
+ # it, which is the #1440 two-overseers failure in a new costume.
32
+ #
33
+ # COORD_BIN — same #1523 requirement as coord-drive-queue.service: the CLI
34
+ # this unit runs must be a PINNED, NON-EDITABLE install, because it is the
35
+ # thing that upgrades everything else. On dellserver ~/.local/bin/coord is a
36
+ # symlink into ~/.coord-venv, which satisfies that and rides the ordinary
37
+ # agent-venv lane. Do NOT install this unit on a dev box whose
38
+ # ~/.local/bin/coord points at a checkout.
39
+ #
40
+ # SELF-UPDATE ORDERING: this unit's own CLI lives in the venv it is about to
41
+ # swap. That is safe *because* #1241 made the swap atomic (blue/green symlink
42
+ # flip) — a running `coord release propagate` keeps executing the fully-old
43
+ # slot it started from; it can never observe a half-written site-packages.
44
+ # The next fire runs the new one.
45
+ #
46
+ # Install:
47
+ # mkdir -p ~/.config/systemd/user
48
+ # cp deploy/coord-release-propagate.service \
49
+ # deploy/coord-release-propagate.timer ~/.config/systemd/user/
50
+ # loginctl enable-linger "$USER"
51
+ # systemctl --user daemon-reload
52
+ # systemctl --user enable --now coord-release-propagate.timer
53
+ #
54
+ # (After the first release carrying #1835, `coord release propagate` installs
55
+ # unit updates itself via each agent's POST /deploy-units — but it only
56
+ # refreshes units a host ALREADY has, so this first install is by hand, once.)
57
+ #
58
+ # This is a Type=oneshot service activated BY THE TIMER — do not
59
+ # `systemctl --user enable coord-release-propagate.service` directly.
60
+ #
61
+ # Read the journal it writes with `coord release history`, not just
62
+ # `journalctl`: the JSONL record survives a half-installed venv and is the
63
+ # thing that makes "the fleet reached that version" checkable after the fact.
64
+
65
+ [Unit]
66
+ Description=Propagate the released version to the fleet at the next quiescent window (#1835)
67
+ After=network-online.target
68
+ Wants=network-online.target
69
+
70
+ [Service]
71
+ Type=oneshot
72
+ # No --force, ever, from a timer. --force rolls over a BUSY fleet and kills
73
+ # in-flight headless workers — the single thing this whole design exists to
74
+ # avoid. It stays an interactive-operator flag.
75
+ #
76
+ # --verify and --rollback-on-red are the defaults; named explicitly here so
77
+ # the unit reads as the policy it is, and so a future default change cannot
78
+ # silently disarm the fleet's rollback.
79
+ ExecStart=%h/.local/bin/coord release propagate --verify --rollback-on-red --config %h/.coord/coordinator.yml
80
+ # A roll waits up to --timeout (180s) per agent for the new version to be
81
+ # reported, across every machine, plus two full verification sweeps. Generous,
82
+ # but well under the timer's cadence so a wedged run cannot run into the next.
83
+ TimeoutStartSec=1800
@@ -0,0 +1,38 @@
1
+ # coord release propagate — fires coord-release-propagate.service
2
+ # (Type=oneshot) on a cadence. See that unit's header for the full rationale.
3
+ #
4
+ # 20 minutes. The tick is cheap and almost always a no-op: it reads the board,
5
+ # finds the fleet busy, records a deferral and exits. What the interval
6
+ # actually buys is *latency from quiescence to deployed* — the window between
7
+ # a drive finishing and the next one launching is minutes wide
8
+ # (coord-drive-queue.timer fires every 3 minutes), so a slow propagation
9
+ # cadence would routinely miss the gap and the fleet would sit a release
10
+ # behind for hours.
11
+ #
12
+ # Not tighter than that, for two reasons. Each fire that *does* find a window
13
+ # restarts every agent, so an over-eager cadence turns a transient idle moment
14
+ # into a fleet restart the operator did not ask for. And a propagation run
15
+ # holds the window it found: an interval shorter than a full roll+verify
16
+ # sweep would just queue up runs behind each other.
17
+ #
18
+ # Persistent=true so a host that was asleep or rebooted catches up on the next
19
+ # boot rather than waiting a full interval — the whole point is that the fleet
20
+ # converges on the released version without anyone thinking about it.
21
+ #
22
+ # RandomizedDelaySec spreads fires if this unit is ever (against the advice in
23
+ # the service header) installed on more than one host: two propagations
24
+ # racing would both find the same window and both start restarting agents.
25
+ # The real defence is "install on the daemon host only"; this is the belt.
26
+
27
+ [Unit]
28
+ Description=Attempt fleet propagation of the released version every 20 minutes (#1835)
29
+
30
+ [Timer]
31
+ OnBootSec=10min
32
+ OnUnitActiveSec=20min
33
+ RandomizedDelaySec=2min
34
+ AccuracySec=1min
35
+ Persistent=true
36
+
37
+ [Install]
38
+ WantedBy=timers.target
@@ -0,0 +1,119 @@
1
+ # coord release nightly-window — guarantee the daemon host rolls instead of
2
+ # waiting for a quiescent window that may never come (#2112).
3
+ #
4
+ # A systemd *user* unit (mirrors coord-drive-queue/coord-release-propagate),
5
+ # paired with coord-release-window.timer. Each fire is one attempt:
6
+ #
7
+ # 1. resolve the released version and the daemon host's own python-lane
8
+ # version; if the daemon host is already current, STOP — the queue is
9
+ # never touched (#2112 acceptance 3);
10
+ # 2. otherwise, `systemctl --user stop coord-drive-queue.timer` — no new
11
+ # drives may launch for the rest of this run;
12
+ # 3. wait (bounded — see TimeoutStartSec below and
13
+ # `coord release nightly-window --drain-deadline`) for whatever is
14
+ # already running to finish, reconciling with `coord drive-queue tick
15
+ # --reconcile-only` on every poll (#2110 — this is what stops the
16
+ # drain from deadlocking now that the timer that normally does that
17
+ # reconciliation is stopped);
18
+ # 4. roll with `coord release propagate --daemon-host <derived>
19
+ # --target <resolved>` — no --force, ever (trap 1);
20
+ # 5. `systemctl --user start coord-drive-queue.timer` — ALWAYS, whatever
21
+ # happened in 1-4, including a crash;
22
+ # 6. report the outcome: stdout (this unit's journal), the
23
+ # `~/.coord/release_window.jsonl` journal (`coord release
24
+ # window-history`), and — for anything short of a clean roll or a
25
+ # confirmed already-current fleet — the same escalation channel
26
+ # `coord drive escalations` and the TUI already surface #2101's
27
+ # drain-deadline escalation through.
28
+ #
29
+ # WHY THIS EXISTS SEPARATELY FROM coord-release-propagate.timer
30
+ # ----------------------------------------------------------------
31
+ # `coord release propagate` already rolls every OTHER host at its own
32
+ # per-host quiescent window (#2067), and cordons a behind host so it drains
33
+ # on its own (#2101). Neither mechanism reaches the daemon host itself: the
34
+ # daemon must lead every roll (the documented 405), so if IT is busy the
35
+ # whole run defers — and dellserver is both the daemon host and the box
36
+ # whose drive-queue tick launches work fleet-wide, so almost any queued
37
+ # drive anywhere keeps it "busy". Cordoning the daemon host would not help:
38
+ # cordoning stops new work from ROUTING there, but does not stop the
39
+ # daemon's own tick from LAUNCHING new drive-queue entries in the first
40
+ # place. The only way to guarantee the daemon host reaches quiescence is to
41
+ # stop the thing that launches work onto it — which is exactly what this
42
+ # unit's `--ensure-queue-running` ExecStopPost= below makes safe to do
43
+ # unattended. See coord/release_window.py's module docstring for the full
44
+ # mechanism and the 2026-08-10 measurement (eleven releases behind, with
45
+ # elitebook idle and rollable the whole time).
46
+ #
47
+ # GATED ON #2110 — HARD PREREQUISITE. Steps 2-3 above are exactly the
48
+ # sequence that deadlocked on 2026-08-10: the reconciler that clears a
49
+ # finished drive's `running` row lives INSIDE `coord drive-queue tick`, so
50
+ # stopping the timer used to stop reconciliation too, and the last drive's
51
+ # row would stay `running` forever — the daemon host would read as busy
52
+ # permanently and this unit would defer every night, unattended, silently,
53
+ # forever. Do not install this unit on a build that predates #2110.
54
+ #
55
+ # WHICH HOST
56
+ # ----------
57
+ # Install ONLY on the daemon host (dellserver in production — the same box
58
+ # that runs coord-serve/coord-drive-queue/coord-release-propagate). This
59
+ # unit stops and restarts coord-drive-queue.timer, which only exists on
60
+ # that host; installing a second copy elsewhere has no queue to stop and
61
+ # would just repeatedly fail `systemctl --user stop coord-drive-queue.timer`
62
+ # — a locally-scoped, harmless-but-noisy failure, not the #1440
63
+ # two-overseers hazard `coord-release-propagate.timer`'s header warns about,
64
+ # but still pointless. Don't.
65
+ #
66
+ # COORD_BIN — same #1523 pinned-non-editable-install requirement as
67
+ # coord-drive-queue.service/coord-release-propagate.service: the CLI this
68
+ # unit runs must not be rewritable out from under a live run. See those
69
+ # units' headers for the full rationale; on dellserver ~/.local/bin/coord
70
+ # already satisfies it.
71
+ #
72
+ # Install:
73
+ # mkdir -p ~/.config/systemd/user
74
+ # cp deploy/coord-release-window.service \
75
+ # deploy/coord-release-window.timer ~/.config/systemd/user/
76
+ # loginctl enable-linger "$USER"
77
+ # systemctl --user daemon-reload
78
+ # systemctl --user enable --now coord-release-window.timer
79
+ #
80
+ # Read the journal it writes with `coord release window-history`, not just
81
+ # `journalctl` — the JSONL record survives a half-installed venv, same
82
+ # reason coord-release-propagate.service's does.
83
+
84
+ [Unit]
85
+ Description=Nightly release window on the daemon host (#2112)
86
+ After=network-online.target coord-drive-queue.service
87
+ Wants=network-online.target
88
+
89
+ [Service]
90
+ Type=oneshot
91
+ # No --force, ever (trap 1) — coord/commands/release.py's
92
+ # release_nightly_window never passes it to the `coord release propagate`
93
+ # it shells out to. A drain that has not finished by the deadline is
94
+ # reported and declined, not overridden.
95
+ ExecStart=%h/.local/bin/coord release nightly-window --config %h/.coord/coordinator.yml
96
+ # #2112 trap 4 / acceptance 4: "the queue timer is running after the job in
97
+ # EVERY path, including the failure ones — test by killing the job
98
+ # mid-window." A `finally` block inside ExecStart cannot survive SIGKILL
99
+ # (TimeoutStartSec below, or an operator's own `systemctl --user kill`).
100
+ # ExecStopPost= is systemd's OWN guarantee of exactly that: it runs after
101
+ # the main process exits for ANY reason — clean exit, non-zero exit, or
102
+ # killed by a signal — so this is the backstop the in-process `finally`
103
+ # cannot be. `--ensure-queue-running` does nothing but
104
+ # `systemctl --user start coord-drive-queue.timer` and is idempotent: on
105
+ # the overwhelmingly common path where ExecStart already restarted the
106
+ # timer cleanly, this is a harmless no-op assertion, not a second action.
107
+ ExecStopPost=%h/.local/bin/coord release nightly-window --ensure-queue-running --config %h/.coord/coordinator.yml
108
+ # The drain alone may wait up to its own --drain-deadline (default 1h) on
109
+ # top of the roll+verify sweep `coord release propagate` performs
110
+ # (generous relative to its own 1800s TimeoutStartSec — see
111
+ # coord-release-propagate.service). 2.5h total leaves this unit's own
112
+ # worst case well inside the 22:00-08:00 quiet-hours window even starting
113
+ # at 03:00, per coord-release-window.timer's header.
114
+ TimeoutStartSec=9000
115
+
116
+ # This is a Type=oneshot service activated BY THE TIMER — do not
117
+ # `systemctl --user enable coord-release-window.service` directly (it has
118
+ # no [Install] section; the timer unit is what you enable — same pattern
119
+ # as coord-release-propagate.service/coord-drive-queue.service).
@@ -0,0 +1,36 @@
1
+ # coord release nightly-window — fires coord-release-window.service
2
+ # (Type=oneshot) once a night. See that unit's header for the full
3
+ # rationale (#2112).
4
+ #
5
+ # 03:00 local, inside the 22:00-08:00 quiet hours already used for
6
+ # notifications (see coord-notify's own schedule) — an operator is least
7
+ # likely to be mid-drive at 03:00, and the drain deadline
8
+ # (coord/release_window.py's DEFAULT_DRAIN_DEADLINE_SECONDS, 1h by default)
9
+ # leaves comfortable room to finish and restart the queue timer well before
10
+ # the working day, even in the worst case where the drain times out.
11
+ #
12
+ # RandomizedDelaySec is small and exists only to avoid firing at the exact
13
+ # same wall-clock instant `coord-drive-queue.timer`'s own OnCalendar-free
14
+ # interval schedule might land on, not because two hosts might race this
15
+ # unit — it belongs on the daemon host ONLY (same rule as
16
+ # coord-release-propagate.timer; see that unit's header for why a second
17
+ # copy elsewhere is the #1440 two-overseers failure in a new costume).
18
+ #
19
+ # Persistent=true (#2112 trap 4 / acceptance-adjacent): a host that was
20
+ # asleep or rebooted through 03:00 still catches up on the next boot, rather
21
+ # than silently skipping a whole night. `coord release nightly-window`
22
+ # itself is idempotent — see its module docstring's `needs_roll`: a fleet
23
+ # already current at whatever time this actually fires does not stop the
24
+ # queue at all.
25
+
26
+ [Unit]
27
+ Description=Nightly release window on the daemon host (#2112)
28
+
29
+ [Timer]
30
+ OnCalendar=*-*-* 03:00:00
31
+ RandomizedDelaySec=5min
32
+ AccuracySec=1min
33
+ Persistent=true
34
+
35
+ [Install]
36
+ WantedBy=timers.target
@@ -0,0 +1,82 @@
1
+ # coord serve — portable control-center daemon (#584/#591).
2
+ #
3
+ # A systemd *user* unit (mirrors coord-agent). Runs the read+write daemon that
4
+ # fronts the one shared ~/.coord/coord.db so any Tailscale machine renders/drives
5
+ # the same board. Install on the always-on box that owns the DB (dellserver).
6
+ #
7
+ # Install:
8
+ # mkdir -p ~/.config/systemd/user
9
+ # cp deploy/coord-serve.service ~/.config/systemd/user/
10
+ # loginctl enable-linger "$USER" # survive logout / reboot
11
+ # systemctl --user daemon-reload
12
+ # systemctl --user enable --now coord-serve
13
+ #
14
+ # Prereqs on the daemon host:
15
+ # - ~/.coord-venv has a coord BUILD that includes `coord serve` (a release
16
+ # >= the #584/#590 cut, or an editable checkout of the branch). PyPI
17
+ # releases before that do NOT have the command.
18
+ # - ~/.coord/coordinator.yml exists (canonical location; the daemon serves it
19
+ # at GET /config so clients need none). Resolution: $COORD_CONFIG →
20
+ # ~/.coord/coordinator.yml → ./coordinator.yml.
21
+ # - ~/.coord/coord.db exists (after the one-time cutover/ETL).
22
+ # - Bearer token (recommended): printf '%s' "<secret>" > ~/.coord/serve_token
23
+ # && chmod 600 ~/.coord/serve_token (resolve_serve_token reads it; never
24
+ # put --token on ExecStart — it would leak via `ps`).
25
+ #
26
+ # See docs/AGENT_OPERATIONS.md → "Control-center daemon (coord serve)".
27
+
28
+ [Unit]
29
+ Description=coord serve — portable control-center daemon (#584)
30
+ After=network-online.target
31
+ Wants=network-online.target
32
+
33
+ [Service]
34
+ Type=simple
35
+ # PATH here merges TWO independent, still-load-bearing fixes — #1831 found
36
+ # them landed a month apart, each unaware of the other, and each one alone
37
+ # regresses the other's issue:
38
+ #
39
+ # - #1814 added ~/.cargo/bin. Defence in depth ONLY — never the fix on its
40
+ # own. A systemd user unit's PATH is systemd's, not a login shell's:
41
+ # ~/.profile is never sourced, so ~/.cargo/bin is absent and `cargo test`
42
+ # inside a `coord merge --revalidate` run died with "command not found".
43
+ # The real fix is in scripts/coord-test-runner.sh, which resolves each
44
+ # toolchain explicitly and reports a MISSING one as an infrastructure
45
+ # error rather than a red suite — because a PATH= line here fixes this
46
+ # host and leaves the next one broken silently, which is the same failure
47
+ # shape as the bug. Keep both.
48
+ # - #1117 added the repo venv (%h/src/claude-coordinator/.venv/bin) so
49
+ # acceptance.drivers subprocesses shelled out via
50
+ # subprocess.run(shell=True) (e.g. the cli-pytest driver's `pytest
51
+ # tests/acceptance/{ms}`) resolve `pytest`/`python3` with the repo's
52
+ # runtime deps (e.g. httpx) instead of falling back to bare
53
+ # /usr/bin/python3, which lacks them -> every acceptance run/record
54
+ # silently failed with an ImportError, not a real test failure.
55
+ #
56
+ # #1831: putting the repo venv FIRST let a stale editable checkout there
57
+ # shadow the pinned release for every `shutil.which("coord")` resolution
58
+ # (coord_argv(), coord/drive.py) — the daemon ran 0.4.105 while everything
59
+ # it spawned ran 0.4.103 editable code from a checkout nobody was updating.
60
+ # ~/.local/bin goes ahead of it (a symlink to ~/.coord-venv/bin/coord, i.e.
61
+ # the pinned release) so `coord` always resolves there first and a stale
62
+ # checkout can never shadow it again; the repo venv stays LAST, after
63
+ # ~/.local/bin, so pytest/python3 still see it for #1117. This ordering is
64
+ # the structural fix — see the `coord health`/`coord doctor` unit_drift
65
+ # check (#1831), which flags any unit whose PATH puts a `.venv/bin` entry
66
+ # ahead of ~/.local/bin or ~/.coord-venv/bin.
67
+ Environment=PATH=%h/.cargo/bin:%h/.local/bin:%h/src/claude-coordinator/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
68
+ ExecStart=%h/.coord-venv/bin/coord serve --config %h/.coord/coordinator.yml --host 0.0.0.0 --port 7435
69
+ Restart=on-failure
70
+ RestartSec=3
71
+ # #2095: coord-web (its sibling on the same host, restarted through the same
72
+ # `_restart_sibling_unit` caller) went down for the length of a fleet roll
73
+ # because nothing bounded how long systemd would wait on its stop — an SSE
74
+ # client held it open indefinitely. coord-serve serves no `text/event-stream`
75
+ # endpoint today so it was not the unit that actually hung, but nothing
76
+ # structural stops a future one (a stuck DB write, a wedged client
77
+ # connection) from doing the same thing here, uncapped, for up to systemd's
78
+ # 90s default. Bounding it explicitly costs nothing on the fast path.
79
+ TimeoutStopSec=10
80
+
81
+ [Install]
82
+ WantedBy=default.target
@@ -0,0 +1,43 @@
1
+ # coord-web-dist-build — pull merged main and rebuild the React webapp bundle
2
+ # that `coord web --dist` serves (#1543), paired with
3
+ # coord-web-dist-build.timer.
4
+ #
5
+ # A systemd *user* unit, Type=oneshot, mirroring coord-drive-queue.service's
6
+ # tick pattern (timer-fired, its own flock so a slow run can't stack). Runs
7
+ # deploy/coord-web-dist-build.sh, which:
8
+ # - fetches origin/main into ~/src/claude-coordinator (read-only — only
9
+ # `git fetch` + `rev-parse`, never touches that checkout's working tree
10
+ # or whatever branch an operator has it parked on),
11
+ # - builds coord/dashboard/webapp from that SHA inside a DEDICATED worktree
12
+ # (~/.coord-web-checkout, never shared with an operator or `coord drive`),
13
+ # - atomically repoints ~/coord-web-dist at the new build.
14
+ #
15
+ # Deliberately separate from coord-agent/coord-serve/coord-web's
16
+ # ~/.coord-venv: this unit never runs `pip install`, never touches that venv,
17
+ # and its failure mode (a broken npm build) leaves the PREVIOUS release live
18
+ # — see the script's header for the exact guarantee. Restarting THIS unit
19
+ # does not restart coord-web, coord-serve, or coord-agent, and does not
20
+ # interrupt an attended /ws/terminal session (no process restart happens at
21
+ # all on a normal publish — see the script header).
22
+ #
23
+ # Install: see deploy/coord-web-dist-build.sh's header, or
24
+ # docs/PHONE_WEBAPP.md / docs/AGENT_OPERATIONS.md.
25
+ #
26
+ # This is a Type=oneshot service activated BY THE TIMER — do not
27
+ # `systemctl --user enable coord-web-dist-build.service` directly (it has no
28
+ # [Install] section; the timer unit is what you enable).
29
+
30
+ [Unit]
31
+ Description=Build coord webapp dist from merged main (#1543)
32
+ After=network-online.target
33
+ Wants=network-online.target
34
+
35
+ [Service]
36
+ Type=oneshot
37
+ ExecStart=%h/.local/bin/coord-web-dist-build.sh
38
+ # The script has its own flock (coord-web-dist-build.lock); this is a
39
+ # generous ceiling for a wedged `npm ci`/`npm run build`, well below the
40
+ # timer's 1-minute cadence so a genuinely stuck run can't run into the next
41
+ # fire indefinitely.
42
+ TimeoutStartSec=300
43
+ Environment=PATH=%h/.local/bin:%h/.cargo/bin:/usr/local/bin:/usr/bin:/bin