Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91147b403b |
@@ -0,0 +1,78 @@
|
||||
# 2026.08.27.02
|
||||
|
||||
Everything in `2026.08.27.01`, which was **refused before any host was contacted** — its plan named no
|
||||
commands and claimed "no schema change", which was false. This plan replaces it. `2026.08.27.01` stays
|
||||
claimed and undeployed; do not try to revive it.
|
||||
|
||||
Plus one fix made because of that refusal: a single 401 no longer signs the owner out of every tab.
|
||||
|
||||
## The schema question, answered per host
|
||||
|
||||
`officer_db/src/dav/schema.ts` was **deleted** — `dav_app_passwords` moved into `plugins/caldav`.
|
||||
`gen-plugin-schemas` follows plugin DIRECTORIES, so the table is only declared on a host that has
|
||||
`plugins/caldav/`. Everywhere else `bun db:push` will propose **dropping** it.
|
||||
|
||||
Surveyed 2026-08-28, every host:
|
||||
|
||||
| Host | `plugins/caldav/` | `dav_app_passwords` |
|
||||
| --- | --- | --- |
|
||||
| `alpha` | present | **8 rows** |
|
||||
| `fofos` | absent | present, 0 rows |
|
||||
| `macbookpro` | absent | present, not read (bun at `/opt/homebrew/bin`) |
|
||||
| `macbruno` | absent | present, 0 rows |
|
||||
| `production-pertento-bridge` | absent | present, 0 rows |
|
||||
| `edge-pertento-bridge` | absent | present, 0 rows |
|
||||
| `deadpool-rezio-bridge` | absent | not read (ssh refused at survey time) |
|
||||
|
||||
So: **alpha pushes normally.** Everywhere else the table is a leftover of a feature that has left core,
|
||||
and it is empty — but it is dropped **explicitly, with a guard**, not as a `db:push` side effect.
|
||||
|
||||
`officer_marketplace` and its new `tags` column: **no host runs `officer-marketplace`** — checked, zero
|
||||
of seven. Nothing to apply. If that changes, the column has no push script and must be added by hand.
|
||||
|
||||
## Every host
|
||||
|
||||
1. `git fetch origin --tags` then `git merge --ff-only deploy/2026.08.27.02`
|
||||
2. **Only if `plugins/caldav/` is ABSENT on this host** — retire the orphan table, refusing to if it
|
||||
holds anything:
|
||||
```
|
||||
psql "$POSTGRES_URL" -Atc "select count(*) from dav_app_passwords"
|
||||
# 0 → drop it: psql "$POSTGRES_URL" -c "drop table dav_app_passwords"
|
||||
# anything else → STOP. Report the count and do not continue on this host.
|
||||
```
|
||||
Read the variable name from `.env` — several hosts use `OFFICERDEV_POSTGRES_URL`.
|
||||
3. `bun db:push < /dev/null` — **stdin closed on purpose**, so a push needing an answer fails instead of
|
||||
hanging. Expect **no changes** after step 2. Anything destructive: stop and report.
|
||||
4. `bun run plugins:update`
|
||||
5. `pm2 restart officer`
|
||||
6. `pm2 restart officer-opencode` — this release rewires how OpenCode gets its skills, its agent identity
|
||||
and its credential, and all of that is built when the sidecar boots. Without this the code ships and
|
||||
nothing about OpenCode changes.
|
||||
|
||||
**`alpha`:** skip step 2 entirely — it has the plugin and 8 real credentials in that table. Step 3 runs
|
||||
normally and must report no changes.
|
||||
|
||||
**`macbookpro`:** prepend `/opt/homebrew/bin` to PATH for `bun`; run `pm2` separately. Asleep is a skip
|
||||
with a note.
|
||||
|
||||
**`deadpool-rezio-bridge`:** login is `br`, checkout `/home/br/officerdev/platform`. SSH refused once
|
||||
during the survey — if it refuses again, note it and move on rather than retrying in a loop.
|
||||
|
||||
**Do not restart `officer-claude-code`.** It ends every live agent session on the host, nothing here needs
|
||||
it, and it is the owner's call.
|
||||
|
||||
## Risk
|
||||
|
||||
Moderate, and concentrated in step 2 — the only destructive command in this release. It is guarded by a
|
||||
count and it is skipped on the one host where the table has contents.
|
||||
|
||||
After the rollout, two things are worth a look: `useThemeAsset` now reads in `requestAnimationFrame`, so
|
||||
a themed logo or wallpaper failing to appear points here; and the notify router's mount order changed, so
|
||||
device registration is worth one check (it forwards correctly on `officer-dev`).
|
||||
|
||||
## Hosts
|
||||
|
||||
alpha · fofos · macbookpro · macbruno · production-pertento-bridge · edge-pertento-bridge ·
|
||||
deadpool-rezio-bridge
|
||||
|
||||
`officer-dev` is skipped: it is the host running the rollout.
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "officer",
|
||||
"version": "2026.08.27.01",
|
||||
"version": "2026.08.27.02",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
|
||||
@@ -206,6 +206,45 @@ const handleRevokedSession = (url: string) => {
|
||||
if (revocationHandled) return;
|
||||
revocationHandled = true;
|
||||
|
||||
/*
|
||||
Guard 2b: CONFIRM the session is dead before destroying it.
|
||||
|
||||
A 401 from any one endpoint used to be enough to wipe the token out of both storages and reload —
|
||||
which signs the owner out of every tab, and keeps them signed out, because there is then no token
|
||||
anywhere to recover from. That is the right response to a revoked session and far too violent a
|
||||
response to one endpoint answering 401 for its own reasons.
|
||||
|
||||
Reported 2026-08-27, mid-demo: every refresh and every new tab came back signed out, with nothing in
|
||||
the logs and a freshly signed token working fine against every route the app loads. One transient
|
||||
401 is indistinguishable from a revocation at the call site — so ask the one endpoint whose whole
|
||||
job is to answer it.
|
||||
|
||||
`/auth/me` is exempt from this handler (the `/auth/` guard above), so this cannot recurse.
|
||||
*/
|
||||
void (async () => {
|
||||
try {
|
||||
// Derived from the URL that just failed, because the base is passed per call rather than held in
|
||||
// config — a relative guess would be wrong on any host that mounts the API elsewhere.
|
||||
const probe = /\/api\//.test(url) ? url.replace(/\/api\/.*$/, '/api/auth/me') : '/api/auth/me';
|
||||
const res = await fetch(probe, { headers: { Authorization: `Bearer ${theToken}` } });
|
||||
// Still good: that 401 belonged to one endpoint, not to the session. Leave everything alone and
|
||||
// let the next revocation try again.
|
||||
if (res.ok) {
|
||||
revocationHandled = false;
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// The server is unreachable — a restart, a dropped network. Emphatically not a revocation, and
|
||||
// wiping the session because the box was rebooting is the failure this guard exists to stop.
|
||||
revocationHandled = false;
|
||||
return;
|
||||
}
|
||||
destroySession();
|
||||
})();
|
||||
};
|
||||
|
||||
/** Actually end it: forget the token everywhere a load could read it back, then reload once. */
|
||||
const destroySession = () => {
|
||||
theToken = null;
|
||||
// Every key `createClient` reads, or the next load signs straight back in with the dead token.
|
||||
for (const key of ['PERTENTO_EDITOR_AUTH_TOKEN', 'BEARER_TOKEN']) {
|
||||
|
||||
Reference in New Issue
Block a user