Fixes #997 ## Summary Give `ateapi` separate PostgreSQL connections for schema ownership and normal application access. Migrations and outbox partition maintenance use the owner connection, allowing application reads and writes to run with restricted permissions. - Each connection assumes its configured role with `SET ROLE`, keeping object ownership and grants separate from login credentials. - Configure owner and read/write connections through `ate-setup` and the installation manifests. - Default the schema to `substrate` and document provisioning for external PostgreSQL and Cloud SQL, including one IAM login with membership in both roles. - Keep bundled PostgreSQL using its existing `postgres` account and role for both pools. Restricted runtime access requires operator-provisioned roles. See `docs/postgres.md` for configuration details. ## Scope This is the base PR for two separate follow-ups: [file-based credentials](https://github.com/agent-substrate/substrate/pull/1979) and [automatic development database bootstrap](https://github.com/agent-substrate/substrate/pull/2025). Neither feature is included here. Operators provision external database roles, login memberships, schema ownership, and grants before startup. Table migrations still run automatically through the owner connection. The documented external database layout assumes a fresh database; this PR does not migrate existing table ownership or grants. Both connections remain inside `ateapi`, so this separates database permissions without isolating owner credentials from the application process. ## Validation - `env -u NO_COLOR make verify` passed. - PostgreSQL integration tests cover restricted runtime access, owner migrations and partition maintenance, and OpenFGA table permissions. - Installer tests cover bundled PostgreSQL, separate external logins, and Cloud SQL configuration.
4.5 KiB
PostgreSQL schema evolution
ateapi applies PostgreSQL migrations before it becomes ready. During a rolling update, the previous binary continues to serve requests while the new binary changes the schema.
Goose commits one migration at a time. A failed migration run can leave any completed migration prefix in place. These rules keep the previous binary safe with every migration prefix.
Compatibility contract
- Keep every migration prefix compatible with reads and writes from the previous binary.
- Treat every migration file boundary as a durable database state.
- Do not depend on a later migration to make an earlier prefix compatible.
- Let the new binary migrate the schema from the previous release.
- Make the new binary wait for all pending migrations before readiness.
- Use explicit column lists in
SELECTandINSERTstatements.
Schema change rules
Prefer additive changes. Add a table, column, or index before the new binary uses it.
Add a column as nullable or give it a compatible database default. Do not make previous binary inserts fail because they omit a new column.
Do not remove or rename a table or column that the previous binary uses. Do not change its type or meaning in an incompatible way.
Do not tighten a constraint while the previous binary can write a value that violates it. Do not remove a default that the previous binary requires.
If two schema structures hold the same data, keep them consistent while both binaries can write. Backfill existing rows before the new binary requires the new structure.
Do not run a large data backfill during startup. Propose a separate migration process before you add such a change.
Keep the actors table partitionable
To preserve the option to partition the actors table by atespace or by name, and every other table with an atespace column by atespace, do not add a schema change or a query that introduces:
- A unique index or constraint on one of these tables that omits
atespace, or onactorsthat omitsname. - A foreign key that references one of these tables by columns that omit
atespace, oractorsby anything other than(atespace, name). - A query on one of these tables that does not filter on
atespace, or a query onactorsthat does not also filter onname. A statement whose result spans every partition by definition, such as a global list, must be listed inTestActorsTablePartitionable. Anything else that reads more than one partition needs an exemption there, agreed with the community.
Expand and contract
Use an expand and contract sequence for a schema replacement or removal:
- In release N, add the new structure and keep the old structure.
- In release N+1, stop using the old structure.
- In release N+2, remove the old structure.
This sequence keeps the previous binary compatible during a rollout and a temporary binary rollback. A binary rollback does not roll back the schema.
Migration file rules
Store migration files in cmd/ateapi/internal/store/atepg/migrations.
Standalone Substrate defaults to the substrate schema, including under Kagent's umbrella chart. Set postgres.schema in Helm or ATE_API_POSTGRES_SCHEMA in local setup to use another schema.
- Use the next sequential
NNNNNN_name.sqlfilename. - Add exactly one
-- +goose Upannotation. - Use SQL migrations only.
- Do not add down migrations.
- Do not use
NO TRANSACTIONorENVSUB. - Do not add SQL transaction control statements.
- Do not use
IF NOT EXISTSfor a schema change. - Keep each startup migration short.
- A database administrator must configure the owner role's default table and sequence privileges for the read/write role before migrations. These grants do not go to
PUBLICor Kagent's roles. In this first pass, migration ledgers receive the same table privileges as other objects.
Before the first stable v1 release, developers can change or squash migration files. Recreate a development database after its migration history changes.
After the first stable v1 release, do not change or delete a released migration file. Add a new migration to correct it.
Do not edit the schema_migrations ledger manually.
Before you submit
- Identify the previous binary reads and writes that use the changed objects.
- Check those operations against every new migration prefix.
- Use expand and contract when one prefix would break an operation.
- Add or update a test for the schema behavior.
- Run the migration verifier and PostgreSQL store tests.
hack/verify/postgresql-migrations.sh
go test ./cmd/ateapi/internal/store/atepg