Commit Graph
3 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 b9524ee4d5 prefix the env with OFFICERDEV_, and keep reading the bare names
PORT, PUBLIC_URL and POSTGRES_URL are names half the software in the world
uses. Bun auto-loads .env into EVERY process started in the platform directory,
so a bare PORT reaches the sidecars, the tools they spawn and anything an agent
runs from here — and a bare POSTGRES_URL reaches things with no business
holding a database credential.

Four parts.

officer-setup now writes OFFICERDEV_-prefixed names, with the reasoning in the
generated file so whoever opens it later knows why.

Every consumer reads `OFFICERDEV_X ?? X` — eleven sites across the databases,
the DAV router, officer-url.mjs, os-user-postgres and two scripts. The bare
name is the FALLBACK, never the source, which is what makes this safe to ship
before every machine has been converted.

`bun run fix:env` converts a file in place. Idempotent, preserves comments and
`export`, leaves the value untouched including an `=` inside it, and refuses to
touch names belonging to other software — prefixing PATH or NODE_ENV does not
rename a variable, it deletes it.

And it runs on every restart from the server settings screen. There rather than
at boot because a restart is the one moment the file can be rewritten and take
effect in the same breath: the process about to start is the one that reads it.
Non-fatal, since the fallback means the worst case is that nothing changed.

gen-index reads either spelling out of the file directly, because it is run by
hand as often as by the setup and a bare shell has no .env loaded.

Verified by converting this machine's own .env: PORT, PUBLIC_URL and the
database all still resolve, gen:index still finds its URL, 1152 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:46:00 +00:00
pastilhas abb8fe4320 the migration script stops needing the app it migrates
It died on its first real use, on a production server, before running a single
statement:

  error: Cannot find module './plugin-schemas.gen' from officer_db/src/schema.ts

It imported `officerdb/db`, which imports `schema.ts`, which imports the
gitignored `plugin-schemas.gen.ts`. That file does not exist on a fresh clone —
which is exactly the state every machine this script is FOR is in. It had been
tested against a scratch database on a machine where the barrel happened to
exist, so the one condition that mattered was the one condition never tested.

A migration issues ALTER statements. It has no business needing the
application's schema barrel, its table objects or its query layer. It now opens
its own `postgres` connection and imports none of them.

Tested the way it failed: barrel moved out of the tree, script run against a
scratch database built in the old shape. Renames the table, the column, both
indexes and all three CHECK constraints, keeps the rows, and the idempotent
path still no-ops. Verified by reading pg_indexes and pg_constraint afterwards
rather than trusting the exit code.

Deployed to edge-pertento today with two real users. Nine grants migrated
intact, old table gone, permissions page confirmed in the browser. The only
casualty was a minute lost to this bug, because the database had not been
touched when it failed — the import blew up before the first query, which is
the one place a crash costs nothing.

`bun db:push` was already immune: it runs scripts/gen-plugin-schemas.ts first.
That fix existed because the same trap was found earlier today in the setup
path. It was not applied here because I did not think of this script as
something that runs on a fresh clone, which is precisely what it is.
2026-08-15 17:03:05 +00:00
pastilhas 9c2d6a97f7 a tested migration script for the capabilities→permissions rename
Other machines have to make the same database change, and pasted SQL is the
wrong way to ship it. `scripts/rename-capabilities-to-permissions.ts` renames
the table, its column, both indexes and its three CHECK constraints in one
transaction, and refuses to guess:

  neither table        nothing to do; db:push will create it
  already renamed      no-op, prints the grant count
  BOTH tables present  stops and says a human must decide
  old table only       migrates, counts before and after, prints every grant

Testing it found a bug that reading it had not. The existence checks were
inside the transaction but ran on the pool rather than on `tx`, so they could
not see the uncommitted rename: `columnExists('role_permissions','capability')`
answered false because that table did not exist yet on that connection, and the
COLUMN rename was silently skipped. The result was a `role_permissions` table
with a `capability` column — half migrated, and only failing on the next query.

That was found by building a scratch database in edge-pertento's exact shape
and running the script against it, rather than by review. Every check now
happens before the transaction, against the old names.

Verified end to end on that scratch: 6 grants migrated intact, a second run
correctly no-ops, and `db:push` afterwards leaves role_permissions and its rows
alone while creating the rest of the schema. Indexes come out as
role_permissions_pkey and uq_role_permissions_role_permission.

Also swept the last all-caps survivors the case-sensitive passes missed:
CORE_CAPABILITIES → CORE_PERMISSIONS, and a react-query key still spelling
['ROLE_CAPABILITIES']. The only CAPABILITIES left in src/ is the wallet's, which
is Lightning and stays.
2026-08-15 16:40:18 +00:00