create-flowdular 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
- package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
- package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.agents/skills/variables/SKILL.md +156 -0
- package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/.ai/README.md +62 -0
- package/agent-template/.ai/agents/README.md +27 -0
- package/agent-template/.ai/agents/module-executor.md +36 -0
- package/agent-template/.ai/agents/reviewer.md +23 -0
- package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
- package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
- package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
- package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
- package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
- package/agent-template/.ai/agents/spec-author.md +29 -0
- package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
- package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
- package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
- package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
- package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
- package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
- package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
- package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
- package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
- package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
- package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
- package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
- package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
- package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
- package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
- package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
- package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
- package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
- package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
- package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
- package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
- package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
- package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
- package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
- package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
- package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
- package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
- package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
- package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
- package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
- package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
- package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
- package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
- package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
- package/agent-template/.ai/blueprints/new-module/README.md +7 -0
- package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
- package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
- package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
- package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
- package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
- package/agent-template/.ai/blueprints/release/README.md +5 -0
- package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
- package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
- package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
- package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
- package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
- package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
- package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
- package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
- package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
- package/agent-template/.ai/blueprints/security-review/README.md +5 -0
- package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
- package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
- package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
- package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
- package/agent-template/.ai/examples/README.md +8 -0
- package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
- package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
- package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
- package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
- package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
- package/agent-template/.ai/examples/client-contribution/README.md +11 -0
- package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
- package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
- package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
- package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
- package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
- package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
- package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
- package/agent-template/.ai/guides/application-development.md +97 -0
- package/agent-template/.ai/policies/capabilities.yaml +164 -0
- package/agent-template/.ai/policies/model-routing.yaml +72 -0
- package/agent-template/.ai/policies/path-ownership.yaml +65 -0
- package/agent-template/.ai/policies/task-budgets.yaml +37 -0
- package/agent-template/.ai/references/catalog/LICENSE +21 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
- package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
- package/agent-template/.ai/references/catalog/module.json +27 -0
- package/agent-template/.ai/references/catalog/package.json +49 -0
- package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
- package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
- package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
- package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
- package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
- package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
- package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
- package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
- package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
- package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
- package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
- package/agent-template/.ai/references/catalog/src/index.ts +31 -0
- package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
- package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
- package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
- package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
- package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
- package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
- package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
- package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
- package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
- package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
- package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
- package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
- package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
- package/agent-template/.ai/references/catalog/translations/en.json +101 -0
- package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
- package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
- package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
- package/agent-template/.ai/references/catalog.provenance.json +55 -0
- package/agent-template/.ai/rules/flowdular.md +86 -0
- package/agent-template/.ai/skills/README.md +36 -0
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
- package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
- package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
- package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
- package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
- package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
- package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
- package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
- package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
- package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
- package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
- package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
- package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
- package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
- package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
- package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
- package/agent-template/.ai/skills/variables/SKILL.md +164 -0
- package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
- package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
- package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.claude/skills/variables/SKILL.md +156 -0
- package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/AGENTS.md +77 -0
- package/agent-template/CLAUDE.md +77 -0
- package/agent-template/docs/adr/0001-development-reload.md +16 -0
- package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
- package/agent-template/docs/adr/0003-module-settings.md +22 -0
- package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
- package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
- package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
- package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
- package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
- package/agent-template/docs/agent-contract.md +45 -0
- package/agent-template/docs/configuration.md +122 -0
- package/agent-template/docs/database-adapters.md +346 -0
- package/agent-template/docs/design-system.md +217 -0
- package/agent-template/docs/modules.md +146 -0
- package/agent-template/platform/scripts/build.mjs +38 -0
- package/agent-template/rulesync.jsonc +11 -0
- package/dist/bin.js +3 -1
- package/package.json +3 -2
- package/template/default/.prettierignore +9 -0
- package/template/default/README.md +12 -0
- package/template/default/flowdular.json +3 -3
- package/template/default/package.json +6 -2
- package/template/default/platform/octane.config.ts +17 -6
- package/template/default/platform/package.json +2 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Runtime options are environment variables prefixed `FD_`. Every value has a
|
|
4
|
+
development default, so a local checkout needs none of them. Production
|
|
5
|
+
deployments must set the secret keys.
|
|
6
|
+
|
|
7
|
+
## Platform
|
|
8
|
+
|
|
9
|
+
| Variable | Default | Purpose |
|
|
10
|
+
| -------------------- | ------------------------------ | ------------------------------------------------ |
|
|
11
|
+
| `FD_ENV` | `NODE_ENV`, else `development` | Environment the CLI and destructive guards check |
|
|
12
|
+
| `FD_PORT` | `3000` | Host port published by the container |
|
|
13
|
+
| `FD_TRUST_PROXY` | `false` | Trust `X-Forwarded-*` behind a reverse proxy |
|
|
14
|
+
| `FD_CSP` | built-in policy | Override the Content Security Policy |
|
|
15
|
+
| `FD_CSP_REPORT_ONLY` | `true` outside production | Report CSP violations instead of enforcing them |
|
|
16
|
+
|
|
17
|
+
## Database
|
|
18
|
+
|
|
19
|
+
Flowdular runs on PostgreSQL. One platform-owned provider serves every module, so
|
|
20
|
+
there is one database and no per-module option. Outside production the adapter
|
|
21
|
+
is PGlite, the same engine embedded in the process, which is why a local
|
|
22
|
+
checkout needs no server.
|
|
23
|
+
|
|
24
|
+
| Variable | Default | Purpose |
|
|
25
|
+
| ---------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------- |
|
|
26
|
+
| `FD_DATABASE_ADAPTER` | `postgresql` in production, else `pglite` | `pglite` or `postgresql`; `pglite` is refused in production |
|
|
27
|
+
| `FD_DATABASE_PGLITE_DIRECTORY` | `.flowdular/data/pglite` | Data directory of the embedded database; ignored under `NODE_ENV=test` |
|
|
28
|
+
| `FD_DATABASE_URL` | none | Runtime PostgreSQL DSN; the role must not hold `BYPASSRLS` |
|
|
29
|
+
| `FD_DATABASE_MIGRATOR_URL` | `FD_DATABASE_URL` outside production | Schema-owning PostgreSQL DSN; required in production |
|
|
30
|
+
| `FD_DATABASE_BACKGROUND_URL` | none | Cross-tenant read-only DSN; without it a `background` lease is refused |
|
|
31
|
+
| `FD_DATABASE_TLS` | `verify-full` | `verify-full`, `require`, or `disable`; production allows only `verify-full` |
|
|
32
|
+
| `FD_DATABASE_TLS_CA` | none | Certificate authority as an inline PEM value |
|
|
33
|
+
| `FD_DATABASE_TLS_CA_FILE` | none | Certificate authority read from a mounted file |
|
|
34
|
+
| `FD_DATABASE_POOL_MIN` | `0` | Minimum pooled connections per role |
|
|
35
|
+
| `FD_DATABASE_POOL_MAX` | `10` | Maximum pooled connections per role |
|
|
36
|
+
| `FD_DATABASE_CONNECT_TIMEOUT_MS` | `5000` | Connection acquisition timeout |
|
|
37
|
+
| `FD_DATABASE_IDLE_TIMEOUT_MS` | `30000` | Idle connection timeout |
|
|
38
|
+
| `FD_DATABASE_STATEMENT_TIMEOUT_MS` | `15000` | Server-side statement timeout |
|
|
39
|
+
| `FD_DATABASE_QUERY_TIMEOUT_MS` | `20000` | Driver query timeout; never shorter than the statement timeout |
|
|
40
|
+
| `FD_DATABASE_LOCK_TIMEOUT_MS` | `5000` | Server-side lock timeout; never longer than the statement timeout |
|
|
41
|
+
|
|
42
|
+
Production refuses a shared runtime and migrator DSN, refuses TLS weaker than
|
|
43
|
+
`verify-full`, and refuses a runtime role with `SUPERUSER` or `BYPASSRLS`.
|
|
44
|
+
`GET /api/ready` reports the adapter and answers 503 while the database is
|
|
45
|
+
unreachable. Set the authority either inline or as a file, never both.
|
|
46
|
+
|
|
47
|
+
## Authentication (`auth.core`)
|
|
48
|
+
|
|
49
|
+
| Variable | Default | Purpose |
|
|
50
|
+
| ------------------------------ | ------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `FD_AUTH_ALLOW_SIGN_UP` | `true` outside production | Expose account creation |
|
|
52
|
+
| `FD_AUTH_SECURE_COOKIE` | `true` in production | Secure flag and `__Host-` prefix on the session cookie |
|
|
53
|
+
| `FD_AUTH_PUBLIC_ORIGIN` | request origin | Absolute public origin, HTTPS unless loopback |
|
|
54
|
+
| `FD_AUTH_SESSION_TTL_HOURS` | `12` (1 to 168) | Absolute session lifetime |
|
|
55
|
+
| `FD_AUTH_SESSION_IDLE_MINUTES` | `120` (5 to 1440) | Idle timeout |
|
|
56
|
+
| `FD_AUTH_PASSWORD_MIN_LENGTH` | `12` (8 to 128) | Minimum password length |
|
|
57
|
+
| `FD_AUTH_EMAIL_CONFIRMATION` | `false` | Hold the session after sign-up until the address is confirmed; requires a composed mail transport |
|
|
58
|
+
| `FD_AUTH_DEVELOPMENT_MAIL` | `false` | In-memory mail delivery, refused in production |
|
|
59
|
+
| `FD_AUTH_SIGN_IN_PROVIDERS` | empty | Comma list of external providers rendered on sign-in |
|
|
60
|
+
| `FD_AUTH_OIDC_PROVIDERS` | empty | JSON array of at most eight OIDC provider configurations |
|
|
61
|
+
| `FD_AUTH_MFA_KEY` | unset | Base64 32-byte key encrypting MFA secrets; MFA enrollment is unavailable without it |
|
|
62
|
+
|
|
63
|
+
Listing a provider in `FD_AUTH_SIGN_IN_PROVIDERS` only surfaces the button; the
|
|
64
|
+
matching `/api/auth/sso/{provider}/start` handler must be composed at the
|
|
65
|
+
platform level.
|
|
66
|
+
|
|
67
|
+
## Agents (`agents.core`)
|
|
68
|
+
|
|
69
|
+
| Variable | Default | Purpose |
|
|
70
|
+
| ---------------------------------- | ----------------- | ---------------------------------------------------- |
|
|
71
|
+
| `FD_AGENT_CREDENTIAL_KEY` | generated dev key | Base64 32-byte key for the provider credential vault |
|
|
72
|
+
| `FD_AGENT_RUN_GRANT_KEY` | generated dev key | Base64 32-byte key signing run grants |
|
|
73
|
+
| `FD_AGENT_WORKER_CONCURRENCY` | `2` (1 to 16) | Parallel run workers |
|
|
74
|
+
| `FD_AGENT_WORKER_LEASE_MS` | `30000` | Run lease before recovery reclaims it |
|
|
75
|
+
| `FD_AGENT_PROVIDER_HOST_ALLOWLIST` | empty | Hostnames an external provider may be called on |
|
|
76
|
+
|
|
77
|
+
Outside production the keys are generated once under `.flowdular/data`. Both are
|
|
78
|
+
required in production: the module refuses to boot without them.
|
|
79
|
+
Rotating `FD_AGENT_CREDENTIAL_KEY` invalidates every stored provider credential.
|
|
80
|
+
|
|
81
|
+
## Workflows (`workflows.core`)
|
|
82
|
+
|
|
83
|
+
| Variable | Default | Purpose |
|
|
84
|
+
| -------------------------- | --------------- | -------------------------------------------- |
|
|
85
|
+
| `FD_WORKFLOWS_PAYLOAD_KEY` | derived dev key | Base64 32-byte key encrypting run payloads |
|
|
86
|
+
| `FD_WORKFLOWS_CURSOR_KEY` | derived dev key | Base64 32-byte key signing execution cursors |
|
|
87
|
+
|
|
88
|
+
Both keys are required in production. Rotating the payload key makes retained
|
|
89
|
+
execution payloads unreadable, so drain runs and let retention remove payloads
|
|
90
|
+
first.
|
|
91
|
+
|
|
92
|
+
## Sandbox
|
|
93
|
+
|
|
94
|
+
| Variable | Default | Purpose |
|
|
95
|
+
| ---------------------- | ----------------- | -------------------------------------------- |
|
|
96
|
+
| `FD_SANDBOX_URL` | unset | Absolute URL of the sandbox the app links to |
|
|
97
|
+
| `FD_SANDBOX_WORKSPACE` | current directory | Workspace the launcher serves |
|
|
98
|
+
| `FD_SANDBOX_PORT` | `4320` | Launcher port |
|
|
99
|
+
| `FD_SANDBOX_MODE` | launcher `--mode` | Local or self-hosted provider mode |
|
|
100
|
+
|
|
101
|
+
## Business modules
|
|
102
|
+
|
|
103
|
+
A module with the `database` capability has no database option of its own. It
|
|
104
|
+
acquires a lease from the platform provider configured above, so the
|
|
105
|
+
`FD_DATABASE_*` values are the only place storage is configured. See
|
|
106
|
+
[database-adapters.md](database-adapters.md).
|
|
107
|
+
|
|
108
|
+
## Landing site
|
|
109
|
+
|
|
110
|
+
| Variable | Default | Purpose |
|
|
111
|
+
| ----------------- | ------- | ---------------- |
|
|
112
|
+
| `FD_LANDING_PORT` | `4330` | Development port |
|
|
113
|
+
|
|
114
|
+
## Generating keys
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
openssl rand -base64 32
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For containers, copy `infra/docker/.env.example` to `infra/docker/.env` and fill
|
|
121
|
+
in every empty value: the four encryption keys above and the four PostgreSQL
|
|
122
|
+
role passwords. See [../infra/README.md](../infra/README.md).
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Database adapters
|
|
2
|
+
|
|
3
|
+
Flowdular runs on PostgreSQL. There is one platform-owned provider and one
|
|
4
|
+
database behind it. Locally you get the same engine embedded in the process
|
|
5
|
+
through PGlite, so a workstation needs no server and no configuration, and a
|
|
6
|
+
deployment points the same code at a real cluster.
|
|
7
|
+
|
|
8
|
+
`@flowdular/sdk/database` is the SQL boundary every module repository builds on. It
|
|
9
|
+
is asynchronous because every supported PostgreSQL driver is asynchronous. A
|
|
10
|
+
module never sees a DSN, a pool or a driver; it acquires a lease from the
|
|
11
|
+
provider and gives it back.
|
|
12
|
+
|
|
13
|
+
## The single provider
|
|
14
|
+
|
|
15
|
+
`platform/src/server/database.ts` builds the provider once:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
createDatabaseProvider,
|
|
20
|
+
databaseProviderConfigFromEnvironment,
|
|
21
|
+
} from '@flowdular/sdk/database';
|
|
22
|
+
import { createPgliteCluster } from '@flowdular/sdk/database-pglite';
|
|
23
|
+
import { Pool } from 'pg';
|
|
24
|
+
|
|
25
|
+
const config = databaseProviderConfigFromEnvironment(
|
|
26
|
+
process.env,
|
|
27
|
+
workspaceRoot,
|
|
28
|
+
);
|
|
29
|
+
const databases = createDatabaseProvider(config, {
|
|
30
|
+
postgresPool: (options) => new Pool(options),
|
|
31
|
+
pgliteCluster: (options) => createPgliteCluster(options),
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`@flowdular/sdk/database` owns no driver, so the caller supplies both. Composition
|
|
36
|
+
injects the result as `PlatformServerContext.databases`, and that is the only
|
|
37
|
+
way a module reaches storage. `GET /api/ready` reports the live adapter and
|
|
38
|
+
answers 503 while the database is unreachable.
|
|
39
|
+
|
|
40
|
+
The sandbox preview builds its own embedded provider under the session data
|
|
41
|
+
directory instead, so a draft never reaches a deployment database.
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
`FD_DATABASE_ADAPTER` selects the adapter. It accepts `pglite` or `postgresql`
|
|
46
|
+
and nothing else. Unset, it is `pglite` outside production and `postgresql`
|
|
47
|
+
when `NODE_ENV=production`. `FD_DATABASE_ADAPTER=pglite` in production is
|
|
48
|
+
refused at startup.
|
|
49
|
+
|
|
50
|
+
| Variable | Default | Purpose |
|
|
51
|
+
| ---------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------- |
|
|
52
|
+
| `FD_DATABASE_ADAPTER` | `postgresql` in production, else `pglite` | `pglite` or `postgresql` |
|
|
53
|
+
| `FD_DATABASE_PGLITE_DIRECTORY` | `.flowdular/data/pglite` | Data directory of the embedded database |
|
|
54
|
+
| `FD_DATABASE_URL` | none | Runtime role DSN; required for the `postgresql` adapter |
|
|
55
|
+
| `FD_DATABASE_MIGRATOR_URL` | `FD_DATABASE_URL` outside production | Schema-owning DSN; required in production |
|
|
56
|
+
| `FD_DATABASE_BACKGROUND_URL` | none | Cross-tenant read-only DSN; without it a `background` lease is refused |
|
|
57
|
+
| `FD_DATABASE_TLS` | `verify-full` | `verify-full`, `require`, or `disable`; production allows only `verify-full` |
|
|
58
|
+
| `FD_DATABASE_TLS_CA` | none | Certificate authority as an inline PEM value |
|
|
59
|
+
| `FD_DATABASE_TLS_CA_FILE` | none | Certificate authority read from a mounted file |
|
|
60
|
+
| `FD_DATABASE_POOL_MIN` | `0` | Minimum pooled connections per role |
|
|
61
|
+
| `FD_DATABASE_POOL_MAX` | `10` | Maximum pooled connections per role |
|
|
62
|
+
| `FD_DATABASE_CONNECT_TIMEOUT_MS` | `5000` | Connection acquisition timeout |
|
|
63
|
+
| `FD_DATABASE_IDLE_TIMEOUT_MS` | `30000` | Idle connection timeout |
|
|
64
|
+
| `FD_DATABASE_STATEMENT_TIMEOUT_MS` | `15000` | Server-side statement timeout |
|
|
65
|
+
| `FD_DATABASE_QUERY_TIMEOUT_MS` | `20000` | Driver query timeout; never shorter than the statement timeout |
|
|
66
|
+
| `FD_DATABASE_LOCK_TIMEOUT_MS` | `5000` | Server-side lock timeout; never longer than the statement timeout |
|
|
67
|
+
|
|
68
|
+
Every URL must use the `postgres://` or `postgresql://` scheme and may not carry
|
|
69
|
+
TLS parameters in its query string; TLS is configured by the `FD_DATABASE_TLS*`
|
|
70
|
+
values, inline or as a file, never both. Pool minimum may not exceed pool
|
|
71
|
+
maximum.
|
|
72
|
+
|
|
73
|
+
With the `pglite` adapter the data directory is the only setting that applies.
|
|
74
|
+
Under `NODE_ENV=test` the directory is ignored and the database is held in
|
|
75
|
+
memory.
|
|
76
|
+
|
|
77
|
+
## Three roles
|
|
78
|
+
|
|
79
|
+
The embedded adapter creates the same roles a deployment configures, so a local
|
|
80
|
+
run enforces the isolation a deployment enforces instead of approximating it.
|
|
81
|
+
|
|
82
|
+
| Role | Owns | Constraints |
|
|
83
|
+
| --------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `coreloom_migrator` | The schema. Serves the `migration` purpose. | Owns every table the migrations create. |
|
|
85
|
+
| `coreloom_runtime` | Request-time reads and writes. | No `SUPERUSER`, no `BYPASSRLS`, and every handle it lends requires a transaction tenant id. |
|
|
86
|
+
| `coreloom_background` | Cross-tenant polls. | Read-only, and no blanket table grant. |
|
|
87
|
+
|
|
88
|
+
## Leases
|
|
89
|
+
|
|
90
|
+
A module asks for a handle by namespace and purpose:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const lease = await context.databases.acquire({
|
|
94
|
+
namespace: 'profile.core',
|
|
95
|
+
purpose: 'runtime',
|
|
96
|
+
requirements: {
|
|
97
|
+
dialectIds: [DATABASE_DIALECT_IDS.postgresql],
|
|
98
|
+
capabilities: [DATABASE_CAPABILITY_IDS.TRANSACTIONS],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Purpose | Role | Used for |
|
|
104
|
+
| ------------ | --------------------- | --------------------------------------------------- |
|
|
105
|
+
| `migration` | `coreloom_migrator` | Applying migrations and resetting a database |
|
|
106
|
+
| `runtime` | `coreloom_runtime` | The deployed application |
|
|
107
|
+
| `preview` | `coreloom_runtime` | A local run and the sandbox preview |
|
|
108
|
+
| `test` | `coreloom_runtime` | A test suite |
|
|
109
|
+
| `background` | `coreloom_background` | A scheduler poll or recovery that precedes a tenant |
|
|
110
|
+
|
|
111
|
+
A runtime acquires its leases lazily, one per runtime, and releases them from
|
|
112
|
+
composition `dispose()`. `modules/profile/src/server/runtime.ts` is the shape:
|
|
113
|
+
take a `migration` lease, run the migrations, release it, then hold one
|
|
114
|
+
tenant-scoped lease for the life of the composition.
|
|
115
|
+
|
|
116
|
+
## Contract
|
|
117
|
+
|
|
118
|
+
The package exposes five distinct roles:
|
|
119
|
+
|
|
120
|
+
- `DatabaseProvider` is owned by platform composition. It resolves secrets,
|
|
121
|
+
pools, TLS, and environment configuration, then lends a `DatabaseHandle` for
|
|
122
|
+
one module namespace. A module never receives a DSN.
|
|
123
|
+
- `DatabaseHandle` executes parameterized statements and opens transactions. It
|
|
124
|
+
cannot dispose shared provider state.
|
|
125
|
+
- `DatabaseAdapter` is the provider-owned handle with an idempotent `dispose()`.
|
|
126
|
+
- `DatabaseTransaction` is pinned to one connection and expires when its
|
|
127
|
+
callback returns or throws.
|
|
128
|
+
- `DatabaseSchemaIntrospector` answers `hasTable`, `hasColumn` and `hasIndex`,
|
|
129
|
+
which is what migration adoption inspects.
|
|
130
|
+
|
|
131
|
+
SQL is explicit. PostgreSQL binds `$1`, `$2`, and so on. Values always travel in
|
|
132
|
+
`DatabaseStatement.parameters`; request data is never concatenated into SQL.
|
|
133
|
+
|
|
134
|
+
`executeScript()` is reserved for trusted, checked-in migration DDL. It has no
|
|
135
|
+
parameter channel by design.
|
|
136
|
+
|
|
137
|
+
### Lifecycle guarantees
|
|
138
|
+
|
|
139
|
+
- An adapter is ready when its constructor returns.
|
|
140
|
+
- A transaction callback receives one connection-bound transaction.
|
|
141
|
+
- Success commits. A thrown error or an aborted transaction rolls back.
|
|
142
|
+
- Calling the root adapter from its own transaction callback is rejected.
|
|
143
|
+
- A transaction retained after its callback is rejected on every operation,
|
|
144
|
+
including schema inspection.
|
|
145
|
+
- `dispose()` is idempotent, refuses new work, waits for already accepted work,
|
|
146
|
+
and then closes the database or pool.
|
|
147
|
+
- `AbortSignal` and `timeoutMs` are accepted by every operation. The
|
|
148
|
+
`capabilities.cancellation` value states whether cancellation can stop only
|
|
149
|
+
queued work (`before-start`) or an active driver query (`driver`). A
|
|
150
|
+
transaction checks cancellation again before commit.
|
|
151
|
+
|
|
152
|
+
The standard `node-postgres` bridge advertises `before-start`, because the
|
|
153
|
+
public `node-postgres` query contract does not provide `AbortSignal`
|
|
154
|
+
cancellation. That is why the pool also sets server-side `statement_timeout`,
|
|
155
|
+
`lock_timeout`, and a driver `query_timeout`. A custom driver bridge may
|
|
156
|
+
advertise `driver` only when it really cancels the active query.
|
|
157
|
+
|
|
158
|
+
Driver error classes, pool scheduling, physical connection identity, and row
|
|
159
|
+
type parsers are deliberately unspecified. Callers may rely only on the
|
|
160
|
+
normalized row and affected-row results plus the documented Flowdular error
|
|
161
|
+
codes.
|
|
162
|
+
|
|
163
|
+
## Tenancy
|
|
164
|
+
|
|
165
|
+
Every tenant table enables and forces row-level security and carries a policy
|
|
166
|
+
whose `USING` and `WITH CHECK` compare `tenant_id` with the transaction-local
|
|
167
|
+
`coreloom.tenant_id` setting:
|
|
168
|
+
|
|
169
|
+
```sql
|
|
170
|
+
ALTER TABLE profile_records ENABLE ROW LEVEL SECURITY;
|
|
171
|
+
ALTER TABLE profile_records FORCE ROW LEVEL SECURITY;
|
|
172
|
+
CREATE POLICY profile_records_tenant_policy ON profile_records
|
|
173
|
+
USING (tenant_id = current_setting('coreloom.tenant_id', true))
|
|
174
|
+
WITH CHECK (tenant_id = current_setting('coreloom.tenant_id', true));
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`database.transaction(body, { tenantId, access })` sets that value for the
|
|
178
|
+
duration of the transaction, so a query that runs outside a tenant transaction
|
|
179
|
+
sees nothing. Every repository operation goes through it:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
async find(tenantId: string, accountId: string): Promise<Profile | null> {
|
|
183
|
+
const result = await this.database.transaction(
|
|
184
|
+
(transaction) =>
|
|
185
|
+
transaction.query<ProfileRow>({
|
|
186
|
+
text: FIND,
|
|
187
|
+
parameters: [tenantId, accountId],
|
|
188
|
+
}),
|
|
189
|
+
{ access: 'read', tenantId },
|
|
190
|
+
);
|
|
191
|
+
const row = result.rows[0];
|
|
192
|
+
return row ? fromRow(row) : null;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The `WHERE tenant_id = $1` in `FIND` stays as defense in depth. The database is
|
|
197
|
+
what enforces the boundary.
|
|
198
|
+
|
|
199
|
+
`modules/profile/src/services/database-repository.ts` is the reference
|
|
200
|
+
repository.
|
|
201
|
+
|
|
202
|
+
## Migrations
|
|
203
|
+
|
|
204
|
+
The ledger is `_coreloom_migrations_v2`. It carries the module namespace because
|
|
205
|
+
every module shares one database. Checksums cover the exact SQL, and a mismatch
|
|
206
|
+
is checked before any outstanding migration runs.
|
|
207
|
+
|
|
208
|
+
`src/services/migration.ts` exports the list:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import type { DatabaseMigration } from '@flowdular/sdk/database';
|
|
212
|
+
import { postgresTenantTableState } from '@flowdular/sdk/database';
|
|
213
|
+
|
|
214
|
+
export const databaseMigrations: readonly DatabaseMigration[] = [
|
|
215
|
+
{
|
|
216
|
+
id: '0001_profile_core',
|
|
217
|
+
sql: { postgresql: PROFILE_MIGRATION_001 },
|
|
218
|
+
inspectExisting: (database) =>
|
|
219
|
+
postgresTenantTableState(
|
|
220
|
+
database,
|
|
221
|
+
'profile_records',
|
|
222
|
+
'profile_records_tenant_policy',
|
|
223
|
+
[() => database.schema.hasIndex('profile_records_tenant_account_idx')],
|
|
224
|
+
),
|
|
225
|
+
},
|
|
226
|
+
];
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Each constant mirrors `migrations/<id>.up.sql` byte for byte, and a module test
|
|
230
|
+
fails on drift. The numbered `.up.sql` and `.down.sql` files are immutable
|
|
231
|
+
source: add a new additive migration rather than editing applied bytes.
|
|
232
|
+
|
|
233
|
+
`inspectExisting` is the module's explicit pre-ledger adoption proof. It returns
|
|
234
|
+
`complete`, `absent`, or `partial`, and the runner refuses `partial` rather than
|
|
235
|
+
guessing. `postgresTenantTableState` is the standard check: the table exists,
|
|
236
|
+
row-level security is enabled and forced, the named policy is present, and every
|
|
237
|
+
extra thunk passes.
|
|
238
|
+
|
|
239
|
+
`runDatabaseMigrations(database, namespace, databaseMigrations)` takes an
|
|
240
|
+
advisory lock and applies every outstanding migration with its ledger row inside
|
|
241
|
+
one connection-bound transaction, so a failure leaves neither.
|
|
242
|
+
|
|
243
|
+
Operator commands:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
flowdular migration new <name> --module <id> --apply # scaffold the up and down pair
|
|
247
|
+
flowdular migration status [--module <id>] # ledger state
|
|
248
|
+
flowdular migration apply --module <id> --apply
|
|
249
|
+
flowdular migration verify # checksums, row security, file and constant parity
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`migration new` writes exactly two files, `migrations/<NNNN>_<stem>_<name>.up.sql`
|
|
253
|
+
and `.down.sql`, with the tenant table, its index, forced row-level security and
|
|
254
|
+
a tenant policy already scaffolded. Replace the placeholder columns with the real
|
|
255
|
+
schema.
|
|
256
|
+
|
|
257
|
+
`migration verify` checks that every applied ledger checksum still matches, that
|
|
258
|
+
every tenant table a migration leaves behind has `ENABLE ROW LEVEL SECURITY`,
|
|
259
|
+
`FORCE ROW LEVEL SECURITY` and a tenant policy declared after the last statement
|
|
260
|
+
that puts the table in place, that a `coreloom_background` policy grants no more
|
|
261
|
+
than `FOR SELECT`, and that every `migrations/*.up.sql` file has a matching id in
|
|
262
|
+
`databaseMigrations` and the other way round.
|
|
263
|
+
|
|
264
|
+
## The background role
|
|
265
|
+
|
|
266
|
+
Almost everything runs on the tenant-scoped runtime handle. Two jobs cannot: the
|
|
267
|
+
automations scheduler has to find due work before it knows whose it is, and a
|
|
268
|
+
webhook is addressed by an id that carries no tenant. Those take a `background`
|
|
269
|
+
lease, a third role that is read-only and holds no default table grant at all.
|
|
270
|
+
|
|
271
|
+
A table it may poll says so itself, in its own migration:
|
|
272
|
+
|
|
273
|
+
```sql
|
|
274
|
+
CREATE POLICY <table>_background_policy ON <table>
|
|
275
|
+
FOR SELECT TO coreloom_background
|
|
276
|
+
USING (<the narrowest predicate that still finds the work>);
|
|
277
|
+
REVOKE SELECT ON <table> FROM coreloom_background;
|
|
278
|
+
GRANT SELECT (<only the columns the poll reads>) ON <table> TO coreloom_background;
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
A table that forgets to is invisible to that role, and every column outside the
|
|
282
|
+
grant stays unreadable on that connection even in a `WHERE` clause: PostgreSQL
|
|
283
|
+
checks column privileges there too. The row the poll returns is routing data
|
|
284
|
+
only, and whatever acts on it reads the record again under the tenant that row
|
|
285
|
+
named. A deployment configures the role with `FD_DATABASE_BACKGROUND_URL`;
|
|
286
|
+
without it a `background` lease is refused with `UNSUPPORTED_CAPABILITY` rather
|
|
287
|
+
than quietly widened to the runtime role.
|
|
288
|
+
|
|
289
|
+
## Testing
|
|
290
|
+
|
|
291
|
+
`createTestDatabaseProvider()` from `@flowdular/sdk/database-testing` gives a suite
|
|
292
|
+
its own PGlite by default or an isolated server PostgreSQL schema in CI. Both
|
|
293
|
+
enforce the runtime and background role boundaries. For an explicitly embedded,
|
|
294
|
+
file-backed test, use `createPgliteTestProvider({ dataDirectory })` and
|
|
295
|
+
`pgliteTestDirectory()` for a disposable name.
|
|
296
|
+
|
|
297
|
+
Booting an embedded PostgreSQL costs about two seconds, so a test file opens one
|
|
298
|
+
provider, migrates it once, and truncates the module tables between cases.
|
|
299
|
+
`modules/profile/tests/support/database.ts` is the reference:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
const provider = createTestDatabaseProvider();
|
|
303
|
+
const lease = await provider.acquire({
|
|
304
|
+
namespace: 'profile.core',
|
|
305
|
+
purpose: 'migration',
|
|
306
|
+
});
|
|
307
|
+
try {
|
|
308
|
+
await migrateProfileDatabase(lease.database);
|
|
309
|
+
} finally {
|
|
310
|
+
await lease.release();
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`pnpm verify` runs module behavior against the embedded database. The separate
|
|
315
|
+
PostgreSQL CI job runs the same suites with `FD_TEST_DATABASE_ADAPTER=postgresql`
|
|
316
|
+
and `FD_TEST_POSTGRES_URL`, `FD_TEST_POSTGRES_RUNTIME_URL`, and
|
|
317
|
+
`FD_TEST_POSTGRES_BACKGROUND_URL`. All three roles are required, and server
|
|
318
|
+
failures never fall back to PGlite. Tenant fixture operations on the migration
|
|
319
|
+
connection also need tenant transactions because its role has no RLS bypass.
|
|
320
|
+
|
|
321
|
+
Tenancy tests take two tenants, prove
|
|
322
|
+
`TENANT_CONTEXT_REQUIRED` without a transaction tenant id, prove that row-level
|
|
323
|
+
security stops a cross-tenant read and a cross-tenant write under the runtime
|
|
324
|
+
role, and cover `WITH CHECK`. Giving that role `BYPASSRLS` must make the isolation
|
|
325
|
+
assertions fail, which proves that they exercise the database boundary.
|
|
326
|
+
|
|
327
|
+
## Resetting a database
|
|
328
|
+
|
|
329
|
+
`resetDatabase(database, { intent: 'confirmed-destructive-reset' })` drops every
|
|
330
|
+
table the handle can see, including the v2 ledger, so the next start migrates
|
|
331
|
+
from zero. It drops with `CASCADE` inside one transaction.
|
|
332
|
+
`databaseResetPlan(database)` returns the same list without changing anything,
|
|
333
|
+
which is the dry run.
|
|
334
|
+
|
|
335
|
+
Both refuse a tenant-scoped runtime handle, because listing tables is a root
|
|
336
|
+
schema operation. Only a `purpose: 'migration'` lease can reset, so request-time
|
|
337
|
+
module code cannot reach it even by accident.
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
flowdular database reset # plan
|
|
341
|
+
flowdular database reset --apply --confirm reset-database
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
One database means the reset is never scoped to a single module: `--module` is
|
|
345
|
+
refused rather than silently dropping another module's tables. The data is not
|
|
346
|
+
recoverable afterwards.
|