@starci/hfs 4.2.17 → 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 (564) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +7 -0
  3. package/db/migrate.mjs +361 -0
  4. package/db/post-cutover.mjs +136 -0
  5. package/emit/db-types.mjs +10 -1
  6. package/lint/run.mjs +1 -1
  7. package/package.json +4 -1
  8. package/release/gate.mjs +298 -0
  9. package/release/index.mjs +103 -0
  10. package/release/push.mjs +110 -0
  11. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  12. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  13. package/runtime/knowledge/hfs/rules.yaml +3437 -160
  14. package/runtime/knowledge/hfs/slots.yaml +430 -92
  15. package/runtime/knowledge/patterns/be/api.yaml +92 -2
  16. package/runtime/knowledge/patterns/be/cli.yaml +37 -9
  17. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  18. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  19. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  20. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  21. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  22. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  23. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  24. package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
  25. package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
  26. package/runtime/knowledge/sonar-gate.yaml +1 -1
  27. package/runtime/modules/kernel/failure-codes.yaml +3128 -47
  28. package/runtime/modules/models/runtimes.yaml +1 -1
  29. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  30. package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
  31. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  32. package/runtime/scripts/api/git/add.mjs +5 -0
  33. package/runtime/scripts/api/git/cat-file.mjs +5 -0
  34. package/runtime/scripts/api/git/init.mjs +7 -0
  35. package/runtime/scripts/api/git/lib.mjs +3 -0
  36. package/runtime/scripts/api/git/tag.mjs +5 -0
  37. package/runtime/scripts/api/node/lib.mjs +20 -0
  38. package/runtime/scripts/api/node/run-node.mjs +6 -0
  39. package/runtime/scripts/api/sops/lib.mjs +41 -0
  40. package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
  41. package/runtime/scripts/api/sops/seal.mjs +1 -1
  42. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  43. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  44. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  45. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  46. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  47. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  48. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
  49. package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
  50. package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
  51. package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
  52. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
  53. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  54. package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
  55. package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
  56. package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
  57. package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
  58. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  59. package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
  60. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  61. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
  62. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
  63. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  64. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  65. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  66. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
  67. package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
  68. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
  69. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  70. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  71. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  72. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  73. package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
  74. package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
  75. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  76. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
  77. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
  78. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
  79. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  80. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  81. package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
  82. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
  83. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
  84. package/runtime/scripts/hfs/check-results.mjs +28 -0
  85. package/runtime/scripts/hfs/check.mjs +63 -72
  86. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  87. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  88. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  89. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  90. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  91. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  92. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  93. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  94. package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
  95. package/runtime/scripts/hfs/path-findings.mjs +32 -2
  96. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  97. package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
  98. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  99. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  100. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  101. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  102. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  103. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  104. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  105. package/runtime/scripts/hfs/rules/contract.mjs +66 -5
  106. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  107. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  108. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  109. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  110. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  111. package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
  112. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  113. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  114. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  115. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  116. package/runtime/scripts/hfs/rules/database.mjs +11 -2
  117. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  118. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  119. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  120. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  121. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  122. package/runtime/scripts/hfs/rules/edition.mjs +11 -10
  123. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  124. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  125. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  126. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  127. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  128. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  129. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  130. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  131. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  132. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  133. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  134. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  135. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  136. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
  137. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  138. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  139. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  140. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  141. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  142. package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
  143. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  144. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  145. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  146. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  147. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  148. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  149. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
  150. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  151. package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
  152. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
  153. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  154. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  155. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  156. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  157. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  158. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  159. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  160. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  161. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  162. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  163. package/runtime/scripts/hfs/secret-document.mjs +97 -0
  164. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  165. package/runtime/scripts/hfs/secret-errors.mjs +20 -0
  166. package/runtime/scripts/hfs/secret-files.mjs +124 -0
  167. package/runtime/scripts/hfs/secret-init.mjs +300 -0
  168. package/runtime/scripts/hfs/secret-output.mjs +100 -0
  169. package/runtime/scripts/hfs/secret-sops.mjs +130 -0
  170. package/runtime/scripts/hfs/secret-store.mjs +163 -0
  171. package/runtime/scripts/hfs/secret.mjs +162 -193
  172. package/runtime/scripts/hfs/slot-imports.mjs +18 -3
  173. package/runtime/scripts/hfs/slot-required.mjs +13 -0
  174. package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
  175. package/runtime/scripts/hfs/slots.mjs +1 -0
  176. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  177. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  178. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  179. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  180. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  181. package/runtime/scripts/hfs/tree-findings.mjs +22 -0
  182. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  183. package/runtime/scripts/lib/compose-values.mjs +11 -0
  184. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  185. package/runtime/scripts/lib/json-clone.mjs +61 -0
  186. package/runtime/scripts/lib/list.mjs +4 -0
  187. package/runtime/scripts/lib/roots.mjs +59 -0
  188. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  189. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  190. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  191. package/runtime/scripts/lib/stack-services.mjs +1 -1
  192. package/runtime/scripts/lib/swarm-stack.mjs +126 -0
  193. package/runtime/scripts/lib/trim-end.mjs +8 -0
  194. package/runtime/scripts/lib/ts-analysis.mjs +23 -0
  195. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  196. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  197. package/scaffold/add-app.mjs +77 -59
  198. package/scaffold/add-cli-lite.mjs +24 -17
  199. package/scaffold/add-compose.mjs +79 -0
  200. package/scaffold/add-database-lite.mjs +14 -0
  201. package/scaffold/add-fe-overlay.mjs +122 -0
  202. package/scaffold/add-fe-page.mjs +269 -0
  203. package/scaffold/add-inbox-lite.mjs +87 -124
  204. package/scaffold/add-plan.mjs +183 -0
  205. package/scaffold/add-table.mjs +132 -70
  206. package/scaffold/add.mjs +186 -204
  207. package/scaffold/app.mjs +128 -43
  208. package/scaffold/edition-gate.mjs +3 -3
  209. package/scaffold/image.mjs +2 -2
  210. package/scaffold/lite-exports.mjs +2 -1
  211. package/scaffold/service.mjs +2 -2
  212. package/src/main.mjs +238 -119
  213. package/stack/age-install.mjs +37 -0
  214. package/stack/backup.mjs +100 -0
  215. package/stack/build-args.mjs +42 -0
  216. package/stack/build.mjs +173 -0
  217. package/stack/cli.mjs +130 -0
  218. package/stack/config.mjs +57 -0
  219. package/stack/context.mjs +57 -0
  220. package/stack/converge.mjs +107 -0
  221. package/stack/deploy.mjs +301 -0
  222. package/stack/digest.mjs +16 -0
  223. package/stack/docker-transport.mjs +53 -0
  224. package/stack/edge-alias.mjs +118 -0
  225. package/stack/errors.mjs +21 -0
  226. package/stack/image-scan.mjs +152 -0
  227. package/stack/ingress-create.mjs +114 -0
  228. package/stack/ingress-publish.mjs +273 -0
  229. package/stack/ingress.mjs +706 -0
  230. package/stack/jobs.mjs +51 -0
  231. package/stack/live-restore.mjs +177 -0
  232. package/stack/prepare.mjs +266 -0
  233. package/stack/private-file.mjs +22 -0
  234. package/stack/provision.mjs +90 -0
  235. package/stack/prune.mjs +46 -0
  236. package/stack/release-store.mjs +87 -0
  237. package/stack/remote.mjs +205 -0
  238. package/stack/render.mjs +149 -0
  239. package/stack/retire.mjs +47 -0
  240. package/stack/sealed.mjs +149 -0
  241. package/stack/task.mjs +42 -0
  242. package/stack/verify.mjs +99 -0
  243. package/sync/ci-commands.mjs +13 -0
  244. package/sync/index.mjs +127 -64
  245. package/sync/stack-images.mjs +127 -0
  246. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  247. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  248. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  249. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  250. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  251. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  252. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  253. package/templates/app/codegen-graphql.mjs.partial +13 -2
  254. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  255. package/templates/app/codegen-prelude.mjs.partial +8 -0
  256. package/templates/app/gitignore +4 -0
  257. package/templates/app/gitignore-lite +4 -0
  258. package/templates/app/hooks/husky/pre-commit +7 -12
  259. package/templates/app/package-scripts/package.json +1 -0
  260. package/templates/app/package-scripts/supabase.json +7 -0
  261. package/templates/app/package-scripts-lite/package.json +1 -7
  262. package/templates/app/skeleton/.editorconfig +1 -0
  263. package/templates/app/skeleton/.gitattributes +7 -1
  264. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  265. package/templates/app/skeleton/.nvmrc +1 -1
  266. package/templates/app/skeleton/.sops.yaml +1 -1
  267. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  268. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  269. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  270. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  271. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  272. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  273. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  275. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  276. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  277. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  278. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  279. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  280. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  281. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  282. package/templates/app/skeleton/README.md +6 -6
  283. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  284. package/templates/app/skeleton-lite/.editorconfig +16 -0
  285. package/templates/app/skeleton-lite/.gitattributes +8 -0
  286. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  287. package/templates/app/skeleton-lite/.nvmrc +1 -0
  288. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  289. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
  290. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  291. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  292. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  293. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  294. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  295. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  296. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  297. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  298. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  299. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  300. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  301. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  302. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  303. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  304. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  305. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  306. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  307. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  308. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  309. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  310. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  311. package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
  312. package/templates/app/skeleton-lite/README.md +8 -4
  313. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  314. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  315. package/templates/app/starciwork.gitignore-lite +6 -0
  316. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  317. package/templates/be/image/worker/Dockerfile +1 -1
  318. package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
  319. package/templates/be/inbox/database-client.ts.tpl +14 -28
  320. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  321. package/templates/be/inbox/event-index.ts.tpl +1 -0
  322. package/templates/be/inbox/events.json.tpl +10 -0
  323. package/templates/be/inbox/migration.sql.tpl +1 -0
  324. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  325. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  326. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  327. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  328. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  329. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  330. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  331. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  332. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  333. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  334. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  335. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  336. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  337. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  338. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  339. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  340. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  341. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  342. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  343. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  344. package/templates/be/inbox/service.spec.ts.tpl +49 -20
  345. package/templates/be/inbox/service.ts.tpl +27 -16
  346. package/templates/be/inbox/sql.ts.tpl +2 -2
  347. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
  348. package/templates/be/inbox/webhook-door.ts.tpl +4 -3
  349. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  350. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  351. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  352. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  353. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  354. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  355. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  356. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  357. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  358. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  359. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  360. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  362. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  363. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  364. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  365. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  366. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  367. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  368. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  369. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  370. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  371. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  372. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  373. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  374. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  375. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  376. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  377. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  378. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  379. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  380. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  381. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  382. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  383. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  384. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  385. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  386. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  387. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  388. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  389. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  390. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  391. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  392. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  393. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  394. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  395. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  396. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
  397. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
  398. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  399. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  400. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  401. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  402. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  403. package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
  404. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
  405. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
  406. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  407. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  408. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
  409. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  410. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  411. package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
  412. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  413. package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
  414. package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
  415. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  416. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  417. package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
  418. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  419. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  420. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  421. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  422. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  423. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  424. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  425. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  426. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  427. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  428. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  429. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  430. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  431. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  432. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  433. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  434. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  435. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  436. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  437. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  438. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  439. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  440. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  441. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  442. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  443. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  444. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  445. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  446. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  447. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  448. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  449. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  450. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
  451. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  452. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  453. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  454. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  455. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  456. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  457. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  458. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  459. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  460. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  461. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  462. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  463. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  464. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  465. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  466. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  468. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  469. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  471. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  472. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  473. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  474. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  475. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  476. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  477. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  478. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  479. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  480. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  481. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  482. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  483. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  484. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  485. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  486. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  487. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  488. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  489. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
  490. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
  491. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
  492. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  493. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  494. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  495. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
  496. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  497. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  498. package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
  499. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
  500. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  501. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  502. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  503. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  504. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  505. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  506. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  507. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  508. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  509. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  510. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  511. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  512. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  513. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  514. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  515. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  516. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  517. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  518. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  519. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  520. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  521. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  522. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  523. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  524. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  525. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  526. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  527. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  528. package/templates/be/table/migration.sql.tpl +1 -0
  529. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  530. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  531. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  532. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  533. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  534. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  535. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  536. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  537. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  538. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  539. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  540. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  541. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  542. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  543. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  544. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  545. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  546. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  547. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  548. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  549. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  550. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  551. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  552. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  553. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  554. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  555. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  556. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  557. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  558. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  559. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  560. package/upgrade/check.mjs +3 -1
  561. package/upgrade/file-state.mjs +68 -0
  562. package/upgrade/index.mjs +58 -94
  563. package/runtime/scripts/api/git/log.mjs +0 -5
  564. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
@@ -0,0 +1,366 @@
1
+ // R267/R268: immutable release references, recovery evidence and declared recurring CLI work.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { loadTypescript } from '../../lib/package-at.mjs';
6
+ import { walkSqlAst, sqlParser, sqlCallArgument } from '../../lib/sql-ast.mjs';
7
+ import { isPlainObject as plain } from '../../../engine/plain-object.mjs';
8
+ import { found, readText } from './read.mjs';
9
+ import { stacksDeploymentContexts } from './stacks-runtime.mjs';
10
+
11
+ // PostgreSQL initializes asynchronously, while HFS finders must stay synchronous.
12
+ // Optional loading also lets dependency-free relocated development gates import this rule.
13
+ async function postgresParser() {
14
+ try {
15
+ const { default: parser } = await import('libpg-query');
16
+ await parser.loadModule();
17
+ return parser;
18
+ } catch { return null; }
19
+ }
20
+
21
+ const pg = await postgresParser();
22
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
23
+ let ts;
24
+
25
+ const DECLARATION = '.starcistacks/application-stacks.yaml';
26
+ const MAX_BYTES = 1024 * 1024;
27
+ const SOURCE = /^(?:be\/)?(?:src|apps)\/.*\.[cm]?[jt]s$/;
28
+ const TEST = /(?:\.(?:spec|test)|-spec)\.[cm]?[jt]s$|\/(?:tests|__tests__)\//;
29
+ const SECRET = /(?:^|\/)(?:secrets|\.claude|\.aws|\.codex)(?:\/|$)|(?:^|\/)\.env(?:\.|$)|\.(?:enc|pem|key|p12|pfx)$/i;
30
+ const CLI_ENTRY = /^(?:be\/)?dist\/apps\/cli\/[A-Za-z0-9_./-]+\.(?:js|mjs|cjs)$/;
31
+ const mapping = value => plain(value) ? value : {};
32
+ const present = value => typeof value === 'string' && Boolean(value.trim());
33
+ const issue = (code, at, message) => ({ code, at, message });
34
+ const scheduleIssue = (at, message) => issue('HFS_SCHEDULE_UNDECLARED', at, message);
35
+
36
+ function safeRelative(relative) {
37
+ if (!present(relative) || SECRET.test(relative) || relative.includes(String.fromCodePoint(92))) return false;
38
+ if (path.isAbsolute(relative) || /^[A-Za-z]:/.test(relative)) return false;
39
+ return relative.split('/').every(part => part && part !== '.' && part !== '..');
40
+ }
41
+
42
+ function publicReference(repoRoot, files, relative) {
43
+ if (!repoRoot || !safeRelative(relative)) return false;
44
+ if (!files.includes(relative)) return false;
45
+ const root = path.resolve(repoRoot), target = path.resolve(root, relative);
46
+ try {
47
+ let cursor = target;
48
+ while (cursor !== root) {
49
+ if (fs.lstatSync(cursor).isSymbolicLink()) return false;
50
+ cursor = path.dirname(cursor);
51
+ }
52
+ const stat = fs.lstatSync(target);
53
+ return stat.isFile() && stat.size > 0 && stat.size <= MAX_BYTES;
54
+ } catch { return false; }
55
+ }
56
+
57
+ function releaseIssues({ environment, spec, repoRoot, files }) {
58
+ const release = mapping(spec.release), at = `environments.${environment}.release`;
59
+ const references = ['imagesLock', 'pointer', 'previous'];
60
+ const assertions = release.healthAssertions;
61
+ const valid = references.every(key => publicReference(repoRoot, files, release[key]))
62
+ && Array.isArray(assertions) && assertions.length > 0 && assertions.every(present);
63
+ return valid ? [] : [issue('STACKS_RELEASE_UNDECLARED', at,
64
+ 'Swarm release must declare tracked public imagesLock, pointer and previous records, and nonempty healthAssertions')];
65
+ }
66
+
67
+ function persistentVolume(volume) {
68
+ if (plain(volume)) return volume.type === 'volume' && present(volume.source);
69
+ if (!present(volume)) return false;
70
+ const source = volume.split(':')[0];
71
+ return volume.includes(':') && /^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(source);
72
+ }
73
+
74
+ function stateful(component, service) {
75
+ return component.role === 'stateful' || component.stateful === true
76
+ || present(component.database) || present(component.database?.provider)
77
+ || (Array.isArray(component.volumes) && component.volumes.length > 0)
78
+ || (Array.isArray(service.volumes) && service.volumes.some(persistentVolume));
79
+ }
80
+
81
+ function recoveryComponents({ doc, spec, model }) {
82
+ const components = mapping(doc.components), services = mapping(model?.services);
83
+ const selected = new Set([...Object.keys(mapping(spec.components)), ...Object.keys(services)]);
84
+ const required = new Set(Object.entries(mapping(doc.backing)).filter(([, entry]) => entry?.managed === true).map(([id]) => id));
85
+ for (const id of selected) if (stateful(mapping(components[id]), mapping(services[id]))) required.add(id);
86
+ return [...required];
87
+ }
88
+
89
+ function backupIssues(context) {
90
+ const { environment, spec, repoRoot, files } = context;
91
+ const backups = Array.isArray(spec.backup) ? spec.backup : [];
92
+ const issues = [];
93
+ for (const id of recoveryComponents(context)) {
94
+ const at = `environments.${environment}.backup.${id}`;
95
+ const records = backups.filter(record => record?.component === id);
96
+ const complete = records.length === 1 && ['target', 'retention', 'owner'].every(key => present(records[0][key]));
97
+ if (!complete) issues.push(issue('BACKUP_UNDECLARED', at, 'stateful components and managed backing must declare one backup target, retention and owner'));
98
+ if (records.length === 1 && !publicReference(repoRoot, files, records[0].restoreProof))
99
+ issues.push(issue('BACKUP_PROOF_MISSING', `${at}.restoreProof`, 'backup must reference a tracked, bounded public restore proof record'));
100
+ }
101
+ return issues;
102
+ }
103
+
104
+ /** Release and recovery declarations are mandatory for Swarm in either edition. */
105
+ export function releaseBackupIssues({ doc = {}, environment, spec = {}, model = {}, repoRoot, files = [] }) {
106
+ if (spec.runtime !== 'docker-swarm') return [];
107
+ const context = { doc, environment, spec, model, repoRoot, files };
108
+ return [...releaseIssues(context), ...backupIssues(context)];
109
+ }
110
+
111
+ function commandWords(value) {
112
+ if (Array.isArray(value)) return value.every(present) ? value : [];
113
+ if (!present(value) || /[;&|<>`\r\n]/.test(value) || value.includes('$(')) return [];
114
+ return value.trim().split(/\s+/).map(word => word.replace(/^['"]|['"]$/g, ''));
115
+ }
116
+
117
+ function cliCommand(value) {
118
+ const words = commandWords(value);
119
+ if (words.length < 3 || words[0] !== 'node' || !CLI_ENTRY.test(words[1]) || words[1].split('/').includes('..')) return null;
120
+ if (!/^[a-z][A-Za-z0-9:_-]*$/.test(words[2])) return null;
121
+ return [words[1].replace(/^be\//, ''), ...words.slice(2)].join(' ');
122
+ }
123
+
124
+ function jobIssues(doc) {
125
+ return Object.entries(mapping(doc.jobs)).flatMap(([id, job]) =>
126
+ present(job?.schedule) && cliCommand(job?.command) ? [] : [scheduleIssue(`jobs.${id}`, 'recurring jobs must declare a nonempty schedule and an app CLI command')]);
127
+ }
128
+
129
+ function cadenceMatches(declared, cadence) {
130
+ if (cadence === null) return true;
131
+ const value = String(cadence);
132
+ return declared === value || declared === `every:${value}` || declared === `${value}ms`;
133
+ }
134
+
135
+ function matchingJob(doc, observation) {
136
+ const jobs = Object.entries(mapping(doc.jobs));
137
+ const candidates = jobs.filter(([id, job]) => id === observation.id || (observation.component && job?.component === observation.id));
138
+ return candidates.find(([, job]) => present(job?.schedule) && cliCommand(job.command)
139
+ && (observation.id || observation.cadence !== null) && cadenceMatches(job.schedule, observation.cadence))?.[1] ?? null;
140
+ }
141
+
142
+ function unwrap(node) {
143
+ while (node && (ts.isAsExpression(node) || ts.isParenthesizedExpression(node) || ts.isSatisfiesExpression(node))) node = node.expression;
144
+ return node;
145
+ }
146
+
147
+ function literal(node, bindings, seen = new Set()) {
148
+ node = unwrap(node);
149
+ if (!node) return null;
150
+ if (ts.isStringLiteralLike(node) || ts.isNumericLiteral(node)) return node.text;
151
+ if (ts.isIdentifier(node) && !seen.has(node.text)) {
152
+ seen.add(node.text);
153
+ return literal(bindings.get(node.text), bindings, seen);
154
+ }
155
+ if (ts.isPropertyAccessExpression(node) && node.expression.getText() === 'CronExpression') return node.getText();
156
+ return null;
157
+ }
158
+
159
+ function property(node, name) {
160
+ node = unwrap(node);
161
+ if (!node || !ts.isObjectLiteralExpression(node)) return null;
162
+ const entry = node.properties.find(item => item.name?.getText().replaceAll('"', '').replaceAll("'", '') === name);
163
+ return entry?.initializer ?? null;
164
+ }
165
+
166
+ function sourceBindings(source) {
167
+ const bindings = new Map(), aliases = new Map([['Cron', 'Cron'], ['Interval', 'Interval']]);
168
+ const visit = node => {
169
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) bindings.set(node.name.text, node.initializer);
170
+ ts.forEachChild(node, visit);
171
+ };
172
+ visit(source);
173
+ for (const statement of source.statements) {
174
+ if (!ts.isImportDeclaration(statement) || statement.moduleSpecifier.text !== '@nestjs/schedule') continue;
175
+ for (const item of statement.importClause?.namedBindings?.elements ?? []) aliases.set(item.name.text, (item.propertyName ?? item.name).text);
176
+ }
177
+ return { bindings, aliases };
178
+ }
179
+
180
+ function callObservation(node, facts) {
181
+ if (!ts.isCallExpression(node)) return null;
182
+ const name = node.expression.getText().split('.').at(-1), kind = facts.aliases.get(name) ?? name;
183
+ if (kind === 'Cron' || kind === 'Interval') {
184
+ const member = ts.isDecorator(node.parent) ? node.parent.parent.name?.getText() : null;
185
+ return { id: literal(property(node.arguments[1], 'name'), facts.bindings) ?? member,
186
+ cadence: literal(node.arguments[0], facts.bindings), node };
187
+ }
188
+ if (name === 'setInterval') return {
189
+ id: node.arguments[0] && ts.isIdentifier(node.arguments[0]) ? node.arguments[0].text : null,
190
+ cadence: literal(node.arguments[1], facts.bindings), node,
191
+ };
192
+ if (name !== 'upsertJobScheduler' && name !== 'addJobScheduler') return null;
193
+ return { id: literal(node.arguments[0], facts.bindings),
194
+ cadence: literal(property(node.arguments[1], 'pattern') ?? property(node.arguments[1], 'every'), facts.bindings), node };
195
+ }
196
+
197
+ function objectObservation(node, facts) {
198
+ if (!ts.isObjectLiteralExpression(node)) return null;
199
+ const id = property(node, 'id'), cadence = property(node, 'everyMs') ?? property(node, 'pattern');
200
+ if (!id || !cadence) return null;
201
+ return { id: literal(id, facts.bindings), cadence: literal(cadence, facts.bindings), node };
202
+ }
203
+
204
+ function sourceObservationIssues(doc, file, source, facts) {
205
+ const issues = [];
206
+ const visit = node => {
207
+ const observation = callObservation(node, facts) ?? objectObservation(node, facts);
208
+ const forwarding = file.includes('/modules/platform/') && observation?.id === null && observation.cadence === null;
209
+ if (observation && !forwarding && !matchingJob(doc, observation)) {
210
+ const line = source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
211
+ issues.push(scheduleIssue(`${file}:${line}`, 'recurring source must match a manifest jobs entry with its schedule and an app CLI command'));
212
+ }
213
+ ts.forEachChild(node, visit);
214
+ };
215
+ visit(source);
216
+ return issues;
217
+ }
218
+
219
+ const walkSql = (tree, visit) => walkSqlAst(tree, visit, true);
220
+ const parseSql = sqlParser(pg);
221
+
222
+ function sqlArgument(call, position, names) {
223
+ const value = sqlCallArgument(call, position, names);
224
+ return value?.A_Const?.sval?.sval ?? value?.TypeCast?.arg?.A_Const?.sval?.sval ?? null;
225
+ }
226
+
227
+ function executableExpression(expression) {
228
+ let query = expression.query;
229
+ if (expression.parseMode === 0) return query;
230
+ if (expression.parseMode >= 3) {
231
+ const assignment = query.indexOf(':=');
232
+ query = assignment < 0 ? query.slice(query.indexOf('=') + 1) : query.slice(assignment + 2);
233
+ }
234
+ return `SELECT ${query}`;
235
+ }
236
+
237
+ function doSqlObservations(statement, file) {
238
+ const body = statement.args?.find(arg => arg.DefElem?.defname === 'as')?.DefElem?.arg?.String?.sval;
239
+ if (!present(body)) return [];
240
+ let tree;
241
+ try { tree = pg.parsePlPgSQLSync("DO '" + body.replaceAll("'", "''") + "';"); }
242
+ catch { return [{ id: null, cadence: null, at: file }]; }
243
+ const observations = [];
244
+ walkSql(tree, value => {
245
+ const expression = value.PLpgSQL_expr;
246
+ if (present(expression?.query)) observations.push(...sqlObservations(executableExpression(expression), file));
247
+ });
248
+ return observations;
249
+ }
250
+
251
+ function sqlObservations(text, file) {
252
+ const tree = parseSql(text);
253
+ if (!tree) return [{ id: null, cadence: null, at: file }];
254
+ const observations = [];
255
+ walkSql(tree, value => {
256
+ if (value.DoStmt) observations.push(...doSqlObservations(value.DoStmt, file));
257
+ const call = value.FuncCall;
258
+ if (!call) return;
259
+ const name = (call.funcname ?? []).map(part => part.String?.sval).join('.');
260
+ if (name === 'cron.unschedule') observations.push({ remove: sqlArgument(call, 0, ['job_name']) });
261
+ if (name === 'cron.schedule' || name === 'cron.schedule_in_database') observations.push({
262
+ id: sqlArgument(call, 0, ['job_name']), cadence: sqlArgument(call, 1, ['schedule']),
263
+ at: `${file}:${text.slice(0, call.location).split('\n').length}`,
264
+ });
265
+ });
266
+ return observations;
267
+ }
268
+
269
+ function sourceSqlObservations(source, file) {
270
+ const observations = [];
271
+ const visit = node => {
272
+ if (ts.isCallExpression(node) && /(?:^|\.)(?:query|execute)$/.test(node.expression.getText())) {
273
+ const argument = unwrap(node.arguments[0]);
274
+ const text = sqlTemplateText(argument);
275
+ if (text !== null) observations.push(...sqlObservations(text, file));
276
+ }
277
+ if (ts.isTaggedTemplateExpression(node) && /(?:^|\.)sql$/.test(node.tag.getText()))
278
+ observations.push(...sqlObservations(sqlTemplateText(node.template), file));
279
+ ts.forEachChild(node, visit);
280
+ };
281
+ visit(source);
282
+ return observations;
283
+ }
284
+
285
+ function sqlTemplateText(node) {
286
+ if (!node) return null;
287
+ if (ts.isStringLiteralLike(node)) return node.text;
288
+ if (!ts.isTemplateExpression(node)) return null;
289
+ return node.head.text + node.templateSpans.map(span => `NULL${span.literal.text}`).join('');
290
+ }
291
+
292
+ function recurringObservations({ doc, repoRoot, files, file, issues }) {
293
+ const sql = file.startsWith('supabase/migrations/') && file.endsWith('.sql') && !file.includes('/_archive/');
294
+ const sourceFile = SOURCE.test(file) && !TEST.test(file);
295
+ if ((!sql && !sourceFile) || !publicReference(repoRoot, files, file)) return [];
296
+ const text = readText(repoRoot, file);
297
+ if (text === null) return [];
298
+ if (sql) return sqlObservations(text, file);
299
+ ts ??= loadTypescript(repoRoot, HERE);
300
+ if (!ts) {
301
+ issues.push(scheduleIssue(file, 'TypeScript is required to inspect recurring application work'));
302
+ return [];
303
+ }
304
+ const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
305
+ issues.push(...sourceObservationIssues(doc, file, source, sourceBindings(source)));
306
+ return sourceSqlObservations(source, file);
307
+ }
308
+
309
+ function recurringSourceIssues({ doc, repoRoot, files }) {
310
+ const issues = [], cron = new Map();
311
+ const paths = [...new Set(files)].sort((left, right) => left.localeCompare(right));
312
+ for (const file of paths) {
313
+ const observations = recurringObservations({ doc, repoRoot, files, file, issues });
314
+ for (const observation of observations) {
315
+ if ('remove' in observation) {
316
+ if (observation.remove) cron.delete(observation.remove);
317
+ } else cron.set(observation.id ?? observation.at, observation);
318
+ }
319
+ }
320
+ for (const observation of cron.values()) if (!matchingJob(doc, observation))
321
+ issues.push(scheduleIssue(observation.at, 'pg_cron job must match a manifest jobs entry with its schedule and an app CLI command'));
322
+ return issues;
323
+ }
324
+
325
+ function scheduledComponentIssues({ doc, environment, spec, model }) {
326
+ const issues = [], components = mapping(doc.components), services = mapping(model?.services);
327
+ const selected = new Set([...Object.keys(mapping(spec.components)), ...Object.keys(services)]);
328
+ for (const id of selected) {
329
+ const component = mapping(components[id]), service = mapping(services[id]);
330
+ const replicated = component.mode === 'replicated-job' || service.deploy?.mode === 'replicated-job';
331
+ if (!replicated && component.schedule === undefined) continue;
332
+ const job = matchingJob(doc, { id, component: true, cadence: component.schedule ?? null });
333
+ const command = cliCommand(service.command);
334
+ const commandMatches = !replicated || (command && cliCommand(job?.command) === command);
335
+ if (!job || (job.component !== undefined && job.component !== id) || !commandMatches)
336
+ issues.push(scheduleIssue(`environments.${environment}.components.${id}`, 'scheduled component must match a manifest job schedule; replicated-job must execute its declared app CLI command'));
337
+ }
338
+ return issues;
339
+ }
340
+
341
+ /** Recurring sources and rendered replicated-jobs name one manifest job and CLI action. */
342
+ export function scheduleIssues({ doc = {}, environment, spec = {}, model = {}, repoRoot, files = [], scanSources = true }) {
343
+ return [...jobIssues(doc), ...(scanSources ? recurringSourceIssues({ doc, repoRoot, files }) : []),
344
+ ...scheduledComponentIssues({ doc, environment, spec, model })];
345
+ }
346
+
347
+ function findingOf(checked) {
348
+ const match = /^(.*\.(?:sql|[cm]?[jt]s)):(\d+)$/.exec(checked.at);
349
+ const sourceFile = /\.(?:sql|[cm]?[jt]s)$/.test(checked.at) ? checked.at : null;
350
+ const file = match?.[1] ?? sourceFile ?? DECLARATION;
351
+ return found(checked.code, file, `${file}: ${checked.at}: ${checked.message}`);
352
+ }
353
+
354
+ /** Both editions receive real findings when a declared Swarm model cannot be judged. */
355
+ export function stacksReleaseFindings(context) {
356
+ if (context.isRoot === false || (context.repo?.profile && context.repo.profile !== 'app')) return [];
357
+ const { doc, deployments, files } = stacksDeploymentContexts(context);
358
+ const findings = scheduleIssues({ doc, repoRoot: context.repoRoot, files }).map(findingOf);
359
+ for (const deployment of deployments) {
360
+ const input = { ...deployment, doc, files, repoRoot: context.repoRoot };
361
+ if (deployment.unavailable) findings.push(found('STACKS_MODEL_UNAVAILABLE', deployment.file,
362
+ `${deployment.file}: environments.${deployment.environment}: a tracked rendered deployment model is required to judge Swarm safety`));
363
+ findings.push(...releaseBackupIssues(input).map(findingOf), ...scheduledComponentIssues(input).map(findingOf));
364
+ }
365
+ return findings;
366
+ }
@@ -0,0 +1,168 @@
1
+ // R10 runtime consistency: app-owned scheduling, managed lite backing and Swarm deployment safety.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { isPlainObject as plain } from '../../../engine/plain-object.mjs';
5
+ import { deploymentFindings } from '../../lib/deployment-findings.mjs';
6
+ import { findStackDeclaration, readDeclaration } from '../../lib/stack-declaration.mjs';
7
+ import { isInside } from '../../lib/walk.mjs';
8
+ import { composeIncludePaths, swarmInventoryIssues, swarmModelIssues } from '../../lib/swarm-stack.mjs';
9
+ import { found, readText } from './read.mjs';
10
+
11
+ const CODE = 'HFS_STACKS_SHAPE';
12
+ const DECLARATION = '.starcistacks/application-stacks.yaml';
13
+ const HOST_CRON_PATH = /^(?:\.github\/workflows|deploy|scripts|\.starcistacks)\//;
14
+ const HOST_CRON = /\bcrontab\s+(?:-l\b|-(?=\s|[;|&)]|$))|(?:\/etc\/)?\bcron\.d(?:\/|\b)|@reboot\b/i;
15
+ const mapping = value => plain(value) ? value : {};
16
+ const issueFinding = (file, issue) => found(CODE, file, `${file}: ${issue.at}: ${issue.message}`);
17
+
18
+ /** The app's tracked automation sources; locations are reported without returning any source text. */
19
+ export function hostCrontabFindings({ repoRoot, files }) {
20
+ const findings = [];
21
+ for (const file of files.filter(file => HOST_CRON_PATH.test(file) && !file.endsWith('.enc'))) {
22
+ const text = readText(repoRoot, file);
23
+ if (text === null) continue;
24
+ for (const [index, line] of text.split(/\r?\n/).entries()) {
25
+ if (HOST_CRON.test(line)) findings.push(found(CODE, file,
26
+ `${file}:${index + 1}: host crontab installation is forbidden; recurring work must use an app scheduler, pgmq/pg_cron, or a declared Swarm scheduler and replicated-job`, { line: index + 1 }));
27
+ }
28
+ }
29
+ return findings;
30
+ }
31
+
32
+ function safeStackFile(repoRoot, envDir, relative) {
33
+ if (typeof relative !== 'string' || path.isAbsolute(relative) || relative.split(/[\\/]/).includes('..')) return null;
34
+ const target = path.resolve(envDir, relative);
35
+ const base = path.resolve(repoRoot);
36
+ if (!isInside(base, envDir) || !isInside(envDir, target)) return null;
37
+ let cursor = target;
38
+ while (true) {
39
+ if (fs.existsSync(cursor) && fs.lstatSync(cursor).isSymbolicLink()) return null;
40
+ if (cursor === base) break;
41
+ cursor = path.dirname(cursor);
42
+ }
43
+ return target;
44
+ }
45
+
46
+ function mergeMapping(current, incoming) {
47
+ const result = { ...mapping(current) };
48
+ for (const [key, value] of Object.entries(mapping(incoming))) {
49
+ result[key] = plain(value) ? mergeMapping(result[key], value) : value;
50
+ }
51
+ return result;
52
+ }
53
+
54
+ function includePaths(doc, relative, file, findings) {
55
+ const { paths, errors } = composeIncludePaths(doc);
56
+ for (const message of errors) findings.push(issueFinding(file, { at: 'include', message }));
57
+ return paths.map(value => path.isAbsolute(value) ? value : path.posix.join(path.posix.dirname(relative), value));
58
+ }
59
+
60
+ function loadSwarmModel({ repoRoot, environment, spec, files, findings }) {
61
+ const prefix = `.starcistacks/${environment}/`, envDir = path.resolve(repoRoot, prefix);
62
+ const queue = [...(Array.isArray(spec.composeFiles) ? spec.composeFiles : [])], seen = new Set();
63
+ let model = {};
64
+ if (!queue.length) findings.push(found(CODE, DECLARATION, `${DECLARATION}: environments.${environment}.composeFiles must declare the Swarm stack sources`));
65
+ while (queue.length) {
66
+ const relative = queue.shift();
67
+ if (seen.has(relative)) continue;
68
+ seen.add(relative);
69
+ const file = `${prefix}${relative}`, target = safeStackFile(repoRoot, envDir, relative);
70
+ const parsed = target && files.includes(file) ? readDeclaration(target) : { error: 'missing, untracked or unsafe stack source' };
71
+ if (parsed.error) {
72
+ findings.push(found(CODE, file, `${file}: Swarm stack cannot be read: ${parsed.error}`));
73
+ continue;
74
+ }
75
+ model = mergeMapping(model, parsed.doc);
76
+ queue.push(...includePaths(parsed.doc, relative, file, findings));
77
+ }
78
+ return { model, sources: new Set([...seen].map(relative => `${prefix}${relative}`)) };
79
+ }
80
+
81
+ function swarmDeploymentFiles(repoRoot, files) {
82
+ return files.filter(file => {
83
+ const environment = /^\.starcistacks\/([^/]+)\/infra\/swarm\//.exec(file)?.[1];
84
+ if (!environment || file.endsWith('.enc')) return false;
85
+ const target = safeStackFile(repoRoot, path.resolve(repoRoot, '.starcistacks', environment), file.split('/').slice(2).join('/'));
86
+ if (!target) return true;
87
+ const parsed = readDeclaration(target);
88
+ return path.basename(file) === 'stack.yaml' || plain(parsed.doc?.services) || Array.isArray(parsed.doc?.include);
89
+ });
90
+ }
91
+
92
+ function liteEnvironmentFindings({ doc, environments, deployments }) {
93
+ const findings = [];
94
+ const delivery = deployments.length || Object.values(environments).some(spec => Array.isArray(spec?.composeFiles) && spec.composeFiles.length);
95
+ if (delivery && (doc.backing?.supabase?.provider !== 'supabase' || doc.backing?.supabase?.managed !== true))
96
+ findings.push(found(CODE, DECLARATION, `${DECLARATION}: lite Swarm delivery must declare backing.supabase with provider: supabase and managed: true`));
97
+ for (const [environment, spec] of Object.entries(environments)) {
98
+ if (!Array.isArray(spec?.composeFiles) || !spec.composeFiles.length) continue;
99
+ if (spec.runtime !== 'docker-swarm' || spec.composeFiles.some(file => typeof file !== 'string' || !file.startsWith('infra/swarm/')))
100
+ findings.push(found(CODE, DECLARATION, `${DECLARATION}: environments.${environment} lite delivery must use runtime: docker-swarm and infra/swarm sources`));
101
+ }
102
+ return findings;
103
+ }
104
+
105
+ function undeclaredSwarmFindings(deployments, environments, sources) {
106
+ return deployments.filter(file => !sources.has(file)).map(file => {
107
+ const environment = file.split('/')[1], spec = environments[environment];
108
+ const reason = spec?.runtime === 'docker-swarm' ? 'is not declared or reachable from composeFiles' : 'has no declared docker-swarm environment';
109
+ return found(CODE, file, `${file}: Swarm deployment ${reason}`);
110
+ });
111
+ }
112
+
113
+ /** Declared Swarm sources are checked for both editions; lite additionally refuses local managed backing images. */
114
+ export function stacksRuntimeFindings({ repoRoot, files, doc, edition = 'full' }) {
115
+ const findings = [], lite = edition === 'lite', components = mapping(doc?.components), environments = mapping(doc?.environments);
116
+ const deployments = swarmDeploymentFiles(repoRoot, files), sources = new Set();
117
+ findings.push(...swarmInventoryIssues({ components, lite }).map(issue => issueFinding(DECLARATION, issue)));
118
+ if (lite) findings.push(...liteEnvironmentFindings({ doc, environments, deployments }));
119
+ for (const [environment, entry] of Object.entries(environments)) {
120
+ const spec = mapping(entry);
121
+ if (spec.runtime !== 'docker-swarm') continue;
122
+ const loaded = loadSwarmModel({ repoRoot, environment, spec, files, findings });
123
+ for (const file of loaded.sources) sources.add(file);
124
+ const rendered = renderedDeployment({ repoRoot, environment, spec, files, edition });
125
+ if (spec.renderedModel !== undefined && !rendered.unavailable) sources.add(rendered.file);
126
+ const file = `.starcistacks/${environment}/${spec.composeFiles?.[0] ?? 'infra/swarm/stack.yaml'}`;
127
+ findings.push(...swarmModelIssues({ model: loaded.model, components, bindings: spec.components, lite }).map(issue => issueFinding(file, issue)));
128
+ }
129
+ findings.push(...undeclaredSwarmFindings(deployments, environments, sources));
130
+ return findings;
131
+ }
132
+
133
+ function renderedDeployment({ repoRoot, environment, spec, files, edition }) {
134
+ const relative = spec.renderedModel ?? spec.composeFiles?.[0] ?? 'infra/swarm/stack.yaml';
135
+ const prefix = `.starcistacks/${environment}/`, file = `${prefix}${relative}`;
136
+ const envDir = path.resolve(repoRoot, prefix);
137
+ const target = safeStackFile(repoRoot, envDir, relative);
138
+ const modelDirectory = edition === 'lite' ? 'infra/swarm/' : 'infra/';
139
+ const allowed = typeof relative === 'string' && relative.startsWith(modelDirectory) && /\.(?:ya?ml|json)$/i.test(relative);
140
+ const parsed = allowed && target && files.includes(file) ? readDeclaration(target) : {};
141
+ const model = mapping(parsed.doc);
142
+ const rendered = plain(model.services) && Object.keys(model.services).length > 0 && model.include === undefined;
143
+ const complete = spec.renderedModel !== undefined || spec.composeFiles?.length === 1;
144
+ return { environment, spec, model, file, unavailable: !rendered || !complete };
145
+ }
146
+
147
+ /** Tracked rendered Swarm models shared by the alpha.15 finders and the app-check gate. */
148
+ export function stacksDeploymentContexts(context = {}) {
149
+ const files = context.all ?? context.files ?? [];
150
+ const empty = { doc: {}, deployments: [], files };
151
+ if (!context.repoRoot || context.isRoot === false) return empty;
152
+ if (context.repo?.profile && context.repo.profile !== 'app') return empty;
153
+ if (!files.includes(DECLARATION)) return empty;
154
+ if (!safeStackFile(context.repoRoot, path.resolve(context.repoRoot, '.starcistacks'), 'application-stacks.yaml')) return empty;
155
+ const doc = findStackDeclaration(context.repoRoot).doc;
156
+ if (!doc) return empty;
157
+ const deployments = Object.entries(mapping(doc.environments))
158
+ .filter(([, spec]) => spec?.runtime === 'docker-swarm')
159
+ .map(([environment, spec]) => renderedDeployment({ repoRoot: context.repoRoot, environment, spec, files, edition: context.repo?.edition }));
160
+ return { doc, deployments, files };
161
+ }
162
+
163
+ /** Apply a static Swarm contract to tracked rendered models and project standard HFS findings. */
164
+ export function stacksDeploymentFindings(context, issuesFor) {
165
+ const { doc, deployments, files } = stacksDeploymentContexts(context);
166
+ return deploymentFindings(deployments,
167
+ entry => issuesFor({ doc, ...entry, files, repoRoot: context.repoRoot, repo: context.repo }), found, DECLARATION);
168
+ }