@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,61 @@
1
+ // json-clone.mjs — a deep copy that keeps exactly what survives a JSON text and nothing else: the plain-data
2
+ // copy of a value, detached from every reference, frozen state and class of the original. `structuredClone` keeps
3
+ // what JSON drops (undefined members, Dates, Maps, sparse holes) and refuses what JSON accepts (functions,
4
+ // `toJSON` objects), so it is not a substitute; this walks the value once under the JSON.stringify rules.
5
+
6
+ /** The boxed primitives JSON.stringify unwraps: recognised by the internal slot a valueOf call reads, as JSON does. */
7
+ const BOXED = [
8
+ { slot: Number.prototype.valueOf, read: Number },
9
+ { slot: String.prototype.valueOf, read: String },
10
+ { slot: Boolean.prototype.valueOf, read: (box) => Boolean.prototype.valueOf.call(box) },
11
+ { slot: BigInt.prototype.valueOf, read: (box) => BigInt.prototype.valueOf.call(box) },
12
+ ];
13
+
14
+ /** Whether `value` carries the internal slot `slot` reads (a valueOf of that boxed type does not throw on it). */
15
+ const hasSlot = (slot, value) => {
16
+ try { slot.call(value); return true; } catch { return false; }
17
+ };
18
+
19
+ /** What JSON.stringify takes of `holder[key]`: the toJSON result when there is one, a boxed primitive unwrapped. */
20
+ const serialized = (holder, key) => {
21
+ let value = holder[key];
22
+ if (value !== null && (typeof value === 'object' || typeof value === 'bigint') && typeof value.toJSON === 'function') value = value.toJSON(key);
23
+ if (value === null || typeof value !== 'object') return value;
24
+ const box = BOXED.find((candidate) => hasSlot(candidate.slot, value));
25
+ return box ? box.read(value) : value;
26
+ };
27
+
28
+ /** A member JSON leaves out of an object (and writes as null in an array). */
29
+ const isAbsent = (value) => value === undefined || typeof value === 'function' || typeof value === 'symbol';
30
+
31
+ const copyValue = (value, ancestors) => {
32
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
33
+ if (typeof value === 'number') return Number.isFinite(value) ? value + 0 : null;
34
+ if (typeof value === 'bigint') throw new TypeError('Do not know how to serialize a BigInt');
35
+ if (ancestors.includes(value)) throw new TypeError('Converting circular structure to JSON');
36
+ const inside = [...ancestors, value];
37
+ if (Array.isArray(value)) {
38
+ return Array.from({ length: value.length }, (_, index) => {
39
+ const item = serialized(value, String(index));
40
+ return isAbsent(item) ? null : copyValue(item, inside);
41
+ });
42
+ }
43
+ const entries = [];
44
+ for (const name of Object.keys(value)) {
45
+ const member = serialized(value, name);
46
+ if (!isAbsent(member)) entries.push([name, copyValue(member, inside)]);
47
+ }
48
+ return Object.fromEntries(entries);
49
+ };
50
+
51
+ /**
52
+ * `value` copied as JSON data: members that are undefined, functions or symbols are dropped (an array item becomes
53
+ * null), non-finite numbers become null and -0 becomes 0, `toJSON` results replace their objects (a Date becomes its ISO string), boxed
54
+ * primitives unwrap, and a cycle or a BigInt throws a TypeError. A value JSON has no text for (undefined, a function,
55
+ * a symbol) throws a SyntaxError, as parsing the missing text does.
56
+ */
57
+ export function jsonClone(value) {
58
+ const top = serialized({ '': value }, '');
59
+ if (isAbsent(top)) throw new SyntaxError('"undefined" is not valid JSON');
60
+ return copyValue(top, []);
61
+ }
@@ -17,6 +17,10 @@ export const objectList = (v) => (Array.isArray(v) ? v : []).filter((item) => it
17
17
  export const stringList = (v, { blank = true } = {}) =>
18
18
  Array.isArray(v) && v.every((s) => typeof s === 'string' && s.length > 0 && (blank || s.trim().length > 0));
19
19
 
20
+ /** A non-empty list of distinct values, all accepted by the supplied predicate. */
21
+ export const nonemptyUniqueList = (values, accepts) => Array.isArray(values) && values.length > 0
22
+ && values.every((entry) => accepts(entry)) && new Set(values).size === values.length;
23
+
20
24
  /** The items of a `sep`-separated list: trimmed, empties dropped; `dedupe` keeps first occurrences only. */
21
25
  export const splitList = (v, { sep = ',', dedupe = false } = {}) => {
22
26
  const items = String(v ?? '').split(sep).map((s) => s.trim()).filter(Boolean);
@@ -0,0 +1,59 @@
1
+ // roots.mjs - the one place that names a root of the runtime and says which tree wins when two hold the same record.
2
+ //
3
+ // A component that needs "which directory" asks here, so no component resolves it by itself (the wrong-tree reads of 2026-10-08/09:
4
+ // a brand record read from the product's main checkout, a capture tool looked up only in product dirs, generated files written
5
+ // through a home record, a Kernel start gated by another tree's build). The roots, each with its one resolver:
6
+ // runtime tree runtimeTree the checkout this code runs from (derived from the module location, never from a record)
7
+ // runtime state runtimeState <runtime tree>/.runtime, or STARCI_LOCAL_ROOT
8
+ // temp root tempRoot STARCI_TEMP_ROOT, the owner config, else the OS directory
9
+ // invocation dir invocationDir where the command was started; only ever a default for a flag the caller may override
10
+ // work dir workDirName the name of the Work records directory inside a product tree
11
+ // product trees treesInOrder the workflow tree first, then the product's main checkout
12
+ // Work records workRecordPath the first tree that holds the record, else the main checkout's path
13
+ // tool lookups toolSearchDirs the tree the work happens in first, the runtime's own install last
14
+ // The workflow tree itself, a job's placement and the Critic directory are registry facts: scripts/machine/workflow-tree.mjs,
15
+ // placement-rebound.mjs and the worktree registry resolve them, and a caller hands the result in as `tree`.
16
+ // The `root-adhoc` self-check (scripts/checks/check-root-adhoc.mjs, rule R240) refuses a new path built from the process working
17
+ // directory or from the Work directory name outside this module.
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ import { skillRoot, runtimeStateDir, starciLocalRoot, starciSourceRoot } from '../../engine/runtime-root.mjs';
21
+ import { tempRoot } from '../../engine/temp-root.mjs';
22
+
23
+ export { tempRoot, starciLocalRoot, starciSourceRoot };
24
+
25
+ /** The runtime tree this code runs from. */
26
+ export const runtimeTree = skillRoot;
27
+
28
+ /** The state directory of a runtime tree (default: this one). */
29
+ export const runtimeState = (tree = runtimeTree) => runtimeStateDir(tree);
30
+
31
+ /** The name of the Work records directory inside a product tree. */
32
+ export const WORK_DIR_NAME = '.starciwork';
33
+
34
+ /** The directory a command was started in: `io.cwd` when the caller injects one, else the process working directory. A default for flags only. */
35
+ export const invocationDir = (io = undefined) => path.resolve(io?.cwd ?? process.cwd());
36
+
37
+ /** The distinct, resolved trees that may hold a product's records, in precedence order: the workflow tree, then the main checkout. */
38
+ export const treesInOrder = ({ tree = null, repo = null } = {}) => [...new Set([tree, repo].filter(Boolean).map((dir) => path.resolve(dir)))];
39
+
40
+ /** The Work directories of those trees, in the same order. */
41
+ export const workDirsInOrder = ({ tree = null, repo = null, workDirName = WORK_DIR_NAME } = {}) => treesInOrder({ tree, repo }).map((dir) => path.join(dir, workDirName));
42
+
43
+ /**
44
+ * The first Work directory (workflow tree first, then the main checkout) that `holds` accepts, else the main checkout's own (the
45
+ * path a refusal names), else the tree's when there is no main checkout.
46
+ */
47
+ export function workDirHolding(holds, { tree = null, repo = null, workDirName = WORK_DIR_NAME } = {}) {
48
+ const dirs = workDirsInOrder({ tree, repo, workDirName });
49
+ return dirs.find((dir) => holds(dir)) ?? (repo ? path.join(path.resolve(repo), workDirName) : (dirs[0] ?? null));
50
+ }
51
+
52
+ /** The file of a Work record (`rel` under the Work directory) from the first tree that has it, else the main checkout's path. */
53
+ export function workRecordPath(rel, { tree = null, repo = null, workDirName = WORK_DIR_NAME, exists = fs.existsSync } = {}) {
54
+ const parts = String(rel).split('/');
55
+ return path.join(workDirHolding((dir) => exists(path.join(dir, ...parts)), { tree, repo, workDirName }) ?? '', ...parts);
56
+ }
57
+
58
+ /** The directories a tool is looked up from: the caller's (the tree the work happens in, then the project) first, the runtime's own install last. */
59
+ export const toolSearchDirs = (dirs, runtime = runtimeTree) => [...dirs.filter(Boolean), runtime].filter(Boolean);
@@ -0,0 +1,13 @@
1
+ // Identify runtime-owned example apps; an unrelated directory named examples grants no fixture behavior.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export function isRuntimeExampleApp(repoRoot) {
6
+ if (path.basename(path.dirname(repoRoot)) !== 'examples') return false;
7
+ try {
8
+ const runtime = JSON.parse(fs.readFileSync(path.resolve(repoRoot, '..', '..', 'hfs.json'), 'utf8'));
9
+ return runtime.kind === 'runtime';
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
@@ -12,6 +12,57 @@ export const FORBIDDEN_FILES = [
12
12
  { name: 'starcistacks-secret-plaintext', test: (f) => /(^|\/)\.starcistacks\/[^/]+\/secrets\//i.test(f) && !/(\.enc|\/\.gitkeep|\/KEYS\.md)$/i.test(f) },
13
13
  { name: 'credentials-json', test: (f) => /(^|\/)(credentials|service-account|client_secret)[^/]*\.json$/i.test(f) },
14
14
  ];
15
+ // `word` in any case (name checks are case-aware by construction, not by flag).
16
+ const fold = (word) => word.replace(/[a-z]/gi, (c) => `[${c.toLowerCase()}${c.toUpperCase()}]`);
17
+
18
+ // A name that holds secret material: the keyword is an identifier segment, so DB_PASSWORD, dbPassword,
19
+ // OPENAI_API_KEY, api_secret and accessToken are candidates while `secondary`, `tokenize`, `monkey`, `secrets.x`
20
+ // and `$secret` are not. A segment starts at the name edge, after `_`/`-`/`.`, or at a camel hump.
21
+ const SECRET_WORD = [
22
+ 'password', 'passwd', 'passphrase', 'secret', 'credentials?', 'token',
23
+ 'api[_-]?key', 'access[_-]?token', 'auth[_-]?token', 'refresh[_-]?token', 'id[_-]?token', 'session[_-]?token',
24
+ 'client[_-]?secret', 'api[_-]?secret', 'session[_-]?secret', 'jwt[_-]?secret',
25
+ 'private[_-]?key', 'secret[_-]?key', 'signing[_-]?key', 'encryption[_-]?key', 'master[_-]?key',
26
+ 'service[_-]?role[_-]?key', 'service[_-]?key', 'session[_-]?key',
27
+ ].map((word) => fold(word)).join('|');
28
+
29
+ // A name whose last segment is configuration, not material: SECRET_FILE and *_PATH point at the materialized
30
+ // secret file (the materializer's own form), PASSWORD_MIN_LENGTH is a number, *_REF/_ID a reference.
31
+ const BENIGN_WORD = [
32
+ 'file', 'path', 'dir', 'url', 'uri', 'endpoint', 'host', 'refs?', 'names?', 'ids?', 'arn', 'urn',
33
+ 'length', 'min', 'max', 'size', 'count', 'budget', 'limit', 'regex', 'pattern', 'policy',
34
+ 'algorithm', 'scheme', 'expiry', 'expires', 'ttl', 'required', 'enabled', 'disabled', 'type', 'version',
35
+ ].map((word) => fold(word)).join('|');
36
+
37
+ const SEGMENT_BEFORE = String.raw`(?:(?<![A-Za-z0-9_$])|(?<=[_\-.])|(?<=[a-z])(?=[A-Z]))`;
38
+ const NAME_REST = '[A-Za-z0-9_$.-]*?';
39
+ const SECRET_NAME = [
40
+ '(?<![A-Za-z0-9_$])',
41
+ String.raw`(?!${NAME_REST}${SEGMENT_BEFORE}(?:${BENIGN_WORD})(?=\s*[:=]))`,
42
+ String.raw`(?=${NAME_REST}${SEGMENT_BEFORE}(?:${SECRET_WORD})(?=[_\-.]|[A-Z]|[^A-Za-z0-9_$]|$))`,
43
+ String.raw`[A-Za-z_$][A-Za-z0-9_$.-]*\s*[:=]\s*`,
44
+ ].join('');
45
+
46
+ // `password = "hunter2hunter2"` — the quoted literal. `env(NAME)` is a reference, never a secret.
47
+ const ASSIGNED_QUOTED = new RegExp(
48
+ SECRET_NAME + String.raw`['"\`](?!env\([A-Z_][A-Z0-9_]*\)['"\`])([^'"\`\n\s$<{]{12,})['"\`]`,
49
+ );
50
+ // `DB_PASSWORD=hunter2hunter2` — the compose list-item and dotenv form; the dotenv value has no `:` so
51
+ // `password: secret:db/password` and `token: ref:vault/api` stay references, and the end boundary keeps
52
+ // calls and member/index references such as `secret = credentialsSource.current` out of the literals.
53
+ const ASSIGNED_UNQUOTED = new RegExp(
54
+ SECRET_NAME + String.raw`(?!['"\`]|env\()([A-Za-z0-9_+=-]{12,})(?![A-Za-z0-9_+=.(\[-])`,
55
+ );
56
+
57
+ const NAME_WORD = (word, tail) => new RegExp(`^${NAME_REST}${SEGMENT_BEFORE}(?:${word})${tail}`);
58
+ const SECRET_NAME_TEST = NAME_WORD(SECRET_WORD, String.raw`(?:[_\-.]|[A-Z]|[^A-Za-z0-9_$]|$)`);
59
+ const BENIGN_TAIL_TEST = NAME_WORD(BENIGN_WORD, '$');
60
+
61
+ /** True when a bare variable `name` holds secret material as an identifier segment (DB_PASSWORD, dbPassword,
62
+ * SUPABASE_SERVICE_ROLE_KEY) and does not end in a config suffix (PASSWORD_MIN_LENGTH, SECRET_FILE,
63
+ * tokenBudget). The same name judgement the `assigned-secret` patterns make, for callers that read keys. */
64
+ export const secretNameLike = (name) => SECRET_NAME_TEST.test(name) && !BENIGN_TAIL_TEST.test(name);
65
+
15
66
  export const SECRET_PATTERNS = [
16
67
  { name: 'private-key-block', re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/ },
17
68
  { name: 'aws-access-key', re: /\bAKIA[0-9A-Z]{16}\b/ },
@@ -23,17 +74,20 @@ export const SECRET_PATTERNS = [
23
74
  { name: 'telegram-bot-token', re: /\b\d{8,10}:AA[0-9A-Za-z_-]{33}\b/ },
24
75
  { name: 'stripe-secret', re: /\b(?:sk|rk)_live_[0-9A-Za-z]{20,}\b/ },
25
76
  { name: 'jwt', re: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
77
+ { name: 'age-secret-key', re: /\bAGE-SECRET-KEY-1[0-9A-Z]{20,}\b/ },
78
+ { name: 'password-connection-string', re: /\b(?:postgres(?:ql)?|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/@]+:[^@\s/${}]{8,}@/iu },
26
79
  // A value written `env(NAME)` (the Supabase config.toml reference form) names a variable, not a secret.
27
80
  // A keyword-assigned value that names itself a stand-in (a test stub's `accessToken: "fixture-..."`) is no
28
81
  // candidate; only this heuristic takes the exemption, never a provider-shaped token above.
29
82
  // A spec file's keyword-assigned values are test inputs (a spec's sign-in
30
83
  // form password and a mocked accessToken once refused a push): the heuristic skips spec files too.
31
- { name: 'assigned-secret', re: new RegExp([
32
- String.raw`\b(?:password|passwd|secret|api[_-]?key|access[_-]?token|client[_-]?secret)`,
33
- String.raw`\b\s*[:=]\s*['"](?!env\([A-Z_][A-Z0-9_]*\)['"])([^'"\s$<{]{12,})`,
34
- `['"]`,
35
- ].join(''), 'i'), placeholder: /fixture|stub|fake|dummy|placeholder|example|sample|changeme|redacted|mock/i,
84
+ // A SCREAMING_SNAKE quoted value in source is a public code (an enum member whose value is an upper-case code), not a credential.
85
+ { name: 'assigned-secret', re: ASSIGNED_QUOTED, sourceCode: /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/, placeholder: /fixture|stub|fake|dummy|placeholder|example|sample|changeme|redacted|mock/i,
36
86
  skipFile: /\.(?:spec|test|e2e-spec)\.[cm]?[jt]sx?$/ },
87
+ // An unquoted value in TypeScript or JavaScript source is an identifier or a type (`token: InjectionToken`,
88
+ // `MODULE_OPTIONS_TOKEN: DATABASE_OPTIONS_TOKEN`), never a literal; the unquoted form is the dotenv, compose and toml shape.
89
+ { name: 'assigned-secret', re: ASSIGNED_UNQUOTED, placeholder: /fixture|stub|fake|dummy|placeholder|example|sample|changeme|redacted|mock/i,
90
+ skipFile: /\.(?:spec|test|e2e-spec)\.[cm]?[jt]sx?$|\.[cm]?[jt]sx?$/ },
37
91
  ];
38
92
 
39
93
 
@@ -43,6 +97,7 @@ export function secretHits(file, text) {
43
97
  for (const rule of SECRET_PATTERNS) {
44
98
  if (rule.skipFile?.test(file ?? '')) continue;
45
99
  const hit = rule.re.exec(text);
100
+ if (hit && rule.sourceCode?.test(hit[1] ?? '') && /\.[cm]?[jt]sx?$/.test(file ?? '')) continue;
46
101
  if (hit && !rule.placeholder?.test(hit[1] ?? '')) names.push(rule.name);
47
102
  }
48
103
  return names;
@@ -0,0 +1,32 @@
1
+ // PostgreSQL AST primitives shared by database verbs and synchronous content rules.
2
+
3
+ /** String identifier components from libpg-query's wrapped String nodes. */
4
+ export const sqlStringParts = (nodes) => (nodes ?? []).map((node) => node?.String?.sval).filter((value) => value !== undefined);
5
+
6
+ /** Traverse SQL/PLpgSQL objects; executable scans leave function declarations inert. */
7
+ export function walkSqlAst(value, visit, executable = false) {
8
+ if (!value || typeof value !== 'object' || (executable && value.CreateFunctionStmt)) return;
9
+ visit(value);
10
+ for (const child of Object.values(value)) walkSqlAst(child, visit, executable);
11
+ }
12
+
13
+ /** Build a synchronous parser with the caller's failure policy and initialized parser. */
14
+ export function sqlParser(parser, reject = () => null) {
15
+ return (text) => {
16
+ try { return parser?.parseSync(text) ?? null; }
17
+ catch (error) { return reject(error); }
18
+ };
19
+ }
20
+
21
+ /** Resolve named arguments before positional arguments, as PostgreSQL does. */
22
+ export function sqlCallArgument(call, position, names = []) {
23
+ const args = call.args ?? [];
24
+ return args.find((item) => names.includes(item?.NamedArgExpr?.name))?.NamedArgExpr?.arg
25
+ ?? args.filter((item) => !item.NamedArgExpr)[position];
26
+ }
27
+
28
+ /** Read a literal string through static casts without evaluating an expression. */
29
+ export function sqlStringLiteral(node) {
30
+ if (node?.TypeCast) return sqlStringLiteral(node.TypeCast.arg);
31
+ return node?.A_Const?.sval?.sval ?? null;
32
+ }
@@ -58,7 +58,7 @@ const KNOWN_IMAGES = Object.freeze({
58
58
  export const STATEFUL_KINDS = Object.freeze(new Set(['database', 'cache', 'identity', 'storage', 'mail', 'queue', 'search']));
59
59
 
60
60
  /** The repository name of an image reference: `quay.io/keycloak/keycloak:26.0` -> `keycloak`. */
61
- const repositoryOf = image => {
61
+ export const repositoryOf = image => {
62
62
  const withoutDigest = image.split('@')[0];
63
63
  const lastSegment = withoutDigest.split('/').at(-1) ?? withoutDigest;
64
64
  return lastSegment.split(':')[0];
@@ -0,0 +1,126 @@
1
+ // Shared static Swarm contract for app checks and rendered deployment evidence.
2
+ import { isPlainObject as plain } from '../../engine/plain-object.mjs';
3
+ import { composeEntries } from './compose-values.mjs';
4
+ import { repositoryOf } from './stack-services.mjs';
5
+
6
+ const PINNED_IMAGE = /@sha256:[a-f0-9]{64}$/i;
7
+ const VERSIONED_SECRET = /-v[1-9][0-9]*$/;
8
+ const BACKING_IMAGE = /^(?:postgres(?:ql)?|postgresql-repmgr|redis(?:-stack(?:-server)?)?|(?:cp-)?kafka(?:-native)?|keycloak|minio|rabbitmq|mysql(?:-server)?|mongo(?:db)?|mongodb-community-server)$/i;
9
+ const mapping = value => plain(value) ? value : {};
10
+ const present = value => typeof value === 'string' && Boolean(value.trim());
11
+ const configured = value => Object.keys(mapping(value)).length > 0;
12
+ const schedulerImage = image => present(image) && /(?:^|\/)swarm-cronjob(?:[:@]|$)/.test(image);
13
+
14
+ /** Compose includes may be a path or an object whose path is a string or a list of source paths. */
15
+ export function composeIncludePaths(doc) {
16
+ const paths = [], errors = [];
17
+ if (doc.include === undefined) return { paths, errors };
18
+ if (!Array.isArray(doc.include)) return { paths, errors: ['Compose include must be an array of source paths'] };
19
+ for (const entry of doc.include) {
20
+ let values = [entry];
21
+ if (plain(entry)) values = Array.isArray(entry.path) ? entry.path : [entry.path];
22
+ if (!values.length || values.some(value => !present(value))) errors.push('Compose include must declare nonempty string source paths');
23
+ else paths.push(...values);
24
+ }
25
+ return { paths, errors };
26
+ }
27
+
28
+ function runnableHealthcheck(value) {
29
+ if (present(value)) return value !== 'NONE';
30
+ if (!Array.isArray(value) || !['CMD', 'CMD-SHELL'].includes(value[0])) return false;
31
+ return value.slice(1).some(present);
32
+ }
33
+
34
+ function jobDeclarationIssues(id, component, components, add) {
35
+ if (component.mode !== 'replicated-job') return;
36
+ const scheduler = components[component.scheduler];
37
+ if (!present(component.scheduler) || !plain(scheduler) || scheduler.role !== 'scheduler' || scheduler.mode === 'replicated-job')
38
+ add(`components.${id}.scheduler`, 'replicated-job must name a declared service component with role scheduler');
39
+ else if (!schedulerImage(scheduler.image)) add(`components.${id}.scheduler`, 'replicated-job scheduler must declare the swarm-cronjob image');
40
+ if (!present(component.schedule)) add(`components.${id}.schedule`, 'replicated-job must declare its cron schedule');
41
+ }
42
+
43
+ /** Inventory rules are independent of a selected environment or rendered deployment model. */
44
+ export function swarmInventoryIssues({ components, lite = false }) {
45
+ const errors = [], catalog = mapping(components);
46
+ const add = (at, message) => errors.push({ at, message });
47
+ for (const [id, entry] of Object.entries(catalog)) {
48
+ const component = mapping(entry);
49
+ if (lite && present(component.image) && BACKING_IMAGE.test(repositoryOf(component.image)))
50
+ add(`components.${id}.image`, 'lite must use managed Supabase backing services; this image duplicates a Supabase-provided service');
51
+ jobDeclarationIssues(id, component, catalog, add);
52
+ }
53
+ return errors;
54
+ }
55
+
56
+ function serviceSafetyIssues(id, service, add) {
57
+ const at = `services.${id}`, deploy = mapping(service.deploy), healthcheck = mapping(service.healthcheck);
58
+ if (!present(service.image)) add(`${at}.image`, 'managed Swarm service needs a nonempty rendered image');
59
+ else if (!PINNED_IMAGE.test(service.image)) add(`${at}.image`, 'Swarm image must be pinned by a sha256 digest');
60
+ if (service.build !== undefined) add(`${at}.build`, 'rendered Swarm model must not retain a build section');
61
+ if (!runnableHealthcheck(healthcheck.test) || healthcheck.disable === true)
62
+ add(`${at}.healthcheck`, 'every Swarm service needs an enabled healthcheck with a test');
63
+ const update = mapping(deploy.update_config);
64
+ if (!['start-first', 'stop-first'].includes(update.order) || update.failure_action !== 'rollback' || !update.monitor)
65
+ add(`${at}.deploy.update_config`, 'Swarm update_config must declare order, failure_action: rollback and monitor');
66
+ if (!configured(deploy.rollback_config)) add(`${at}.deploy.rollback_config`, 'every Swarm service needs deploy.rollback_config');
67
+ if (!['none', 'any', 'on-failure'].includes(deploy.restart_policy?.condition))
68
+ add(`${at}.deploy.restart_policy`, 'every Swarm service needs deploy.restart_policy with a condition');
69
+ }
70
+
71
+ function jobServiceIssues(id, service, component, services, bindings, add) {
72
+ const deploy = mapping(service.deploy), at = `services.${id}.deploy`;
73
+ if (component.mode !== 'replicated-job') {
74
+ if (deploy.mode === 'replicated-job') add(`${at}.mode`, 'replicated-job deployment must be declared as mode: replicated-job in the component inventory');
75
+ return;
76
+ }
77
+ if (deploy.mode !== 'replicated-job') add(`${at}.mode`, 'replicated-job component must use deploy.mode: replicated-job');
78
+ if (deploy.replicas !== 0) add(`${at}.replicas`, 'scheduled replicated-job must keep deploy.replicas: 0 until its scheduler triggers it');
79
+ if (deploy.restart_policy?.condition !== 'none') add(`${at}.restart_policy.condition`, 'scheduled replicated-job must use restart_policy.condition: none');
80
+ const labels = Object.fromEntries(composeEntries(deploy.labels));
81
+ if (String(labels['swarm.cronjob.enable']) !== 'true') add(`${at}.labels`, 'scheduled replicated-job needs swarm.cronjob.enable: "true"');
82
+ if (!present(component.schedule) || labels['swarm.cronjob.schedule'] !== component.schedule)
83
+ add(`${at}.labels`, 'swarm.cronjob.schedule must match the component cron schedule');
84
+ if (!services[component.scheduler] || !Object.hasOwn(bindings, component.scheduler))
85
+ add(`components.${id}.scheduler`, 'the declared scheduler must be bound and deployed in this environment');
86
+ else if (!schedulerImage(services[component.scheduler].image))
87
+ add(`components.${id}.scheduler`, 'the deployed scheduler must run the declared swarm-cronjob image');
88
+ }
89
+
90
+ function secretIssues(model, services, add) {
91
+ const secrets = mapping(model.secrets);
92
+ for (const [id, entry] of Object.entries(secrets)) {
93
+ const secret = mapping(entry);
94
+ if (secret.external !== true || !present(secret.name) || !VERSIONED_SECRET.test(secret.name) || secret.file !== undefined || secret.environment !== undefined || secret.content !== undefined)
95
+ add(`secrets.${id}`, 'Swarm secrets must be external: true with a versioned name <logical>-v<N> and no embedded value source');
96
+ }
97
+ for (const [id, service] of Object.entries(services)) {
98
+ for (const entry of Array.isArray(service?.secrets) ? service.secrets : []) {
99
+ const source = plain(entry) ? entry.source : entry;
100
+ if (!Object.hasOwn(secrets, source)) add(`services.${id}.secrets`, 'Swarm service secret must reference a declared external versioned secret');
101
+ }
102
+ }
103
+ }
104
+
105
+ /** Validate the same deployment model in HFS and the deployment evidence gate. */
106
+ export function swarmModelIssues({ model, components, bindings, lite = false }) {
107
+ const errors = [], services = mapping(model?.services), catalog = mapping(components), bound = mapping(bindings);
108
+ const add = (at, message) => errors.push({ at, message });
109
+ for (const [id, entry] of Object.entries(services)) {
110
+ const service = mapping(entry), component = mapping(catalog[id]);
111
+ serviceSafetyIssues(id, service, add);
112
+ if (!Object.hasOwn(catalog, id)) add(`services.${id}`, 'Swarm service name must be a declared component id');
113
+ if (service.ports !== undefined && !Array.isArray(service.ports))
114
+ add(`services.${id}.ports`, 'Swarm ports must be an array of published port definitions');
115
+ else if (service.ports?.length && component.role !== 'ingress')
116
+ add(`services.${id}.ports`, 'only a component with role ingress may publish Swarm ports');
117
+ if (lite && present(service.image) && BACKING_IMAGE.test(repositoryOf(service.image)))
118
+ add(`services.${id}.image`, 'lite stack must not run an image that duplicates a Supabase-provided backing service');
119
+ jobServiceIssues(id, service, component, services, bound, add);
120
+ }
121
+ for (const id of Object.keys(bound)) {
122
+ if (!Object.hasOwn(services, id)) add(`services.${id}`, 'environment component binding has no deployed Swarm service');
123
+ }
124
+ secretIssues(mapping(model), services, add);
125
+ return errors;
126
+ }
@@ -0,0 +1,8 @@
1
+ // trim-end.mjs — a string without the run of characters at its end that `matches` accepts.
2
+
3
+ /** `value` with its trailing characters removed while `matches(char)` holds. */
4
+ export const trimEndWhile = (value, matches) => {
5
+ let end = value.length;
6
+ while (end > 0 && matches(value[end - 1])) end -= 1;
7
+ return value.slice(0, end);
8
+ };
@@ -0,0 +1,23 @@
1
+ /** The expression under the wrappers selected by an AST check's contract. */
2
+ export function unwrapExpression(ts, expression, { awaitExpressions = false, typeAssertions = true, nonNullExpressions = true, satisfiesExpressions = true } = {}) {
3
+ while (expression && (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression)
4
+ || (typeAssertions && ts.isTypeAssertionExpression(expression)) || (nonNullExpressions && ts.isNonNullExpression(expression))
5
+ || (satisfiesExpressions && (ts.isSatisfiesExpression?.(expression) ?? false))
6
+ || (awaitExpressions && ts.isAwaitExpression(expression)))) expression = expression.expression;
7
+ return expression;
8
+ }
9
+
10
+ /** Local name -> imported name and module; callers select their existing module key and literal policy. */
11
+ export function importsOf(ts, sourceFile, { moduleKey = 'module', stringLiteralsOnly = false } = {}) {
12
+ const imports = new Map();
13
+ for (const statement of sourceFile.statements) {
14
+ if (!ts.isImportDeclaration(statement) || (stringLiteralsOnly && !ts.isStringLiteralLike(statement.moduleSpecifier))) continue;
15
+ const bindings = statement.importClause?.namedBindings;
16
+ if (!bindings || !ts.isNamedImports(bindings)) continue;
17
+ for (const binding of bindings.elements) imports.set(binding.name.text, { name: (binding.propertyName ?? binding.name).text, [moduleKey]: statement.moduleSpecifier.text });
18
+ }
19
+ return imports;
20
+ }
21
+
22
+ /** Catalog values unwrap parentheses, `as`, and `satisfies`, preserving assertions and non-null nodes. */
23
+ export const unwrapCatalogExpression = (ts, node) => unwrapExpression(ts, node, { typeAssertions: false, nonNullExpressions: false });
@@ -11,8 +11,18 @@ export const hasAnyFlag = (flags, mask) => ((flags ?? 0) & mask) !== 0;
11
11
  /** Whether the variable `declaration` belongs to a `const` list: the TypeScript node-flag test, an explicit comparison of the masked flags. */
12
12
  export const isConstVariable = (ts, declaration) => (ts.getCombinedNodeFlags(declaration.parent) & ts.NodeFlags.Const) !== 0;
13
13
 
14
- /** The text of an identifier or a string-literal-like `node`, or null (property names, literal specifiers). */
15
- export const nameText = (ts, node) => (node && (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) ? node.text : null);
14
+ /** Text of a string-literal-like node or an enabled identifier/numeric name; null for other expressions. */
15
+ export function nameText(ts, node, { identifiers = true, numbers = false } = {}) {
16
+ if (!node) return null;
17
+ if (ts.isStringLiteralLike(node) || (identifiers && ts.isIdentifier(node)) || (numbers && ts.isNumericLiteral(node))) return node.text;
18
+ return null;
19
+ }
20
+
21
+ /** A statically selected property name, including string-literal element access; null for computed selections. */
22
+ export function accessName(ts, node) {
23
+ if (ts.isPropertyAccessExpression(node)) return node.name.text;
24
+ return ts.isElementAccessExpression(node) ? literalText(ts, node.argumentExpression) : null;
25
+ }
16
26
 
17
27
  /** The text of a string-literal-like `node` (a string literal or a no-substitution template), or null. */
18
28
  export const literalText = (ts, node) => (node && ts.isStringLiteralLike(node) ? node.text : null);
@@ -42,3 +52,18 @@ export function propertyText(ts, text, { file = 'source.ts', key, declaration =
42
52
  visit(source);
43
53
  return value;
44
54
  }
55
+
56
+ /** Initializers of every identifier-named variable, grouped in traversal order across the source's scopes. */
57
+ export function variableInitializerGroups(ts, source) {
58
+ const groups = new Map();
59
+ const collect = (node) => {
60
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
61
+ const initializers = groups.get(node.name.text) ?? [];
62
+ initializers.push(node.initializer);
63
+ groups.set(node.name.text, initializers);
64
+ }
65
+ ts.forEachChild(node, collect);
66
+ };
67
+ collect(source);
68
+ return groups;
69
+ }
@@ -0,0 +1,8 @@
1
+ // GitHub workflow `on` supports a scalar event, an event list or an options map.
2
+
3
+ /** Convert shorthand workflow events into an options map; mappings retain their values. */
4
+ export function workflowEventOptions(on, eventName = (event) => event) {
5
+ if (typeof on === 'string') return { [on]: null };
6
+ if (Array.isArray(on)) return Object.fromEntries(on.map((event) => [eventName(event), null]));
7
+ return on;
8
+ }