Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
121a77ce7f | ||
|
|
a7c9dab22c | ||
|
|
5005726994 |
@@ -0,0 +1,170 @@
|
|||||||
|
# 2026.08.26.01 — report
|
||||||
|
|
||||||
|
**5 of 5 target hosts succeeded.** Two hosts skipped by plan (`officer-dev`, `edge-pertento-bridge`);
|
||||||
|
neither is a failure. Every target ended at `e9f1760f` = `deploy/2026.08.26.01` exactly.
|
||||||
|
|
||||||
|
**No schema change in this release**, and that was verified rather than assumed — the diff between
|
||||||
|
`deploy/2026.08.25.01` and this tag touches no `schema.ts` and leaves `bun.lock` untouched. **No
|
||||||
|
`bun install` was run anywhere.** **No direct SQL was run on any host**; none was authorised.
|
||||||
|
|
||||||
|
First release under a new watcher, and the first the loop has ever executed. The claim was taken by the
|
||||||
|
watcher before this agent was spawned; this agent recorded `done` after the rollout.
|
||||||
|
|
||||||
|
## One finding the owner should see: `db:push` is not a no-op on any host
|
||||||
|
|
||||||
|
`bun db:push` exited 0 and printed `Changes applied` on all five. Per doctrine that is not evidence, so
|
||||||
|
each host was re-run with `--verbose` to see what push actually wanted. **Every host wants the same
|
||||||
|
statement, every single run:**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE "command_hq_sessions" ALTER COLUMN "reference_paths" SET DEFAULT '{}';
|
||||||
|
```
|
||||||
|
|
||||||
|
`alpha` additionally wants a drop-and-re-add of `pk_music_now_playing` (the music plugin's composite
|
||||||
|
primary key).
|
||||||
|
|
||||||
|
**This release did not cause it and it is not damage.** The reasoning is deductive rather than a guess:
|
||||||
|
this release changes zero `schema.ts` files, so drizzle's desired state is byte-identical before and
|
||||||
|
after the merge — anything push wants is pre-existing drift. It was verified by query that the database
|
||||||
|
is already in the state the schema code asks for:
|
||||||
|
|
||||||
|
| Host | `command_hq_sessions.reference_paths` default | `pk_music_now_playing` | `roles` |
|
||||||
|
| ---------------------------- | --------------------------------------------- | ---------------------- | ------- |
|
||||||
|
| `alpha` | `'{}'::text[]` — correct | present (`contype=p`) | 4 |
|
||||||
|
| `fofos` | `'{}'::text[]` — correct | n/a | 4 |
|
||||||
|
| `production-pertento-bridge` | `'{}'::text[]` — correct | n/a | 4 |
|
||||||
|
| `deadpool-rezio-bridge` | `'{}'::text[]` — correct | n/a | 4 |
|
||||||
|
| `macbookpro` | `'{}'::text[]` — correct | n/a | 4 |
|
||||||
|
|
||||||
|
So drizzle cannot match `'{}'` against the stored `'{}'::text[]` and re-emits the ALTER forever. It is
|
||||||
|
the array-default cousin of the named-composite-constraint mis-diff already documented in
|
||||||
|
`src/databases/CLAUDE.md` → "Composite keys", and `pk_music_now_playing` on `alpha` is that exact
|
||||||
|
documented case.
|
||||||
|
|
||||||
|
**Why it matters even though it is harmless today:** it permanently spends the signal. The plan said
|
||||||
|
"expect no changes to platform tables — if it wants to alter one, stop and report it", and that stop
|
||||||
|
condition can never fire cleanly while a platform table is on the standing-diff list. A future release
|
||||||
|
that really does want an unexpected ALTER will look exactly like tonight. Worth fixing at the schema
|
||||||
|
code, not on the hosts.
|
||||||
|
|
||||||
|
The rollout was not stopped for it. Non-destructive, pre-existing, and the databases are in the state
|
||||||
|
the code asks for.
|
||||||
|
|
||||||
|
`postdb:push` seeders ran clean everywhere: `seed:roles` 0 seeded / 4 already present on all five,
|
||||||
|
`backfill:session-models` 0 seeded on all five.
|
||||||
|
|
||||||
|
## The reason for this release — verified on `fofos`
|
||||||
|
|
||||||
|
The plan exists because `proxy-manager` imports `ChatHeader`, which did not exist before `2ed47fa6`, so
|
||||||
|
the plugin's frontend rebuild failed and the SPA silently kept its old bundle.
|
||||||
|
|
||||||
|
Verified after the restart, from the logs and the build output rather than by eye:
|
||||||
|
|
||||||
|
- `[plugins] frontend built in 7693ms (3 outputs)` at out-log line **1777 of 1808** — the current boot.
|
||||||
|
- The `No matching export … for import "ChatHeader"` line is in the error log **above** the last
|
||||||
|
`gen-index` / `server.tsx` boot markers, i.e. it belongs to the **previous** boot. No `rebuild failed`
|
||||||
|
after the successful build.
|
||||||
|
- `build/` was rewritten at 04:46:04, seconds after the restart, and `chunk-b7epbzfm.js` contains
|
||||||
|
`proxy-manager` 41 times.
|
||||||
|
|
||||||
|
`ChatHeader` itself greps to 0 in the bundle — expected, production bundling renames it. The absence of
|
||||||
|
the build error is the signal, not the presence of the symbol.
|
||||||
|
|
||||||
|
**The owner still needs a hard refresh (Ctrl/Cmd-Shift-R) before judging `/proxy-manager` in a browser**
|
||||||
|
that is holding the old bundle. Not checked from a browser by this agent.
|
||||||
|
|
||||||
|
## Per host
|
||||||
|
|
||||||
|
Steps in plan order everywhere: ff-merge to the tag → `bun db:push` → `bun run plugins:update` →
|
||||||
|
`pm2 restart officer`.
|
||||||
|
|
||||||
|
### `alpha` — success
|
||||||
|
|
||||||
|
- HEAD `007cc9a1` → `e9f1760f`. Tree clean, no untracked files.
|
||||||
|
- `db:push` exit 0. Standing diff as above; database verified correct by query.
|
||||||
|
- `plugins:update`: **14/14 updated in 59s**, exit 0. This bounced fourteen sidecars on its own, before
|
||||||
|
any restart the plan asked for — the report line below understates what actually moved.
|
||||||
|
- `pm2 restart officer`: restart_time 3 → 4, `online`, uptime 14s at check.
|
||||||
|
- All **21** PM2 entries `online`. `officer-claude-code` untouched (uptime 28h).
|
||||||
|
|
||||||
|
### `fofos` — success
|
||||||
|
|
||||||
|
- HEAD `007cc9a1` → `e9f1760f`. Tracked-clean. **One untracked file: `scripts/chk.ts`** — noted, not a
|
||||||
|
reason to skip, and not touched.
|
||||||
|
- `db:push` exit 0; `proxy_manager_connection`, `proxy_manager_dns_credentials`, `proxy_manager_domains`
|
||||||
|
all present, as the plan predicted for a proxy-manager host.
|
||||||
|
- `plugins:update`: 3/3 updated in 28s, exit 0 (browser, gitea, proxy-manager).
|
||||||
|
- `pm2 restart officer`: restart_time 29 → 30, `online`, uptime 26s at check.
|
||||||
|
- All **11** PM2 entries `online`. Frontend rebuild verified — see the section above.
|
||||||
|
- Note: this host's `.env` names the database `OFFICERDEV_POSTGRES_URL`, not `POSTGRES_URL` as on the
|
||||||
|
others. Cost one failed probe. Worth knowing for the next orchestrator that reads `.env` directly.
|
||||||
|
|
||||||
|
### `production-pertento-bridge` — success
|
||||||
|
|
||||||
|
- HEAD `007cc9a1` → `e9f1760f`. Clean, no untracked files.
|
||||||
|
- `db:push` exit 0; standing diff only; verified correct by query. 39 tables in `public`.
|
||||||
|
- `plugins:update`: 1/1 updated in 4s, exit 0 (gitea).
|
||||||
|
- `pm2 restart officer`: restart_time 8 → 9, `online`, uptime 22s at check.
|
||||||
|
- All **15** PM2 entries `online`. `officer-claude-code` untouched (uptime 76h).
|
||||||
|
- Every command on this host prints an nvm/`.npmrc` `globalconfig` warning. Pre-existing, cosmetic,
|
||||||
|
nothing failed because of it — but it is noise an orchestrator has to filter, and it will eventually
|
||||||
|
hide something.
|
||||||
|
|
||||||
|
### `deadpool-rezio-bridge` — success
|
||||||
|
|
||||||
|
- Login `br`, checkout `/home/br/officerdev/platform`, taken from PM2's `pm_cwd`.
|
||||||
|
- HEAD `007cc9a1` → `e9f1760f`. Clean, no untracked files.
|
||||||
|
- `db:push` exit 0; standing diff only; verified correct by query.
|
||||||
|
- `plugins:update`: 2/2 updated in 8s, exit 0 (email, offscale).
|
||||||
|
- `pm2 restart officer`: restart_time 14 → 15, `online`, uptime 22s at check.
|
||||||
|
- All **9** PM2 entries `online`.
|
||||||
|
|
||||||
|
### `macbookpro` — success (it was awake)
|
||||||
|
|
||||||
|
- Checkout `/Users/pastilhas/projects/officer-suite/platform`. `PATH` **appended** with
|
||||||
|
`/opt/homebrew/bin` for the `bun` commands; `pm2` run as a separate command, per `docs/servers.md`.
|
||||||
|
- HEAD `007cc9a1` → `e9f1760f`. Clean, no untracked files.
|
||||||
|
- `db:push` exit 0; standing diff only; verified correct by query.
|
||||||
|
- `plugins:update`: **no plugins installed — nothing to update**, exit 0.
|
||||||
|
- `pm2 restart officer`: restart_time 16 → 17, `online`, uptime 27s at check.
|
||||||
|
- All **6** PM2 entries `online`.
|
||||||
|
|
||||||
|
## Skipped
|
||||||
|
|
||||||
|
### `officer-dev` — skipped, by plan and by doctrine
|
||||||
|
|
||||||
|
Not pulled, nothing restarted. This agent runs inside `officer-claude-code` on this machine; restarting
|
||||||
|
`officer` here ends the rollout. It already carries this code — its HEAD was `e9f1760f` before the
|
||||||
|
rollout began.
|
||||||
|
|
||||||
|
**Its working tree holds another session's uncommitted work** — modified `src/servers/hono.ts` and
|
||||||
|
`src/workspaces/hooks/src/useClient.ts`, plus an untracked `src/servers/hono-error-shape.test.ts`. Left
|
||||||
|
alone. The report commit below names only its own path.
|
||||||
|
|
||||||
|
The owner restarts this host deliberately, as the last step, once happy with the rest.
|
||||||
|
|
||||||
|
### `edge-pertento-bridge` — skipped, by plan. Not contacted.
|
||||||
|
|
||||||
|
**And `docs/servers.md` disagrees with the plan about this host.** The doc's "Deliberately skipped"
|
||||||
|
section says "None today" and lists `edge-pertento-bridge` in the platforms table as "a deploy target
|
||||||
|
again". The plan says skip it: 300+ commits behind, **no `roles` table**, and a plain `db:push` there
|
||||||
|
would create a foreign key onto an empty table. Its catch-up is a by-hand job the owner is doing
|
||||||
|
separately.
|
||||||
|
|
||||||
|
The plan won — it is authoritative for a release, and it is the more recent statement. Nothing was sent
|
||||||
|
to that host, not even a reachability probe. **`docs/servers.md` should be corrected**, because the next
|
||||||
|
orchestrator that reads the doc rather than the plan will `db:push` a host that cannot take one. Not
|
||||||
|
fixed here: a rollout does not edit documentation.
|
||||||
|
|
||||||
|
## What was NOT done
|
||||||
|
|
||||||
|
- **`officer-claude-code` was not restarted on any host**, per the plan. The widened chat credential
|
||||||
|
(an API key follows its holder, minus `admin`) lives in that sidecar, so **it is not active anywhere
|
||||||
|
yet**. It is the owner's call when to take that — it ends every live agent session on the host,
|
||||||
|
including this one. **Pending on all five targets plus `officer-dev`.**
|
||||||
|
- **`officer-dev` was not deployed.** Skipped whole, per above.
|
||||||
|
- **The sonnet default was not confirmed from a UI on any host.** The plan names it as the easiest
|
||||||
|
visible check; this agent has no browser. It shipped in the code that is now at `e9f1760f` on all
|
||||||
|
five, unverified at the surface.
|
||||||
|
- **`/proxy-manager` was not opened in a browser.** Verified from the build and the logs instead.
|
||||||
|
- `bun run docs:check` was not run — not a rollout step, and this agent does not fix documentation.
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# 2026.08.26.02
|
||||||
|
|
||||||
|
**One fix, and it is a crash.** A screen whose layout names a panel it does not own now renders instead of
|
||||||
|
failing with React #185, "maximum update depth exceeded".
|
||||||
|
|
||||||
|
**No schema change. No dependency change. Do not run `bun install`.** `bun db:push` stays a step because it
|
||||||
|
is cheap and proves the database agrees with the code, but it must report no changes.
|
||||||
|
|
||||||
|
## What broke, and where it shows
|
||||||
|
|
||||||
|
`useAuthoredLayout` shipped in `2026.08.26.01`: a locked screen persists its SIZES and takes its STRUCTURE
|
||||||
|
from source on every render. That is still right, and it is what lets a plugin change its layout and reach
|
||||||
|
people who have already opened it.
|
||||||
|
|
||||||
|
What it did not account for is `WorkspaceView`'s `appTypes` guard, which repairs an unrecognised `appType`
|
||||||
|
and **writes the repair back**. That was self-limiting while structure was persisted — the repair stuck and
|
||||||
|
the next render agreed with it. With structure coming from source it can never stick, so it is recomputed,
|
||||||
|
rewritten, discarded and recomputed every render until React gives up.
|
||||||
|
|
||||||
|
**The `browser` plugin's layout names `officerdev/chat`**, which is not one of its own panel keys. So
|
||||||
|
`/browser` fails to render on any host with that plugin installed. That is every host running
|
||||||
|
`2026.08.26.01` with it — which is the fleet.
|
||||||
|
|
||||||
|
The fix drops a write whose result equals the current value. Not an optimisation: it is what terminates the
|
||||||
|
loop, and persisting an identical tree was pointless anyway.
|
||||||
|
|
||||||
|
## Every host
|
||||||
|
|
||||||
|
1. `git fetch origin --tags` then `git merge --ff-only deploy/2026.08.26.02`
|
||||||
|
2. `bun db:push` — expect **no changes**. If it wants to alter something, stop and report it.
|
||||||
|
3. `bun run plugins:update`
|
||||||
|
4. `pm2 restart officer`
|
||||||
|
|
||||||
|
**`fofos` is already at the fix** — it was fast-forwarded by hand at 05:22 to `a7c9dab2` while the owner was
|
||||||
|
working on it. Step 1 will fast-forward it to this tag anyway, which only adds the version bump and this
|
||||||
|
plan. Nothing about that is a problem; note it rather than treating it as drift.
|
||||||
|
|
||||||
|
**`macbookpro`:** append `/opt/homebrew/bin` to PATH for the `bun` commands, run `pm2` separately. Asleep is
|
||||||
|
a skip with a note.
|
||||||
|
|
||||||
|
**Do not restart `officer-claude-code`.** Unchanged from the last release, same reason: it ends every live
|
||||||
|
agent session on the host, and it is the owner's call. Nothing in this release needs it.
|
||||||
|
|
||||||
|
## Hosts
|
||||||
|
|
||||||
|
- **`officer-dev`** — skip it. Restarting `officer` there ends the rollout. It already has this code.
|
||||||
|
- **`edge-pertento-bridge`** — skipped. Still 300+ commits behind with no `roles` table; its catch-up is a
|
||||||
|
by-hand job. Do not contact it.
|
||||||
|
- **`alpha`**, **`fofos`**, **`production-pertento-bridge`**, **`deadpool-rezio-bridge`** — the targets.
|
||||||
|
- **`macbookpro`** — a target if it is awake.
|
||||||
|
|
||||||
|
## What to check
|
||||||
|
|
||||||
|
**Open `/browser`.** It should render rather than showing "This screen failed to render". That is the whole
|
||||||
|
release, and it is one click to confirm. Hard refresh first — a stale bundle looks identical to the bug.
|
||||||
|
|
||||||
|
No direct SQL is needed anywhere, and none is authorised.
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "officer",
|
"name": "officer",
|
||||||
"version": "2026.08.26.01",
|
"version": "2026.08.26.02",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@@ -96,3 +96,34 @@ describe('applySizes', () => {
|
|||||||
expect(JSON.stringify(source)).toBe(before);
|
expect(JSON.stringify(source)).toBe(before);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The loop that broke `/browser`.
|
||||||
|
*
|
||||||
|
* `WorkspaceView` repairs an appType it does not recognise and writes the repair back. With the structure
|
||||||
|
* coming from source, that repair can never persist — so unless an identical write is dropped, it is
|
||||||
|
* recomputed and rewritten every render until React gives up (#185).
|
||||||
|
*/
|
||||||
|
describe('a repair that cannot stick must not be written', () => {
|
||||||
|
test('grafting a repaired structure yields exactly the current value', () => {
|
||||||
|
// What the guard produces: the same tree with a disallowed appType swapped for the fallback.
|
||||||
|
const repaired = group('root', [
|
||||||
|
{
|
||||||
|
node: group('lists', [
|
||||||
|
{ node: panel('hosts', 'proxy-manager-hosts'), size: 62 },
|
||||||
|
{ node: panel('certs', 'proxy-manager-certificates'), size: 38 },
|
||||||
|
]),
|
||||||
|
size: 66,
|
||||||
|
},
|
||||||
|
// `chat` replaced by the fallback — the substitution the allow-list makes every render.
|
||||||
|
{ node: panel('chat', 'proxy-manager-hosts'), size: 34 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const current = applySizes(source, collectSizes(source));
|
||||||
|
const afterRepair = applySizes(source, collectSizes(repaired));
|
||||||
|
|
||||||
|
// Identical, because only sizes survive and the repair changed none. `setValue` compares these and
|
||||||
|
// returns without writing; if it ever writes, the render loop is back.
|
||||||
|
expect(JSON.stringify(afterRepair)).toBe(JSON.stringify(current));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -93,7 +93,27 @@ export function useAuthoredLayout(key: string, source: LayoutNode) {
|
|||||||
const setValue = useCallback(
|
const setValue = useCallback(
|
||||||
(update: LayoutNode | ((prev: LayoutNode) => LayoutNode)) => {
|
(update: LayoutNode | ((prev: LayoutNode) => LayoutNode)) => {
|
||||||
const next = typeof update === 'function' ? update(value) : update;
|
const next = typeof update === 'function' ? update(value) : update;
|
||||||
stored.setValue(applySizes(source, collectSizes(next)));
|
const merged = applySizes(source, collectSizes(next));
|
||||||
|
|
||||||
|
/*
|
||||||
|
A write that changes nothing is dropped, and this is not an optimisation — it is what stops an
|
||||||
|
infinite render loop.
|
||||||
|
|
||||||
|
`WorkspaceView`'s `appTypes` guard repairs an unrecognised appType and WRITES THE REPAIR BACK. That
|
||||||
|
was self-limiting while the structure was persisted: the repair stuck and the next render agreed
|
||||||
|
with it. Here the structure always comes from `source`, so the repair can never stick — it is
|
||||||
|
recomputed, rewritten, discarded and recomputed, every render, forever. React gives up with
|
||||||
|
"maximum update depth exceeded" (#185) and the screen fails to render.
|
||||||
|
|
||||||
|
Not hypothetical: the `browser` plugin's layout names `officerdev/chat`, which is not one of its own
|
||||||
|
panel keys, so every render of `/browser` produced a repair. Shipping the sizes-only hook turned a
|
||||||
|
silent, harmless fallback into a hard crash on that screen.
|
||||||
|
|
||||||
|
Comparing the serialised value is enough — these trees are small, this runs only on a real change,
|
||||||
|
and it is the same check `setValue` would otherwise force React Query to make against a new object.
|
||||||
|
*/
|
||||||
|
if (JSON.stringify(merged) === JSON.stringify(value)) return;
|
||||||
|
stored.setValue(merged);
|
||||||
},
|
},
|
||||||
// `stored.setValue` is stable per key; `value` is memoised above.
|
// `stored.setValue` is stable per key; `value` is memoised above.
|
||||||
[source, value, stored],
|
[source, value, stored],
|
||||||
|
|||||||
Reference in New Issue
Block a user