Files
platform/deploy/2026.08.23.06.md
pastilhasandClaude Opus 5 2646842c32 release 2026.08.23.06
Swedish, plugin capabilities read where they live, dev tools as a property of a
role, a commander as a directory, and atomic uploads.

TWO schema changes and bun db:push can apply neither. Both drop a column while
adding others, which is the shape drizzle-kit cannot tell from a rename — it
asks, bails with no terminal to answer, and the wrapper still exits 0. The plan
carries both blocks, guarded and idempotent, tested against a scratch database:
they apply, re-apply cleanly once already applied, and the roles guard still
refuses a host where anything wrote a provisioning_script.

The command_hq block DELETES every commander rather than migrating one. That is
the owner's call and the plan says to tell people what was removed and put it in
the report — a commander is somebody's standing job, not a row.

Also carries a cleanup no plan format covers: three hosts hold a copy of gitea's
affordance published by the mechanism this release removes, and affordances are
deliberately not de-duplicated, so without the removal the agent reads the same
guidance twice.

The verification step is a conversation rather than a query. The capability
refactor's failure mode is an agent that quietly knows less, which no schema
check can see.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-24 02:15:40 +00:00

7.8 KiB

2026.08.23.06

Swedish, a plugin's capabilities read where they live instead of being copied, dev tools as a property of a role, a commander as a directory rather than a session, and uploads that land atomically.

There are TWO schema changes and bun db:push can apply NEITHER of them. Both stop and ask a question no terminal is there to answer, and then exit 0. Read "The schema changes" before running anything.

There is also a one-off cleanup on three hosts, and skipping it means the agent reads one affordance twice. See "The orphan copies".

No dependency change. bun.lock is untouched. Do not run bun install.

What is in it

  • Swedish. sv-SE, complete across all three catalogues — six shipped locales now.
  • Parity checks itself. The three parity tests listed their locales by hand, so adding a sixth added a file none of them looked at; they read the directory now. They also check both directions — the old check only asked whether every English key was present, so a rename reported half of itself.
  • A plugin's capabilities are LOADED, not copied. Affordances, notifications and skills are read from each installed and enabled plugin's own directory. capabilities/ is meant to be forked and was accumulating files nobody authored. A copy also went stale on update and survived a disable.
  • Roles phase 3. rolePermitsDevTools() was role === 'Developer' gating a Postgres role and a rootless Docker daemon. Two boolean columns carry it now, so a role created at runtime can hold dev tools.
  • Installing a plugin grants it to every role. Deny-by-default meant every install needed a second decision on another screen, which mostly got forgotten. The owner revokes instead. Rows, not a rule change — absence still denies.
  • A commander is a directory, not a session. Its own $DATA_PATH/<email>/commanders/<id>/ with a CLAUDE.md written at creation. commander-block.ts is gone.
  • Uploads land atomically — written aside and renamed, so a watcher never sees a half-written file. Same for SendOff.
  • Whisper priming prompts for 23 more languages. Inert until an STT server is configured; none is.

The schema changes

Two tables. Run this SQL first, then bun db:push to confirm it took.

Why not db:push: roles drops a column while adding two, and command_hq_sessions does the same shape — drizzle-kit cannot tell a drop-plus-add from a rename, so it asks. With no terminal it bails, the wrapper still exits 0, and the postdb:push seed then fails against an unchanged schema. Verified on officer-dev twice. See src/databases/CLAUDE.md.

Both blocks are idempotent and safe to run twice.

-- ── roles: dev tools become a property of the role ──────────────────────────────────────────────
DO $$
DECLARE used int;
BEGIN
  SELECT count(*) INTO used FROM roles WHERE provisioning_script IS NOT NULL;
  IF used > 0 THEN
    RAISE EXCEPTION 'STOP: % role(s) have a provisioning_script. Do not continue; report this.', used;
  END IF;
EXCEPTION WHEN undefined_column THEN
  NULL;  -- already dropped by a previous run
END $$;

ALTER TABLE roles ADD COLUMN IF NOT EXISTS grants_postgres boolean NOT NULL DEFAULT false;
ALTER TABLE roles ADD COLUMN IF NOT EXISTS grants_docker   boolean NOT NULL DEFAULT false;
ALTER TABLE roles DROP COLUMN IF EXISTS provisioning_script;

-- ── command_hq_sessions: the row owns its identity, the transcript id becomes a pointer ──────────
-- Every existing commander is DELETED rather than migrated. This is deliberate and the owner's call:
-- a commander's identity, directory and CLAUDE.md are all new, so migrating means writing a path that
-- runs once and never again. Re-arm afterwards; it also exercises the flow this release exists to fix.
DELETE FROM command_hq_sessions;

ALTER TABLE command_hq_sessions ALTER COLUMN session_id DROP NOT NULL;
ALTER TABLE command_hq_sessions ADD COLUMN IF NOT EXISTS dir_name text NOT NULL DEFAULT '';
ALTER TABLE command_hq_sessions ALTER COLUMN dir_name DROP DEFAULT;
ALTER TABLE command_hq_sessions ADD COLUMN IF NOT EXISTS reference_paths text[] NOT NULL DEFAULT '{}';

Tell the owner what you deleted before running it, and put it in the report — a commander is somebody's standing job, and "we removed yours" is not a footnote. On officer-dev it was one, Platform Deployer, already deleted there.

Then verify by query, not by an exit code:

SELECT name, is_system, grants_postgres, grants_docker FROM roles ORDER BY name;

SELECT column_name, is_nullable FROM information_schema.columns
WHERE table_name = 'command_hq_sessions' ORDER BY column_name;

Developer must have both flags true and the other three both false. If Developer comes back false the seed did not run — fix it before restarting, or Developer accounts lose their Postgres role and Docker daemon at their next provisioning. provisioning_script must be absent.

command_hq_sessions must show dir_name and reference_paths, with session_id nullable.

The orphan copies

alpha, fofos and production-pertento-bridge each have capabilities/affordances/officerdev/gitea/ — published by the mechanism this release removes. The gitea plugin still ships that affordance in its own directory, and affordances are deliberately NOT de-duplicated, so after this release both load and the agent reads the same guidance twice.

cd <the capabilities directory beside the checkout>
git status --porcelain          # expect only ?? extensions/ afterwards
rm -rf affordances/officerdev

Safe because it is a copy: the source is plugins/gitea/affordances/, which is what now gets read. deadpool-rezio-bridge has none; check anyway.

extensions/ stays. The platform's own bootstrap writes it every boot. This release gitignores it, and the ignore arrives with the capabilities pull in step 4.

Every host

  1. git fetch origin --tags then git merge --ff-only deploy/2026.08.23.06
  2. The SQL above, both blocks, then the two verification queries. Stop on the RAISE EXCEPTION.
  3. bun db:push — expect no changes and no prompt. If it asks anything, the SQL did not take.
  4. bun run plugins:update — this pulls the capabilities repository too, which is how the new .gitignore arrives.
  5. The orphan removal, on the three hosts that have one.
  6. pm2 restart officer

officer only at step 6. Step 4 restarts each plugin's own sidecar, which belongs in the report.

macbookpro: append /opt/homebrew/bin to PATH for the bun commands, run pm2 separately. It has psql now and a .zshenv that puts it on a non-interactive PATH, so the SQL can be run there directly.

What to check, this release

Open a chat and ask the agent what skills it has. The preamble is built from a new code path — capability-sources.ts resolving roots per session — and the failure mode is an agent that quietly knows less rather than an error. It should name the commander, deploy and platform skills, plus anything a plugin ships on that host. On a host with the gitea plugin, its affordance should appear once.

The language picker should offer six. Switch to Swedish and look at a panel, not just the shell.

Creating a commander should produce a directory. $DATA_PATH/<email>/commanders/<n>/ holding CLAUDE.md and state/. The setup pane asks for a reference path now, not a working directory.

No direct SQL beyond the block above is needed, and none is authorised.

Hosts

  • edge-pertento-bridge — skipped. Down, per docs/servers.md.
  • officer-dev — skip it. Both schema changes are already applied and verified by query there, its capabilities repository is clean, and restarting officer on the host running the rollout ends the rollout. Name it as skipped and say it is current.