feat(workflow-executor): allow custom DB schema via DATABASE_SCHEMA env var#1754
Conversation
…nv var The standalone CLI was locked to the `forest` schema: it read no schema env var and never set `database.schema`, so resolveSchema always fell back to DEFAULT_SCHEMA. Add a DATABASE_SCHEMA env var (mirroring DATABASE_SSL) so the CLI can target another schema; blank/unset keeps the `forest` default. Library consumers could already pass database.schema — this closes the CLI gap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…up log Replace the bare 'forest' literal in logStartup with the existing DEFAULT_SCHEMA constant, removing the duplicated default, and drop the redundant what/where comment on the DATABASE_SCHEMA read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Coverage Impact ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (2)
🛟 Help
|
|
Nice — clean change and the 1. Validate
|
… privilege The schema now flows into raw DDL, so validate it as a Postgres identifier at the boundary (fail fast with ConfigurationError instead of a cryptic SQL error), and reject values past the 63-char limit Postgres would silently truncate. Document in the README that boot runs CREATE SCHEMA IF NOT EXISTS, which needs CREATE on the database even when the schema already exists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Both addressed in f5efb65. 1. Validation — 2. README — documented. I verified the privilege behavior empirically on PG 15 rather than trust my assumption: with a role lacking Worth noting this requirement is not new to this PR: the boot-time |
…xists Postgres checks the database-level CREATE privilege for CREATE SCHEMA IF NOT EXISTS even when the schema is present, so an operator who pre-creates the schema still needed database-level CREATE just to boot. Probe pg_namespace first (world-readable, no privilege) inside the migration lock and skip the CREATE when the schema exists, so a DB user with only schema-level CREATE on a pre-created schema can run the executor. Document both grant paths in the README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scra3
left a comment
There was a problem hiding this comment.
Automated review (code / tests / comments). No blocking issues — the change is well-constructed: SQL injection is closed at the CLI boundary, the pg_namespace probe is correct and stays inside the advisory lock, and comments are why-only. Inline notes below are all non-blocking.
Two findings couldn't be anchored to diff lines:
src/build-workflow-executor.ts:313—new DatabaseStore({ schema: mergedOptions.schema })forwards the schema to raw DDL unvalidated; only the CLI path validates. Defense-in-depth (operator-supplied config, same trust asuri), not exploitable — worth centralizing validation inDatabaseStore/runMigrationsif you want the programmatic API guarded too.src/stores/schema-migrations.ts:65— pre-existing doc comment still says theforestschema is created; it's now configurable, reword to "the configured schema (defaultforest)".
| sequelize.query(`CREATE SCHEMA IF NOT EXISTS "${schema}"`, { transaction }), | ||
| ); | ||
| await withMigrationLock(sequelize, async transaction => { | ||
| if (!(await schemaExists(sequelize, schema, transaction))) { |
There was a problem hiding this comment.
praise: clean probe-then-skip — gating CREATE SCHEMA on a world-readable pg_namespace lookup so a pre-created schema boots without database-level CREATE is the right call, and keeping it inside the lock preserves serialization.
There was a problem hiding this comment.
Also hardened the programmatic path in the same commit: the identifier check moved to isValidPostgresIdentifier and is now enforced in resolveSchema, so buildDatabaseExecutor({ database: { schema } }) is validated too, not only the CLI env boundary.
| }, | ||
| ); | ||
|
|
||
| it('rejects a DATABASE_SCHEMA longer than 63 characters', () => { |
There was a problem hiding this comment.
suggestion: only the 64-char rejection is tested; add a case asserting a 63-char identifier is accepted so a >→>= regression on the boundary gets caught.
There was a problem hiding this comment.
Fixed in f249390 — added accepts a DATABASE_SCHEMA of exactly 63 characters asserting the value round-trips, so a >→>= regression on the boundary now fails a test.
| ); | ||
| }); | ||
|
|
||
| it('probes pg_namespace for the schema before creating it', async () => { |
There was a problem hiding this comment.
suggestion: the ordering test doesn't record the probe, so moving it outside the lock — breaking the documented serialization — would still pass; add probe to the recorded calls sequence.
There was a problem hiding this comment.
Fixed in f249390 — the ordering mock now records the pg_namespace probe and the assertion is ['guard','lock','probe','schema','guard','lock','migrate'], so moving the probe outside the lock breaks the test.
| ); | ||
| }); | ||
|
|
||
| it('skips CREATE SCHEMA when the schema already exists', async () => { |
There was a problem hiding this comment.
nit: this asserts CREATE SCHEMA was skipped but not that the probe ran, so a silent no-op would pass too — assert the pg_namespace probe was called to make the skip causally explicit.
There was a problem hiding this comment.
Fixed in f249390 — the skip test now also asserts the pg_namespace probe was called, making the skip causally explicit (not a silent no-op).
…API too Address review findings on PR #1754: - centralize the Postgres-identifier check in `isValidPostgresIdentifier` and enforce it in `resolveSchema`, so `buildDatabaseExecutor({ database: { schema } })` is guarded, not only the CLI env boundary - test the 63-char boundary is accepted (only the 64-char rejection was covered) - record the pg_namespace probe in the migration-ordering assertion so moving it outside the lock is caught - assert the probe ran in the skip-CREATE test (skip is caused by the probe, not a no-op) - fix stale comments that still described the schema as fixed `forest` Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# @forestadmin/workflow-executor [1.19.0](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/workflow-executor@1.18.0...@forestadmin/workflow-executor@1.19.0) (2026-07-21) ### Features * **workflow-executor:** allow custom DB schema via DATABASE_SCHEMA env var ([#1754](#1754)) ([5114744](5114744))

Summary
The standalone workflow-executor CLI was locked to the Postgres
forestschema. It read no schema env var and never setdatabase.schema, soresolveSchemaalways fell back toDEFAULT_SCHEMA. Library consumers could already passdatabase.schema— this closes the CLI gap.Changes
cli-core.ts: newDATABASE_SCHEMAenv var (mirrorsDATABASE_SSL) — read intoCliConfig, injected into thedatabaseoptions, surfaced in--helpand the startup log. Blank/unset resolves to theforestdefault.buildDatabase, and the startup-log schema.DATABASE_SCHEMAin the Database connection table + note.No store/build changes: the downstream plumbing and
forestfallback already existed.Test plan
yarn workspace @forestadmin/workflow-executor test -- test/cli.test.ts(108 passed)yarn workspace @forestadmin/workflow-executor lint(0 errors)yarn workspace @forestadmin/workflow-executor buildFixes PRD-773
🤖 Generated with Claude Code
Note
Add
DATABASE_SCHEMAenv var support to workflow-executor for custom Postgres schemaDATABASE_SCHEMAenvironment variable to cli-core.ts that sets the Postgres schema used by the executor, defaulting to"forest".ConfigurationErroron startup.DatabaseExecutorOptionswhen set, and includes it in structured startup logs.CREATEprivilege on the database at boot time due toCREATE SCHEMA IF NOT EXISTS.Changes since #1754 opened
runMigrationsinschema-migrations.tsto probepg_namespacefor schema existence before attempting schema creation [b6c7707]database-store-schema.test.tsto verify schema existence probing behavior [b6c7707]CLAUDE.mdandREADME.mdto document schema existence probing and reduced privilege requirements [b6c7707]resolveSchemafunction inschema-migrationsmodule to throw an error when a configured schema is not a valid Postgres identifier on non-sqlite dialects, while sqlite dialects return undefined without validation [f249390]schema-migrationsmodule includingPOSTGRES_IDENTIFIERregex constant,MAX_IDENTIFIER_LENGTHconstant set to 63, andisValidPostgresIdentifierfunction [f249390]parseSchemaEnvfunction incli-coremodule to use shared validation utilities fromschema-migrationsmodule instead of locally defined constants and regex [f249390]cli.test.ts, Postgres-specific validation with pg_namespace probe queries indatabase-store-schema.test.ts, sqlite dialect ignoring invalid schemas, and programmatic API validation for invalid values and excessive length [f249390]Macroscope summarized f5efb65.