opencode-thrum-pro 0.1.5 → 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.
- package/assets/commands/thrum-_compact-runtime-commands.md +61 -0
- package/assets/commands/thrum-_snapshot-protocol.md +17 -6
- package/assets/commands/thrum-_stamp-protocol.md +9 -2
- package/assets/commands/thrum-compact-extended.md +352 -0
- package/assets/commands/thrum-compact.md +300 -0
- package/assets/commands/thrum-inbox.md +1 -1
- package/assets/commands/thrum-restart-extended.md +84 -11
- package/assets/commands/thrum-restart.md +105 -13
- package/assets/commands/thrum-send.md +4 -1
- package/assets/commands/thrum-sleep-extended.md +67 -46
- package/assets/commands/thrum-sleep.md +67 -58
- package/assets/skills/adversarial-critique/SKILL.md +5 -8
- package/assets/skills/brainstorm-steward/SKILL.md +65 -11
- package/assets/skills/brainstormer-staying-current/SKILL.md +1 -4
- package/assets/skills/choosing-subagent-models/SKILL.md +14 -0
- package/assets/skills/conversation-only-context/SKILL.md +11 -11
- package/assets/skills/conversation-only-context/resources/driver-subagent-pattern.md +1 -1
- package/assets/skills/conversation-only-context/resources/remote-environment-scenarios.md +1 -1
- package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +98 -6
- package/assets/skills/coordinator-context-monitoring/SKILL.md +29 -19
- package/assets/skills/coordinator-deploying-a-node/SKILL.md +124 -0
- package/assets/skills/coordinator-dispatching-work/SKILL.md +24 -11
- package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +62 -20
- package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +6 -6
- package/assets/skills/coordinator-merging-code/SKILL.md +25 -11
- package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +28 -7
- package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +3 -3
- package/assets/skills/coordinator-rolling-out-to-nodes/SKILL.md +92 -0
- package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +3 -3
- package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +21 -20
- package/assets/skills/coordinator-running-review-cycles/SKILL.md +15 -4
- package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +0 -2
- package/assets/skills/efficient-multi-agent-research/SKILL.md +3 -3
- package/assets/skills/implementer-orchestrating-execution/SKILL.md +11 -14
- package/assets/skills/orchestrate/SKILL.md +47 -35
- package/assets/skills/persistent-watcher-archetype/SKILL.md +172 -25
- package/assets/skills/persistent-watcher-archetype/resources/capture_archive.py +618 -0
- package/assets/skills/persistent-watcher-archetype/resources/jev_watcher_filter.py +837 -0
- package/assets/skills/persistent-watcher-archetype/resources/roster-watch.sh +1140 -0
- package/assets/skills/persistent-watcher-archetype/resources/roster-watch_test.sh +291 -0
- package/assets/skills/persistent-watcher-archetype/resources/thrum-capture-fallback.sh +2 -2
- package/assets/skills/persistent-watcher-archetype/resources/thrum-watch-pane-capture.sh +20 -8
- package/assets/skills/prime-agent/SKILL.md +10 -0
- package/assets/skills/project-setup/SKILL.md +22 -16
- package/assets/skills/project-setup/resources/implementation-agent.md +56 -35
- package/assets/skills/researcher-investigating/SKILL.md +2 -5
- package/assets/skills/thrum/SKILL.md +14 -7
- package/assets/skills/thrum/resources/ANTI_PATTERNS.md +16 -18
- package/assets/skills/thrum/resources/CLI_REFERENCE.md +17 -11
- package/assets/skills/thrum/resources/LISTENER_PATTERN.md +9 -3
- package/assets/skills/thrum/resources/MESSAGING.md +18 -11
- package/assets/skills/thrum/resources/WORKTREES.md +1 -1
- package/assets/skills/thrum-memory/SKILL.md +1 -1
- package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +1 -1
- package/assets/skills/update-queue/SKILL.md +6 -8
- package/assets/skills/using-the-dashboard/SKILL.md +30 -0
- package/assets/skills/using-the-queue/SKILL.md +76 -5
- package/assets/skills/using-thrum-state/SKILL.md +83 -0
- package/assets/skills/verify-against-plan/SKILL.md +120 -5
- package/assets/skills/watcher-archetype/SKILL.md +18 -0
- package/assets/skills/writing-durable-markdown/SKILL.md +5 -0
- package/dist/installer.js +4 -4
- package/dist/permission-fixture.integration.test.js +4 -4
- package/package.json +1 -1
- package/assets/skills/coordinator-deploying-the-fleet/SKILL.md +0 -396
- package/dist/allowlist-sync.test.d.ts +0 -1
- 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
|
-
});
|