96 commits. commanders become usable, agents get a credential that can be bounded, and a nudge finally arrives. one schema change, additive: two nullable arrays on api_keys, both null on every existing key including the mobile apps'. no dependency change. edge-pertento-bridge is excluded and the plan says why at length, because it is the part somebody will otherwise get wrong. it came back 288 commits behind with no roles table and two accounts holding 'Super Admin' and 'Developer' — verified on the host. current schema makes users.role a foreign key onto roles.name, and the seeder runs from postdb:push, AFTER the push. so one db:push would create that constraint against an empty roles table. every other host got the table seeded by 08.23.04 and the constraint by 08.23.05, in that order, which is why nobody else hits this. it also missed two plans carrying one-off steps a fast-forward does not replay. bringing it forward is by hand and watched, straight after this rollout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
deploy/
One file per release, and one report per release.
deploy/
├── 2026.08.21.01.md the plan — what to run, in order, on which hosts
└── 2026.08.21.01.report.md what actually happened, written by the commander
The plan is written by hand before the release commit. The report is written by the deploy commander afterwards and pushed back here.
A release is a tag, deploy/<version>, and nothing else triggers a deploy — not a commit message,
not a branch. A tag cannot be written into prose by accident, which a code word can and which every
document about deploying does.
What marks a release deployed is LOCAL: $OFFICER_ROOT/data/deploys/<version>.done. Not the report.
If the lock were the pushed report, a failed push would mean the release looked undeployed and the next
tick would roll it out again — and pushes here fail for unrelated reasons. So the report is an artefact,
valuable and not load-bearing.
See prompts/deploy-commander.md.