Carries the SQL, because bun db:push cannot apply this one and will not say so: dropping provisioning_script while adding two columns makes drizzle-kit ask whether it is a rename, and with no terminal it bails while the wrapper exits 0. Tested against a scratch database — applies, re-applies cleanly once the column is already gone, and refuses to drop a provisioning_script anything actually wrote. Also carries a one-off cleanup that no plan format covers. alpha, fofos and production-pertento-bridge each hold capabilities/affordances/officerdev/gitea/, a copy from the mechanism this release removes. The gitea plugin still ships that affordance in its own directory and affordances are deliberately not de-duplicated, so without the removal the agent reads the same guidance twice. The verification step is a chat rather than a query, because the failure mode of the capability refactor is an agent that quietly knows less rather than an error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
6.5 KiB
2026.08.23.06
A plugin's capabilities are read where they live instead of being copied into capabilities/, dev tools
become a property of a role rather than a hardcoded name, and Whisper learns 23 more languages.
There IS a schema change, and bun db:push cannot apply it. Read "The schema change" below before
running anything — it will stop and ask a question no terminal is there to answer, and then exit 0.
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
- A plugin's capabilities are LOADED, not copied. Installing used to publish a plugin's affordances,
notifications and skills into
capabilities/— a repository meant to be forked, so a fork accumulated files it did not author. They are now read from each installed and enabled plugin's own directory. A copy also went stale onplugins:updateand survivedplugins:disable; neither can happen now. - Notification codes moved from the path to the manifest. They were derived from the copy's location,
<publisher>/<appName>/file.yaml. Read in place there is no such path, so every plugin code would have vanished silently. The identity comes from the manifest instead, which is validated at discovery. - Roles phase 3.
rolePermitsDevTools()wasrole === 'Developer'— one line gating a Postgres login role with CREATEDB and a rootless Docker daemon. Two boolean columns carry it now, so a role created at runtime can hold dev tools.provisioning_scriptis dropped rather than left dead. - Whisper priming prompts for 23 more languages, Swedish included. The table had
enandpt; every other language got no prompt, which is why non-English transcription had nothing steering its punctuation or diacritics. Inert on every host until an STT server is configured, which none is. - The mailbox picker in the email plugin, reaching the multi-account backend that shipped without a UI.
The schema change
Two columns added to roles, one dropped.
bun db:push will not do this. Dropping a column while adding another makes drizzle-kit ask whether
it is a rename:
Is grants_postgres column in roles table created or renamed from another column?
With no terminal it bails, the wrapper still exits 0, and the postdb:push seed then fails against an
unchanged schema — reporting a missing column several steps from the thing that actually stopped. Verified
on officer-dev on 2026-08-23. See src/databases/CLAUDE.md.
Run this first. It is idempotent and safe to run twice.
-- Nothing ever wrote this column. Prove it rather than assume, before dropping it.
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;
Then verify by query:
SELECT name, is_system, grants_postgres, grants_docker FROM roles ORDER BY name;
Four rows. Developer must have both flags true and the other three both false — that is exactly the
behaviour rolePermitsDevTools had, preserved. If Developer comes back false, the seed did not run: fix
it before restarting, or Developer accounts silently lose their Postgres role and Docker daemon on their
next provisioning.
provisioning_script must be absent from the column list.
The orphan copies
alpha, fofos and production-pertento-bridge each have capabilities/affordances/officerdev/gitea/
— a copy published by the old mechanism. 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.
Remove the copy on those three hosts. deadpool-rezio-bridge does not have one; check anyway.
cd "$(dirname "$OFFICER_PLATFORM_DIR")/capabilities" # or the capabilities dir 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/ and that is what now gets read.
extensions/ stays. It is written by the platform's own bootstrap every boot, not published by a
plugin. This release gitignores it, and the ignore arrives with the capabilities pull in step 4.
Every host
git fetch origin --tagsthengit merge --ff-only deploy/2026.08.23.06- The SQL above, then the verification query. Stop on the
RAISE EXCEPTIONand report it. bun db:push— expect no changes and no prompt. If it asks anything, the SQL did not take.bun run plugins:update— this pulls the capabilities repository too, which is how the new.gitignorearrives.- The orphan removal above, on the three hosts that have one.
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 now
has psql at /opt/homebrew/opt/libpq/bin/psql and a .zshenv that puts it on a non-interactive PATH,
so the SQL above can be run there directly rather than through Bun.
What to check, this release
Open a chat and confirm the agent still has its skills. 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. Ask it what skills it has; it should name the commander, deploy and platform
ones, plus anything a plugin ships on that host.
On a host with the gitea plugin, confirm the gitea affordance appears once.
No direct SQL beyond the block above is needed, and none is authorised.
Hosts
edge-pertento-bridge— skipped. Down, perdocs/servers.md.officer-dev— skip it. The schema is already applied there and verified by query, its capabilities repository is already clean, and restartingofficeron the host running the rollout ends the rollout. Name it as skipped and say it is current.