@starci/hfs 4.3.0 → 4.4.1

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 (538) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/db/migrate.mjs +361 -0
  4. package/db/post-cutover.mjs +136 -0
  5. package/emit/contracts.mjs +2 -2
  6. package/emit/db-types.mjs +12 -3
  7. package/lint/run.mjs +3 -3
  8. package/package.json +4 -1
  9. package/release/gate.mjs +298 -0
  10. package/release/index.mjs +103 -0
  11. package/release/push.mjs +110 -0
  12. package/report/sonar.mjs +1 -1
  13. package/runtime/config.example.yaml +7 -6
  14. package/runtime/engine/config.mjs +10 -18
  15. package/runtime/engine/removed-vocabulary.mjs +0 -2
  16. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  17. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  18. package/runtime/knowledge/hfs/rules.yaml +2855 -163
  19. package/runtime/knowledge/hfs/slots.yaml +333 -62
  20. package/runtime/knowledge/patterns/be/api.yaml +32 -5
  21. package/runtime/knowledge/patterns/be/cli.yaml +14 -4
  22. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  23. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  24. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  25. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  26. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  27. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  28. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  29. package/runtime/knowledge/patterns/be/webhooks.yaml +23 -4
  30. package/runtime/knowledge/patterns/fe/folder.yaml +1 -0
  31. package/runtime/knowledge/sonar-gate.yaml +1 -1
  32. package/runtime/modules/kernel/failure-codes.yaml +3091 -43
  33. package/runtime/modules/kernel/removed-vocabulary.yaml +30 -0
  34. package/runtime/modules/models/runtimes.yaml +5 -7
  35. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  36. package/runtime/packages/cli/src/catalog.generated.mjs +13769 -0
  37. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  38. package/runtime/scripts/api/git/add.mjs +5 -0
  39. package/runtime/scripts/api/git/init.mjs +7 -0
  40. package/runtime/scripts/api/git/lib.mjs +4 -1
  41. package/runtime/scripts/api/git/tag.mjs +5 -0
  42. package/runtime/scripts/api/node/lib.mjs +20 -0
  43. package/runtime/scripts/api/node/run-node.mjs +6 -0
  44. package/runtime/scripts/api/sops/lib.mjs +42 -1
  45. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  46. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  47. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  48. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  49. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  50. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  51. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +1 -8
  52. package/runtime/scripts/hfs/architecture/backend.mjs +2 -7
  53. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  54. package/runtime/scripts/hfs/architecture/config.mjs +1 -1
  55. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  56. package/runtime/scripts/hfs/architecture/dead-exports.mjs +9 -41
  57. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  58. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  59. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  60. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  61. package/runtime/scripts/hfs/architecture/index.mjs +1 -1
  62. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +28 -13
  63. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  64. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  65. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  66. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  67. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -2
  68. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  69. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +211 -11
  70. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  72. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  73. package/runtime/scripts/hfs/architecture/tiers.mjs +4 -4
  74. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +8 -2
  75. package/runtime/scripts/hfs/architecture.mjs +1 -1
  76. package/runtime/scripts/hfs/check-results.mjs +28 -0
  77. package/runtime/scripts/hfs/check.mjs +46 -47
  78. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  79. package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
  80. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  81. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  82. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  83. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  84. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  85. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  86. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  87. package/runtime/scripts/hfs/manifest-shape.mjs +69 -9
  88. package/runtime/scripts/hfs/param-names.mjs +1 -1
  89. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  90. package/runtime/scripts/hfs/rule-catalog.mjs +158 -11
  91. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  92. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  93. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  94. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  95. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  96. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  97. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  98. package/runtime/scripts/hfs/rules/cli.mjs +1 -1
  99. package/runtime/scripts/hfs/rules/contract-compat.mjs +1 -1
  100. package/runtime/scripts/hfs/rules/contract.mjs +67 -6
  101. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  102. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  103. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  104. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  105. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  106. package/runtime/scripts/hfs/rules/database-migrations.mjs +286 -7
  107. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  108. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  109. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  110. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  111. package/runtime/scripts/hfs/rules/database.mjs +9 -0
  112. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  113. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  114. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  115. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  116. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  117. package/runtime/scripts/hfs/rules/edition.mjs +9 -8
  118. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  119. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  120. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  121. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  122. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  123. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  124. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  125. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  126. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  127. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  128. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  129. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  130. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  131. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  132. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  133. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  134. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  135. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  136. package/runtime/scripts/hfs/rules/secrets.mjs +13 -0
  137. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  138. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  139. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  140. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  141. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  142. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  143. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +41 -1
  144. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  145. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +2 -2
  146. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  147. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  148. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  149. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  150. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  151. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  152. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  153. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  154. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  155. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  156. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  157. package/runtime/scripts/hfs/secret-init.mjs +127 -19
  158. package/runtime/scripts/hfs/secret-sops.mjs +38 -6
  159. package/runtime/scripts/hfs/secret-store.mjs +56 -54
  160. package/runtime/scripts/hfs/secret.mjs +34 -5
  161. package/runtime/scripts/hfs/slot-imports.mjs +18 -5
  162. package/runtime/scripts/hfs/slots.mjs +3 -2
  163. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  164. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  165. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  166. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  167. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  168. package/runtime/scripts/hfs/typescript-programs.mjs +0 -8
  169. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  170. package/runtime/scripts/lib/compose-values.mjs +11 -0
  171. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  172. package/runtime/scripts/lib/env.mjs +2 -2
  173. package/runtime/scripts/lib/git.mjs +1 -1
  174. package/runtime/scripts/lib/i18n.mjs +0 -2
  175. package/runtime/scripts/lib/json-clone.mjs +61 -0
  176. package/runtime/scripts/lib/language.mjs +1 -1
  177. package/runtime/scripts/lib/list.mjs +4 -0
  178. package/runtime/scripts/lib/roots.mjs +2 -4
  179. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  180. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  181. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  182. package/runtime/scripts/lib/stack-services.mjs +1 -1
  183. package/runtime/scripts/lib/swarm-stack.mjs +5 -18
  184. package/runtime/scripts/lib/trim-end.mjs +8 -0
  185. package/runtime/scripts/lib/ts-analysis.mjs +5 -2
  186. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  187. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  188. package/scaffold/add-app.mjs +77 -59
  189. package/scaffold/add-cli-lite.mjs +14 -6
  190. package/scaffold/add-compose.mjs +79 -0
  191. package/scaffold/add-database-lite.mjs +14 -0
  192. package/scaffold/add-fe-overlay.mjs +1 -0
  193. package/scaffold/add-fe-page.mjs +1 -0
  194. package/scaffold/add-inbox-lite.mjs +74 -42
  195. package/scaffold/add-table.mjs +110 -87
  196. package/scaffold/add.mjs +159 -44
  197. package/scaffold/app.mjs +124 -39
  198. package/scaffold/edition-gate.mjs +3 -3
  199. package/scaffold/lite-exports.mjs +2 -1
  200. package/scaffold/service.mjs +2 -2
  201. package/scaffold/supabase-ports.mjs +1 -1
  202. package/src/main.mjs +229 -116
  203. package/stack/age-install.mjs +37 -0
  204. package/stack/backup.mjs +100 -0
  205. package/stack/build-args.mjs +42 -0
  206. package/stack/build.mjs +173 -0
  207. package/stack/cli.mjs +130 -0
  208. package/stack/config.mjs +57 -0
  209. package/stack/context.mjs +57 -0
  210. package/stack/converge.mjs +107 -0
  211. package/stack/deploy.mjs +301 -0
  212. package/stack/digest.mjs +16 -0
  213. package/stack/docker-transport.mjs +53 -0
  214. package/stack/edge-alias.mjs +118 -0
  215. package/stack/errors.mjs +21 -0
  216. package/stack/image-scan.mjs +152 -0
  217. package/stack/ingress-create.mjs +114 -0
  218. package/stack/ingress-publish.mjs +273 -0
  219. package/stack/ingress.mjs +706 -0
  220. package/stack/jobs.mjs +51 -0
  221. package/stack/live-restore.mjs +177 -0
  222. package/stack/prepare.mjs +266 -0
  223. package/stack/private-file.mjs +22 -0
  224. package/stack/provision.mjs +90 -0
  225. package/stack/prune.mjs +46 -0
  226. package/stack/release-store.mjs +87 -0
  227. package/stack/remote.mjs +205 -0
  228. package/stack/render.mjs +149 -0
  229. package/stack/retire.mjs +47 -0
  230. package/stack/sealed.mjs +149 -0
  231. package/stack/task.mjs +42 -0
  232. package/stack/verify.mjs +99 -0
  233. package/sync/ci-commands.mjs +13 -0
  234. package/sync/format.mjs +1 -1
  235. package/sync/hygiene.mjs +1 -1
  236. package/sync/index.mjs +110 -54
  237. package/sync/stack-images.mjs +127 -0
  238. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  239. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  240. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  241. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  242. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  243. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  244. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  245. package/templates/app/codegen-graphql.mjs.partial +13 -2
  246. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  247. package/templates/app/codegen-prelude.mjs.partial +8 -0
  248. package/templates/app/gitignore +3 -0
  249. package/templates/app/gitignore-lite +3 -0
  250. package/templates/app/package-scripts/package.json +1 -0
  251. package/templates/app/package-scripts/supabase.json +7 -0
  252. package/templates/app/package-scripts-lite/package.json +1 -7
  253. package/templates/app/skeleton/.editorconfig +1 -0
  254. package/templates/app/skeleton/.gitattributes +7 -1
  255. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  256. package/templates/app/skeleton/.nvmrc +1 -1
  257. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  258. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  259. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  260. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  261. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  262. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  263. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  264. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  265. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  266. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  267. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  268. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  269. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  270. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  271. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  272. package/templates/app/skeleton/README.md +6 -6
  273. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  274. package/templates/app/skeleton-lite/.editorconfig +16 -0
  275. package/templates/app/skeleton-lite/.gitattributes +8 -0
  276. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  277. package/templates/app/skeleton-lite/.nvmrc +1 -0
  278. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  279. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +178 -1
  280. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  281. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  282. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  283. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  284. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  285. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  286. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  287. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  288. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  289. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  290. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  291. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  292. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  293. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  294. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  295. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  296. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  297. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  298. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  299. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  300. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  301. package/templates/app/skeleton-lite/README.md +5 -3
  302. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  303. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  304. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  305. package/templates/be/image/worker/Dockerfile +1 -1
  306. package/templates/be/inbox/database-client.spec.ts.tpl +17 -18
  307. package/templates/be/inbox/database-client.ts.tpl +12 -11
  308. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  309. package/templates/be/inbox/event-index.ts.tpl +1 -0
  310. package/templates/be/inbox/events.json.tpl +10 -0
  311. package/templates/be/inbox/migration.sql.tpl +1 -0
  312. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  313. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  314. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  315. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  316. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  317. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  318. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  319. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  320. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  321. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  322. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  323. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  324. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  325. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  326. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  327. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  328. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  329. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  330. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  331. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  332. package/templates/be/inbox/service.spec.ts.tpl +42 -9
  333. package/templates/be/inbox/service.ts.tpl +23 -8
  334. package/templates/be/inbox/webhook-door.spec.ts.tpl +1 -1
  335. package/templates/be/inbox/webhook-door.ts.tpl +2 -2
  336. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  337. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  338. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  339. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  340. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  341. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  342. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  343. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  344. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  345. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  346. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  347. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  348. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  349. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  350. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  351. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  352. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  353. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  354. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  355. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  356. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  357. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  358. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  359. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  360. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  362. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  363. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  364. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  365. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  366. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  367. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  368. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  369. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  370. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  371. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  372. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  373. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  374. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  375. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  376. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  377. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  378. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  379. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  380. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  381. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  382. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  383. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  384. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  385. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  386. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  387. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  388. package/templates/be/skeleton/apps/__app__/src/app.module.ts +11 -9
  389. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  390. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  391. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +2 -0
  392. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  393. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  394. package/templates/be/skeleton/apps/__app__/src/main.ts +15 -1
  395. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  396. package/templates/be/skeleton/apps/cli/src/cli.options.ts +6 -14
  397. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  398. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  399. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  400. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  401. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  402. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  403. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  404. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  405. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  406. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  407. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  408. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  409. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  410. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  411. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  412. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  413. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  414. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  415. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  416. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  417. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  418. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  419. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  420. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  421. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  422. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  423. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  424. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  425. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  426. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  427. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  428. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  429. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  430. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  431. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  432. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  433. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  434. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  435. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  436. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  437. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  438. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  439. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  440. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  441. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  442. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  443. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  444. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  445. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  446. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  447. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  448. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  449. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  450. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  451. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  452. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  453. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  454. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  455. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  456. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  457. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  458. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  459. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  460. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  461. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  462. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  463. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  464. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  465. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  466. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  467. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  468. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  469. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +14 -18
  470. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  471. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  472. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  473. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +2 -0
  474. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  475. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  476. package/templates/be/skeleton-lite/apps/api/src/main.ts +2 -3
  477. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  478. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  479. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  480. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  481. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  482. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  483. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  484. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  485. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  486. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  487. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  488. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  489. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  490. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  491. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  492. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  493. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  494. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  495. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  496. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  497. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  498. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  499. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  500. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  501. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  502. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  503. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  504. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  505. package/templates/be/table/migration.sql.tpl +1 -0
  506. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  507. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  508. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  509. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  510. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  511. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  512. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  513. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  514. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  515. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  516. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  517. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  518. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  519. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  520. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  521. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  522. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  523. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  524. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  525. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  526. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  527. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  528. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  529. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  530. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  531. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  532. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  533. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  534. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  535. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  536. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  537. package/upgrade/check.mjs +3 -1
  538. package/upgrade/index.mjs +54 -41
@@ -56,11 +56,14 @@
56
56
  // findings fail the check.
57
57
  import fs from 'node:fs';
58
58
  import path from 'node:path';
59
+ import { appWiring, BOOT_PROOF_CODE } from './app-wiring.mjs';
60
+ import { coverageFrom as coverageFromReport } from './coverage-from.mjs';
59
61
  import { skillRoot } from '../../engine/runtime-root.mjs';
60
62
  import { ARCHITECTURE_RULE_IDS, checkArchitecture } from './architecture/index.mjs';
61
63
  import { parseYaml } from '../../engine/yaml.mjs';
62
64
  import { readCatalogOnce } from './catalog-once.mjs';
63
- import { APP_SCOPE, HFS_DECLARATION_FILE, appRelativeMessages, HfsSlotsError, SIDES, createSlotResolver, loadRuleCatalog, loadSlotManifest, readRepoDeclaration, resolveRepoDeclaration } from './slots.mjs';
65
+ import { APP_SCOPE, HFS_DECLARATION_FILE, appRelativeMessages, HfsSlotsError, SIDES, createSlotResolver, loadSlotManifest, readRepoDeclaration, resolveRepoDeclaration } from './slots.mjs';
66
+ import { judgedFindings, summarize, withWhy } from './check-results.mjs';
64
67
  import { RUNTIME_KIND } from './manifest-shape.mjs';
65
68
  import { lsFiles } from '../api/git/ls-files.mjs';
66
69
  import { diff as gitDiff } from '../api/git/diff.mjs';
@@ -82,22 +85,18 @@ import { testTopologyFindings } from './rules/test-topology.mjs';
82
85
  import { feNoTestsFindings } from './rules/fe-no-tests.mjs';
83
86
  import { editionFindings } from './rules/edition.mjs';
84
87
  import { feStackFindings } from './rules/fe-stack.mjs';
88
+ import { A15_CODES, a15Findings } from './rules/a15-findings.mjs';
89
+ import { trackedStacksGateFindings } from './stacks-gate.mjs';
85
90
  import { byCodeUnit } from '../lib/list.mjs';
86
91
  import { pinFindings } from './pin-findings.mjs';
87
92
  import { requiredFileApplies } from './slot-required.mjs';
93
+ import { REFUSAL_CODES } from './refusal-codes.mjs';
94
+ export { REFUSAL_CODES } from './refusal-codes.mjs';
88
95
  // The database rules (R213-R216) read SQL through a WASM parser, so they are async: `starci app check` calls them beside the other emitters and passes the findings in as `extraFindings`.
89
96
  export { checkDatabase } from './rules/database.mjs';
90
97
 
91
98
  const CANON_PINS_FILE = 'knowledge/hfs/canon-pins.yaml';
92
99
  export const FAILURE_CODES_FILE = 'modules/kernel/failure-codes.yaml';
93
- /**
94
- * The codes `starci app check` reports when it cannot judge (an unreadable repository, a refused declaration or manifest, a missing
95
- * formatter): infrastructure refusals, never obligations, so no rule of knowledge/hfs/rules.yaml owns them.
96
- */
97
- export const REFUSAL_CODES = Object.freeze([
98
- 'HFS_REPO_UNREADABLE',
99
- 'HFS_DECLARATION_INVALID', 'HFS_MANIFEST_MAJOR_MISMATCH', 'HFS_MANIFEST_INVALID', 'HFS_FORMAT_TOOL_MISSING', 'HFS_EDITION_INVALID',
100
- ]);
101
100
  /** The codes this module emits that are not the slot loader's own: the why bundle of packages/hfs ships exactly these plus the loader's. */
102
101
  export const CHECK_CODES = Object.freeze([
103
102
  'HFS_SLOT_UNDECLARED', 'HFS_SLOT_NOT_ENABLED', 'HFS_SLOT_AMBIGUOUS', 'HFS_TRACKED_MUST_BE_IGNORED', 'HFS_FORBIDDEN_PRESENT',
@@ -107,8 +106,11 @@ export const CHECK_CODES = Object.freeze([
107
106
  'BE_TEST_TOPOLOGY', 'BE_SPEC_PLACEMENT', 'HFS_REPO_LOCAL_CHECK', 'HFS_LINT_SUPPRESSION_FILE', 'HFS_PROOF_COMMAND_FILE_MISSING', 'HFS_PEER_INTEGRATION_MISSING', 'BE_INTEGRATION_SPEC_MISSING', 'FE_GRAPHQL_CONTRACT', 'FE_NO_TESTS', 'HFS_MONO_WORKSPACES', 'HFS_MONO_FE_WORKSPACE', 'HFS_MONO_NEST_PROJECTS', 'HFS_MONO_WORKSPACE_DEP', 'BE_CLI_REQUIRED', 'FE_WIRE_GENERATED', 'FE_I18N_PLACEMENT', 'FE_I18N_CATALOG', 'HFS_SERVICE_PLACEMENT', 'HFS_IMAGE_UNPINNED', 'HFS_SERVICE_STACK_DECLARATION', 'HFS_EVENT_CONTRACT', 'BE_ASYNC_SPEC_MISSING', 'BE_SAGA_STEP_COMPENSATION', 'BE_SAGA_STATE_VERSIONED', 'BE_SAGA_EVENT_CONTRACT', 'BE_SAGA_CONSUMER_DEDUPE', 'BE_EVENT_CLASS_CONTRACT', 'BE_PATTERN_SPEC_MISSING', 'BE_CONTRACT_BREAKING', 'BE_KIND_DECLARATION', 'BE_KIND_EMPTY', 'HFS_DOCKER_BUILD_CONTEXT', 'HFS_DOCKER_STAGES', 'HFS_DOCKER_ENTRY', 'HFS_DOCKER_BASE_PIN', 'HFS_DOCKER_SECRETS', 'BE_SAGA_E2E_MISSING',
108
107
  'FE_STACK_FORBIDDEN', 'HFS_EDITION_FORBIDDEN_PRESENT', 'HFS_LITE_SECRET_CUSTODY', 'HFS_STARCIWORK_LITE_FORBIDDEN',
109
108
  'DB_MIGRATION_SHAPE', 'DB_RLS_REQUIRED', 'DB_DYNAMIC_DDL', 'DB_POLICY_SHAPE', 'DB_DEFINER_SAFE', 'DB_STORAGE_POLICY', 'DB_CONFIG_POLICY', 'DB_TYPES_DRIFT',
109
+ 'HFS_DOCKER_HEALTHCHECK_FAKE', 'DB_BE_ONLY_ACCESS', 'DB_REALTIME_UNLISTED', 'DB_APP_ROLE', 'DB_FORCE_RLS_DEFAULT',
110
+ 'DB_POLICY_TAUTOLOGY', 'DB_VIEW_UNSAFE', 'DB_STORAGE_PREFIX', 'DB_DEFINER_REVOKE',
110
111
  'HFS_GITIGNORE_BLOCK_DRIFT', 'HFS_SONAR_CONFIG', 'HFS_COVERAGE_SCOPE_DRIFT', 'HFS_FORMAT',
111
112
  'HFS_EMPTY_DIR', 'HFS_GHOST_TREE', 'HFS_UNTRACKED_ROOT_ENTRY',
113
+ ...A15_CODES,
112
114
  ...REFUSAL_CODES,
113
115
  ]);
114
116
  /** Every code `starci app check` can report: its own and every code the architecture machine can emit (derived from the machine's rule id lists). */
@@ -172,34 +174,6 @@ const requiredOf = (slot, instance) => (slot.requires ?? []).map((entry) => {
172
174
 
173
175
  const appOf = (p) => /^apps\/([^/]+)\//.exec(p)?.[1];
174
176
 
175
- function withWhy(findings, why) {
176
- return findings.map((f) => ({ ...f, title: why[f.code].title, titleVi: why[f.code].titleVi, whyVi: why[f.code].whyVi, nextStepVi: why[f.code].nextStepVi }));
177
- }
178
-
179
- function summarize(findings) {
180
- const byCode = {};
181
- for (const f of findings) {
182
- byCode[f.code] ??= { level: f.level, count: 0 };
183
- byCode[f.code].count += 1;
184
- }
185
- return {
186
- error: findings.filter((f) => f.level === 'error').length,
187
- warning: findings.filter((f) => f.level === 'warning').length,
188
- info: findings.filter((f) => f.level === 'info').length,
189
- byCode,
190
- };
191
- }
192
-
193
- /**
194
- * THE edition filter of findings: a finding whose code belongs, through the rule catalog, to a rule the app's edition does not
195
- * judge (rules.yaml `editions`) is dropped. Codes outside the catalog (refusals, machine codes without a rule) stay. This is the
196
- * only place the edition touches a finding; no check below it knows the edition.
197
- */
198
- function judgedFindings(findings, edition, root) {
199
- const catalog = loadRuleCatalog({ root });
200
- return findings.filter((finding) => catalog.judgedIn(finding.code, edition));
201
- }
202
-
203
177
  /** A side-relative path (or null) as an app-relative one. */
204
178
  const onSide = (side, p) => (p ? path.posix.normalize(`${side}/${p}`) : p);
205
179
 
@@ -229,7 +203,7 @@ function minimumInstanceFindings(resolver, required, instances) {
229
203
  return findings;
230
204
  }
231
205
 
232
- function contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pins, inScope, isRoot, editionDeclaration, manifest }) {
206
+ function contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pins, inScope, isRoot, appSide, editionDeclaration, manifest, phase }) {
233
207
  const findings = [
234
208
  ...pinFindings({ repoRoot, files, profile: repo.profile, pins, only: scoped }),
235
209
  ...supabaseSecretFindings({ repoRoot, files: files.filter((file) => inScope(file)), resolver, repo }),
@@ -237,10 +211,12 @@ function contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pin
237
211
  ...feStackFindings({ repoRoot, files: files.filter((file) => inScope(file)), repo, manifest }),
238
212
  ...repoLocalCheckFindings({ repoRoot, files }),
239
213
  ...lintSuppressionFindings({ repoRoot, files }),
214
+ ...a15Findings({ repoRoot, files, all, repo, resolver, manifest, isRoot, appSide, inScope }),
240
215
  ];
216
+ findings.push(...trackedStacksGateFindings({ repoRoot, files, all, repo, resolver, manifest, isRoot, inScope, existingFindings: findings }));
241
217
  if (isRoot) {
242
218
  findings.push(
243
- ...appRootFindings({ repoRoot, files, all, repo, resolver, pins }),
219
+ ...appRootFindings({ repoRoot, files, all, repo, resolver, pins, phase }),
244
220
  ...checkAppRoot({ root: repoRoot, resolver, tree: trackedTreeView(all) }).violations.map((item) => ({ code: item.ruleId, level: 'error', path: item.path, line: item.line, column: item.column, source: 'machine', message: `${item.path}: ${item.message}` })),
245
221
  );
246
222
  } else if (repo.profile === 'be') {
@@ -269,7 +245,7 @@ function sizeSoftFindings({ repoRoot, resolver, files, inScope }) {
269
245
  * relative to it, exactly as the standalone repository root was). `files` are the scope's tracked paths; `all` (root only) every
270
246
  * tracked path of the app, for the rules that read across it (dependency skew, proof commands).
271
247
  */
272
- function scopeFindings({ repoRoot, root, repo, resolver, files, all = files, scoped, manifest, editionDeclaration = true }) {
248
+ function scopeFindings({ repoRoot, root, repo, resolver, files, all = files, scoped, manifest, appSide = false, editionDeclaration = true, phase }) {
273
249
  const inScope = (file) => !scoped || scoped.has(file);
274
250
  // A required directory of the root (be/, fe/) is present through the files below it, which are the sides' own.
275
251
  const trackedSet = new Set(all);
@@ -283,7 +259,7 @@ function scopeFindings({ repoRoot, root, repo, resolver, files, all = files, sco
283
259
  if (repo.kind === RUNTIME_KIND) return findings;
284
260
 
285
261
  const pins = readPins(root);
286
- findings.push(...contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pins, inScope, isRoot, editionDeclaration, manifest }));
262
+ findings.push(...contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pins, inScope, isRoot, appSide, editionDeclaration, manifest, phase }));
287
263
  if (!isRoot) findings.push(...sizeSoftFindings({ repoRoot, resolver, files, inScope }));
288
264
  return findings;
289
265
  }
@@ -298,8 +274,11 @@ function scopeFindings({ repoRoot, root, repo, resolver, files, all = files, sco
298
274
  * The root scope and each side run their own rules over their own files (hfs checks per side, the side folder as the old repository
299
275
  * root, plus the root checks); every finding path is app-relative. Returns {ok, profile, apps, manifest, tracked, findings, counts};
300
276
  * a missing or invalid hfs.json is one HFS_DECLARATION_INVALID / HFS_MANIFEST_MAJOR_MISMATCH error finding, never an exception.
277
+ * `phase: 'source'` is the runtime's no-install example-render pass: tracked obligations still run, while ignored generated
278
+ * artifact presence/freshness belongs to the default `workspace` phase after codegen. Ordinary app check/lint always use it.
301
279
  */
302
- export function checkRepo({ repoRoot, root = skillRoot, declaration, files, only, extraFindings = [], tree = files === undefined, manifest = loadSlotManifest({ root }), surface = 'all' }) {
280
+ export function checkRepo({ repoRoot, root = skillRoot, declaration, files, only, extraFindings = [], tree = files === undefined, manifest = loadSlotManifest({ root }), surface = 'all', phase = 'workspace' }) {
281
+ if (!['source', 'workspace'].includes(phase)) throw new Error(`unknown HFS inspection phase ${phase}`);
303
282
  const why = readWhy(root);
304
283
  let repo;
305
284
  try {
@@ -317,18 +296,18 @@ export function checkRepo({ repoRoot, root = skillRoot, declaration, files, only
317
296
  const keep = (list, scopeRoot) => (surface === 'check' ? list.filter((finding) => !(finding.origin === 'repo' && onLintSurface(scopeRoot, finding))) : list);
318
297
  if (repo.profile === APP_SCOPE) {
319
298
  const own = tracked.filter((file) => resolver.sideOf(file) === null);
320
- findings.push(...keep(scopeFindings({ repoRoot, root, repo, resolver, files: own, all: tracked, scoped, manifest }), repoRoot));
299
+ findings.push(...keep(scopeFindings({ repoRoot, root, repo, resolver, files: own, all: tracked, scoped, manifest, phase }), repoRoot));
321
300
  for (const side of SIDES) {
322
301
  const prefix = `${side}/`;
323
302
  const sideRoot = path.join(repoRoot, side);
324
303
  const sideFiles = tracked.filter((file) => file.startsWith(prefix)).map((file) => file.slice(prefix.length));
325
304
  const sideScoped = scoped ? new Set([...scoped].filter((file) => file.startsWith(prefix)).map((file) => file.slice(prefix.length))) : null;
326
- const sideFindings = scopeFindings({ repoRoot: sideRoot, root, repo: repo.sides[side], resolver: resolver.sides[side], files: sideFiles, scoped: sideScoped, manifest, editionDeclaration: false });
305
+ const sideFindings = scopeFindings({ repoRoot: sideRoot, root, repo: repo.sides[side], resolver: resolver.sides[side], files: sideFiles, scoped: sideScoped, manifest, appSide: true, editionDeclaration: false, phase });
327
306
  const message = appRelativeMessages(side, sideRoot);
328
307
  findings.push(...keep(sideFindings, sideRoot).map((finding) => ({ ...finding, side, path: onSide(side, finding.path), message: message(finding.message) })));
329
308
  }
330
309
  } else {
331
- findings.push(...keep(scopeFindings({ repoRoot, root, repo, resolver, files: tracked, scoped, manifest }), repoRoot));
310
+ findings.push(...keep(scopeFindings({ repoRoot, root, repo, resolver, files: tracked, scoped, manifest, phase }), repoRoot));
332
311
  }
333
312
  findings.push(...extraFindings);
334
313
  const untrackedSecrets = untrackedSecretFindings({ repoRoot, files: tracked, repo });
@@ -381,7 +360,7 @@ function machineFindings(report) {
381
360
  * templates or run the formatter (packages/hfs/sync), judged with checkRepo's own. `machine` is injectable for specs. A missing
382
361
  * merge-base under `fast` is an Error, never a silent full pass. `repoRoot` may also be a side folder: that side alone is checked.
383
362
  */
384
- export function checkRepository({ repoRoot, root = skillRoot, fast = false, base, extraFindings = [], manifest = loadSlotManifest({ root }), machine = checkArchitecture }) {
363
+ export function checkRepository({ repoRoot, root = skillRoot, fast = false, base, extraFindings = [], manifest = loadSlotManifest({ root }), machine = checkArchitecture, wiring = false, coverageFrom }) {
385
364
  const changed = fast ? changedSince(repoRoot, base) : null;
386
365
  const slotResult = checkRepo({ repoRoot, root, manifest, extraFindings, surface: 'check', ...(changed ? { only: changed.files, tree: false } : {}) });
387
366
  if (slotResult.profile === null) return { ...slotResult, machine: { status: 'skipped', reason: 'hfs.json is not valid' } };
@@ -400,7 +379,7 @@ export function checkRepository({ repoRoot, root = skillRoot, fast = false, base
400
379
  const findings = [...slotResult.findings, ...withWhy(judgedFindings(found, repo.edition ?? 'full', root), why)];
401
380
  const counts = summarize(findings);
402
381
  const ran = runs.filter((info) => info.status === 'ran');
403
- return {
382
+ const result = {
404
383
  ...slotResult,
405
384
  ok: counts.error === 0,
406
385
  findings,
@@ -410,6 +389,26 @@ export function checkRepository({ repoRoot, root = skillRoot, fast = false, base
410
389
  : { status: 'skipped', reason: runs.map((info) => info.reason).join('; '), sides: runs, ...(changed ? { base: changed.base } : {}) },
411
390
  ...(changed ? { fast: { base: changed.base, changed: changed.files.length } } : {}),
412
391
  };
392
+ return withCheckProofs(result, { repoRoot, root, manifest, wiring, coverageFrom });
393
+ }
394
+
395
+ /** Optional CLI proofs run over the whole tracked tree, including after a dry edition override. */
396
+ export function withCheckProofs(result, { repoRoot, root = skillRoot, manifest = loadSlotManifest({ root }), wiring = false, coverageFrom, edition } = {}) {
397
+ if ((!wiring && coverageFrom === undefined) || result.profile === null) return result;
398
+ const declared = readRepoDeclaration(manifest, repoRoot);
399
+ const repo = { ...declared, edition: result.editionOverride?.judged ?? edition ?? declared.edition };
400
+ if (repo.sides) repo.sides = Object.fromEntries(Object.entries(repo.sides).map(([side, declaration]) => [side, { ...declaration, edition: repo.edition }]));
401
+ const context = { repoRoot, repo, manifest, files: trackedFiles(repoRoot) };
402
+ const reports = {};
403
+ if (wiring) reports.wiring = appWiring(context);
404
+ if (coverageFrom !== undefined) reports.coverage = coverageFromReport({ ...context, coverageFrom });
405
+ const proofFindings = Object.values(reports).flatMap(report => report.findings);
406
+ const why = readWhy(root, [...new Set(proofFindings.map(finding => finding.code))]);
407
+ // The explicit whole-tree wiring proof supersedes the ordinary finder's scoped result.
408
+ const previous = result.findings.filter(finding => !wiring || finding.code !== BOOT_PROOF_CODE);
409
+ const findings = [...previous, ...withWhy(judgedFindings(proofFindings, repo.edition ?? 'full', root), why)];
410
+ const counts = summarize(findings);
411
+ return { ...result, ...reports, findings, counts, ok: counts.error === 0 };
413
412
  }
414
413
 
415
414
  /** The architecture machine over one scope (a side folder): `{ info, findings }`, finding paths app-relative. */
@@ -0,0 +1,70 @@
1
+ // starci app check --coverage-from <lcov>: the optional, read-only coverage proof and its declared boundary (R273).
2
+ import path from 'node:path';
3
+ import { invocationDir } from '../lib/roots.mjs';
4
+ import { isMain } from '../lib/is-main.mjs';
5
+ import { gitOutputOf } from '../lib/git.mjs';
6
+ import { lsFiles } from '../api/git/ls-files.mjs';
7
+ import { loadSlotManifest, readRepoDeclaration } from './slots.mjs';
8
+ import { coverageBelowReport } from './rules/coverage-below.mjs';
9
+
10
+ /** Report for the coordinator's dispatcher; supply its findings as checkRepository's extraFindings. */
11
+ export function coverageFrom({ repoRoot = invocationDir(), coverageFrom: reportPath, files, repo, manifest = loadSlotManifest(), lcov } = {}) {
12
+ const declaration = repo ?? readRepoDeclaration(manifest, repoRoot);
13
+ if (!['app', 'be'].includes(declaration.profile)) throw new Error('coverage proof requires an app root or its be side');
14
+ const tracked = files ?? gitOutputOf(lsFiles(['-z', '--cached', '--exclude-standard'], { dir: repoRoot, maxBuffer: 256 * 1024 * 1024 }), 'git ls-files').split('\0').filter(Boolean);
15
+ return coverageBelowReport({ repoRoot, files: tracked, repo: declaration, manifest, coverageFrom: reportPath, lcov });
16
+ }
17
+
18
+ function setValueOption(opts, flag, value) {
19
+ const key = flag === '--cwd' ? 'cwd' : 'coverageFrom';
20
+ if (value === undefined || !value || value.startsWith('--')) throw new Error(`${flag} needs a value`);
21
+ if (Object.hasOwn(opts, key)) throw new Error(`${flag} may be given only once`);
22
+ opts[key] = value;
23
+ }
24
+
25
+ function parse(argv) {
26
+ const opts = {};
27
+ for (let index = 0; index < argv.length; index += 1) {
28
+ const [flag, inline] = argv[index].split(/=(.*)/su);
29
+ if (flag === '--json' && inline === undefined) opts.json = true;
30
+ else if (['--coverage-from', '--cwd'].includes(flag)) {
31
+ const value = inline ?? argv[++index];
32
+ setValueOption(opts, flag, value);
33
+ } else throw new Error(`unknown argument ${argv[index]}`);
34
+ }
35
+ if (!opts.coverageFrom) throw new Error('usage: starci app check --coverage-from <lcov> [--cwd <app>] [--json]');
36
+ return opts;
37
+ }
38
+
39
+ /** Human-readable measured scope, excluded scope and every unmeasured source file; suitable for normal check output. */
40
+ export function formatCoverageReport(report) {
41
+ const roles = report.scope?.roles ?? [];
42
+ const rolePattern = roles.length === 1 ? roles[0] : '{' + roles.join(',') + '}';
43
+ const lines = [
44
+ `coverage: ${report.ok ? 'passed' : 'refused'} (${report.measured.length} measured logic files; lines/functions/branches at 100% per file)`,
45
+ 'statement coverage is enforced by the unit runner; lcov does not carry statement counts',
46
+ 'measured scope:',
47
+ ...(report.scope?.roots ?? []).map((root) => ` ${report.prefix ?? ''}${root}**/*.${rolePattern}.ts`),
48
+ 'unmeasured scope:',
49
+ ...report.unmeasuredScopes.map((entry) => ` ${entry}${entry.endsWith('/') ? '**' : ''}`),
50
+ 'unmeasured source files:',
51
+ ...(report.unmeasured.length ? report.unmeasured.map((file) => ` ${file}`) : [' (none)']),
52
+ ...report.findings.map((finding) => `${finding.code} ${finding.path}: ${finding.message}`),
53
+ ];
54
+ return `${lines.join('\n')}\n`;
55
+ }
56
+
57
+ /** Standalone coordinator entry point: exit 0 proof passed, 2 proof or invocation refused. */
58
+ function coverageFromMain(argv, { cwd = invocationDir(), stdout = (text) => process.stdout.write(text), stderr = (text) => process.stderr.write(text), run = coverageFrom } = {}) {
59
+ try {
60
+ const opts = parse(argv);
61
+ const report = run({ repoRoot: path.resolve(cwd, opts.cwd ?? '.'), coverageFrom: opts.coverageFrom });
62
+ stdout(opts.json ? `${JSON.stringify(report, null, 2)}\n` : formatCoverageReport(report));
63
+ return report.ok ? 0 : 2;
64
+ } catch (error) {
65
+ stderr(`starci app check --coverage-from: ${String(error.code ?? error.message).split('\n')[0]}\n`);
66
+ return 2;
67
+ }
68
+ }
69
+
70
+ if (isMain(import.meta.url)) process.exitCode = coverageFromMain(process.argv.slice(2));
@@ -17,7 +17,7 @@ import { byCodeUnit } from '../lib/list.mjs';
17
17
  import { trimTrailingSlashes } from './trailing-slashes.mjs';
18
18
 
19
19
  /** The component that holds the shared infrastructure: the platform tier and every capability more than one service app composes. */
20
- export const PLATFORM_COMPONENT = 'platform';
20
+ const PLATFORM_COMPONENT = 'platform';
21
21
  const SERVICE_APP_KINDS = new Set(['api', 'worker']);
22
22
  /** `text` with every `<placeholder>` (a `<`, at least one character, the next `>`) replaced by `*`; a `<` with no closing `>` stays. */
23
23
  export const star = (text) => {
@@ -0,0 +1,276 @@
1
+ // app db baseline: schema-only dump, explicit cutover and repair commands; preview writes nothing.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import pg from 'libpg-query';
5
+ import { invocationDir } from '../lib/roots.mjs';
6
+ import { sqlStringParts as names, walkSqlAst as walk, sqlParser } from '../lib/sql-ast.mjs';
7
+ import { databaseDump } from '../api/supabase/database-dump.mjs';
8
+ import { databaseQuery } from '../api/supabase/database-query.mjs';
9
+ import { migrationRepair } from '../api/supabase/migration-repair.mjs';
10
+ import { migrationStamp, isMigrationName, databaseSqlTokens, databaseExecutableTokens } from './rules/database-migrations.mjs';
11
+ import { baselineMarkers, baselineSourceFindings } from './rules/database-baseline.mjs';
12
+ import { databaseQueueDeclarations } from './rules/database-queues.mjs';
13
+ import { MIGRATIONS_DIR, MIGRATIONS_ARCHIVE_DIR } from './rules/database-constants.mjs';
14
+ import { dbPlanMain } from './db-plan-verb.mjs';
15
+
16
+ await pg.loadModule();
17
+
18
+ export class DbVerbError extends Error {}
19
+ const LINKED_PASSWORD_ENV = 'SUPABASE_DB_PASSWORD';
20
+ const TELEMETRY_DISABLED_ENV = 'SUPABASE_TELEMETRY_DISABLED';
21
+ const TELEMETRY_DEBUG_ENV = 'SUPABASE_TELEMETRY_DEBUG';
22
+ const CLI_DEBUG_ENV = 'SUPABASE_DEBUG';
23
+
24
+ /** Strict shared options; unknown flags never reach the database CLI. */
25
+ export function dbOptions(argv, allowed) {
26
+ const opts = {};
27
+ for (let index = 0; index < argv.length; index += 1) {
28
+ const [flag, inline] = argv[index].split(/=(.*)/s);
29
+ const name = flag.slice(2);
30
+ if (!flag.startsWith('--') || !allowed.includes(name) || Object.hasOwn(opts, name)) throw new DbVerbError('unknown or duplicate database option');
31
+ if (['apply', 'local', 'json'].includes(name)) {
32
+ if (inline !== undefined && !['true', 'false'].includes(inline)) throw new DbVerbError(`${flag} expects true or false`);
33
+ opts[name] = inline !== 'false';
34
+ } else {
35
+ const value = inline ?? argv[++index];
36
+ if (!value || value.startsWith('--')) throw new DbVerbError(`${flag} requires a value`);
37
+ opts[name] = value;
38
+ }
39
+ }
40
+ return opts;
41
+ }
42
+
43
+ export function dbAppRoot(cwd, opts) {
44
+ const root = path.resolve(cwd, opts.cwd ?? '.');
45
+ let declaration;
46
+ try { declaration = JSON.parse(fs.readFileSync(path.join(root, 'hfs.json'), 'utf8')); } catch { throw new DbVerbError('run at an app root containing hfs.json'); }
47
+ if (declaration.kind !== 'app' || !['lite', 'full'].includes(declaration.edition ?? 'full')) throw new DbVerbError('database verbs require a lite or full app');
48
+ return root;
49
+ }
50
+
51
+ /** Immediate SQL files only, never secrets or Supabase's temporary credential files. */
52
+ export function dbSqlFiles(root, directory = MIGRATIONS_DIR) {
53
+ const dir = path.join(root, directory);
54
+ if (!fs.existsSync(dir)) return [];
55
+ return fs.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith('.sql')).map((entry) => {
56
+ const match = /^(\d{14})_/.exec(entry.name);
57
+ if (!isMigrationName(entry.name) || !match || migrationStamp(match[1]) === null) throw new DbVerbError('migration filenames must carry valid UTC stamps');
58
+ return { file: `${directory}/${entry.name}`, name: entry.name, stamp: match[1] };
59
+ }).sort((left, right) => left.name < right.name ? -1 : Number(left.name > right.name));
60
+ }
61
+
62
+ /** Supabase adapter uses supabaseSpawn's (repoRoot, args, options) contract; raw failures are private. */
63
+ const DB_CALLS = { 'db dump': databaseDump, 'db query': databaseQuery, 'migration repair': migrationRepair };
64
+
65
+ function runDatabaseCommand(root, args, options) {
66
+ const call = DB_CALLS[args.slice(0, 2).join(' ')];
67
+ if (!call) throw new DbVerbError('unsupported database command');
68
+ return call(root, args.slice(2), options);
69
+ }
70
+
71
+ export function runDbSupabase(root, args, ports = {}) {
72
+ const env = ports.env ?? process.env;
73
+ // The linked CLI can mint a temporary remote login when no existing password is supplied.
74
+ // Check custody presence only; the value stays within the CLI environment.
75
+ if (args.includes('--linked') && !ports.runSupabase && !ports.run && (typeof env[LINKED_PASSWORD_ENV] !== 'string' || env[LINKED_PASSWORD_ENV].length === 0)) throw new DbVerbError('linked database operations require an existing SUPABASE_DB_PASSWORD in the CLI environment');
76
+ const cliEnv = { ...env, [TELEMETRY_DISABLED_ENV]: '1', [TELEMETRY_DEBUG_ENV]: '0', [CLI_DEBUG_ENV]: '0' };
77
+ const result = (ports.runSupabase ?? runDatabaseCommand)(root, args, { env: cliEnv, ...(ports.run ? { run: ports.run } : {}) });
78
+ if (result?.error || !(result?.ok === true || result?.status === 0)) throw new DbVerbError('Supabase command failed; inspect the CLI privately');
79
+ return String(result.stdout ?? '');
80
+ }
81
+
82
+ export const repairArgs = (stamp, status, local) => ['migration', 'repair', stamp, '--status', status, local ? '--local' : '--linked'];
83
+ export const dbFailure = (verb, error, stderr) => { stderr(`starci app db ${verb}: ${error instanceof DbVerbError ? error.message : 'operation failed; database output was withheld'}\n`); return 2; };
84
+
85
+ function baselinePlan(root, opts, now) {
86
+ const active = dbSqlFiles(root);
87
+ if (!active.length) throw new DbVerbError('baseline requires an existing active migration chain');
88
+ const archived = dbSqlFiles(root, MIGRATIONS_ARCHIVE_DIR);
89
+ const oldStamps = [...active, ...archived].map((entry) => entry.stamp);
90
+ const last = oldStamps.sort((left, right) => left.localeCompare(right)).at(-1);
91
+ const stamp = opts.stamp ?? now().toISOString().replace(/\D/g, '').slice(0, 14);
92
+ if (!/^\d{14}$/.test(stamp) || migrationStamp(stamp) === null || stamp <= last) throw new DbVerbError('--stamp must be a valid UTC version after the archive boundary');
93
+ const file = `${MIGRATIONS_DIR}/${stamp}_baseline.sql`;
94
+ if (fs.existsSync(path.join(root, file)) || active.some((entry) => fs.existsSync(path.join(root, MIGRATIONS_ARCHIVE_DIR, entry.name)))) throw new DbVerbError('baseline would overwrite an existing migration');
95
+ return { active, last, stamp, file };
96
+ }
97
+
98
+ const EXCLUDED_SCHEMAS = new Set(['information_schema', '_analytics', '_realtime', '_supavisor', 'auth', 'etl', 'extensions', 'pgbouncer', 'realtime', 'storage', 'supabase_functions', 'supabase_migrations', 'cron', 'dbdev', 'graphql', 'graphql_public', 'net', 'pgmq', 'pgmq_public', 'pgsodium', 'pgsodium_masks', 'pgtle', 'repack', 'tiger', 'tiger_data', 'topology', 'vault']);
99
+ const ROLE_OPTIONS = new Set(['canlogin', 'inherit', 'superuser', 'createdb', 'createrole', 'isreplication', 'bypassrls', 'connectionlimit']);
100
+ const identifier = (name) => `"${name.replaceAll('"', '""')}"`;
101
+ const literal = (value) => `'${value.replaceAll("'", "''")}'`;
102
+ const excludedSchema = (name) => EXCLUDED_SCHEMAS.has(name) || /^(?:pg_|timescaledb_|_timescaledb_)/.test(name ?? '');
103
+
104
+ /** Inventory source paths only; no credential, generated or vendor tree is traversed. */
105
+ function baselineFiles(root) {
106
+ const files = [];
107
+ const visit = (directory) => {
108
+ const full = path.join(root, directory);
109
+ if (!fs.existsSync(full)) return;
110
+ for (const entry of fs.readdirSync(full, { withFileTypes: true })) {
111
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
112
+ const file = `${directory}/${entry.name}`;
113
+ if (entry.isDirectory()) visit(file);
114
+ else if (entry.isFile() && /\.(?:sql|[cm]?[jt]sx?)$/.test(entry.name)) files.push(file);
115
+ }
116
+ };
117
+ for (const directory of ['supabase', 'be/src', 'be/apps', 'src', 'apps']) visit(directory);
118
+ return files;
119
+ }
120
+
121
+ const parsed = sqlParser(pg, () => { throw new DbVerbError('migration SQL could not be prepared safely for a baseline'); });
122
+
123
+ function statementSql(text, entry) {
124
+ const bytes = Buffer.from(text);
125
+ const start = entry.stmt_location ?? 0;
126
+ const statement = bytes.subarray(start, entry.stmt_len ? start + entry.stmt_len : bytes.length).toString('utf8');
127
+ return `${statement.slice(databaseSqlTokens(statement)[0]?.index ?? 0).trim().replace(/;\s*$/, '')};\n`;
128
+ }
129
+
130
+ function ownedSchemas(node) {
131
+ const schemas = [node.relation?.schemaname, node.table?.schemaname, node.schemaname, node.newschema];
132
+ for (const field of ['funcname', 'typeName', 'typename', 'type', 'object', 'domainname', 'collname', 'opfamilyname', 'opclassname']) {
133
+ const parts = names(Array.isArray(node[field]) ? node[field] : node[field]?.names ?? node[field]?.List?.items);
134
+ if (parts.length > 1) schemas.push(parts[0]);
135
+ }
136
+ for (const object of node.objects ?? []) {
137
+ const parts = names(object.List?.items ?? object.ObjectWithArgs?.objname);
138
+ schemas.push(object.RangeVar?.schemaname);
139
+ if (parts.length > 1 || /^OBJECT_SCHEMA$/.test(node.objtype ?? node.removeType ?? '')) schemas.push(parts[0]);
140
+ }
141
+ return schemas;
142
+ }
143
+
144
+ function excludedOwnership(stmt) {
145
+ if (ownedSchemas(stmt).some(excludedSchema)) return true;
146
+ let unsupported = false;
147
+ walk(stmt, (node) => {
148
+ if (node.DefElem?.defname === 'schemas' && names(node.DefElem.arg?.List?.items).some(excludedSchema)) unsupported = true;
149
+ if (node.FuncCall && names(node.FuncCall.funcname)[0] === 'vault') unsupported = true;
150
+ });
151
+ if (stmt.name === 'search_path') unsupported ||= (stmt.args ?? []).some((node) => excludedSchema(node.A_Const?.sval?.sval));
152
+ return unsupported;
153
+ }
154
+
155
+ function dataMutation(stmt) {
156
+ if (stmt.CreateFunctionStmt || stmt.CreateTrigStmt || stmt.CreatePolicyStmt || stmt.AlterPolicyStmt || stmt.CreateStmt || stmt.ViewStmt) return false;
157
+ let mutation = false;
158
+ walk(stmt, (node) => { if (['InsertStmt', 'UpdateStmt', 'DeleteStmt', 'MergeStmt', 'CopyStmt', 'TruncateStmt', 'IntoClause'].some((key) => node[key])) mutation = true; });
159
+ return mutation;
160
+ }
161
+
162
+ function unsupportedCall(call) {
163
+ const name = names(call.funcname).join('.');
164
+ const args = (call.args ?? []).map((arg) => arg.NamedArgExpr?.arg ?? arg);
165
+ if (name === 'pgmq.create' || name === 'pgmq.drop_queue') return args.length !== 1 || !args[0]?.A_Const?.sval;
166
+ if (name === 'cron.schedule' || name === 'cron.unschedule') return false;
167
+ if (name === 'pg_catalog.set_config') return args[0]?.A_Const?.sval?.sval !== 'search_path' || args[1]?.A_Const?.sval?.sval !== '';
168
+ return true;
169
+ }
170
+
171
+ function sourceExecution(stmt) {
172
+ if (stmt.CallStmt) return true;
173
+ if (!stmt.SelectStmt) return false;
174
+ let unsupported = false;
175
+ walk(stmt, (node) => { if (node.FuncCall && unsupportedCall(node.FuncCall)) unsupported = true; });
176
+ return unsupported;
177
+ }
178
+
179
+ function unsupportedDo(node) {
180
+ const body = (node.args ?? []).find((item) => item.DefElem?.defname === 'as')?.DefElem?.arg?.String?.sval ?? '';
181
+ const tokens = databaseExecutableTokens(body);
182
+ const unsupported = tokens.some((token, index) => {
183
+ const next = tokens[index + 1]?.v;
184
+ return (token.t === 'word' && ['execute', 'role', 'user', 'extension', 'insert', 'update', 'delete', 'merge', 'copy', 'truncate'].includes(token.v))
185
+ || (excludedSchema(token.v) && next === '.' && !['pgmq', 'cron'].includes(token.v));
186
+ });
187
+ if (unsupported) return true;
188
+ let tree;
189
+ try { tree = pg.parsePlPgSQLSync(`do ${literal(body)};`); } catch { return true; }
190
+ let effects = false;
191
+ walk(tree, (item) => {
192
+ if (item.PLpgSQL_expr?.parseMode !== 0) return;
193
+ const sql = parsed(item.PLpgSQL_expr.query);
194
+ if ((sql.stmts ?? []).some((entry) => dataMutation(entry.stmt) || sourceExecution(entry.stmt))) effects = true;
195
+ });
196
+ return effects;
197
+ }
198
+
199
+ function roleDeclaration(stmt, sql, roles) {
200
+ const create = stmt.CreateRoleStmt;
201
+ const alter = stmt.AlterRoleStmt;
202
+ const setting = stmt.AlterRoleSetStmt;
203
+ const declaration = create ?? alter ?? setting;
204
+ if (!declaration) return false;
205
+ const name = create?.role ?? declaration.role?.rolename;
206
+ if (!name || name.startsWith('pg_') || (declaration.options ?? []).some((option) => !ROLE_OPTIONS.has(option.DefElem?.defname))) throw new DbVerbError('baseline role declarations require static non-secret role attributes');
207
+ if (setting && setting.setstmt?.name !== 'search_path') throw new DbVerbError('baseline supports only non-secret search_path role settings');
208
+ if (create) roles.set(name, [sql]);
209
+ else {
210
+ if (!roles.has(name)) throw new DbVerbError('baseline cannot reconstruct alterations to a role it does not declare');
211
+ roles.get(name).push(sql);
212
+ }
213
+ return true;
214
+ }
215
+
216
+ function extensionDeclaration(stmt, sql, extensions) {
217
+ if (stmt.CreateExtensionStmt) {
218
+ extensions.set(stmt.CreateExtensionStmt.extname, sql.replace(/\bcreate\s+extension\s+(?!if\s+not\s+exists\b)/i, 'create extension if not exists '));
219
+ return true;
220
+ }
221
+ if (!/^OBJECT_EXTENSION$/.test(stmt.DropStmt?.removeType ?? '')) return false;
222
+ for (const object of stmt.DropStmt.objects ?? []) extensions.delete(names(object.List?.items)[0]);
223
+ return true;
224
+ }
225
+
226
+ function baselineStatement(state, stmt, sql) {
227
+ if (roleDeclaration(stmt, sql, state.roles) || extensionDeclaration(stmt, sql, state.extensions)) return;
228
+ if (stmt.DropRoleStmt) { for (const role of stmt.DropRoleStmt.roles ?? []) state.roles.delete(role.RoleSpec?.rolename); return; }
229
+ if (stmt.GrantRoleStmt || stmt.AlterExtensionStmt || stmt.AlterExtensionContentsStmt || /^OBJECT_ROLE$/.test(stmt.RenameStmt?.renameType ?? '')) throw new DbVerbError('baseline cannot reconstruct role membership, role renames or extension alterations');
230
+ if (dataMutation(stmt) || sourceExecution(stmt)) throw new DbVerbError('baseline cannot reconstruct migration-owned data changes or custom execution from a schema-only dump');
231
+ const node = Object.values(stmt)[0];
232
+ const queueGrant = stmt.GrantStmt && (node.objects ?? []).every((object) => object.RangeVar?.schemaname === 'pgmq');
233
+ if ((excludedOwnership(node) && !queueGrant) || (stmt.DoStmt && unsupportedDo(node))) throw new DbVerbError('baseline cannot reconstruct application changes to excluded managed schemas or dynamic operational declarations');
234
+ }
235
+
236
+ function baselineMetadata(root, plan, files) {
237
+ const queues = databaseQueueDeclarations({ repoRoot: root, files, all: files });
238
+ if (queues.findings.length) throw new DbVerbError('baseline requires valid migration-owned queues, grants and cron declarations');
239
+ const state = { roles: new Map(), extensions: new Map() };
240
+ for (const migration of plan.active) {
241
+ const text = fs.readFileSync(path.join(root, migration.file), 'utf8');
242
+ for (const entry of parsed(text).stmts ?? []) baselineStatement(state, entry.stmt, statementSql(text, entry));
243
+ }
244
+ return { ...queues, roles: [...state.roles.values()].flat().join(''), extensions: [...state.extensions.values()].join('') };
245
+ }
246
+
247
+ function metadataSql(metadata) {
248
+ const queueSql = metadata.queues.map((queue) => `select pgmq.create(${literal(queue)});\n`).join('');
249
+ const grants = (metadata.grants ?? []).map((grant) => `grant ${grant.privileges.join(', ')} on table ${grant.tables.map((table) => table.split('.').map(identifier).join('.')).join(', ')} to ${grant.roles.map(identifier).join(', ')}${grant.grantOption ? ' with grant option' : ''};\n`).join('');
250
+ const cron = metadata.cron.map((job) => `select cron.schedule(${literal(job.name)}, ${literal(job.schedule)}, ${literal(job.command)});\n`).join('');
251
+ return `${queueSql}${grants}${cron}`;
252
+ }
253
+
254
+ /** Numeric CLI exit code, injectable IO/clock/CLI; neither files nor history change without --apply. */
255
+ export function dbBaselineMain(argv, { cwd = invocationDir(), stdout = (text) => process.stdout.write(text), stderr = (text) => process.stderr.write(text), now = () => new Date(), ...ports } = {}) {
256
+ try {
257
+ const opts = dbOptions(argv, ['cwd', 'stamp', 'apply', 'local']);
258
+ const root = dbAppRoot(cwd, opts);
259
+ const plan = baselinePlan(root, opts, now);
260
+ const files = baselineFiles(root);
261
+ if (baselineSourceFindings({ repoRoot: root, files, all: files, git: ports.git }).length) throw new DbVerbError('baseline sources and archives must match committed migration history');
262
+ const metadata = baselineMetadata(root, plan, files);
263
+ if (dbPlanMain(['--json', ...(opts.local ? ['--local'] : [])], { ...ports, cwd: root, stdout: () => {}, stderr: () => {} }) !== 0) throw new DbVerbError('baseline requires a clean database plan with all local migrations already applied');
264
+ const schema = runDbSupabase(root, ['db', 'dump', opts.local ? '--local' : '--linked'], ports);
265
+ if (!schema.trim() || baselineMarkers(schema).length || /\bvault\.(?:create_secret|update_secret)\s*\(|\bpassword\s+'[^']+'/i.test(schema)) throw new DbVerbError('schema dump is empty or contains unsupported secret/baseline statements');
266
+ const repairs = [...plan.active.map((entry) => repairArgs(entry.stamp, 'reverted', opts.local)), repairArgs(plan.stamp, 'applied', opts.local)];
267
+ stdout(`${opts.apply ? 'apply' : 'preview'} baseline ${plan.file}; archive ${plan.active.length} migrations\n`);
268
+ for (const args of repairs) stdout(`supabase ${args.join(' ')}\n`);
269
+ if (!opts.apply) return 0;
270
+ for (const args of repairs) runDbSupabase(root, args, ports);
271
+ fs.mkdirSync(path.join(root, MIGRATIONS_ARCHIVE_DIR), { recursive: true });
272
+ fs.writeFileSync(path.join(root, plan.file), `-- starci:baseline ${plan.last}\n${metadata.extensions}${metadata.roles}${schema.replace(/^\s+/, '')}\n${metadataSql(metadata)}`, { flag: 'wx' });
273
+ for (const entry of plan.active) fs.renameSync(path.join(root, entry.file), path.join(root, MIGRATIONS_ARCHIVE_DIR, entry.name));
274
+ return 0;
275
+ } catch (error) { return dbFailure('baseline', error, stderr); }
276
+ }