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
This commit is contained in:
2026-08-24 02:15:40 +00:00
co-authored by Claude Opus 5
parent b6d6de7e53
commit 2646842c32
2 changed files with 77 additions and 62 deletions
+76 -61
View File
@@ -1,10 +1,10 @@
# 2026.08.23.06 # 2026.08.23.06
A plugin's capabilities are read where they live instead of being copied into `capabilities/`, dev tools Swedish, a plugin's capabilities read where they live instead of being copied, dev tools as a property of a
become a property of a role rather than a hardcoded name, and Whisper learns 23 more languages. role, a commander as a directory rather than a session, and uploads that land atomically.
**There IS a schema change, and `bun db:push` cannot apply it.** Read "The schema change" below before **There are TWO schema changes and `bun db:push` can apply NEITHER of them.** Both stop and ask a question
running anything — it will stop and ask a question no terminal is there to answer, and then exit 0. 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 **There is also a one-off cleanup on three hosts**, and skipping it means the agent reads one affordance
twice. See "The orphan copies". twice. See "The orphan copies".
@@ -13,40 +13,37 @@ twice. See "The orphan copies".
## What is in it ## What is in it
- **A plugin's capabilities are LOADED, not copied.** Installing used to publish a plugin's affordances, - **Swedish.** `sv-SE`, complete across all three catalogues — six shipped locales now.
notifications and skills into `capabilities/` — a repository meant to be forked, so a fork accumulated - **Parity checks itself.** The three parity tests listed their locales by hand, so adding a sixth added a
files it did not author. They are now read from each installed and enabled plugin's own directory. A file none of them looked at; they read the directory now. They also check both directions — the old check
copy also went stale on `plugins:update` and survived `plugins:disable`; neither can happen now. only asked whether every English key was present, so a rename reported half of itself.
- **Notification codes moved from the path to the manifest.** They were derived from the copy's location, - **A plugin's capabilities are LOADED, not copied.** Affordances, notifications and skills are read from
`<publisher>/<appName>/file.yaml`. Read in place there is no such path, so every plugin code would have each installed and enabled plugin's own directory. `capabilities/` is meant to be forked and was
vanished silently. The identity comes from the manifest instead, which is validated at discovery. accumulating files nobody authored. A copy also went stale on update and survived a disable.
- **Roles phase 3.** `rolePermitsDevTools()` was `role === 'Developer'` — one line gating a Postgres login - **Roles phase 3.** `rolePermitsDevTools()` was `role === 'Developer'` gating a Postgres role and a
role with CREATEDB and a rootless Docker daemon. Two boolean columns carry it now, so a role created at rootless Docker daemon. Two boolean columns carry it now, so a role created at runtime can hold dev tools.
runtime can hold dev tools. `provisioning_script` is dropped rather than left dead. - **Installing a plugin grants it to every role.** Deny-by-default meant every install needed a second
- **Whisper priming prompts for 23 more languages**, Swedish included. The table had `en` and `pt`; every decision on another screen, which mostly got forgotten. The owner revokes instead. Rows, not a rule
other language got no prompt, which is why non-English transcription had nothing steering its change — absence still denies.
punctuation or diacritics. Inert on every host until an STT server is configured, which none is. - **A commander is a directory, not a session.** Its own `$DATA_PATH/<email>/commanders/<id>/` with a
- **The mailbox picker** in the email plugin, reaching the multi-account backend that shipped without a UI. `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 change ## The schema changes
Two columns added to `roles`, one dropped. Two tables. **Run this SQL first**, then `bun db:push` to confirm it took.
**`bun db:push` will not do this.** Dropping a column while adding another makes drizzle-kit ask whether **Why not `db:push`:** `roles` drops a column while adding two, and `command_hq_sessions` does the same
it is a rename: 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.
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.
```sql ```sql
-- Nothing ever wrote this column. Prove it rather than assume, before dropping it. -- ── roles: dev tools become a property of the role ──────────────────────────────────────────────
DO $$ DO $$
DECLARE used int; DECLARE used int;
BEGIN BEGIN
@@ -61,71 +58,89 @@ 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_postgres boolean NOT NULL DEFAULT false;
ALTER TABLE roles ADD COLUMN IF NOT EXISTS grants_docker 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; 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 '{}';
``` ```
Then verify **by query**: **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:
```sql ```sql
SELECT name, is_system, grants_postgres, grants_docker FROM roles ORDER BY name; 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;
``` ```
Four rows. **`Developer` must have both flags true and the other three both false** — that is exactly the **`Developer` must have both flags true and the other three both false.** If Developer comes back false the
behaviour `rolePermitsDevTools` had, preserved. If Developer comes back false, the seed did not run: fix seed did not run — fix it before restarting, or Developer accounts lose their Postgres role and Docker
it before restarting, or Developer accounts silently lose their Postgres role and Docker daemon on their daemon at their next provisioning. `provisioning_script` must be absent.
next provisioning.
`provisioning_script` must be absent from the column list. `command_hq_sessions` must show `dir_name` and `reference_paths`, with `session_id` nullable.
## The orphan copies ## The orphan copies
**`alpha`, `fofos` and `production-pertento-bridge` each have `capabilities/affordances/officerdev/gitea/` **`alpha`, `fofos` and `production-pertento-bridge` each have
— a copy published by the old mechanism.** The gitea plugin still ships that affordance in its own `capabilities/affordances/officerdev/gitea/`** — published by the mechanism this release removes. The gitea
directory, and affordances are deliberately NOT de-duplicated, so after this release both load and the plugin still ships that affordance in its own directory, and affordances are deliberately NOT
agent reads the same guidance twice. 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.
```bash ```bash
cd "$(dirname "$OFFICER_PLATFORM_DIR")/capabilities" # or the capabilities dir beside the checkout cd <the capabilities directory beside the checkout>
git status --porcelain # expect only ?? extensions/ afterwards git status --porcelain # expect only ?? extensions/ afterwards
rm -rf affordances/officerdev rm -rf affordances/officerdev
``` ```
Safe because it is a copy: the source is `plugins/gitea/affordances/` and that is what now gets read. 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.** It is written by the platform's own bootstrap every boot, not published by a **`extensions/` stays.** The platform's own bootstrap writes it every boot. This release gitignores it, and
plugin. This release gitignores it, and the ignore arrives with the capabilities pull in step 4. the ignore arrives with the capabilities pull in step 4.
## Every host ## Every host
1. `git fetch origin --tags` then `git merge --ff-only deploy/2026.08.23.06` 1. `git fetch origin --tags` then `git merge --ff-only deploy/2026.08.23.06`
2. **The SQL above**, then the verification query. Stop on the `RAISE EXCEPTION` and report it. 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. 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 4. `bun run plugins:update` — this pulls the capabilities repository too, which is how the new
`.gitignore` arrives. `.gitignore` arrives.
5. **The orphan removal above**, on the three hosts that have one. 5. **The orphan removal**, on the three hosts that have one.
6. `pm2 restart officer` 6. `pm2 restart officer`
`officer` only at step 6. Step 4 restarts each plugin's own sidecar, which belongs in the report. `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 **`macbookpro`:** append `/opt/homebrew/bin` to PATH for the `bun` commands, run `pm2` separately. It has
has `psql` at `/opt/homebrew/opt/libpq/bin/psql` and a `.zshenv` that puts it on a non-interactive PATH, `psql` now and a `.zshenv` that puts it on a non-interactive PATH, so the SQL can be run there directly.
so the SQL above can be run there directly rather than through Bun.
## What to check, this release ## 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 — **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 `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 less rather than an error. It should name the commander, deploy and platform skills, plus anything a plugin
ones, plus anything a plugin ships on that host. ships on that host. On a host with the gitea plugin, its affordance should appear **once**.
On a host with the gitea plugin, confirm the gitea affordance appears **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. No direct SQL beyond the block above is needed, and none is authorised.
## Hosts ## Hosts
- **`edge-pertento-bridge`** — skipped. Down, per `docs/servers.md`. - **`edge-pertento-bridge`** — skipped. Down, per `docs/servers.md`.
- **`officer-dev`** — skip it. The schema is already applied there and verified by query, its capabilities - **`officer-dev`** — skip it. Both schema changes are already applied and verified by query there, its
repository is already clean, and restarting `officer` on the host running the rollout ends the rollout. capabilities repository is clean, and restarting `officer` on the host running the rollout ends the
Name it as skipped and say it is current. rollout. Name it as skipped and say it is current.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "officer", "name": "officer",
"version": "2026.08.23.05", "version": "2026.08.23.06",
"private": true, "private": true,
"type": "module", "type": "module",
"workspaces": [ "workspaces": [