@starci/hfs 4.3.0 → 4.4.0

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 (515) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/db/migrate.mjs +361 -0
  3. package/db/post-cutover.mjs +136 -0
  4. package/emit/db-types.mjs +10 -1
  5. package/lint/run.mjs +1 -1
  6. package/package.json +4 -1
  7. package/release/gate.mjs +298 -0
  8. package/release/index.mjs +103 -0
  9. package/release/push.mjs +110 -0
  10. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  11. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  12. package/runtime/knowledge/hfs/rules.yaml +3401 -152
  13. package/runtime/knowledge/hfs/slots.yaml +344 -62
  14. package/runtime/knowledge/patterns/be/api.yaml +32 -5
  15. package/runtime/knowledge/patterns/be/cli.yaml +14 -4
  16. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  17. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  18. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  19. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  20. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  21. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  22. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  23. package/runtime/knowledge/patterns/be/webhooks.yaml +23 -4
  24. package/runtime/knowledge/patterns/fe/folder.yaml +1 -0
  25. package/runtime/knowledge/sonar-gate.yaml +1 -1
  26. package/runtime/modules/kernel/failure-codes.yaml +3113 -43
  27. package/runtime/modules/models/runtimes.yaml +1 -1
  28. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  29. package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
  30. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  31. package/runtime/scripts/api/git/add.mjs +5 -0
  32. package/runtime/scripts/api/git/init.mjs +7 -0
  33. package/runtime/scripts/api/git/lib.mjs +3 -0
  34. package/runtime/scripts/api/git/tag.mjs +5 -0
  35. package/runtime/scripts/api/node/lib.mjs +20 -0
  36. package/runtime/scripts/api/node/run-node.mjs +6 -0
  37. package/runtime/scripts/api/sops/lib.mjs +41 -0
  38. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  39. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  40. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  41. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  42. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  43. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  44. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +1 -8
  45. package/runtime/scripts/hfs/architecture/backend.mjs +2 -7
  46. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  47. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  48. package/runtime/scripts/hfs/architecture/dead-exports.mjs +9 -41
  49. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  50. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  51. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  52. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  53. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +28 -13
  54. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  55. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  56. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  57. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  58. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -2
  59. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  60. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +211 -11
  61. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -1
  62. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  63. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  64. package/runtime/scripts/hfs/architecture/tiers.mjs +3 -3
  65. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +8 -2
  66. package/runtime/scripts/hfs/check-results.mjs +28 -0
  67. package/runtime/scripts/hfs/check.mjs +46 -47
  68. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  69. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  70. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  71. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  72. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  73. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  74. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  75. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  76. package/runtime/scripts/hfs/manifest-shape.mjs +67 -7
  77. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  78. package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
  79. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  80. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  81. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  82. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  83. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  84. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  85. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  86. package/runtime/scripts/hfs/rules/contract.mjs +66 -5
  87. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  88. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  89. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  90. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  91. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  92. package/runtime/scripts/hfs/rules/database-migrations.mjs +286 -7
  93. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  94. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  95. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  96. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  97. package/runtime/scripts/hfs/rules/database.mjs +9 -0
  98. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  99. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  100. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  101. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  102. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  103. package/runtime/scripts/hfs/rules/edition.mjs +9 -8
  104. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  105. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  106. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  107. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  108. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  109. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  110. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  111. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  112. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  113. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  114. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  115. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  116. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  117. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  118. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  119. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  120. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  121. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  122. package/runtime/scripts/hfs/rules/secrets.mjs +13 -0
  123. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  124. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  125. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  126. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  127. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  128. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  129. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +41 -1
  130. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  131. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
  132. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  133. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  134. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  135. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  136. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  137. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  138. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  139. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  140. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  141. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  142. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  143. package/runtime/scripts/hfs/secret-init.mjs +127 -19
  144. package/runtime/scripts/hfs/secret-sops.mjs +38 -6
  145. package/runtime/scripts/hfs/secret-store.mjs +56 -54
  146. package/runtime/scripts/hfs/secret.mjs +34 -5
  147. package/runtime/scripts/hfs/slot-imports.mjs +18 -5
  148. package/runtime/scripts/hfs/slots.mjs +1 -0
  149. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  150. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  151. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  152. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  153. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  154. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  155. package/runtime/scripts/lib/compose-values.mjs +11 -0
  156. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  157. package/runtime/scripts/lib/json-clone.mjs +61 -0
  158. package/runtime/scripts/lib/list.mjs +4 -0
  159. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  160. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  161. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  162. package/runtime/scripts/lib/stack-services.mjs +1 -1
  163. package/runtime/scripts/lib/swarm-stack.mjs +5 -18
  164. package/runtime/scripts/lib/trim-end.mjs +8 -0
  165. package/runtime/scripts/lib/ts-analysis.mjs +5 -2
  166. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  167. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  168. package/scaffold/add-app.mjs +77 -59
  169. package/scaffold/add-cli-lite.mjs +14 -6
  170. package/scaffold/add-compose.mjs +79 -0
  171. package/scaffold/add-database-lite.mjs +14 -0
  172. package/scaffold/add-fe-overlay.mjs +122 -0
  173. package/scaffold/add-fe-page.mjs +269 -0
  174. package/scaffold/add-inbox-lite.mjs +74 -42
  175. package/scaffold/add-table.mjs +110 -87
  176. package/scaffold/add.mjs +159 -44
  177. package/scaffold/app.mjs +124 -39
  178. package/scaffold/edition-gate.mjs +3 -3
  179. package/scaffold/lite-exports.mjs +2 -1
  180. package/scaffold/service.mjs +2 -2
  181. package/src/main.mjs +229 -116
  182. package/stack/age-install.mjs +37 -0
  183. package/stack/backup.mjs +100 -0
  184. package/stack/build-args.mjs +42 -0
  185. package/stack/build.mjs +173 -0
  186. package/stack/cli.mjs +130 -0
  187. package/stack/config.mjs +57 -0
  188. package/stack/context.mjs +57 -0
  189. package/stack/converge.mjs +107 -0
  190. package/stack/deploy.mjs +301 -0
  191. package/stack/digest.mjs +16 -0
  192. package/stack/docker-transport.mjs +53 -0
  193. package/stack/edge-alias.mjs +118 -0
  194. package/stack/errors.mjs +21 -0
  195. package/stack/image-scan.mjs +152 -0
  196. package/stack/ingress-create.mjs +114 -0
  197. package/stack/ingress-publish.mjs +273 -0
  198. package/stack/ingress.mjs +706 -0
  199. package/stack/jobs.mjs +51 -0
  200. package/stack/live-restore.mjs +177 -0
  201. package/stack/prepare.mjs +266 -0
  202. package/stack/private-file.mjs +22 -0
  203. package/stack/provision.mjs +90 -0
  204. package/stack/prune.mjs +46 -0
  205. package/stack/release-store.mjs +87 -0
  206. package/stack/remote.mjs +205 -0
  207. package/stack/render.mjs +149 -0
  208. package/stack/retire.mjs +47 -0
  209. package/stack/sealed.mjs +149 -0
  210. package/stack/task.mjs +42 -0
  211. package/stack/verify.mjs +99 -0
  212. package/sync/ci-commands.mjs +13 -0
  213. package/sync/index.mjs +108 -52
  214. package/sync/stack-images.mjs +127 -0
  215. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  216. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  217. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  218. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  219. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  220. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  221. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  222. package/templates/app/codegen-graphql.mjs.partial +13 -2
  223. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  224. package/templates/app/codegen-prelude.mjs.partial +8 -0
  225. package/templates/app/gitignore +3 -0
  226. package/templates/app/gitignore-lite +3 -0
  227. package/templates/app/package-scripts/package.json +1 -0
  228. package/templates/app/package-scripts/supabase.json +7 -0
  229. package/templates/app/package-scripts-lite/package.json +1 -7
  230. package/templates/app/skeleton/.editorconfig +1 -0
  231. package/templates/app/skeleton/.gitattributes +7 -1
  232. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  233. package/templates/app/skeleton/.nvmrc +1 -1
  234. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  235. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  236. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  237. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  238. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  239. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  240. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  241. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  242. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  243. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  244. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  245. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  246. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  247. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  248. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  249. package/templates/app/skeleton/README.md +6 -6
  250. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  251. package/templates/app/skeleton-lite/.editorconfig +16 -0
  252. package/templates/app/skeleton-lite/.gitattributes +8 -0
  253. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  254. package/templates/app/skeleton-lite/.nvmrc +1 -0
  255. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  256. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +178 -1
  257. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  258. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  259. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  260. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  261. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  262. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  263. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  264. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  265. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  266. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  267. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  268. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  269. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  270. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  271. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  272. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  273. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  274. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  275. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  276. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  277. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  278. package/templates/app/skeleton-lite/README.md +5 -3
  279. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  280. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  281. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  282. package/templates/be/image/worker/Dockerfile +1 -1
  283. package/templates/be/inbox/database-client.spec.ts.tpl +17 -18
  284. package/templates/be/inbox/database-client.ts.tpl +12 -11
  285. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  286. package/templates/be/inbox/event-index.ts.tpl +1 -0
  287. package/templates/be/inbox/events.json.tpl +10 -0
  288. package/templates/be/inbox/migration.sql.tpl +1 -0
  289. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  290. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  291. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  292. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  293. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  294. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  295. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  296. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  297. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  298. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  299. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  300. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  301. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  302. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  303. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  304. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  305. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  306. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  307. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  308. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  309. package/templates/be/inbox/service.spec.ts.tpl +42 -9
  310. package/templates/be/inbox/service.ts.tpl +23 -8
  311. package/templates/be/inbox/webhook-door.spec.ts.tpl +1 -1
  312. package/templates/be/inbox/webhook-door.ts.tpl +2 -2
  313. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  314. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  315. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  316. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  317. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  318. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  319. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  320. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  321. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  322. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  323. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  324. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  325. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  326. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  327. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  328. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  329. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  330. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  331. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  332. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  333. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  334. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  335. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  336. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  337. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  338. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  339. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  340. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  341. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  342. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  343. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  344. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  345. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  346. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  347. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  348. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  349. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  350. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  351. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  352. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  353. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  354. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  355. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  356. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  357. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  358. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  359. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  360. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  362. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  363. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  364. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  365. package/templates/be/skeleton/apps/__app__/src/app.module.ts +11 -9
  366. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  367. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  368. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +2 -0
  369. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  370. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  371. package/templates/be/skeleton/apps/__app__/src/main.ts +15 -1
  372. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  373. package/templates/be/skeleton/apps/cli/src/cli.options.ts +6 -14
  374. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  375. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  376. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  377. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  378. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  379. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  380. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  381. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  382. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  383. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  384. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  385. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  386. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  387. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  388. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  389. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  390. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  391. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  392. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  393. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  394. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  395. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  396. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  397. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  398. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  399. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  400. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  401. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  402. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  403. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  404. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  405. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  406. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  407. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  408. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  409. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  410. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  411. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  412. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  413. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  414. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  415. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  416. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  417. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  418. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  419. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  420. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  421. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  422. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  423. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  424. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  425. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  426. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  427. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  428. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  429. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  430. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  431. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  432. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  433. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  434. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  435. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  436. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  437. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  438. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  439. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  440. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  441. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  442. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  443. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  444. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  445. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  446. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +14 -18
  447. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  448. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  449. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  450. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +2 -0
  451. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  452. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  453. package/templates/be/skeleton-lite/apps/api/src/main.ts +2 -3
  454. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  455. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  456. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  457. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  458. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  459. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  460. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  461. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  462. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  463. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  464. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  465. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  466. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  467. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  468. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  469. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  470. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  471. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  472. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  473. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  474. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  475. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  476. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  477. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  478. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  479. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  480. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  481. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  482. package/templates/be/table/migration.sql.tpl +1 -0
  483. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  484. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  485. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  486. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  487. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  488. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  489. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  490. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  491. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  492. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  493. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  494. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  495. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  496. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  497. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  498. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  499. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  500. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  501. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  502. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  503. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  504. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  505. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  506. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  507. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  508. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  509. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  510. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  511. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  512. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  513. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  514. package/upgrade/check.mjs +3 -1
  515. package/upgrade/index.mjs +53 -40
@@ -0,0 +1,343 @@
1
+ // R254-R256 inspect the final database access state, so later revokes and policy drops retire old browser doors.
2
+ import { found } from './read.mjs';
3
+ import { parseSql } from '../sql/pg-parse.mjs';
4
+ import { eachInOrder } from '../../lib/in-order.mjs';
5
+
6
+ export const DB_BE_ONLY_ACCESS = 'DB_BE_ONLY_ACCESS';
7
+ export const DB_REALTIME_UNLISTED = 'DB_REALTIME_UNLISTED';
8
+ export const DB_APP_ROLE = 'DB_APP_ROLE';
9
+ export const DB_FORCE_RLS_DEFAULT = 'DB_FORCE_RLS_DEFAULT';
10
+ export const DB_POLICY_TAUTOLOGY = 'DB_POLICY_TAUTOLOGY';
11
+ export const DB_VIEW_UNSAFE = 'DB_VIEW_UNSAFE';
12
+ export const DB_STORAGE_PREFIX = 'DB_STORAGE_PREFIX';
13
+
14
+ const sval = (value) => value?.String?.sval;
15
+ const names = (values) => (values ?? []).map(sval).filter(Boolean).join('.');
16
+ const qualify = (name) => name.includes('.') ? name : `public.${name}`;
17
+ const relationName = (relation, schema = 'public') => `${relation.schemaname ?? schema}.${relation.relname}`;
18
+ const roleName = (role) => role?.rolename ?? (role?.roletype === 'ROLESPEC_PUBLIC' ? 'public' : null);
19
+ const unwrap = (node) => node?.TypeCast ? unwrap(node.TypeCast.arg) : node;
20
+ const stringValue = (node) => unwrap(node)?.A_Const?.sval?.sval;
21
+ const operator = (node) => names(node.name);
22
+ const publicRead = (policy) => policy.cmd === 'select' && policy.name.endsWith('_public_read');
23
+ const anonymous = (role) => role === 'anon' || role === 'public';
24
+ const browserRole = (role) => anonymous(role) || role === 'authenticated';
25
+ const AND = ['AND', 'EXPR'].join('_');
26
+ const OR = ['OR', 'EXPR'].join('_');
27
+ const NOT = ['NOT', 'EXPR'].join('_');
28
+ const SEQUENCE = ['OBJECT', 'SEQUENCE'].join('_');
29
+ const MATVIEW = ['OBJECT', 'MATVIEW'].join('_');
30
+ const note = (code, source, message, extra = {}) => found(code, source.file, `${source.file}:${source.line} ${message}`, { line: source.line, ...extra });
31
+
32
+ function someNode(node, predicate) {
33
+ if (!node || typeof node !== 'object') return false;
34
+ if (predicate(node)) return true;
35
+ return Object.values(node).some((child) => someNode(child, predicate));
36
+ }
37
+
38
+ function structural(node) {
39
+ if (Array.isArray(node)) return node.map(structural);
40
+ if (!node || typeof node !== 'object') return node;
41
+ return Object.fromEntries(Object.entries(node).filter(([key]) => key !== 'location').map(([key, value]) => [key, structural(value)]));
42
+ }
43
+
44
+ function principalCall(call, helpers) {
45
+ const name = names(call.funcname);
46
+ if (['auth.uid', 'auth.jwt'].includes(name) || helpers.has(name)) return true;
47
+ return ['current_setting', 'pg_catalog.current_setting'].includes(name)
48
+ && ['request.jwt.claims', 'request.jwt.claim.sub'].includes(stringValue(call.args?.[0]));
49
+ }
50
+
51
+ const hasPrincipal = (node, helpers) => someNode(node, (item) => Boolean(item.FuncCall && principalCall(item.FuncCall, helpers)));
52
+ const literalFalse = (node) => unwrap(node)?.A_Const?.boolval?.boolval === false;
53
+
54
+ function principalPredicate(expression, helpers) {
55
+ const node = unwrap(expression);
56
+ if (!node) return false;
57
+ if (literalFalse(node)) return true;
58
+ if (node.NullTest || node.BooleanTest) return false;
59
+ const bool = node.BoolExpr;
60
+ if (bool?.boolop === AND) return bool.args.some((arg) => principalPredicate(arg, helpers));
61
+ if (bool?.boolop === OR) return bool.args.every((arg) => principalPredicate(arg, helpers));
62
+ if (bool?.boolop === NOT) return false;
63
+ const expr = node.A_Expr;
64
+ if (expr && JSON.stringify(structural(expr.lexpr)) === JSON.stringify(structural(expr.rexpr))) return false;
65
+ return hasPrincipal(node, helpers);
66
+ }
67
+
68
+ async function principalHelpers(analyses) {
69
+ const functions = analyses.flatMap(({ facts }) => facts.functions);
70
+ const bodies = [];
71
+ await eachInOrder(functions, async (fn) => {
72
+ const body = (fn.node.options ?? []).find((option) => option.DefElem?.defname === 'as')?.DefElem?.arg?.List?.items?.[0]?.String?.sval;
73
+ if (!body) return;
74
+ try { bodies.push({ name: fn.name, ast: await parseSql(body) }); } catch { /* PL/pgSQL safety is checked separately. */ }
75
+ });
76
+ const helpers = new Set();
77
+ for (let pass = 0; pass < bodies.length; pass += 1) {
78
+ for (const body of bodies) if (hasPrincipal(body.ast, helpers)) helpers.add(body.name);
79
+ }
80
+ return helpers;
81
+ }
82
+
83
+ function applyPolicies(policies, events, file) {
84
+ for (const event of events) {
85
+ const item = event.kind === 'create' ? event.policy : event;
86
+ const key = `${item.schema}.${item.table}/${item.name}`;
87
+ if (event.kind === 'drop') policies.delete(key);
88
+ else if (event.kind === 'create') policies.set(key, { ...item, file });
89
+ else policies.set(key, { ...policies.get(key), ...item, file, cmd: policies.get(key)?.cmd ?? 'all' });
90
+ }
91
+ }
92
+
93
+ function grantTargets(grant) {
94
+ if (!['OBJECT_TABLE', SEQUENCE].includes(grant.objtype)) return [];
95
+ if (grant.targtype === 'ACL_TARGET_ALL_IN_SCHEMA') return grant.objects.map((object) => `${sval(object)}.*`);
96
+ return grant.objects.filter((object) => object.RangeVar).map((object) => relationName(object.RangeVar, grant.defaultSchema));
97
+ }
98
+
99
+ function applyGrant(grants, grant, file) {
100
+ for (const target of grantTargets(grant)) {
101
+ for (const role of grant.grantees) {
102
+ const key = `${target}/${role}`;
103
+ const state = grants.get(key) ?? { target, role, privileges: new Set(), revokedRead: false, objtype: grant.objtype };
104
+ const privileges = grant.all || !grant.privileges.length ? ['all'] : grant.privileges;
105
+ updatePrivileges(state, grant.grant, privileges);
106
+ grants.set(key, { ...state, file, line: grant.line });
107
+ }
108
+ }
109
+ }
110
+
111
+ function updatePrivileges(state, granting, privileges) {
112
+ if (granting) {
113
+ for (const privilege of privileges) state.privileges.add(privilege);
114
+ if (privileges.some((privilege) => ['select', 'all'].includes(privilege))) state.revokedRead = false;
115
+ return;
116
+ }
117
+ if (privileges.includes('all')) state.privileges.clear();
118
+ else for (const privilege of privileges) state.privileges.delete(privilege);
119
+ if (privileges.some((privilege) => ['select', 'all'].includes(privilege))) state.revokedRead = true;
120
+ }
121
+
122
+ function grantAccessFindings(grants, policies, exposed, directAccess) {
123
+ const findings = [];
124
+ for (const state of grants.values()) {
125
+ if (!state.privileges.size || !browserRole(state.role)) continue;
126
+ if (!exposed.has(state.target.split('.')[0]) || directAccess.has(state.target)) continue;
127
+ const readOnly = state.privileges.size === 1 && state.privileges.has('select');
128
+ const policy = policies.some((item) => `${item.schema}.${item.table}` === state.target && publicRead(item)
129
+ && item.roles.some((role) => role === state.role || role === 'public'));
130
+ if (anonymous(state.role) && state.objtype === 'OBJECT_TABLE' && readOnly && policy) continue;
131
+ findings.push(note(DB_BE_ONLY_ACCESS, state, `grants ${state.role} data privileges on ${state.target}; browser data access requires supabase.directAccess, or anonymous SELECT beside *_public_read`, { table: state.target }));
132
+ }
133
+ return findings;
134
+ }
135
+
136
+ function defaultAccessFindings(analyses, exposed) {
137
+ const findings = [];
138
+ for (const { file, facts } of analyses) {
139
+ for (const defaults of facts.defaultPrivileges) {
140
+ if (!defaults.grant || !['OBJECT_TABLE', SEQUENCE].includes(defaults.objtype)) continue;
141
+ if (defaults.schemas?.length && !defaults.schemas.some((schema) => exposed.has(schema))) continue;
142
+ if (defaults.grantees.some(browserRole)) findings.push(note(DB_BE_ONLY_ACCESS, { ...defaults, file }, 'grants default browser data privileges; exposed data grants must be explicit per table'));
143
+ }
144
+ }
145
+ return findings;
146
+ }
147
+
148
+ function policyFindings(policies, exposed, directAccess, appRole, helpers) {
149
+ const findings = [];
150
+ for (const policy of policies) {
151
+ const table = `${policy.schema}.${policy.table}`;
152
+ const listed = directAccess.has(table);
153
+ if (exposed.has(policy.schema) && !listed && policy.roles.some((role) => browserRole(role) && !(anonymous(role) && publicRead(policy)))) {
154
+ findings.push(note(DB_BE_ONLY_ACCESS, policy, `policy ${policy.name} opens ${table} to browser roles; list intentional access in supabase.directAccess`, { table, policy: policy.name }));
155
+ }
156
+ if (policy.roles.every((role) => role === appRole) || publicRead(policy)) continue;
157
+ const predicates = [policy.qual, policy.withCheck].filter((expression) => expression !== undefined);
158
+ if (!predicates.length || predicates.some((expression) => !principalPredicate(expression, helpers))) {
159
+ findings.push(note(DB_POLICY_TAUTOLOGY, policy, `policy ${policy.name} must constrain a principal; constants, IS NOT NULL and self comparisons grant no ownership boundary`, { table, policy: policy.name }));
160
+ }
161
+ }
162
+ return findings;
163
+ }
164
+
165
+ function roleFindings(analyses, appRole) {
166
+ const findings = [];
167
+ for (const { file, facts } of analyses) {
168
+ for (const statement of facts.statements) findings.push(...roleStatementFindings({ ...statement, file }, appRole));
169
+ for (const grant of facts.grants) {
170
+ if (grant.grant && grant.grantees.includes(appRole) && (grant.all || !grant.privileges.length)) {
171
+ findings.push(note(DB_APP_ROLE, { ...grant, file }, `${appRole} receives ALL privileges; grant the capabilities it uses explicitly`));
172
+ }
173
+ }
174
+ }
175
+ return findings;
176
+ }
177
+
178
+ function roleStatementFindings(statement, appRole) {
179
+ const { type, node } = statement;
180
+ if (!['CreateRoleStmt', 'AlterRoleStmt'].includes(type)) return [];
181
+ const role = type === 'CreateRoleStmt' ? node.role : roleName(node.role);
182
+ if (role !== appRole) return [];
183
+ const options = new Map((node.options ?? []).map(({ DefElem: option }) => [option.defname, option.arg?.Boolean?.boolval]));
184
+ const unsafe = ['superuser', 'bypassrls', 'createdb', 'createrole', 'replication', 'inherit'].some((option) => options.get(option) === true);
185
+ const creationInvalid = type === 'CreateRoleStmt' && (options.get('canlogin') !== true || options.get('inherit') !== false);
186
+ if (!unsafe && !creationInvalid) return [];
187
+ return [note(DB_APP_ROLE, statement, `${appRole} must LOGIN NOINHERIT without SUPERUSER, BYPASSRLS or administrative role attributes`)];
188
+ }
189
+
190
+ function publicationTargets(node, defaultSchema) {
191
+ if (node.for_all_tables) return ['*'];
192
+ return (node.pubobjects ?? []).map(({ PublicationObjSpec: spec }) => {
193
+ if (spec.pubtable?.relation) return relationName(spec.pubtable.relation, defaultSchema);
194
+ return `${spec.name ?? defaultSchema}.*`;
195
+ });
196
+ }
197
+
198
+ function publicationFindings(analyses, directAccess) {
199
+ const tables = new Map();
200
+ for (const { file, facts } of analyses) {
201
+ for (const statement of facts.statements) {
202
+ applyPublication(tables, { ...statement, file });
203
+ }
204
+ }
205
+ return [...tables].filter(([table]) => !directAccess.has(table)).map(([table, source]) => note(DB_REALTIME_UNLISTED, source, `supabase_realtime publishes unlisted ${table}; every browser data table must be in supabase.directAccess`, { table }));
206
+ }
207
+
208
+ function applyPublication(tables, statement) {
209
+ const { type, node, defaultSchema } = statement;
210
+ if (!['CreatePublicationStmt', 'AlterPublicationStmt'].includes(type) || node.pubname !== 'supabase_realtime') return;
211
+ if (type === 'CreatePublicationStmt' || node.action === 'AP_SetObjects') tables.clear();
212
+ for (const table of publicationTargets(node, defaultSchema)) {
213
+ if (node.action === 'AP_DropObjects') tables.delete(table);
214
+ else tables.set(table, statement);
215
+ }
216
+ }
217
+
218
+ function tableStates(analyses) {
219
+ const tables = new Map();
220
+ let owner = null;
221
+ for (const { file, facts } of analyses) {
222
+ owner = null;
223
+ for (const statement of facts.statements) {
224
+ const { type, node, defaultSchema } = statement;
225
+ if (type === 'VariableSetStmt' && ['role', 'session_authorization'].includes(node.name)) owner = stringValue(node.args?.[0]) ?? null;
226
+ const relation = type === 'CreateTableAsStmt' ? node.into?.rel : node.relation;
227
+ if (['CreateStmt', 'CreateTableAsStmt'].includes(type) && relation && node.objtype !== MATVIEW && relation.relpersistence !== 't') {
228
+ tables.set(relationName(relation, defaultSchema), { owner, forced: false, ...statement, file });
229
+ }
230
+ applyTableAlter(tables, { ...statement, file });
231
+ }
232
+ }
233
+ return tables;
234
+ }
235
+
236
+ function applyTableAlter(tables, statement) {
237
+ const { type, node, defaultSchema } = statement;
238
+ if (!node.relation) return;
239
+ const key = relationName(node.relation, defaultSchema);
240
+ const table = tables.get(key) ?? { forced: false };
241
+ if (type === 'AlterObjectSchemaStmt' && node.objectType === 'OBJECT_TABLE') {
242
+ tables.delete(key);
243
+ tables.set(`${node.newschema}.${node.relation.relname}`, { ...table, ...statement });
244
+ }
245
+ if (type !== 'AlterTableStmt' || node.objtype !== 'OBJECT_TABLE') return;
246
+ for (const { AlterTableCmd: cmd } of node.cmds ?? []) {
247
+ if (cmd.subtype === 'AT_ChangeOwner') Object.assign(table, statement, { owner: roleName(cmd.newowner) });
248
+ if (cmd.subtype === 'AT_ForceRowSecurity') table.forced = true;
249
+ if (cmd.subtype === 'AT_NoForceRowSecurity') Object.assign(table, statement, { forced: false });
250
+ }
251
+ tables.set(key, table);
252
+ }
253
+
254
+ function forceFindings(analyses, supabase, appRole) {
255
+ const exemptions = new Set((supabase.forceRlsExemptions ?? []).filter((item) => typeof item?.table === 'string' && typeof item.reason === 'string' && item.reason.trim()).map((item) => qualify(item.table)));
256
+ return [...tableStates(analyses)].filter(([name, table]) => table.owner === appRole && !table.forced && !exemptions.has(name))
257
+ .map(([table, source]) => note(DB_FORCE_RLS_DEFAULT, source, `${appRole} owns ${table} without FORCE ROW LEVEL SECURITY; an exemption needs supabase.forceRlsExemptions with a table and reason`, { table }));
258
+ }
259
+
260
+ function viewFindings(analyses, grants, exposed) {
261
+ const views = new Map();
262
+ for (const { file, facts } of analyses) for (const view of facts.views) views.set(`${view.schema}.${view.name}`, { ...view, file });
263
+ const findings = [];
264
+ for (const [table, view] of views) {
265
+ if (!exposed.has(view.schema) || view.securityInvoker) continue;
266
+ if (['anon', 'authenticated'].every((role) => grants.get(`${table}/${role}`)?.revokedRead)) continue;
267
+ findings.push(note(DB_VIEW_UNSAFE, view, `exposed ${view.kind} ${table} needs security_invoker = true (ordinary views), or SELECT revoked from anon and authenticated`, { table }));
268
+ }
269
+ return findings;
270
+ }
271
+
272
+ function folderPrefix(node) {
273
+ const value = unwrap(node);
274
+ const subscript = value?.A_Indirection;
275
+ const call = subscript?.arg?.FuncCall;
276
+ return names(call?.funcname) === 'storage.foldername'
277
+ && names(unwrap(call?.args?.[0])?.ColumnRef?.fields) === 'name'
278
+ && subscript.indirection?.[0]?.A_Indices?.uidx?.A_Const?.ival?.ival === 1;
279
+ }
280
+
281
+ function ownershipExpression(expression, helpers) {
282
+ const node = unwrap(expression);
283
+ if (!node) return false;
284
+ const op = node.A_Expr;
285
+ if (!op || operator(op) !== '=') return false;
286
+ return (folderPrefix(op.lexpr) && hasPrincipal(op.rexpr, helpers)) || (folderPrefix(op.rexpr) && hasPrincipal(op.lexpr, helpers));
287
+ }
288
+
289
+ function publicBucketExpression(expression) {
290
+ const node = unwrap(expression);
291
+ if (!node) return false;
292
+ const op = node.A_Expr;
293
+ if (!op || operator(op) !== '=') return false;
294
+ const leftColumn = names(unwrap(op.lexpr)?.ColumnRef?.fields);
295
+ const rightColumn = names(unwrap(op.rexpr)?.ColumnRef?.fields);
296
+ return (leftColumn === 'bucket_id' && stringValue(op.rexpr)?.endsWith('_public'))
297
+ || (rightColumn === 'bucket_id' && stringValue(op.lexpr)?.endsWith('_public'));
298
+ }
299
+
300
+ function storageBoundaryExpression(expression, helpers) {
301
+ const node = unwrap(expression);
302
+ if (!node) return false;
303
+ const bool = node.BoolExpr;
304
+ if (bool?.boolop === AND) return bool.args.some((arg) => storageBoundaryExpression(arg, helpers));
305
+ if (bool?.boolop === OR) return bool.args.every((arg) => storageBoundaryExpression(arg, helpers));
306
+ return publicBucketExpression(node) || ownershipExpression(node, helpers);
307
+ }
308
+
309
+ function storagePrefixFindings(policies, helpers) {
310
+ const findings = [];
311
+ for (const policy of policies) {
312
+ if (policy.schema !== 'storage' || policy.table !== 'objects') continue;
313
+ const expressions = [policy.qual, policy.withCheck].filter((expression) => expression !== undefined);
314
+ if (expressions.length && expressions.every((expression) => storageBoundaryExpression(expression, helpers))) continue;
315
+ findings.push(note(DB_STORAGE_PREFIX, policy, `storage.objects policy ${policy.name} must compare storage.foldername(name)[1] with the principal, unless scoped to an *_public bucket`, { policy: policy.name }));
316
+ }
317
+ return findings;
318
+ }
319
+
320
+ /** All new access checks run on the ordered active migrations in either edition, through checkDatabase. */
321
+ export async function databaseAccessFindings({ analyses, exposed, supabase = {} }) {
322
+ const declared = supabase ?? {};
323
+ const appRole = declared.appRole ?? 'app_be';
324
+ const directAccess = new Set((declared.directAccess ?? []).filter((table) => typeof table === 'string').map(qualify));
325
+ const policyMap = new Map();
326
+ const grants = new Map();
327
+ for (const { file, facts } of analyses) {
328
+ applyPolicies(policyMap, facts.policyEvents, file);
329
+ for (const grant of facts.grants) applyGrant(grants, grant, file);
330
+ }
331
+ const policies = [...policyMap.values()];
332
+ const helpers = await principalHelpers(analyses);
333
+ return [
334
+ ...grantAccessFindings(grants, policies, exposed, directAccess),
335
+ ...defaultAccessFindings(analyses, exposed),
336
+ ...policyFindings(policies, exposed, directAccess, appRole, helpers),
337
+ ...publicationFindings(analyses, directAccess),
338
+ ...roleFindings(analyses, appRole),
339
+ ...forceFindings(analyses, declared, appRole),
340
+ ...viewFindings(analyses, grants, exposed),
341
+ ...storagePrefixFindings(policies, helpers),
342
+ ];
343
+ }
@@ -0,0 +1,142 @@
1
+ // R257: an archive is immutable history retired by one explicit active baseline.
2
+ import { lsTree } from '../../api/git/ls-tree.mjs';
3
+ import { show } from '../../api/git/show.mjs';
4
+ import { mergeBase } from '../../api/git/merge-base.mjs';
5
+ import { withoutGitLocalEnv } from '../../lib/git.mjs';
6
+ import { sameText } from '../../lib/same-text.mjs';
7
+ import { found, readText } from './read.mjs';
8
+ import { migrationStamp } from './database-migrations.mjs';
9
+ import { DB_BASELINE_SHAPE, DB_ARCHIVE_MUTATED, MIGRATIONS_DIR, MIGRATIONS_ARCHIVE_DIR } from './database-constants.mjs';
10
+
11
+ const ACTIVE = /^supabase\/migrations\/\d{14}_[^/]+\.sql$/;
12
+ const ARCHIVED = /^supabase\/migrations\/_archive\/\d{14}_[^/]+\.sql$/;
13
+ const stampOf = (file) => /(?:^|\/)(\d{14})_/.exec(file)?.[1] ?? null;
14
+
15
+ /** Header comments only: SQL literals and later comments cannot impersonate a baseline marker. */
16
+ export function baselineMarkers(text) {
17
+ const lines = String(text ?? '').replace(/^\uFEFF/, '').split(/\r?\n/);
18
+ const header = [];
19
+ for (const line of lines) {
20
+ if (line.trim() && !/^\s*--/.test(line)) break;
21
+ header.push(line);
22
+ }
23
+ return header.flatMap((line) => {
24
+ const marker = /^\s*--\s*starci:baseline\s+(\d{14})\s*$/.exec(line);
25
+ return marker ? [marker[1]] : [];
26
+ });
27
+ }
28
+
29
+ function explicitRestamp(text, archiveBoundary, newStamp) {
30
+ const header = String(text ?? '').split(/\r?\n/).findIndex((line) => line.trim() && !/^\s*--/.test(line));
31
+ const lines = String(text ?? '').split(/\r?\n/).slice(0, header < 0 ? undefined : header);
32
+ return lines.some((line) => {
33
+ const marker = /^\s*--\s*starci:restamp\s+(\d{14})\s+(\d{14})\s*$/.exec(line);
34
+ return marker && migrationStamp(marker[1]) !== null && marker[1] >= archiveBoundary && marker[2] === newStamp;
35
+ });
36
+ }
37
+
38
+ /** Synchronous git seam: (args, {cwd}) -> {ok, stdout}; no diff or blob containing SQL is printed. */
39
+ function baselineGit(args, { cwd }) {
40
+ const [verb, ...rest] = args;
41
+ if (verb === 'merge-base') {
42
+ const sha = mergeBase(cwd, rest[0], rest[1]);
43
+ return { ok: sha !== null, stdout: sha ?? '' };
44
+ }
45
+ const result = (verb === 'ls-tree' ? lsTree : show)(rest, { cwd, env: withoutGitLocalEnv(process.env), maxBuffer: 64 * 1024 * 1024 });
46
+ return { ok: !result.error && result.status === 0, stdout: String(result.stdout ?? '') };
47
+ }
48
+
49
+ function snapshot(repoRoot, git, base) {
50
+ let revision = null;
51
+ for (const ref of base ? [base] : ['origin/main', 'main']) {
52
+ const result = git(['merge-base', 'HEAD', ref], { cwd: repoRoot });
53
+ if (result?.ok && result.stdout.trim()) { revision = result.stdout.trim(); break; }
54
+ }
55
+ // An unconfigured repository still has a committed archive that must remain immutable.
56
+ revision ??= 'HEAD';
57
+ const result = git(['ls-tree', '-r', '--name-only', '-z', revision, '--', MIGRATIONS_DIR], { cwd: repoRoot });
58
+ return result?.ok ? { revision, files: result.stdout.split('\0').filter(Boolean) } : null;
59
+ }
60
+
61
+ const oldText = (repoRoot, git, revision, file) => {
62
+ const result = git(['show', `${revision}:./${file}`], { cwd: repoRoot });
63
+ return result?.ok ? String(result.stdout) : null;
64
+ };
65
+
66
+ function shapeFindings(repoRoot, active, archived) {
67
+ const marked = active.flatMap((file) => baselineMarkers(readText(repoRoot, file)).map((stamp) => ({ file, stamp })));
68
+ if (!archived.length) return { findings: [], marked };
69
+ const findings = [];
70
+ if (marked.length !== 1) {
71
+ findings.push(found(DB_BASELINE_SHAPE, MIGRATIONS_DIR, 'a nonempty _archive requires exactly one active -- starci:baseline <archived-last-stamp> header marker'));
72
+ return { findings, marked };
73
+ }
74
+ const latest = archived.map(stampOf).filter((stamp) => stamp && migrationStamp(stamp) !== null).sort((left, right) => left.localeCompare(right)).at(-1);
75
+ const marker = marked[0];
76
+ const current = stampOf(marker.file);
77
+ if (!latest || marker.stamp !== latest || migrationStamp(marker.stamp) === null) {
78
+ findings.push(found(DB_BASELINE_SHAPE, marker.file, 'the baseline header must identify the last valid archived migration stamp'));
79
+ }
80
+ if (!current || migrationStamp(current) === null || (latest && current <= latest && !explicitRestamp(readText(repoRoot, marker.file), latest, current))) {
81
+ findings.push(found(DB_BASELINE_SHAPE, marker.file, 'the baseline stamp must follow the archive boundary, or document the exact boundary and replacement in a -- starci:restamp <old> <new> header'));
82
+ }
83
+ for (const file of archived) {
84
+ // Archived files are immutable history and never applied: only the name shape is checked, not stamp validity.
85
+ if (!ARCHIVED.test(file)) findings.push(found(DB_BASELINE_SHAPE, file, 'archived migrations retain their original timestamped SQL filename'));
86
+ }
87
+ return { findings, marked };
88
+ }
89
+
90
+ function archiveFindings(repoRoot, current, archived, marked, prior, git) {
91
+ if (!prior) return [];
92
+ const findings = [];
93
+ const oldFiles = new Set(prior.files);
94
+ const newBaseline = marked.length === 1 && !oldFiles.has(marked[0].file);
95
+ for (const file of prior.files.filter((entry) => entry.startsWith(`${MIGRATIONS_ARCHIVE_DIR}/`))) {
96
+ if (!current.has(file) || !sameText(readText(repoRoot, file), oldText(repoRoot, git, prior.revision, file))) {
97
+ findings.push(found(DB_ARCHIVE_MUTATED, file, 'an archived migration is immutable: restore its committed path and content'));
98
+ }
99
+ }
100
+ for (const file of archived.filter((entry) => !oldFiles.has(entry))) {
101
+ const source = `${MIGRATIONS_DIR}/${file.slice(MIGRATIONS_ARCHIVE_DIR.length + 1)}`;
102
+ const moved = oldFiles.has(source) && !current.has(source);
103
+ const unchanged = moved && sameText(readText(repoRoot, file), oldText(repoRoot, git, prior.revision, source));
104
+ if (!newBaseline || !unchanged) {
105
+ findings.push(found(DB_ARCHIVE_MUTATED, file, 'a new archive entry must move an existing active migration unchanged in the same diff that adds the marked baseline'));
106
+ }
107
+ }
108
+ return findings;
109
+ }
110
+
111
+ /** A cutover may archive only unchanged, committed source files from the same history the finder judges. */
112
+ export function baselineSourceFindings({ repoRoot, files = [], all = files, git = baselineGit, base }) {
113
+ const active = all.filter((file) => ACTIVE.test(file));
114
+ const archived = all.filter((file) => file.startsWith(`${MIGRATIONS_ARCHIVE_DIR}/`));
115
+ const prior = snapshot(repoRoot, git, base);
116
+ if (!prior) return [found(DB_BASELINE_SHAPE, MIGRATIONS_DIR, 'a baseline cutover requires readable committed migration history')];
117
+ const { findings, marked } = shapeFindings(repoRoot, active, archived);
118
+ const current = new Set(all);
119
+ findings.push(...archiveFindings(repoRoot, current, archived, marked, prior, git));
120
+ const oldFiles = new Set(prior.files);
121
+ for (const file of active) {
122
+ if (!oldFiles.has(file) || !sameText(readText(repoRoot, file), oldText(repoRoot, git, prior.revision, file))) {
123
+ findings.push(found(DB_ARCHIVE_MUTATED, file, 'baseline sources must be committed unchanged before they enter the archive'));
124
+ }
125
+ }
126
+ for (const file of prior.files.filter((entry) => ACTIVE.test(entry))) {
127
+ const retired = `${MIGRATIONS_ARCHIVE_DIR}/${file.slice(MIGRATIONS_DIR.length + 1)}`;
128
+ if (!current.has(file) && !current.has(retired)) findings.push(found(DB_ARCHIVE_MUTATED, file, 'restore removed migration history before creating a baseline'));
129
+ }
130
+ return findings;
131
+ }
132
+
133
+ /** R257 content finder, synchronous in both editions; all paths judge cross-file cutovers even under --fast. */
134
+ export function databaseBaselineFindings({ repoRoot, files = [], all, repo, isRoot, inScope, base, git = baselineGit }) {
135
+ if (isRoot === false || (repo?.edition && !['lite', 'full'].includes(repo.edition))) return [];
136
+ const inventory = Array.isArray(all) ? all : files;
137
+ const active = inventory.filter((file) => ACTIVE.test(file));
138
+ const archived = inventory.filter((file) => file.startsWith(`${MIGRATIONS_ARCHIVE_DIR}/`));
139
+ const { findings, marked } = shapeFindings(repoRoot, active, archived);
140
+ findings.push(...archiveFindings(repoRoot, new Set(inventory), archived, marked, snapshot(repoRoot, git, base), git));
141
+ return typeof inScope === 'function' ? findings.filter((finding) => inScope(finding.path) || finding.path === MIGRATIONS_DIR || inventory.some((file) => file.startsWith(`${MIGRATIONS_DIR}/`) && inScope(file))) : findings;
142
+ }