# 2026.08.29.02 — a plugin can ship a commander **This one changes the database.** Two columns, both nullable additions, on a table that already exists. There is no rename in it, nothing is dropped, and no existing row changes — but `db:push` is still not to be trusted on its exit code, so there is a per-host query below and the report must carry its answer for each host. No dependency change: `package.json` and `bun.lock` are untouched since `2026.08.29.01`. **There is no `bun install` step in this plan** — which also sidesteps the step-order problem the last rollout found, where the standard `plugins:update` ran before the plan's install. ## What is in it - **Plugins can ship commanders.** A plugin with `commanders//COMMANDER.md` offers one; it appears in Command HQ under a new **From plugins** section, arrives **not running**, and must be configured before it can arm. - **av-club ships the first one** — *Convert on arrival*: watches a folder, converts what lands in it. It reaches each host through the standard `bun run plugins:update` step, not through this plan. - Marketplace, file browser and Convert Image work from earlier today rides along. ## Schema — CHANGED. `db:push` is required. ``` command_hq_sessions + plugin text null which plugin's template made this commander + plugin_config jsonb null the owner's answers to that template's inputs ``` Verified as the whole of it: ```bash git diff --name-only deploy/2026.08.29.01..deploy/2026.08.29.02 | grep -iE 'schema|/db/|databases' # → src/databases/officer_db/src/command-hq/queries.ts # src/databases/officer_db/src/command-hq/schema.ts ``` `queries.ts` is code, not schema. The only structural change is the two columns above. **Both are nullable with no default and no constraint**, so every existing row is valid the moment they exist and there is nothing to backfill. This is the shape `db:push` handles cleanly — there is no create-then-constrain here, and no `--force` is needed or permitted. ## Every host — the commands After the standard steps in `prompts/deploy-commander.md` (dirty check, `git fetch origin --tags`, `git merge --ff-only deploy/2026.08.29.02`, `bun run plugins:update`), in the checkout from PM2's `pm_cwd`: ### 1. Back the database up first ```bash pg_dump "$(grep -m1 -E '^(OFFICERDEV_)?POSTGRES_URL=' .env | cut -d= -f2-)" > ~/db-backups/officer-$(date +%Y%m%d-%H%M).sql ``` Read the variable name rather than assuming it: **several hosts use `OFFICERDEV_POSTGRES_URL`**, not `POSTGRES_URL`. `mkdir -p ~/db-backups` first if it is not there. ### 2. Push the schema ```bash bun db:push < /dev/null ``` **stdin closed**, so it can never sit waiting on a prompt in an unattended run — it fails instead, which is the outcome you want. Expect `[✓] Changes applied` and then the two post-push scripts. ### 3. VERIFY BY QUERY. The exit code is not the answer. ```bash bun -e " const { db } = await import('officerdb/db'); const { sql } = await import('drizzle-orm'); const r = await db.execute(sql\`select column_name from information_schema.columns where table_name='command_hq_sessions' and column_name in ('plugin','plugin_config') order by column_name\`); console.log([...((r as any).rows ?? r)].map((x:any)=>x.column_name).join(' ')); process.exit(0)" ``` Must print exactly: ``` plugin plugin_config ``` **Put that line in the report, per host.** Not "schema ✓" — the output. A host printing one column, or none, is a **failed host**: report it, move on, and do not attempt a repair you invented. ### 4. Restart ```bash pm2 restart officer ``` `officer` only. `plugins:update` has already restarted the plugin sidecars itself. ### 5. Check it came back `pm2 jlist`: `officer` online and `restart_time` moved, then `/api/health` on the host's own port (9010 on `macbookpro` and `production-pertento-bridge`, 9000 elsewhere). `200` or `401` are both healthy. ## Per host | Host | Note | | --- | --- | | `officer-dev` | **SKIP ENTIRELY.** The rollout runs here. Its schema is already pushed; nothing else is needed. Name it as skipped. | | `alpha` | Nothing special. | | `fofos` | The one that matters this time — `av-club` is installed there and the commander template is the point of the release. | | `macbookpro` | macOS, port **9010**, a laptop. Unreachable is a SKIP with a note. | | `macbruno` | macOS, login `brunoalcadapadezmonteirodaconceicao`. | | `production-pertento-bridge` | Port **9010**. | | `edge-pertento-bridge` | Nothing special. | | `deadpool-rezio-bridge` | Login **`br`**, checkout `/home/br/officerdev/platform`. | ## What "done" looks like Per host: HEAD before and after, whether the backup was written, `db:push` result, **the exact output of the verification query**, and `officer` back online with a moved `restart_time`. The line that matters in the summary: **which hosts print `plugin plugin_config`**. That is the only thing this release actually changes about a machine.