opencode-thrum-pro 0.1.4 → 0.1.6

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 (69) hide show
  1. package/assets/commands/thrum-_compact-runtime-commands.md +61 -0
  2. package/assets/commands/thrum-_snapshot-protocol.md +17 -6
  3. package/assets/commands/thrum-_stamp-protocol.md +9 -2
  4. package/assets/commands/thrum-compact-extended.md +352 -0
  5. package/assets/commands/thrum-compact.md +300 -0
  6. package/assets/commands/thrum-inbox.md +1 -1
  7. package/assets/commands/thrum-restart-extended.md +84 -11
  8. package/assets/commands/thrum-restart.md +105 -13
  9. package/assets/commands/thrum-send.md +4 -1
  10. package/assets/commands/thrum-sleep-extended.md +67 -46
  11. package/assets/commands/thrum-sleep.md +67 -58
  12. package/assets/skills/adversarial-critique/SKILL.md +5 -8
  13. package/assets/skills/agent-messaging-protocol/SKILL.md +35 -0
  14. package/assets/skills/brainstorm-steward/SKILL.md +65 -11
  15. package/assets/skills/brainstormer-staying-current/SKILL.md +113 -0
  16. package/assets/skills/choosing-subagent-models/SKILL.md +14 -0
  17. package/assets/skills/conversation-only-context/SKILL.md +11 -11
  18. package/assets/skills/conversation-only-context/resources/driver-subagent-pattern.md +1 -1
  19. package/assets/skills/conversation-only-context/resources/remote-environment-scenarios.md +1 -1
  20. package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +98 -6
  21. package/assets/skills/coordinator-context-monitoring/SKILL.md +29 -19
  22. package/assets/skills/coordinator-deploying-a-node/SKILL.md +124 -0
  23. package/assets/skills/coordinator-dispatching-work/SKILL.md +24 -11
  24. package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +62 -20
  25. package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +6 -6
  26. package/assets/skills/coordinator-merging-code/SKILL.md +25 -11
  27. package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +28 -7
  28. package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +3 -3
  29. package/assets/skills/coordinator-rolling-out-to-nodes/SKILL.md +92 -0
  30. package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +3 -3
  31. package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +58 -71
  32. package/assets/skills/coordinator-running-review-cycles/SKILL.md +15 -4
  33. package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +0 -2
  34. package/assets/skills/efficient-multi-agent-research/SKILL.md +6 -7
  35. package/assets/skills/implementer-orchestrating-execution/SKILL.md +11 -14
  36. package/assets/skills/orchestrate/SKILL.md +47 -35
  37. package/assets/skills/persistent-watcher-archetype/SKILL.md +188 -37
  38. package/assets/skills/persistent-watcher-archetype/resources/capture_archive.py +618 -0
  39. package/assets/skills/persistent-watcher-archetype/resources/jev_watcher_filter.py +837 -0
  40. package/assets/skills/persistent-watcher-archetype/resources/roster-watch.sh +1140 -0
  41. package/assets/skills/persistent-watcher-archetype/resources/roster-watch_test.sh +291 -0
  42. package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +2 -2
  43. package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +20 -8
  44. package/assets/skills/prime-agent/SKILL.md +10 -0
  45. package/assets/skills/project-setup/SKILL.md +22 -16
  46. package/assets/skills/project-setup/resources/implementation-agent.md +56 -35
  47. package/assets/skills/researcher-investigating/SKILL.md +2 -5
  48. package/assets/skills/state-migration/SKILL.md +1 -1
  49. package/assets/skills/thrum/SKILL.md +14 -7
  50. package/assets/skills/thrum/resources/ANTI_PATTERNS.md +16 -18
  51. package/assets/skills/thrum/resources/CLI_REFERENCE.md +17 -11
  52. package/assets/skills/thrum/resources/LISTENER_PATTERN.md +9 -3
  53. package/assets/skills/thrum/resources/MESSAGING.md +18 -11
  54. package/assets/skills/thrum/resources/WORKTREES.md +1 -1
  55. package/assets/skills/thrum-memory/SKILL.md +1 -1
  56. package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +1 -1
  57. package/assets/skills/update-queue/SKILL.md +6 -8
  58. package/assets/skills/using-the-dashboard/SKILL.md +30 -0
  59. package/assets/skills/using-the-queue/SKILL.md +76 -5
  60. package/assets/skills/using-thrum-state/SKILL.md +84 -1
  61. package/assets/skills/verify-against-plan/SKILL.md +120 -5
  62. package/assets/skills/watcher-archetype/SKILL.md +18 -0
  63. package/assets/skills/writing-durable-markdown/SKILL.md +5 -0
  64. package/dist/installer.js +4 -4
  65. package/dist/permission-fixture.integration.test.js +4 -4
  66. package/package.json +1 -1
  67. package/assets/skills/coordinator-deploying-the-fleet/SKILL.md +0 -396
  68. package/dist/allowlist-sync.test.d.ts +0 -1
  69. package/dist/allowlist-sync.test.js +0 -44
@@ -1,396 +0,0 @@
1
- ---
2
- name: coordinator-deploying-the-fleet
3
- description: "Use when rolling a build across ALL boxes - fleet deploy, fleet roll, rolling the fleet, deploy everywhere, ship to every box, canary then the rest, promote the canary, fleet rollout, deploying after a merge batch. Orchestrates the staged multi-box order and the promotion gate between stages, and delegates each individual box to the coordinator-deploying-a-box runbook. Load this BEFORE dispatching any box."
4
- ---
5
-
6
- # Coordinator: Deploying a Build Across the WHOLE FLEET — staged order, canary, promotion gate
7
-
8
- **This skill is the ORCHESTRATION layer. It does not replace
9
- `coordinator-deploying-a-box` — it calls it, once per box.**
10
-
11
- - **This skill answers:** which box goes first, what must be true before the next
12
- one goes, who runs each one, and what is owed after the last one.
13
- - **`coordinator-deploying-a-box` answers:** how to deploy one box safely.
14
-
15
- > # 🔴🔴 LOAD `coordinator-deploying-a-box` FOR EVERY BOX — **INCLUDING THE BUILD BOX, YOUR OWN BOX. NO EXCEPTIONS.**
16
- >
17
- > **This is the #1 way this skill fails, and it has failed this way MORE THAN ONCE
18
- > (Leon, 2026-09-03).** You dispatch the per-box runbook to every REMOTE coordinator —
19
- > and then you drive your OWN box (the build box) by hand, improvising the install and restart,
20
- > because *"I'm the coordinator, I'll just do it."* **That improvisation IS the failure.**
21
- > The per-box runbook carries steps that are invisible until they bite:
22
- > - 🔴 **On macOS you MUST restart via `scripts/mac-daemon-restart-via-cron.sh`, NEVER a
23
- > bare `thrum daemon restart`** — a terminal/VSCode-parented daemon is silently DENIED
24
- > Local Network (TCC) permission and cannot dial LAN peers. A new binary is a new cdhash,
25
- > so the "Allow devices on the local network" prompt MUST re-fire and the operator MUST
26
- > click Allow. **A bare restart on the build box looks successful and leaves every LAN peer
27
- > bridge hanging "connecting" forever** (§6b-mac of the per-box runbook).
28
- > - the backup sizing formula, the SHA-CONTAINS-the-fix ancestry check, the by-effect
29
- > verification, the strand-gate, the phase reconcile — none of which you carry in your head.
30
- >
31
- > **⇒ THE MOMENT YOU REACH YOUR OWN BOX (or ANY box you drive yourself), STOP AND LOAD
32
- > `coordinator-deploying-a-box`.** Executing-directly (not dispatching) is the ONLY thing
33
- > that differs on your own box — you still LOAD IT AND FOLLOW IT, step for step. Reaching for
34
- > `thrum daemon restart`, `install.sh`, or `thrum backup` on your own box WITHOUT the per-box
35
- > runbook loaded is the red flag; if you're doing that, you have already skipped this.
36
-
37
- **Read both. Skipping the per-box runbook because you read this one — or because the box is
38
- your OWN and you'll "just do it" — is the failure this split is designed to prevent.**
39
-
40
- ---
41
-
42
- ## 0. THE ONE RULE THAT OUTRANKS EVERYTHING HERE
43
-
44
- 🔴 **NEVER ssh-deploy INTO a box. Each box's OWN coordinator drives its OWN
45
- deploy, LOCALLY.** (Leon, standing, binding.)
46
-
47
- A non-interactive ssh deploy silently drops node/pnpm from PATH, `make build-ui`
48
- exits 127, **the deploy CONTINUES and reports success**, and lands an UNBUILT
49
- SHA. The rule exists because the failure reports green.
50
-
51
- **So your job in a fleet roll is DISPATCH AND GATE, never execution — except on
52
- your own box, which you EXECUTE directly (not dispatch) but STILL under
53
- `coordinator-deploying-a-box`, loaded and followed step for step (see the 🔴🔴 block
54
- above).** "Except on your own box" means you run the commands yourself instead of
55
- dispatching them — it does NOT mean you improvise them from memory. If you find
56
- yourself typing `ssh` and `make install` in the same command, stop — and if you find
57
- yourself running `thrum daemon restart` / `install.sh` / `thrum backup` on your own box
58
- without having loaded the per-box runbook, stop there too.
59
-
60
- ⚠️ **CLARIFICATION — an `scp` of the signed pro-bundle zip into a box's
61
- `~/.thrum/update/` is DISTRIBUTION OF AN ARTIFACT, not a remote build or
62
- install, and does NOT violate this rule.** The rule forbids driving a box's
63
- build/install/restart FROM another machine. Copying an already-built,
64
- already-signed bundle to where that box's own coordinator will unzip and
65
- install it FROM is the sanctioned distribution mechanism (see §4) — the box
66
- still does its own unzip + `install.sh` + `thrum daemon restart`, locally, on
67
- its own account. Never conflate "the bytes moved over the network" with "the
68
- box was remote-driven."
69
-
70
- ---
71
-
72
- ## 1. THE ORDER — ONE fixed point, the rest composed per roll
73
-
74
- 🔴 **ONLY ONE THING IS A STANDING RULE: the designated canary (per
75
- `.fleet.deploy_roster`) goes FIRST, ALONE.**
76
-
77
- **EVERYTHING ELSE IS COMPOSED FRESH EACH TIME.** Recorded rolls disagree, and
78
- they disagree deliberately.
79
-
80
- 🔴 **"COMPOSED FRESH" IS THE ORDER, NOT THE MEMBERSHIP.** Every box in the roster
81
- MUST be rolled — the roster is the fleet. Compose the order per roll, never the
82
- set. Deferring a box requires the owner's explicit sign-off for that roll and a
83
- message to that box's coordinator that it is deferred.
84
-
85
- ⚠️ **DO NOT WRITE "THE BUILD BOX GOES LAST" INTO YOUR PLAN AS A RULE. IT IS A
86
- DEFAULT, AND IT HAS BEEN DELIBERATELY OVERRIDDEN** — on 2026-07-28 the build box
87
- went SECOND, because the owner needed the changes demonstrable at a meetup.
88
- **The owner sets the order; this skill sequences whatever order is set.**
89
-
90
- **Sensible default when nobody says otherwise:** the canary → the remaining
91
- boxes (parallel where they share no state) → the build box last.
92
- **Reason to keep the build box late:** it hosts the most agents and is the
93
- coordinator's own box, so a bad build there costs you the ability to drive the
94
- recovery. **That is a reason, not a law.**
95
-
96
- Each box's coordinator and deploy target are defined in `.thrum/config.json`
97
- under `.fleet.deploy_roster` — read them there; **never hardcode box
98
- identities in this skill.**
99
-
100
- 🔑 **This skill runs from the fleet orchestrator's own box (the build box); the
101
- roster lives in that box's config.** Resolve the box list and, for each box,
102
- its `ssh_alias` / `coordinator` / `daemon_id` from that same
103
- `.fleet.deploy_roster` entry:
104
-
105
- ```bash
106
- jq -r '.fleet.deploy_roster | keys[] | select(startswith("_") | not)' .thrum/config.json
107
- # box list — underscore-prefixed
108
- # keys (e.g. `_note`) are roster
109
- # metadata, not boxes; filter them
110
- jq '.fleet.deploy_roster["<box>"]' .thrum/config.json # one box's entry
111
- # (ssh_alias, coordinator, daemon_id)
112
- ```
113
-
114
- **Resolve coordinators by `daemon_id` JOIN, NEVER by name** — a name is a
115
- label somebody chose; an agent named for a box has been found resident on a
116
- different one. **The public-line box is EXCLUDED** (public 0.10.x line — see below).
117
-
118
- ⚠️ **At least one box's position in the roll is INCONSISTENT across rolls** —
119
- sometimes explicitly "in order", sometimes trailing. **Ask; do not assume.** It
120
- is always driven by its own coordinator, never dispatched around.
121
-
122
- ### PREP-AND-HOLD — the mechanism that makes a staged roll safe
123
- Dispatch several boxes at once to do **READ-ONLY prep only**: measure current
124
- state, take the backup, `git pull`, capture BEFORE readings. Then:
125
-
126
- > **"PREPARE AND HOLD. CANCEL NOTHING. DO NOT RESTART until I release you BY
127
- > NAME."**
128
-
129
- **Release one box at a time, by name.** This gets the slow prep done in parallel
130
- without any box mutating itself before the canary has reported.
131
-
132
- ⚠️ **The public-line box's coordinator runs the PUBLIC 0.10.x line and is
133
- EXCLUDED from 0.11 rolls.** Do not dispatch it.
134
-
135
- 🔴 **RESOLVE EVERY COORDINATOR FROM `thrum team` AT DISPATCH TIME, NOT FROM A
136
- CACHED NAME.** A name is a label somebody chose; the authoritative method is a
137
- `daemon_id` join — join the `daemon_id` from `.fleet.deploy_roster` against
138
- `thrum team`'s live roster, never match on name. An agent's NAME and its
139
- WORKTREE NAME are not its location — an agent named for a box has been found
140
- resident on a different one, and obeying that name would have driven the
141
- forbidden ssh path.
142
- ⚠️ **`thrum agent list --all` returns ZERO for agents that certainly exist** —
143
- do not use it as your roster instrument, and do not read its empty output as an
144
- absence.
145
-
146
- ---
147
-
148
- ## 2. THE PROMOTION GATE — what the canary must prove before anyone else goes
149
-
150
- **Do not release stages 2-5 on "the canary finished." Release them on these,
151
- each measured BY EFFECT on that box:**
152
-
153
- - Installed binary `--version` **equals the pin exactly** — not "close", not
154
- "the branch".
155
- - Daemon restarted with a **NEW PID**, and `daemon status` reports the pin.
156
- ⇒ **MERGED == INSTALLED == RUNNING, all three agreeing.**
157
- - Schema **before AND after** — proves migrating-vs-not by effect rather than
158
- from the delta.
159
- - UI built for real (module count, signed) — **not the exit-127 shape**.
160
- - Built from a **CLEAN detached worktree at the pin**, `git status --porcelain`
161
- empty ⇒ contamination closed BY CONSTRUCTION, not by inspection.
162
- - 🔑 **`sync_checkpoints` count BEFORE and AFTER, and they match.** A reset is
163
- the silent peer-stranding failure that has **no repair command**. **This check
164
- is worthless without a BEFORE reading** — take your own; do not skip it
165
- because the canary's was clean.
166
- - `crash-fatal.log` — 0 bytes, or read it before declaring success.
167
-
168
- ⚠️ **"CANARY GREEN" IS NOT "ALL THREE PASSES PROVEN."** The canary usually
169
- proves the BINARY pass only. Say which passes you verified, every time.
170
-
171
- 🔴 **THE CANARY RESULT DOES NOT TRANSFER. EVERY BOX IS MEASURED ON ITS OWN
172
- NUMBERS.** Measured instance: the canary came back green while another box
173
- had **207 matured tombstones over live agents**. The canary proves the BUILD is
174
- sound; it says nothing about any other box's state. **Do not let a green canary
175
- talk a box out of taking its own BEFORE readings.**
176
-
177
- ⏱ **GIVE A CONVERGENCE CHECK LONG ENOUGH TO DISCRIMINATE.** A short wait cannot
178
- tell "the fix works" from "it had already converged before you looked." One roll
179
- made a full 3-minute wait a hard requirement after a too-short check produced an
180
- uninterpretable pass. **Pick the interval from what the mechanism needs, not
181
- from impatience.**
182
-
183
- ### The go/no-go has never been a fixed number — get it from the owner
184
- The go/no-go has never been a fixed number. **State YOUR
185
- bar explicitly before promoting, so it can be challenged.** An unstated bar is
186
- indistinguishable from no bar.
187
-
188
- ---
189
-
190
- ## 3. THE THREE PASSES — a "deploy" silently conflates three independent things
191
-
192
- **Every box owes all three. They fail independently and a box can be current on
193
- one and month-stale on another.**
194
-
195
- | Pass | What it updates | Who runs it |
196
- |---|---|---|
197
- | 1. install the pre-built, Developer-ID-signed pro-bundle via its own `install.sh`, **then** `thrum daemon restart` | the **binary** | the box's own coordinator |
198
- | 2. `thrum roles refresh` **then** `thrum roles deploy` | **role preambles** | the box's own coordinator |
199
- | 3. plugin refresh / reinstall (installs the BUNDLE's plugins — already made correct/complete by the plugin-bundle spine — rather than the box's own checkout) | **skills, commands, hooks** | 🔴 **the OPERATOR (Leon)** — a coordinator CANNOT self-serve this |
200
-
201
- 🔴 **THE BOX NEVER BUILDS OR SIGNS ITS OWN BINARY. IT INSTALLS A DELIVERED
202
- ARTIFACT.** `make install` (build-from-local-checkout-and-sign) is retired from
203
- the Pass-1 fleet/box binary-update path. The box's job is: obtain the pro-bundle
204
- zip (see §4), unzip it, run its `install.sh`, restart the daemon. Nothing on the
205
- box compiles Go or codesigns.
206
-
207
- 🔴 **PASS 2 ORDER IS LOAD-BEARING AND NOT OBVIOUS:** `refresh` FIRST (shipped
208
- templates → role template), THEN `deploy` (role template → per-agent preamble).
209
- **Running `deploy` alone re-renders agents from the OLD templates and reports
210
- success.**
211
-
212
- 🔴 **RUN PASS 2 BETWEEN THE BUNDLE INSTALL AND THE DAEMON RESTART, ALWAYS:**
213
- bundle `install.sh` → `thrum roles refresh` → `thrum roles deploy` → daemon
214
- restart. Do the refresh+deploy the instant `install.sh` finishes and BEFORE
215
- restarting the daemon, so the current preamble is already in place when any
216
- agent next runs `thrum prime`, restarts, or checks its preamble.
217
-
218
- 🔴 **PASS 2 IS MANDATORY WHENEVER THE BUILD CHANGES
219
- `internal/context/roleconfig/templates/` OR the compiled-in preamble.**
220
- Preambles are RENDERED, not read live — installing a binary changes the source
221
- and changes **nothing any agent reads**. There is **no staleness signal**: a box
222
- serving a month-old render looks identical to a current one.
223
-
224
- 🔴 **PASS 3 REQUIRES A PLUGIN VERSION BUMP OR IT REACHES NOBODY.** The install
225
- cache is keyed by version; an unchanged version means the installer skips the
226
- re-copy and every skill edit stays invisible. Invoke the `plugin-update` skill;
227
- do not improvise it.
228
-
229
- ### 🔴 THE BOX'S **REPO CHECKOUT** MUST BE AT THE PIN BEFORE PASS 3 — THE BINARY BEING AT THE PIN IS NOT SUFFICIENT
230
-
231
- The plugin source is a **LOCAL PATH**, so pass 3 reads the checkout, not the
232
- binary — a box whose binary is at the pin can still install a plugin version
233
- one bump behind if its working tree lags.
234
-
235
- ⚠️ **AND THE BOX'S OWN VERIFICATION CANNOT SEE IT.** It compared installed
236
- skills against its own source, byte-for-byte, zero mismatches — and
237
- **both sides were the stale version.** That proves the box is internally
238
- consistent WITH ITSELF and is structurally incapable of detecting a stale
239
- source. Same shape as render-fidelity-vs-doctrine-currency for preambles, except
240
- **pass 3 has no equivalent of the ancestry check**, so nothing warns you.
241
-
242
- 🔴 **IT IS SELF-SEALING.** The cache is keyed by version, so at the stale version
243
- the installer sees a match and **skips the re-copy permanently.** Re-running the
244
- refresh does NOT fix it. Only bringing the checkout to the pin and re-running
245
- does.
246
-
247
- **⇒ Before handing a box to the operator for pass 3, verify BY EFFECT that all
248
- FOUR manifests read the pin's version.** A partial bump is worse than none — the
249
- runtimes then disagree about which build they are. And name the cache directory
250
- **explicitly** when verifying; never `ls | tail -1`, because version directories
251
- do not sort the way you expect (`…1.12` sorts BELOW `…1.3` and `…1.5`, and
252
- several versions coexist).
253
-
254
- **When reporting a deploy, name which passes you verified.** "Deployed" that
255
- silently covers all three is how staleness hides.
256
-
257
- ---
258
-
259
- ## 4. PRE-FLIGHT — before dispatching box 1
260
-
261
- 0. 🔴 **FIRST ACTION, NON-OPTIONAL: `git fetch origin` then fast-forward-only
262
- pull the repo root to `origin/<branch>`.** If the pull is not
263
- fast-forwardable, STOP and surface it — never blind-merge in a shared tree.
264
- Do this before reading any other pre-flight state below; a stale checkout
265
- makes every subsequent check (trunk-green, pin, schema) a check against the
266
- wrong tree.
267
- 1. **Is trunk green?** Trunk-green is defined as `make gate` passing. A green
268
- lane, a green package, or a clean dual review is **not** that claim.
269
- ⚠️ **Publishing a green without naming its invocation is the most common
270
- error here:** 0-of-6 and 0-of-15 are different claims in identical words.
271
- 2. **PIN AN EXACT SHA. NEVER "tip".** Tip moves under boxes mid-roll — two boxes
272
- told to deploy "tip" ship different code and both report success.
273
- 3. **Migrating or not?** Compare `CurrentVersion` at the pin against each box's
274
- running schema. A migrating roll pulls in the whole heavy-migration section
275
- of the per-box runbook; a non-migrating one does not.
276
- 4. **Forward-only:** each box's current SHA must be an ancestor of the pin.
277
- Pair the check with a control that MUST refuse (an invented SHA).
278
- 5. **Backup sizing** — the per-box runbook carries the formula. A percentage
279
- rule is not it, and a stale `current/` answers "are we protected?" with a
280
- wrong yes.
281
- 6. **Announce the pin and the order** to every coordinator before starting, so
282
- nobody deploys a different SHA.
283
- 7. 🔴 **BUILD ONCE, DISTRIBUTE THE SAME ZIP TO EVERY BOX.** The build box
284
- produces exactly ONE `make pro-bundle` at the pinned SHA. That
285
- single zip — never rebuilt per box — is what every box in the roll installs
286
- from. The staged roll therefore orchestrates three phases in order:
287
- **build once** (the build box, `make pro-bundle` at the pin) **→ distribute**
288
- (hand the same zip to each box's coordinator, via drop-folder or direct
289
- hand-off — see below) **→ each box installs from the delivered bundle**
290
- (§3 Pass 1). No box re-derives the artifact; every box's binary is
291
- byte-identical because it came from the same zip.
292
- - **Distribution mechanism 1 — drop-folder:** the built zip is placed where
293
- each box's own sync/fetch path picks it up into its local
294
- `~/.thrum/update/` for that box's coordinator to find and unzip.
295
- - **Distribution mechanism 2 — hand-off:** the coordinator running the roll
296
- hands the zip directly to a box's coordinator (e.g. `scp` into
297
- `~/.thrum/update/` — see §0's clarification: this is artifact
298
- distribution, not remote build/install).
299
- - Whichever mechanism is used, confirm the box installed from the SAME
300
- bundle (matching version/checksum) that was built at the pin — never a
301
- locally-reconstructed one.
302
- - 🔴 **NEVER pass `PRO_BUNDLE_VERSION=$(git describe)` when building the pro
303
- bundle — trust the Makefile default** (`v0.11.0-rc.1` as of this writing,
304
- re-check the current default before citing it). `git describe` falls back
305
- to the nearest ancestor tag when the exact commit isn't tagged, which can
306
- silently produce a WRONG version string — this is exactly how a stale
307
- `v0.10.6-rc.4` string previously shipped in a v0.11 build: the
308
- `v0.11.0-rc.1` tag was stranded on an unmerged release branch, so
309
- `git describe` fell back to an ancestor v0.10.6 tag.
310
-
311
- ---
312
-
313
- ## 5. DISPATCHING A BOX — what the message must carry
314
-
315
- 🔑 **Resolve the box's `ssh_alias` / `coordinator` / `daemon_id` from
316
- `.fleet.deploy_roster` in `.thrum/config.json` before dispatching** (see §1) —
317
- never from a remembered name. Join the coordinator by `daemon_id` against
318
- `thrum team`'s live roster, NEVER by name.
319
-
320
- Give each coordinator, explicitly:
321
-
322
- - **The exact pinned SHA** and the instruction to build from a clean detached
323
- worktree at it.
324
- - **An instruction to load `coordinator-deploying-a-box` and follow it** — not a
325
- summary of it. ⚠️ **A summary you wrote reads as complete precisely because
326
- you wrote it**, and its omissions are silent.
327
- - **Which passes it owes** (1 and 2; pass 3 is the operator's).
328
- - **Its own BEFORE readings** — `sync_checkpoints`, schema, PID.
329
- - **That acceptance is BY EFFECT, never by exit code.**
330
- - **That it must report which passes it verified**, separately.
331
-
332
- ---
333
-
334
- ## 6. AFTER THE LAST BOX
335
-
336
- - 🔴 **ROSTER COMPLETENESS GATE — first.** Re-enumerate the roster from config,
337
- not from propagated deploy-state (a box with a wedged sync never propagates its
338
- row). Confirm every box is rolled on the pin or owner-deferred with its
339
- coordinator notified. Name each box and its disposition in the roll-complete
340
- report.
341
- - **Agent phase reconciliation, per box.** A restart leaves recorded `phase`
342
- disagreeing with reality — live agents read `stale`, dead ones can read
343
- `active`. Phase is local state and does not cross the peer boundary, so every
344
- box runs it against its own daemon.
345
- - **Restart the orchestrators and long-lived agents** so they pick up the new
346
- preambles and skills. 🔴 **Agents adopt a new preamble ON THEIR NEXT RESTART,
347
- not immediately** — a live agent keeps behaving per the identity it was born
348
- with. Track adoption by session birth time, never file mtime.
349
- - **Pre-release test suite — CHECK WHETHER IT IS OWED; do not assume it is.**
350
- It is documented as a gate for cutting an RC/release, and a remote box runs it
351
- after fleet deploys by standing arrangement. ⚠️ **A search of past rolls did
352
- NOT establish it as a mandatory step for a routine binary/preamble/plugin
353
- roll.** Read the run index: if its newest entry predates this deploy, the suite
354
- has not run against what is now deployed — then decide, with the owner,
355
- whether this roll warrants one. **Stated as a gap rather than invented as a
356
- rule.**
357
- - **Update the deploy-state record** — per box: serving SHA, schema, AS OF, and
358
- how established. **Do not normalise every row to one SHA**; an unmeasured row
359
- is not a measured negative, and a tidied row is indistinguishable from a fresh
360
- measurement.
361
- - **Sweep for SHA-anchored rules that just went stale.** A superseded SHA has
362
- two roles that fail in OPPOSITE directions: as a GUARD it refuses legitimate
363
- work and presents as caution; as a TARGET it validates the wrong artifact and
364
- produces **a green that means nothing**. The target direction is the dangerous
365
- one and it is the one a guard-sweep misses. Prefer re-deriving the live build
366
- at run time over re-anchoring, which only re-arms the trap for the next roll.
367
-
368
- ---
369
-
370
- ## 7. HOLDS
371
-
372
- - **A fleet deploy is the OWNER'S CALL.** A lifted prohibition is not a
373
- permission.
374
- - **Any box may hold its own deploy** and must say why. A box-local blocker that
375
- does not touch the deploy path is not a reason to hold — check whether the
376
- runbook's acceptance checks actually need the thing that is broken.
377
- - **If the canary fails, the roll stops.** Do not promote on a partial result.
378
- - **Deploy often, in small batches.** A large roll bundles unrelated changes, so
379
- when a box misbehaves afterwards, attribution across a hundred commits gets
380
- expensive fast.
381
-
382
- ---
383
-
384
- ## Red flags — STOP
385
-
386
- - `ssh` and `make install` in the same command.
387
- - Dispatching a box by NAME without resolving its coordinator at dispatch time.
388
- - Promoting past the canary on "it finished" rather than on the §2 checks.
389
- - Reporting "deployed" without saying WHICH of the three passes was verified.
390
- - Running `roles deploy` without `roles refresh` first.
391
- - Expecting skill changes to reach agents without a plugin version bump.
392
- - Deploying "tip" instead of a pinned SHA.
393
- - Reading an installer's exit 0 as proof the binary landed.
394
- - Taking an `after` reading with no `before` — especially `sync_checkpoints`.
395
- - Summarising the per-box runbook into a dispatch instead of telling the
396
- coordinator to load it.
@@ -1 +0,0 @@
1
- export {};
@@ -1,44 +0,0 @@
1
- // Drift guard (allowlist round 2, finding 9): the canonical allowlist
2
- // (internal/permissions/thrum_allowlist.json, Go-owned) is copied verbatim
3
- // into this plugin's bundled asset (opencode-plugin/assets/thrum_allowlist.
4
- // json) by scripts/sync-skills.sh's sync_opencode step, since TypeScript
5
- // cannot import a Go package. This test fails LOUDLY the moment the two
6
- // diverge — a hand-edit of either copy, or a Go-side change that never got
7
- // synced.
8
- //
9
- // Deliberately compares RAW BYTES, not parsed-then-reserialized JSON: a
10
- // round-trip through JSON.parse/JSON.stringify would normalize whitespace,
11
- // key order, and formatting, and could mask a real drift (e.g. a
12
- // re-ordered or re-indented — but content-different — copy would compare
13
- // "equal" after reserialization while still being a byte-for-byte
14
- // divergence from the Go source of truth).
15
- import { test } from "node:test";
16
- import assert from "node:assert/strict";
17
- import fs from "fs";
18
- import path from "path";
19
- // This test file compiles to dist/allowlist-sync.test.js (flat src ->
20
- // flat dist, see tsconfig rootDir/outDir), so at runtime
21
- // path.dirname(import.meta.url) is opencode-plugin/dist. One level up is
22
- // opencode-plugin/ (matches the existing ALLOWLIST_ASSET_PATH convention in
23
- // installer.permissions.test.ts); two levels up is the repo root, from
24
- // which internal/permissions/ is reachable.
25
- const HERE = path.dirname(new URL(import.meta.url).pathname);
26
- const PLUGIN_ROOT = path.resolve(HERE, "..");
27
- const REPO_ROOT = path.resolve(PLUGIN_ROOT, "..");
28
- const OPENCODE_COPY = path.join(PLUGIN_ROOT, "assets", "thrum_allowlist.json");
29
- const CANONICAL_SOURCE = path.join(REPO_ROOT, "internal", "permissions", "thrum_allowlist.json");
30
- test("opencode-plugin/assets/thrum_allowlist.json is byte-identical to internal/permissions/thrum_allowlist.json", (t) => {
31
- // The canonical source only exists in the monorepo (github.com/leonletto/thrum);
32
- // the public standalone opencode-thrum-pro repo this test also ships in does not
33
- // carry internal/permissions/, so skip gracefully there instead of failing —
34
- // mirrors the binary-absent skip idiom in permission-fixture.integration.test.ts.
35
- if (!fs.existsSync(CANONICAL_SOURCE)) {
36
- t.skip(`canonical source not found at ${CANONICAL_SOURCE} — expected outside the monorepo (e.g. public opencode-thrum-pro), skipping drift check`);
37
- return;
38
- }
39
- assert.ok(fs.existsSync(OPENCODE_COPY), `synced copy not found at ${OPENCODE_COPY}`);
40
- const canonical = fs.readFileSync(CANONICAL_SOURCE);
41
- const copy = fs.readFileSync(OPENCODE_COPY);
42
- assert.ok(canonical.equals(copy), "opencode-plugin/assets/thrum_allowlist.json has drifted from internal/permissions/thrum_allowlist.json — " +
43
- "re-run scripts/sync-skills.sh (sync_opencode step) to resync, never hand-edit either copy independently");
44
- });