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.
Files changed (269) hide show
  1. package/README.md +11 -0
  2. package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
  3. package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
  4. package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
  5. package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
  6. package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
  7. package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
  8. package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
  9. package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
  10. package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  11. package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
  12. package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
  13. package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
  14. package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
  15. package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
  16. package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
  17. package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
  18. package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
  19. package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
  20. package/agent-template/.agents/skills/variables/SKILL.md +156 -0
  21. package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
  22. package/agent-template/.ai/README.md +62 -0
  23. package/agent-template/.ai/agents/README.md +27 -0
  24. package/agent-template/.ai/agents/module-executor.md +36 -0
  25. package/agent-template/.ai/agents/reviewer.md +23 -0
  26. package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
  27. package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
  28. package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
  29. package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
  30. package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
  31. package/agent-template/.ai/agents/spec-author.md +29 -0
  32. package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
  33. package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
  34. package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
  35. package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
  36. package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
  37. package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
  38. package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
  39. package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
  40. package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
  41. package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
  42. package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
  43. package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
  44. package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
  45. package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
  46. package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
  47. package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
  48. package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
  49. package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
  50. package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
  51. package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
  52. package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
  53. package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
  54. package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
  55. package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
  56. package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
  57. package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
  58. package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
  59. package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
  60. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
  61. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
  62. package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
  63. package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
  64. package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
  65. package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
  66. package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
  67. package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
  68. package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
  69. package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
  70. package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
  71. package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
  72. package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
  73. package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
  74. package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
  75. package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
  76. package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
  77. package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
  78. package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
  79. package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
  80. package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
  81. package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
  82. package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
  83. package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
  84. package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
  85. package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
  86. package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
  87. package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
  88. package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
  89. package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
  90. package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
  91. package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
  92. package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
  93. package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
  94. package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
  95. package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
  96. package/agent-template/.ai/blueprints/new-module/README.md +7 -0
  97. package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
  98. package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
  99. package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
  100. package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
  101. package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
  102. package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
  103. package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
  104. package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
  105. package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
  106. package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
  107. package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
  108. package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
  109. package/agent-template/.ai/blueprints/release/README.md +5 -0
  110. package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
  111. package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
  112. package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
  113. package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
  114. package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
  115. package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
  116. package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
  117. package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
  118. package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
  119. package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
  120. package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
  121. package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
  122. package/agent-template/.ai/blueprints/security-review/README.md +5 -0
  123. package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
  124. package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
  125. package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
  126. package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
  127. package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
  128. package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
  129. package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
  130. package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
  131. package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
  132. package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
  133. package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
  134. package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
  135. package/agent-template/.ai/examples/README.md +8 -0
  136. package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
  137. package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
  138. package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
  139. package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
  140. package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
  141. package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
  142. package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
  143. package/agent-template/.ai/examples/client-contribution/README.md +11 -0
  144. package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
  145. package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
  146. package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
  147. package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
  148. package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
  149. package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
  150. package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
  151. package/agent-template/.ai/guides/application-development.md +97 -0
  152. package/agent-template/.ai/policies/capabilities.yaml +164 -0
  153. package/agent-template/.ai/policies/model-routing.yaml +72 -0
  154. package/agent-template/.ai/policies/path-ownership.yaml +65 -0
  155. package/agent-template/.ai/policies/task-budgets.yaml +37 -0
  156. package/agent-template/.ai/references/catalog/LICENSE +21 -0
  157. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
  158. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
  159. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
  160. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
  161. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
  162. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
  163. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
  164. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
  165. package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
  166. package/agent-template/.ai/references/catalog/module.json +27 -0
  167. package/agent-template/.ai/references/catalog/package.json +49 -0
  168. package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
  169. package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
  170. package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
  171. package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
  172. package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
  173. package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
  174. package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
  175. package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
  176. package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
  177. package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
  178. package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
  179. package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
  180. package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
  181. package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
  182. package/agent-template/.ai/references/catalog/src/index.ts +31 -0
  183. package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
  184. package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
  185. package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
  186. package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
  187. package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
  188. package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
  189. package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
  190. package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
  191. package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
  192. package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
  193. package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
  194. package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
  195. package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
  196. package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
  197. package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
  198. package/agent-template/.ai/references/catalog/translations/en.json +101 -0
  199. package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
  200. package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
  201. package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
  202. package/agent-template/.ai/references/catalog.provenance.json +55 -0
  203. package/agent-template/.ai/rules/flowdular.md +86 -0
  204. package/agent-template/.ai/skills/README.md +36 -0
  205. package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
  206. package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
  207. package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
  208. package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
  209. package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
  210. package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
  211. package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
  212. package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
  213. package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  214. package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
  215. package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
  216. package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
  217. package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
  218. package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
  219. package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
  220. package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
  221. package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
  222. package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
  223. package/agent-template/.ai/skills/variables/SKILL.md +164 -0
  224. package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
  225. package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
  226. package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
  227. package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
  228. package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
  229. package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
  230. package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
  231. package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
  232. package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
  233. package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  234. package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
  235. package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
  236. package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
  237. package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
  238. package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
  239. package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
  240. package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
  241. package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
  242. package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
  243. package/agent-template/.claude/skills/variables/SKILL.md +156 -0
  244. package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
  245. package/agent-template/AGENTS.md +77 -0
  246. package/agent-template/CLAUDE.md +77 -0
  247. package/agent-template/docs/adr/0001-development-reload.md +16 -0
  248. package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
  249. package/agent-template/docs/adr/0003-module-settings.md +22 -0
  250. package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
  251. package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
  252. package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
  253. package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
  254. package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
  255. package/agent-template/docs/agent-contract.md +45 -0
  256. package/agent-template/docs/configuration.md +122 -0
  257. package/agent-template/docs/database-adapters.md +346 -0
  258. package/agent-template/docs/design-system.md +217 -0
  259. package/agent-template/docs/modules.md +146 -0
  260. package/agent-template/platform/scripts/build.mjs +38 -0
  261. package/agent-template/rulesync.jsonc +11 -0
  262. package/dist/bin.js +3 -1
  263. package/package.json +3 -2
  264. package/template/default/.prettierignore +9 -0
  265. package/template/default/README.md +12 -0
  266. package/template/default/flowdular.json +3 -3
  267. package/template/default/package.json +6 -2
  268. package/template/default/platform/octane.config.ts +17 -6
  269. 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.