@starci/hfs 4.0.7 → 4.0.9

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 (704) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +86 -43
  3. package/emit/contracts.mjs +56 -9
  4. package/emit/db-types.mjs +169 -0
  5. package/emit/events.mjs +41 -0
  6. package/emit/operations-worker.mjs +1 -1
  7. package/emit/operations.mjs +2 -2
  8. package/emit/schema-worker.mjs +1 -1
  9. package/lint/bound-sys.cjs +50 -0
  10. package/lint/run.mjs +54 -20
  11. package/package.json +14 -9
  12. package/report/sonar.mjs +8 -8
  13. package/runtime/engine/config.mjs +494 -0
  14. package/runtime/engine/invalid-config.mjs +15 -0
  15. package/runtime/engine/orca-config.mjs +27 -0
  16. package/runtime/engine/runtime-root.mjs +5 -0
  17. package/runtime/engine/yaml.mjs +3 -3
  18. package/runtime/knowledge/hfs/canon-pins.yaml +163 -32
  19. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
  20. package/runtime/knowledge/hfs/rules.yaml +2059 -0
  21. package/runtime/knowledge/hfs/slots.yaml +915 -123
  22. package/runtime/knowledge/patterns/be/api.yaml +369 -0
  23. package/runtime/knowledge/patterns/be/cli.yaml +297 -0
  24. package/runtime/knowledge/patterns/be/domain.yaml +113 -0
  25. package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
  26. package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
  27. package/runtime/knowledge/patterns/be/projections.yaml +178 -0
  28. package/runtime/knowledge/patterns/be/queues.yaml +236 -0
  29. package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
  30. package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
  31. package/runtime/knowledge/patterns/be/saga.yaml +248 -0
  32. package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
  33. package/runtime/knowledge/patterns/fe/folder.yaml +133 -27
  34. package/runtime/knowledge/sonar-gate.yaml +53 -19
  35. package/runtime/modules/kernel/failure-codes.yaml +580 -50
  36. package/runtime/modules/models/registry.yaml +591 -0
  37. package/runtime/modules/models/runtimes.yaml +701 -0
  38. package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
  39. package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
  40. package/runtime/scripts/api/fs/lib.mjs +14 -0
  41. package/runtime/scripts/api/fs/safe-remove.mjs +104 -0
  42. package/runtime/scripts/api/git/config-get.mjs +10 -0
  43. package/runtime/scripts/api/git/diff.mjs +5 -0
  44. package/runtime/scripts/api/git/lib.mjs +17 -27
  45. package/runtime/scripts/api/git/log.mjs +5 -0
  46. package/runtime/scripts/api/git/ls-files.mjs +6 -0
  47. package/runtime/scripts/api/git/ls-tree.mjs +5 -0
  48. package/runtime/scripts/api/git/merge-base.mjs +9 -0
  49. package/runtime/scripts/api/git/remote.mjs +5 -0
  50. package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
  51. package/runtime/scripts/api/git/show.mjs +6 -0
  52. package/runtime/scripts/api/sops/decrypt.mjs +10 -6
  53. package/runtime/scripts/api/sops/lib.mjs +35 -0
  54. package/runtime/scripts/api/sops/seal.mjs +18 -0
  55. package/runtime/scripts/{lib/hfs-allows.mjs → hfs/allows.mjs} +3 -3
  56. package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
  57. package/runtime/scripts/{checks → hfs}/architecture/backend.mjs +17 -24
  58. package/runtime/scripts/{checks → hfs}/architecture/background-unowned.mjs +16 -34
  59. package/runtime/scripts/{checks → hfs}/architecture/client-reaches-server.mjs +3 -1
  60. package/runtime/scripts/{checks → hfs}/architecture/clones.mjs +4 -4
  61. package/runtime/scripts/{checks → hfs}/architecture/config.mjs +11 -8
  62. package/runtime/scripts/{checks → hfs}/architecture/connection-map.mjs +21 -9
  63. package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
  64. package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
  65. package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
  66. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
  67. package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
  68. package/runtime/scripts/{checks → hfs}/architecture/contract-fixture-guard.mjs +5 -11
  69. package/runtime/scripts/{checks → hfs}/architecture/contracts.mjs +33 -162
  70. package/runtime/scripts/{checks → hfs}/architecture/dead-exports.mjs +58 -29
  71. package/runtime/scripts/{checks → hfs}/architecture/doc-language.mjs +1 -1
  72. package/runtime/scripts/{checks → hfs}/architecture/entrypoint.mjs +2 -1
  73. package/runtime/scripts/{checks → hfs}/architecture/fe-slot-allows.mjs +4 -4
  74. package/runtime/scripts/{checks → hfs}/architecture/feature-shape.mjs +6 -3
  75. package/runtime/scripts/{checks → hfs}/architecture/framework-pinned.mjs +1 -1
  76. package/runtime/scripts/{checks → hfs}/architecture/frontend.mjs +17 -43
  77. package/runtime/scripts/{checks → hfs}/architecture/hfs-graph.mjs +1 -1
  78. package/runtime/scripts/{checks → hfs}/architecture/hfs.mjs +27 -42
  79. package/runtime/scripts/{checks → hfs}/architecture/i18n-keys.mjs +1 -1
  80. package/runtime/scripts/{checks → hfs}/architecture/index.mjs +17 -4
  81. package/runtime/scripts/{checks → hfs}/architecture/machine-ast.mjs +44 -11
  82. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
  83. package/runtime/scripts/{checks → hfs}/architecture/module-per-transport.mjs +30 -6
  84. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +96 -0
  85. package/runtime/scripts/{checks → hfs}/architecture/next-data.mjs +40 -168
  86. package/runtime/scripts/{checks → hfs}/architecture/owners.mjs +35 -15
  87. package/runtime/scripts/{checks → hfs}/architecture/register-once.mjs +2 -1
  88. package/runtime/scripts/{checks → hfs}/architecture/registration.mjs +23 -81
  89. package/runtime/scripts/{checks → hfs}/architecture/required-files.mjs +21 -13
  90. package/runtime/scripts/{checks → hfs}/architecture/schema-owner.mjs +17 -114
  91. package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
  92. package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
  93. package/runtime/scripts/{checks → hfs}/architecture/source-names.mjs +27 -147
  94. package/runtime/scripts/{checks → hfs}/architecture/sql-owner.mjs +59 -18
  95. package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
  96. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
  97. package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
  98. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
  99. package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
  100. package/runtime/scripts/{checks → hfs}/architecture/surface.mjs +10 -4
  101. package/runtime/scripts/{checks → hfs}/architecture/test-world-files.mjs +11 -21
  102. package/runtime/scripts/{checks → hfs}/architecture/tiers.mjs +47 -5
  103. package/runtime/scripts/{checks → hfs}/architecture/transport-owner.mjs +3 -3
  104. package/runtime/scripts/{checks → hfs}/architecture/typescript.mjs +13 -27
  105. package/runtime/scripts/{checks → hfs}/architecture.mjs +5 -5
  106. package/runtime/scripts/{lib/hfs-check.mjs → hfs/check.mjs} +95 -84
  107. package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
  108. package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
  109. package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
  110. package/runtime/scripts/hfs/edition-slots.mjs +66 -0
  111. package/runtime/scripts/hfs/manifest-shape.mjs +247 -0
  112. package/runtime/scripts/hfs/param-names.mjs +18 -0
  113. package/runtime/scripts/{lib/hfs-path-findings.mjs → hfs/path-findings.mjs} +7 -7
  114. package/runtime/scripts/{lib → hfs}/repo-identity.mjs +9 -6
  115. package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
  116. package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
  117. package/runtime/scripts/hfs/rules/cli.mjs +37 -0
  118. package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
  119. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/contract.mjs +11 -9
  120. package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
  121. package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
  122. package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
  123. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
  124. package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
  125. package/runtime/scripts/hfs/rules/database.mjs +68 -0
  126. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/deps.mjs +1 -1
  127. package/runtime/scripts/hfs/rules/docker.mjs +259 -0
  128. package/runtime/scripts/hfs/rules/edition.mjs +111 -0
  129. package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
  130. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +66 -0
  131. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/fe-no-tests.mjs +6 -5
  132. package/runtime/scripts/{lib/hfs-rules/frontend.mjs → hfs/rules/frontend-tree.mjs} +9 -9
  133. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/integration-specs.mjs +3 -6
  134. package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
  135. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/lint-suppression.mjs +1 -1
  136. package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
  137. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/peer-integrations.mjs +3 -3
  138. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/pipeline.mjs +11 -21
  139. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/proof-commands.mjs +2 -2
  140. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/read.mjs +1 -1
  141. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/repo-local-checks.mjs +2 -2
  142. package/runtime/scripts/hfs/rules/saga.mjs +170 -0
  143. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/secrets.mjs +4 -4
  144. package/runtime/scripts/hfs/rules/services.mjs +220 -0
  145. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/spec-placement.mjs +1 -1
  146. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/stacks.mjs +5 -5
  147. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
  148. package/runtime/scripts/{lib/hfs-rules → hfs/rules}/test-topology.mjs +2 -2
  149. package/runtime/scripts/hfs/secret.mjs +175 -0
  150. package/runtime/scripts/{lib/hfs-slots.mjs → hfs/slots.mjs} +114 -150
  151. package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
  152. package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
  153. package/runtime/scripts/{lib/hfs-tree.mjs → hfs/tree.mjs} +6 -5
  154. package/runtime/scripts/{checks → hfs}/typescript-programs.mjs +0 -5
  155. package/runtime/scripts/{lib/hfs-view.mjs → hfs/view.mjs} +6 -5
  156. package/runtime/scripts/lib/dockerfile.mjs +124 -0
  157. package/runtime/scripts/lib/env.mjs +10 -0
  158. package/runtime/scripts/lib/event-contract.mjs +103 -0
  159. package/runtime/scripts/lib/git.mjs +36 -0
  160. package/runtime/scripts/lib/graphql-contract.mjs +429 -0
  161. package/runtime/scripts/lib/i18n.mjs +86 -0
  162. package/runtime/scripts/lib/is-main.mjs +13 -0
  163. package/runtime/scripts/lib/json.mjs +13 -3
  164. package/runtime/scripts/lib/language.mjs +26 -7
  165. package/runtime/scripts/lib/list.mjs +20 -0
  166. package/runtime/scripts/lib/package-at.mjs +37 -0
  167. package/runtime/scripts/lib/path-key.mjs +59 -2
  168. package/runtime/scripts/lib/read-text.mjs +34 -0
  169. package/runtime/scripts/lib/regex.mjs +4 -0
  170. package/runtime/scripts/lib/same-text.mjs +5 -0
  171. package/runtime/scripts/lib/secret-patterns.mjs +4 -3
  172. package/runtime/scripts/lib/sleep-sync.mjs +2 -1
  173. package/runtime/scripts/lib/sops-envelope.mjs +58 -0
  174. package/runtime/scripts/lib/stack-declaration.mjs +2 -2
  175. package/runtime/scripts/lib/stack-services.mjs +7 -31
  176. package/runtime/scripts/lib/ts-ast.mjs +35 -0
  177. package/runtime/scripts/{checks/common.mjs → lib/walk.mjs} +5 -15
  178. package/scaffold/add-app.mjs +174 -0
  179. package/scaffold/add-cli-lite.mjs +257 -0
  180. package/scaffold/add-inbox-lite.mjs +314 -0
  181. package/scaffold/add-table.mjs +344 -0
  182. package/scaffold/add.mjs +299 -0
  183. package/scaffold/app.mjs +359 -70
  184. package/scaffold/edition-gate.mjs +25 -0
  185. package/scaffold/image.mjs +19 -0
  186. package/scaffold/lite-exports.mjs +186 -0
  187. package/scaffold/service.mjs +33 -27
  188. package/scaffold/supabase-ports.mjs +26 -0
  189. package/src/main.mjs +327 -0
  190. package/sync/cli.mjs +6 -9
  191. package/sync/format.mjs +1 -1
  192. package/sync/hygiene.mjs +17 -17
  193. package/sync/index.mjs +171 -71
  194. package/sync/managed.mjs +19 -11
  195. package/sync/sonar-key.mjs +5 -0
  196. package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
  197. package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
  198. package/templates/app/ci-workflows/sonar-steps.yml +9 -0
  199. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
  200. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
  201. package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
  202. package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
  203. package/templates/app/docker-ignore/dockerignore +50 -0
  204. package/templates/app/gitignore-lite +35 -0
  205. package/templates/app/hooks/husky/pre-commit +3 -8
  206. package/templates/app/hooks/husky/pre-push +45 -6
  207. package/templates/app/package-scripts/core.json +8 -0
  208. package/templates/app/package-scripts/package.json +3 -10
  209. package/templates/app/package-scripts-lite/package.json +15 -0
  210. package/templates/app/quality-config/codecov.yml +15 -3
  211. package/templates/app/quality-config/sonar-base.properties +4 -0
  212. package/templates/app/quality-config/sonar-project.properties +2 -5
  213. package/templates/app/quality-config-lite/sonar-project.properties +5 -0
  214. package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
  215. package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
  216. package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
  217. package/templates/app/skeleton/README.md +7 -5
  218. package/templates/app/skeleton-lite/.sops.yaml +3 -0
  219. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
  220. package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
  221. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
  222. package/templates/app/skeleton-lite/README.md +40 -0
  223. package/templates/app/skeleton-lite/supabase/config.toml +41 -0
  224. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
  225. package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
  226. package/templates/app/task-graph/turbo.json +23 -0
  227. package/templates/app/tool-config-lite/prettierignore +2 -0
  228. package/templates/app/tool-config-lite/prettierrc +1 -0
  229. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
  230. package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
  231. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
  232. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
  233. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
  234. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
  235. package/templates/be/common/main-listen.ts.partial +14 -0
  236. package/templates/be/common/sql-ident.ts.partial +7 -0
  237. package/templates/be/common/sql-tag.ts.partial +5 -0
  238. package/templates/be/image/api/Dockerfile +38 -0
  239. package/templates/be/image/cli/Dockerfile +38 -0
  240. package/templates/be/image/worker/Dockerfile +36 -0
  241. package/templates/be/inbox/database-client.ts.tpl +50 -0
  242. package/templates/be/inbox/index.ts.tpl +2 -0
  243. package/templates/be/inbox/migration.sql.tpl +29 -0
  244. package/templates/be/inbox/module-definition.ts.tpl +6 -0
  245. package/templates/be/inbox/module.ts.tpl +14 -0
  246. package/templates/be/inbox/options.ts.tpl +2 -0
  247. package/templates/be/inbox/service.ts.tpl +33 -0
  248. package/templates/be/inbox/sql.ts.tpl +16 -0
  249. package/templates/be/inbox/webhook-door.ts.tpl +36 -0
  250. package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
  251. package/templates/be/patterns/api/application.command.ts.tpl +10 -0
  252. package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
  253. package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
  254. package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
  255. package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
  256. package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
  257. package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
  258. package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
  259. package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
  260. package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
  261. package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
  262. package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
  263. package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
  264. package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
  265. package/templates/be/patterns/api/http.module.ts.tpl +7 -0
  266. package/templates/be/patterns/api/http.request.ts.tpl +10 -0
  267. package/templates/be/patterns/api/http.response.ts.tpl +5 -0
  268. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
  269. package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
  270. package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
  271. package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
  272. package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
  273. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
  274. package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
  275. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
  276. package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
  277. package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
  278. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
  279. package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
  280. package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
  281. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
  282. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
  283. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
  284. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
  285. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
  286. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
  287. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
  288. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
  289. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
  290. package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
  291. package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
  292. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
  293. package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
  294. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
  295. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
  296. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
  297. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
  298. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
  299. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
  300. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
  301. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
  302. package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
  303. package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
  304. package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
  305. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
  306. package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
  307. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
  308. package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
  309. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
  310. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
  311. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
  312. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
  313. package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
  314. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
  315. package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
  316. package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
  317. package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
  318. package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
  319. package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
  320. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
  321. package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
  322. package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
  323. package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
  324. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
  325. package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
  326. package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
  327. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
  328. package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
  329. package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
  330. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
  331. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
  332. package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
  333. package/templates/be/patterns/projections/migration.ts.tpl +18 -0
  334. package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
  335. package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
  336. package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
  337. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
  338. package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
  339. package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
  340. package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
  341. package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
  342. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
  343. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
  344. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
  345. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
  346. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
  347. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
  348. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
  349. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
  350. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
  351. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
  352. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
  353. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
  354. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
  355. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
  356. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
  357. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
  358. package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
  359. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
  360. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
  361. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
  362. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
  363. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
  364. package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
  365. package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
  366. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
  367. package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
  368. package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
  369. package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
  370. package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
  371. package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
  372. package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
  373. package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
  374. package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
  375. package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
  376. package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
  377. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
  378. package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
  379. package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
  380. package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
  381. package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
  382. package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
  383. package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
  384. package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
  385. package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
  386. package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
  387. package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
  388. package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
  389. package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
  390. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
  391. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
  392. package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
  393. package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
  394. package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
  395. package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
  396. package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
  397. package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
  398. package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
  399. package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
  400. package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
  401. package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
  402. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
  403. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
  404. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
  405. package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
  406. package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
  407. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
  408. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
  409. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
  410. package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
  411. package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
  412. package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
  413. package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
  414. package/templates/be/skeleton/src/features/cli/index.ts +1 -0
  415. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
  416. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
  417. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
  418. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
  419. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
  420. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
  421. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
  422. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
  423. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
  424. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
  425. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
  426. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
  427. package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
  428. package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
  429. package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
  430. package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
  431. package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
  432. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
  433. package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
  434. package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
  435. package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
  436. package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
  437. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
  438. package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
  439. package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
  440. package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
  441. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
  442. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
  443. package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
  444. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
  445. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
  446. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
  447. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
  448. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
  449. package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
  450. package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
  451. package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
  452. package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
  453. package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
  454. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
  455. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
  456. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
  457. package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
  458. package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
  459. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
  460. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
  461. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
  462. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
  463. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
  464. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
  465. package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
  466. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
  468. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
  469. package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
  471. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
  472. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
  473. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
  474. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.ts +2 -2
  475. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
  476. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
  477. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
  478. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
  479. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
  480. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
  481. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
  482. package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
  483. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
  484. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
  485. package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
  486. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
  487. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
  488. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
  489. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
  490. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
  491. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
  492. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
  493. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
  494. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
  495. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
  496. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
  497. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
  498. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
  499. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
  500. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
  501. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
  502. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
  503. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
  504. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
  505. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
  506. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
  507. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
  508. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
  509. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
  510. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
  511. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
  512. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
  513. package/templates/be/table/error.ts.tpl +16 -0
  514. package/templates/be/table/index.ts.tpl +2 -0
  515. package/templates/be/table/migration.sql.tpl +51 -0
  516. package/templates/be/table/module-definition.ts.tpl +7 -0
  517. package/templates/be/table/module.ts.tpl +14 -0
  518. package/templates/be/table/options.ts.tpl +2 -0
  519. package/templates/be/table/rows.ts.tpl +4 -0
  520. package/templates/be/table/service.ts.tpl +23 -0
  521. package/templates/be/table/sql.ts.tpl +4 -0
  522. package/templates/be/tool-config/jest.config.js +3 -1
  523. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
  524. package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
  525. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
  526. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
  527. package/templates/common/gitignore.base +1 -2
  528. package/templates/fe/app/package.json.tpl +1 -0
  529. package/templates/fe/app/tsconfig.json +12 -0
  530. package/templates/fe/common/app-layout.tsx.partial +8 -0
  531. package/templates/fe/common/site-shell.tsx.partial +9 -0
  532. package/templates/fe/image/next/Dockerfile +42 -0
  533. package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
  534. package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
  535. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +3 -3
  536. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
  537. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
  538. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
  539. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
  540. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
  541. package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
  542. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
  543. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
  544. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
  545. package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
  546. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
  547. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
  548. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
  549. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  550. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
  551. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
  552. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
  553. package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
  554. package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
  555. package/templates/fe/skeleton/apps/landing/next.config.ts +21 -0
  556. package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
  557. package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
  558. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
  559. package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
  560. package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
  561. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
  562. package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
  563. package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
  564. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
  565. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
  566. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
  567. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
  568. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
  569. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
  570. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
  571. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
  572. package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
  573. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
  574. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
  575. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
  576. package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
  577. package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
  578. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
  579. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
  580. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
  581. package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
  582. package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
  583. package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
  584. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
  585. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
  586. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
  587. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
  588. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
  589. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
  590. package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
  591. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
  592. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
  593. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
  594. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
  595. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
  596. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
  597. package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
  598. package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
  599. package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
  600. package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
  601. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
  602. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
  603. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
  604. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
  605. package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
  606. package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
  607. package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
  608. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
  609. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
  610. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
  611. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
  612. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
  613. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
  614. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
  615. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
  616. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
  617. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
  618. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  619. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
  620. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
  621. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
  622. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
  623. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
  624. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
  625. package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
  626. package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
  627. package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
  628. package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
  629. package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
  630. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
  631. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
  632. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
  633. package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/modules/i18n/request.ts +4 -2
  634. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
  635. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
  636. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
  637. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
  638. package/templates/fe/table/read.ts.tpl +16 -0
  639. package/templates/fe/table/write.ts.tpl +16 -0
  640. package/upgrade/check.mjs +137 -0
  641. package/upgrade/index.mjs +524 -0
  642. package/bin/hfs.mjs +0 -233
  643. package/runtime/engine/admission.mjs +0 -284
  644. package/runtime/engine/digest.mjs +0 -10
  645. package/runtime/engine/ledger-db.mjs +0 -1245
  646. package/runtime/engine/machine-db.mjs +0 -1565
  647. package/runtime/engine/migrations/machine/0001-init.sql +0 -887
  648. package/runtime/engine/migrations/machine/0002-worktrees-no-workflow-kind.sql +0 -13
  649. package/runtime/engine/migrations/machine/0003-worktrees-workflow-orca.sql +0 -21
  650. package/runtime/engine/migrations/runtime/0001-init.sql +0 -1072
  651. package/runtime/engine/migrations/runtime/0003-usage-unavailable.sql +0 -13
  652. package/runtime/engine/migrations/runtime/0004-attempt-why.sql +0 -43
  653. package/runtime/engine/migrations/runtime/0005-ended-workflow-views.sql +0 -93
  654. package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
  655. package/runtime/scripts/lib/artifact-hold.mjs +0 -89
  656. package/runtime/scripts/lib/artifact-store.mjs +0 -103
  657. package/runtime/scripts/lib/redact.mjs +0 -148
  658. package/runtime/scripts/lib/safe-remove.mjs +0 -275
  659. package/runtime/scripts/lib/test-secrets.mjs +0 -116
  660. package/templates/fe/skeleton/apps/__app__/next.config.ts +0 -11
  661. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
  662. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
  663. package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
  664. package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
  665. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -21
  666. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -17
  667. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -26
  668. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -15
  669. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -25
  670. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
  671. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -27
  672. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
  673. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -32
  674. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
  675. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
  676. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
  677. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
  678. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
  679. package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
  680. package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
  681. /package/runtime/scripts/{checks → hfs}/architecture/config-unread.mjs +0 -0
  682. /package/runtime/scripts/{checks → hfs}/architecture/constructor-deps.mjs +0 -0
  683. /package/runtime/scripts/{checks → hfs}/architecture/cross-app-duplicate.mjs +0 -0
  684. /package/runtime/scripts/{checks → hfs}/architecture/default-deny.mjs +0 -0
  685. /package/runtime/scripts/{checks → hfs}/architecture/error-codes.mjs +0 -0
  686. /package/runtime/scripts/{checks → hfs}/architecture/error-masked.mjs +0 -0
  687. /package/runtime/scripts/{checks → hfs}/architecture/hooks-are-hooks.mjs +0 -0
  688. /package/runtime/scripts/{checks → hfs}/architecture/injection-token-exported.mjs +0 -0
  689. /package/runtime/scripts/{checks → hfs}/architecture/package-shape.mjs +0 -0
  690. /package/runtime/scripts/{checks → hfs}/architecture/reachability.mjs +0 -0
  691. /package/runtime/scripts/{checks → hfs}/architecture/route-files-thin.mjs +0 -0
  692. /package/runtime/scripts/{checks → hfs}/architecture/sql-tokens.mjs +0 -0
  693. /package/runtime/scripts/{checks → hfs}/architecture/symbols.mjs +0 -0
  694. /package/runtime/scripts/{checks → hfs}/architecture/unit-spec-providers.mjs +0 -0
  695. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
  696. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
  697. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
  698. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
  699. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
  700. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
  701. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
  702. /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
  703. /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
  704. /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
@@ -2,27 +2,39 @@
2
2
  // which slot owns path P slotOf(P) / classifyPath(P) (an unknown path reports its nearest slot)
3
3
  // is import A -> B allowed importAllowed(A, B) (tier matrix, cross-owner entry, cross-app, layers)
4
4
  // which files are required requiredFiles(P) / requiredPaths()
5
- // is this path tracked isTracked(P) / trackingOf(P)
6
- // The rule catalog (knowledge/hfs/rules.yaml, modules/schemas/hfs-rules.schema.yaml) loads through loadRuleCatalog / rules().
5
+ // is this path tracked isTracked(P) / trackingOf(P); the rule catalog (knowledge/hfs/rules.yaml, modules/schemas/hfs-rules.schema.yaml) loads through loadRuleCatalog / rules().
7
6
  // Every check and lint rule of HFS reads knowledge/hfs/slots.yaml through this module; none keeps its own path
8
7
  // list. The manifest shape is modules/schemas/hfs-slots.schema.yaml and hfs.json is modules/schemas/hfs-repo.schema.yaml;
9
- // the installed runtime carries no npm dependency, so this file re-states those shapes instead of loading ajv
10
- // (tests/hfs-slots.spec.mjs proves the two agree).
8
+ // the installed runtime carries no npm dependency, so this file re-states those shapes instead of loading ajv (tests/hfs/hfs-slots.spec.mjs proves the two agree).
11
9
  //
12
10
  // A product is ONE app repository: hfs.json at the app root has kind `app` and declares its two sides, `be` and `fe`, each in
13
11
  // the folder of that name. The resolver of the app answers for the whole tree: a root path with the slots of profile `app`,
14
12
  // a path below a side folder with that side's resolver (profile be or fe, the side folder as its root: the side view), so
15
13
  // every check and lint rule runs unchanged with a side folder as its repository root. Nothing crosses sides except the
16
14
  // paths the declaration lists in `sides.<side>.reads` (a subset of the manifest's `sides.<side>.reads`).
15
+ //
16
+ // One loader, two manifest kinds. `kind: app` (the default; knowledge/hfs/slots.yaml) is the product standard above.
17
+ // `kind: runtime` (knowledge/hfs/runtime-slots.yaml, schema starci/runtime-slots@<major>) is the standard of the StarCi
18
+ // runtime repository itself: one profile `runtime`, no sides and no app kinds, slot ids runtime.<name>, the tracked value
19
+ // `generated` (a copy written only by the slot's `generatedBy`). A runtime repository declares itself with
20
+ // hfs.json {"hfs": <major>, "kind": "runtime", "project": <name>}.
21
+ import { ruleParamsProblems } from './rule-params-shape.mjs';
17
22
  import fs from 'node:fs';
18
23
  import path from 'node:path';
19
24
  import { skillRoot } from '../../engine/runtime-root.mjs';
20
25
  import { parseYaml } from '../../engine/yaml.mjs';
21
- import { braceVariants, globExpression } from './glob.mjs';
22
- import { posixPath } from './path-key.mjs';
26
+ import { braceVariants, globExpression } from '../lib/glob.mjs';
27
+ import { posixPath } from '../lib/path-key.mjs';
28
+ import { captureNames } from '../lib/i18n.mjs';
23
29
  import { isPlainObject } from '../../engine/plain-object.mjs';
24
-
30
+ import { APP_KIND, EDITIONS, ENV_PREFIX, MANIFEST_KINDS, NAME, PRESENCE, RUNTIME_KIND, SEMVER, TESTS, TRACKED, manifestKind, runtimeSemanticProblems, runtimeShapeProblems, slotProblems, tierMapProblems } from './manifest-shape.mjs';
31
+ import { declaredSlotEnabled, optionalSlotProblems, triggerProblems } from './declaration-slots.mjs';
32
+ import { declarationShapeProblems } from './declaration-shape.mjs';
33
+ import { declarationEdition, editionRuleParams, effectiveSlot, enforcerJudgedInEdition, judgedInEdition, litePresenceOf, ruleEditionProblems, slotInEdition } from './edition-slots.mjs';
34
+ export { litePresenceOf, slotInEdition } from './edition-slots.mjs';
25
35
  export const HFS_MANIFEST_FILE = 'knowledge/hfs/slots.yaml';
36
+ /** The manifest of kind runtime: the standard tree of the StarCi runtime repository (judged by scripts/hfs/runtime-check.mjs). */
37
+ export const RUNTIME_MANIFEST_FILE = 'knowledge/hfs/runtime-slots.yaml';
26
38
  export const HFS_DECLARATION_FILE = 'hfs.json';
27
39
 
28
40
  /** A refusal with a catalogued code (modules/kernel/failure-codes.yaml) and the facts a check reports. */
@@ -35,13 +47,6 @@ export class HfsSlotsError extends Error {
35
47
  }
36
48
  }
37
49
 
38
- const SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
39
- const NAME = /^[a-z][a-z0-9-]*$/;
40
- const ENV_PREFIX = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/;
41
- const SLOT_ID = /^(app|repo|be|fe)\.[a-z0-9-]+(\.[a-z0-9-]+)*$/;
42
- const PRESENCE = ['required', 'optional', 'opt-in', 'forbidden'];
43
- const TRACKED = ['tracked', 'ignored', 'external'];
44
- const TESTS = ['unit-beside', 'e2e', 'none'];
45
50
  /** The sides of an app; each lives in the folder of its name and is the profile of its slots. */
46
51
  const PROFILES = ['be', 'fe'];
47
52
  export const SIDES = Object.freeze([...PROFILES]);
@@ -67,13 +72,11 @@ export function appRelativeMessages(side, sideRoot) {
67
72
  }
68
73
 
69
74
  const SCOPES = [APP_SCOPE, ...PROFILES];
70
- const APP_KIND = 'app';
71
- const strList = (v) => Array.isArray(v) && v.every((s) => typeof s === 'string' && s.length > 0);
72
75
 
73
76
  // ------------------------------------------------------------------------------------------------ patterns
74
77
 
75
78
  const VAR = /<([A-Za-z][A-Za-z0-9-]*)>/g;
76
- const varsOf = (text) => [...String(text).matchAll(VAR)].map((m) => m[1]);
79
+ const varsOf = (text) => captureNames(text, VAR);
77
80
  const hasWildcard = (segment) => /[*?]/.test(segment);
78
81
 
79
82
  /** Weight of one pattern segment: literal 4, literal mixed with a variable 3, a bare variable 2, a wildcard 1, `**` 0. */
@@ -151,7 +154,9 @@ const fail = (code, message, details) => { throw new HfsSlotsError(code, message
151
154
  function manifestShapeProblems(m) {
152
155
  const bad = [];
153
156
  if (!isPlainObject(m)) return ['the manifest is not a map'];
154
- const allowed = new Set(['schema', 'version', 'versioning', 'presenceValues', 'trackedValues', 'testValues', 'sides', 'appKinds', 'tiers', 'ruleParams', 'crossOwner', 'crossApp', 'slots', 'consumers']);
157
+ if (!MANIFEST_KINDS.includes(manifestKind(m))) return [`kind must be one of ${MANIFEST_KINDS.join(', ')}`];
158
+ if (manifestKind(m) === RUNTIME_KIND) return runtimeShapeProblems(m);
159
+ const allowed = new Set(['schema', 'kind', 'version', 'versioning', 'presenceValues', 'trackedValues', 'testValues', 'editions', 'sides', 'appKinds', 'triggerKinds', 'tiers', 'ruleParams', 'crossOwner', 'crossApp', 'slots', 'consumers', 'naming']);
155
160
  for (const key of Object.keys(m)) if (!allowed.has(key)) bad.push(`unknown top-level key ${key}`);
156
161
  if (!/^starci\/hfs-slots@\d+$/.test(String(m.schema))) bad.push('schema must be starci/hfs-slots@<major>');
157
162
  if (!SEMVER.test(String(m.version))) bad.push('version must be MAJOR.MINOR.PATCH');
@@ -159,11 +164,13 @@ function manifestShapeProblems(m) {
159
164
  if (JSON.stringify(m.presenceValues) !== JSON.stringify(PRESENCE)) bad.push(`presenceValues must be ${PRESENCE.join(', ')}`);
160
165
  if (JSON.stringify(m.trackedValues) !== JSON.stringify(TRACKED)) bad.push(`trackedValues must be ${TRACKED.join(', ')}`);
161
166
  if (JSON.stringify(m.testValues) !== JSON.stringify(TESTS)) bad.push(`testValues must be ${TESTS.join(', ')}`);
167
+ if (JSON.stringify(m.editions) !== JSON.stringify(EDITIONS)) bad.push(`editions must be ${EDITIONS.join(', ')}`);
162
168
  if (!isPlainObject(m.sides) || Object.keys(m.sides).sort().join() !== PROFILES.join()) bad.push('sides must be a map with exactly be and fe');
163
169
  else for (const side of PROFILES) {
164
170
  const def = m.sides[side];
165
- if (!isPlainObject(def) || Object.keys(def).join() !== 'reads' || !Array.isArray(def.reads) || !def.reads.every((r) => typeof r === 'string' && /^(be|fe)\/([^/]+\/)+$/.test(r)) || new Set(def.reads).size !== def.reads.length) bad.push(`sides.${side} must be {reads: [unique <side>/<dir>/ paths]}`);
171
+ if (!isPlainObject(def) || Object.keys(def).join() !== 'reads' || !Array.isArray(def.reads) || !def.reads.every((r) => typeof r === 'string' && /^[a-z][a-z0-9-]*\/([^/]+\/)+$/.test(r)) || new Set(def.reads).size !== def.reads.length) bad.push(`sides.${side} must be {reads: [unique <owner>/<dir>/ paths]}`);
166
172
  }
173
+ if (m.triggerKinds !== undefined && (!Array.isArray(m.triggerKinds) || !m.triggerKinds.length || !m.triggerKinds.every((k) => NAME.test(String(k))) || new Set(m.triggerKinds).size !== m.triggerKinds.length)) bad.push('triggerKinds must be a non-empty list of unique names');
167
174
  for (const key of ['appKinds', 'tiers']) {
168
175
  if (!isPlainObject(m[key])) { bad.push(`${key} must be a map with be and fe`); continue; }
169
176
  for (const extra of Object.keys(m[key])) if (!PROFILES.includes(extra)) bad.push(`${key}.${extra} is not a profile`);
@@ -174,67 +181,11 @@ function manifestShapeProblems(m) {
174
181
  if (kinds !== undefined && (!Array.isArray(kinds) || !kinds.length || !kinds.every((k) => NAME.test(String(k))) || new Set(kinds).size !== kinds.length)) bad.push(`appKinds.${profile} must be a non-empty list of unique names`);
175
182
  const tiers = m.tiers?.[profile];
176
183
  if (tiers === undefined) continue;
177
- if (!isPlainObject(tiers) || !Object.keys(tiers).length) { bad.push(`tiers.${profile} must be a non-empty map`); continue; }
178
- for (const [tier, def] of Object.entries(tiers)) {
179
- if (!NAME.test(tier)) bad.push(`tiers.${profile}.${tier} is not a tier name`);
180
- if (!isPlainObject(def) || !Array.isArray(def.mayImport) || !def.mayImport.every((t) => NAME.test(String(t)))) bad.push(`tiers.${profile}.${tier}.mayImport must be a list of tier names`);
181
- for (const key of Object.keys(def ?? {})) if (!['mayImport', 'acyclic', 'lowerLayerOnly'].includes(key)) bad.push(`tiers.${profile}.${tier}.${key} is not a tier field`);
182
- }
183
- }
184
- const blockOk = (v) => isPlainObject(v) && Number.isInteger(v.lines) && v.lines >= 2 && Number.isInteger(v.tokens) && v.tokens >= 1 && Object.keys(v).length === 2;
185
- const fileLinesOk = (v) => isPlainObject(v) && Number.isInteger(v.soft) && v.soft >= 1 && typeof v.hardGrowth === 'boolean' && Object.keys(v).length === 2;
186
- const rp = m.ruleParams;
187
- if (!isPlainObject(rp) || Object.keys(rp).some((k) => !PROFILES.includes(k)) || !PROFILES.every((p) => isPlainObject(rp[p]))) bad.push('ruleParams must be a map with be and fe');
188
- else {
189
- if (!fileLinesOk(rp.be.fileLines) || !blockOk(rp.be.duplicateBlock) || Object.keys(rp.be).length !== 7) bad.push('ruleParams.be needs fileLines {soft, hardGrowth}, duplicateBlock {lines >= 2, tokens >= 1}, infraOwners, specDoubles, suffixes, bannedSuffixes and contractShape {helper}');
190
- if (!isPlainObject(rp.be.contractShape) || Object.keys(rp.be.contractShape).length !== 1 || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(String(rp.be.contractShape.helper))) bad.push('ruleParams.be.contractShape must be {helper: <identifier>}');
191
- const owners = rp.be.infraOwners;
192
- const ownerId = /^(platform|integrations)\/[a-z][a-z0-9-]*$/;
193
- if (!isPlainObject(owners) || !Object.keys(owners).length || !Object.entries(owners).every(([key, list]) => key && Array.isArray(list) && list.every((o) => ownerId.test(String(o))) && new Set(list).size === list.length)) bad.push('ruleParams.be.infraOwners must map a non-empty specifier to a list of unique platform/<capability> or integrations/<provider> owners ([] means nowhere)');
194
- const roleList = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => /^[a-z][a-z0-9-]*$/.test(String(x))) && new Set(v).size === v.length;
195
- if (!roleList(rp.be.suffixes)) bad.push('ruleParams.be.suffixes must be a non-empty list of unique kebab-case role suffixes');
196
- if (!roleList(rp.be.bannedSuffixes)) bad.push('ruleParams.be.bannedSuffixes must be a non-empty list of unique kebab-case suffixes');
197
- else if (roleList(rp.be.suffixes) && rp.be.suffixes.some((x) => rp.be.bannedSuffixes.includes(x))) bad.push('ruleParams.be.suffixes and bannedSuffixes must be disjoint');
198
- const formNames = ['call', 'new', 'curried', 'object', 'primitive', 'array'];
199
- const formsOk = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => formNames.includes(x)) && new Set(v).size === v.length;
200
- const doubleOk = (v) => isPlainObject(v) && /^[A-Za-z][A-Za-z0-9]*$/.test(String(v.double)) && formsOk(v.forms);
201
- const regexOk = (v) => { try { return typeof v === 'string' && v.length > 0 && Boolean(new RegExp(v)); } catch { return false; } };
202
- const sd = rp.be.specDoubles;
203
- if (!isPlainObject(sd) || Object.keys(sd).sort().join() !== 'doubles,fallback,kit' || typeof sd.kit !== 'string' || !sd.kit || !Array.isArray(sd.doubles) || !sd.doubles.length || !sd.doubles.every((e) => doubleOk(e) && regexOk(e.token) && Object.keys(e).length === 3) || !doubleOk(sd.fallback) || Object.keys(sd.fallback).length !== 2) bad.push('ruleParams.be.specDoubles must be {kit, doubles: [{token: regex, double, forms}], fallback: {double, forms}} with forms drawn from call, new, curried, object, primitive, array');
204
- if (!fileLinesOk(rp.fe.fileLines) || !blockOk(rp.fe.duplicateBlock) || Object.keys(rp.fe).length !== 2) bad.push('ruleParams.fe needs fileLines {soft, hardGrowth} and duplicateBlock {lines >= 2, tokens >= 1}');
184
+ bad.push(...tierMapProblems(profile, tiers));
205
185
  }
186
+ bad.push(...ruleParamsProblems(m));
206
187
  if (!Array.isArray(m.slots) || !m.slots.length) { bad.push('slots must be a non-empty list'); return bad; }
207
- const slotKeys = new Set(['id', 'profiles', 'path', 'presence', 'tracked', 'tier', 'tests', 'owner', 'appKind', 'minInstances', 'requiredWhen', 'requiredInstances', 'requires', 'allows', 'forbids', 'layers', 'kinds', 'roles', 'composedBy', 'budget', 'managedBy', 'rules', 'goesTo', 'why', 'since', 'retiredIn', 'successor']);
208
- m.slots.forEach((slot, index) => {
209
- const at = isPlainObject(slot) && typeof slot.id === 'string' ? `slot ${slot.id}` : `slots[${index}]`;
210
- if (!isPlainObject(slot)) { bad.push(`${at} is not a map`); return; }
211
- for (const key of Object.keys(slot)) if (!slotKeys.has(key)) bad.push(`${at}: unknown field ${key}`);
212
- if (!SLOT_ID.test(String(slot.id))) bad.push(`${at}: id must look like be.transport.http`);
213
- if (!Array.isArray(slot.profiles) || !slot.profiles.length || !slot.profiles.every((p) => SCOPES.includes(p)) || new Set(slot.profiles).size !== slot.profiles.length || (slot.profiles.includes(APP_SCOPE) && slot.profiles.length !== 1)) bad.push(`${at}: profiles must be [app] or a unique non-empty subset of be, fe`);
214
- else if ((slot.profiles[0] === APP_SCOPE) !== String(slot.id).startsWith(`${APP_SCOPE}.`)) bad.push(`${at}: an app-root slot has profiles [app] and an id app.<name>, and only it`);
215
- if (typeof slot.path !== 'string' || !slot.path) bad.push(`${at}: path is required`);
216
- if (!PRESENCE.includes(slot.presence)) bad.push(`${at}: presence must be one of ${PRESENCE.join(', ')}`);
217
- if (!TRACKED.includes(slot.tracked)) bad.push(`${at}: tracked must be one of ${TRACKED.join(', ')}`);
218
- if (!NAME.test(String(slot.tier))) bad.push(`${at}: tier must be a tier name, none or inherit`);
219
- if (!TESTS.includes(slot.tests)) bad.push(`${at}: tests must be one of ${TESTS.join(', ')}`);
220
- if (slot.owner !== undefined && typeof slot.owner !== 'boolean') bad.push(`${at}: owner must be a boolean`);
221
- if (slot.appKind !== undefined && !NAME.test(String(slot.appKind))) bad.push(`${at}: appKind must be a name`);
222
- if (slot.minInstances !== undefined && !(Number.isInteger(slot.minInstances) && slot.minInstances >= 1)) bad.push(`${at}: minInstances must be a positive integer`);
223
- if (slot.requiredWhen !== undefined && slot.requiredWhen !== 'connections') bad.push(`${at}: requiredWhen may only be connections`);
224
- if (slot.requiredInstances !== undefined && !(isPlainObject(slot.requiredInstances) && Object.values(slot.requiredInstances).every((v) => strList(v) && v.length))) bad.push(`${at}: requiredInstances must map a variable to a non-empty list of names`);
225
- for (const key of ['requires', 'allows', 'forbids', 'layers', 'kinds']) if (slot[key] !== undefined && !strList(slot[key])) bad.push(`${at}: ${key} must be a list of strings`);
226
- if (slot.kinds !== undefined && strList(slot.kinds) && (!slot.kinds.length || new Set(slot.kinds).size !== slot.kinds.length || slot.kinds.some((k) => !NAME.test(k) || (slot.layers ?? []).includes(k)))) bad.push(`${at}: kinds must be a non-empty list of unique folder names that are not layers`);
227
- if (slot.roles !== undefined && !(isPlainObject(slot.roles) && Object.keys(slot.roles).length && Object.entries(slot.roles).every(([role, file]) => NAME.test(role) && typeof file === 'string' && file && !file.includes('/')))) bad.push(`${at}: roles must map a role name to a file name`);
228
- if (slot.composedBy !== undefined && !(strList(slot.composedBy) && slot.composedBy.length && new Set(slot.composedBy).size === slot.composedBy.length)) bad.push(`${at}: composedBy must be a non-empty list of unique app kinds`);
229
- if (slot.budget !== undefined && !(isPlainObject(slot.budget) && Object.keys(slot.budget).length && Object.values(slot.budget).every((v) => Number.isInteger(v) && v >= 1))) bad.push(`${at}: budget must map names to positive integers`);
230
- if (slot.managedBy !== undefined && !NAME.test(String(slot.managedBy))) bad.push(`${at}: managedBy must be a template id`);
231
- if (slot.rules !== undefined && !(Array.isArray(slot.rules) && slot.rules.every((r) => /^[A-Z][A-Z0-9_]*\*?$/.test(String(r))) && new Set(slot.rules).size === slot.rules.length)) bad.push(`${at}: rules must be unique rule ids`);
232
- if (slot.since !== undefined && !SEMVER.test(String(slot.since))) bad.push(`${at}: since must be a version`);
233
- if (slot.retiredIn !== undefined && !(Number.isInteger(slot.retiredIn) && slot.retiredIn >= 2)) bad.push(`${at}: retiredIn must be a major`);
234
- if (slot.retiredIn !== undefined && typeof slot.successor !== 'string') bad.push(`${at}: a retired slot names its successor`);
235
- if (slot.tracked === 'external' && (typeof slot.goesTo !== 'string' || slot.presence !== 'forbidden')) bad.push(`${at}: an external slot is forbidden and says where it goes (goesTo)`);
236
- if (slot.presence === 'forbidden' && slot.tracked !== 'external') bad.push(`${at}: a forbidden slot is external`);
237
- });
188
+ m.slots.forEach((slot, index) => bad.push(...slotProblems(slot, index, APP_KIND, { appScope: APP_SCOPE, scopes: SCOPES })));
238
189
  return bad;
239
190
  }
240
191
 
@@ -256,6 +207,7 @@ function manifestSemanticProblems(m) {
256
207
  const tiers = m.tiers[profile];
257
208
  if (slot.tier !== 'none' && slot.tier !== 'inherit' && !(slot.tier in tiers)) bad.push(`slot ${slot.id}: tier ${slot.tier} is not a ${profile} tier`);
258
209
  if (slot.appKind !== undefined && !m.appKinds[profile].includes(slot.appKind)) bad.push(`slot ${slot.id}: app kind ${slot.appKind} is not a ${profile} kind`);
210
+ bad.push(...triggerProblems(slot, m.triggerKinds));
259
211
  }
260
212
  if (slot.appKind === undefined) {
261
213
  for (const variant of braceVariants(slot.path)) {
@@ -267,6 +219,15 @@ function manifestSemanticProblems(m) {
267
219
  }
268
220
  if (slot.appKind !== undefined && !pathVars.has('app')) bad.push(`slot ${slot.id}: an app-kind slot binds <app> in its path`);
269
221
  if (slot.requiredWhen !== undefined && slot.presence !== 'required') bad.push(`slot ${slot.id}: requiredWhen belongs to a required slot`);
222
+ if (!slotInEdition(m, slot, 'lite') && (slot.litePresence !== undefined || slot.lite !== undefined)) bad.push(`slot ${slot.id}: litePresence/lite on a slot lite never sees (editions)`);
223
+ if (isPlainObject(slot.litePresence) && slot.profiles.includes(APP_SCOPE)) bad.push(`slot ${slot.id}: an app-root slot takes a bare litePresence, not a per-side map`);
224
+ for (const profile of slot.profiles) if (litePresenceOf(slot, profile) === 'forbidden' && typeof slot.goesTo !== 'string') bad.push(`slot ${slot.id}: forbidden in lite for ${profile}, so it says where the content goes (goesTo)`);
225
+ if (slot.lite?.path !== undefined) {
226
+ for (const name of varsOf(slot.lite.path)) if (!pathVars.has(name)) bad.push(`slot ${slot.id}: lite.path binds <${name}>, which the path does not`);
227
+ for (const variant of braceVariants(slot.lite.path)) {
228
+ try { compileVariant(slot, variant); } catch (error) { bad.push(`slot ${slot.id}: lite.path ${variant} does not compile (${error.message})`); }
229
+ }
230
+ }
270
231
  for (const name of Object.keys(slot.requiredInstances ?? {})) if (!pathVars.has(name)) bad.push(`slot ${slot.id}: requiredInstances names <${name}>, which the path does not bind`);
271
232
  for (const file of Object.values(slot.roles ?? {})) for (const name of varsOf(file)) if (!pathVars.has(name)) bad.push(`slot ${slot.id}: roles names <${name}> in ${file}, which the path does not bind`);
272
233
  for (const entry of slot.requires ?? []) for (const name of varsOf(entry)) if (!pathVars.has(name)) bad.push(`slot ${slot.id}: requires ${entry} uses <${name}>, which the path does not bind`);
@@ -277,11 +238,15 @@ function manifestSemanticProblems(m) {
277
238
  for (const kind of slot.composedBy ?? []) if (!slot.profiles.every((p) => m.appKinds[p].includes(kind))) bad.push(`slot ${slot.id}: composedBy names ${kind}, which is not an app kind of every profile of the slot`);
278
239
  if (slot.layers !== undefined && slot.tier !== 'none' && !slot.profiles.every((p) => m.tiers[p][slot.tier]?.lowerLayerOnly)) bad.push(`slot ${slot.id}: layers need a lowerLayerOnly tier`);
279
240
  }
241
+ if (manifestKind(m) === RUNTIME_KIND) return [...bad, ...runtimeSemanticProblems(m)];
280
242
  for (const side of PROFILES) for (const read of m.sides[side].reads) {
281
243
  const [owner, ...rest] = read.split('/');
282
244
  const below = rest.join('/');
283
245
  if (owner === side) bad.push(`sides.${side}.reads names ${read}, which is its own side`);
284
- else if (!m.slots.some((s) => s.profiles.includes(owner) && s.presence !== 'forbidden' && braceVariants(s.path).some((v) => v.startsWith(below)))) bad.push(`sides.${side}.reads names ${read}, which no ${owner} slot owns`);
246
+ // A side read is a path of the other side (be/contracts/); an app-root read (supabase/types/) names a tree an app slot owns.
247
+ else if (PROFILES.includes(owner)) {
248
+ if (!m.slots.some((s) => s.profiles.includes(owner) && s.presence !== 'forbidden' && braceVariants(s.path).some((v) => v.startsWith(below)))) bad.push(`sides.${side}.reads names ${read}, which no ${owner} slot owns`);
249
+ } else if (!m.slots.some((s) => s.profiles.includes(APP_SCOPE) && s.presence !== 'forbidden' && braceVariants(s.path).some((v) => v.startsWith(read)))) bad.push(`sides.${side}.reads names ${read}, which no app-root slot owns`);
285
250
  }
286
251
  for (const profile of PROFILES) {
287
252
  for (const [tier, def] of Object.entries(m.tiers[profile])) {
@@ -313,56 +278,13 @@ export function loadSlotManifest({ root = skillRoot, file = path.join(root, HFS_
313
278
 
314
279
  // ------------------------------------------------------------------------------------- declaration
315
280
 
316
- /** Shape problems of a parsed hfs.json, in the words of modules/schemas/hfs-repo.schema.yaml. */
317
- function declarationShapeProblems(d) {
318
- const bad = [];
319
- if (!isPlainObject(d)) return ['hfs.json is not an object'];
320
- for (const key of Object.keys(d)) if (!['hfs', 'kind', 'project', 'sides'].includes(key)) bad.push(`unknown key ${key}`);
321
- if (!(Number.isInteger(d.hfs) && d.hfs >= 1)) bad.push('hfs must be the pinned manifest major (an integer, 1 or more)');
322
- if (d.kind !== APP_KIND) bad.push(`kind must be ${APP_KIND}: a product is one app repository with a be and an fe side`);
323
- if (!NAME.test(String(d.project))) bad.push('project must be a project name');
324
- if (!isPlainObject(d.sides) || Object.keys(d.sides).sort().join() !== PROFILES.join()) { bad.push('sides must declare exactly be and fe'); return bad; }
325
- const names = new Map();
326
- for (const side of PROFILES) {
327
- const s = d.sides[side];
328
- const at = `sides.${side}`;
329
- if (!isPlainObject(s)) { bad.push(`${at} must be an object`); continue; }
330
- for (const key of Object.keys(s)) if (!['apps', 'optionalSlots', 'connections', 'reads'].includes(key)) bad.push(`${at} has unknown key ${key}`);
331
- if (!Array.isArray(s.apps) || !s.apps.length) bad.push(`${at}.apps must list every ${side}/apps/<name> with its kind`);
332
- else s.apps.forEach((app, i) => {
333
- if (!isPlainObject(app) || !NAME.test(String(app.name)) || !NAME.test(String(app.kind)) || Object.keys(app).some((k) => k !== 'name' && k !== 'kind')) bad.push(`${at}.apps[${i}] must be {name, kind}`);
334
- // The root scripts name every app (start:<app>), so an app name is unique across both sides.
335
- else if (names.has(app.name)) bad.push(`app ${app.name} is declared twice (${names.get(app.name)} and ${side})`);
336
- else names.set(app.name, side);
337
- });
338
- if (s.optionalSlots !== undefined && (!Array.isArray(s.optionalSlots) || !s.optionalSlots.every((v) => SLOT_ID.test(String(v))) || new Set(s.optionalSlots).size !== s.optionalSlots.length)) bad.push(`${at}.optionalSlots must be a unique list of slot ids`);
339
- if (s.reads !== undefined && (!Array.isArray(s.reads) || !s.reads.every((r) => typeof r === 'string' && r.length > 0) || new Set(s.reads).size !== s.reads.length)) bad.push(`${at}.reads must be a unique list of paths`);
340
- if (s.connections !== undefined) {
341
- if (side !== 'be') { bad.push('connections belong to the be side'); continue; }
342
- // One physical database = one entry (R84): {name, envPrefix}; names and env prefixes unique, no prefix inside another's keys.
343
- const list = Array.isArray(s.connections) ? s.connections : null;
344
- if (!list || !list.every((c) => isPlainObject(c) && NAME.test(String(c.name)) && ENV_PREFIX.test(String(c.envPrefix)) && Object.keys(c).length === 2)) bad.push('connections must be a list of {name: kebab-case database name, envPrefix: UPPER_SNAKE prefix of its env keys}');
345
- else {
346
- if (new Set(list.map((c) => c.name)).size !== list.length) bad.push('connections names must be unique');
347
- for (const a of list) for (const b of list) if (a !== b && `${b.envPrefix}_`.startsWith(`${a.envPrefix}_`)) bad.push(`connections ${a.name} and ${b.name} share env keys (${a.envPrefix}_ covers ${b.envPrefix}_)`);
348
- }
349
- }
350
- }
351
- return bad;
352
- }
353
-
354
281
  const declarationInvalid = (problems, file) => fail('HFS_DECLARATION_INVALID', `hfs.json is refused: ${problems.slice(0, 5).join('; ')}${problems.length > 5 ? `; and ${problems.length - 5} more` : ''}`, { file, problems });
355
282
 
356
283
  /** One side of a declaration checked against the manifest: app kinds of the profile, opt-in slots, required app kinds, reads. */
357
284
  function sideProblems(manifest, side, s) {
358
285
  const bad = [];
359
286
  for (const app of s.apps) if (!manifest.appKinds[side].includes(app.kind)) bad.push(`${side} app ${app.name} has kind ${app.kind}, which is not a ${side} kind (${manifest.appKinds[side].join(', ')})`);
360
- for (const id of s.optionalSlots ?? []) {
361
- const slot = manifest.slots.find((candidate) => candidate.id === id);
362
- if (!slot || !slot.profiles.includes(side)) bad.push(`sides.${side}.optionalSlots names ${id}, which is not a ${side} slot`);
363
- else if (slot.presence !== 'opt-in') bad.push(`sides.${side}.optionalSlots names ${id}, which is ${slot.presence}, not opt-in`);
364
- else if (slot.appKind !== undefined) bad.push(`sides.${side}.optionalSlots names ${id}; an app of kind ${slot.appKind} enables it`);
365
- }
287
+ bad.push(...optionalSlotProblems(manifest, side, s));
366
288
  for (const read of s.reads ?? []) if (!manifest.sides[side].reads.includes(read)) bad.push(`sides.${side}.reads names ${read}; ${side} may read only ${manifest.sides[side].reads.join(', ') || 'nothing of the other side'}`);
367
289
  const connections = s.connections ?? [];
368
290
  for (const slot of manifest.slots) {
@@ -380,24 +302,47 @@ function sideProblems(manifest, side, s) {
380
302
  * its two sides under `sides`), or with `side` that side's view: the declaration a check of the side folder runs under.
381
303
  */
382
304
  export function resolveRepoDeclaration(manifest, declaration, { file = HFS_DECLARATION_FILE, side = null } = {}) {
383
- const problems = declarationShapeProblems(declaration);
305
+ const problems = declarationShapeProblems(declaration, PROFILES);
384
306
  if (problems.length) declarationInvalid(problems, file);
307
+ if (declaration.kind !== manifestKind(manifest)) declarationInvalid([`hfs.json is of kind ${declaration.kind}, but the manifest it is judged by is of kind ${manifestKind(manifest)}`], file);
385
308
  if (declaration.hfs !== manifest.major)
386
309
  fail('HFS_MANIFEST_MAJOR_MISMATCH', `hfs.json pins manifest major ${declaration.hfs} but the manifest is ${manifest.version}`, { pinned: declaration.hfs, manifest: manifest.version, manifestMajor: manifest.major, file });
310
+ if (declaration.kind === RUNTIME_KIND) {
311
+ if (side !== null) fail('HFS_DECLARATION_INVALID', 'a runtime repository has no sides', { file, side });
312
+ return Object.freeze({
313
+ hfs: declaration.hfs,
314
+ kind: RUNTIME_KIND,
315
+ project: declaration.project,
316
+ side: null,
317
+ profile: RUNTIME_KIND,
318
+ apps: Object.freeze([]),
319
+ optionalSlots: Object.freeze([]),
320
+ connections: Object.freeze([]),
321
+ reads: Object.freeze([]),
322
+ manifestVersion: manifest.version,
323
+ });
324
+ }
325
+ const { edition, known, valid } = declarationEdition(manifest, declaration);
326
+ if (!valid) fail('HFS_EDITION_INVALID', `hfs.json edition is ${JSON.stringify(declaration.edition)}; the editions this manifest knows are ${known.join(', ')} (absent means full)`, { file, edition: declaration.edition });
387
327
  const bad = PROFILES.flatMap((name) => sideProblems(manifest, name, declaration.sides[name]));
388
328
  if (bad.length) declarationInvalid(bad, file);
389
329
  if (side !== null && !PROFILES.includes(side)) fail('HFS_DECLARATION_INVALID', `${side} is not a side of an app (be, fe)`, { file, side });
330
+ // A provider declared on any connection enables the provider slots of every profile (a fe side declares none): each view carries the union.
331
+ const providers = Object.freeze([...new Set(PROFILES.flatMap((name) => (declaration.sides[name].connections ?? []).map((c) => c.provider).filter((p) => p !== undefined)))]);
390
332
  const sides = Object.fromEntries(PROFILES.map((name) => {
391
333
  const s = declaration.sides[name];
392
334
  return [name, Object.freeze({
393
335
  hfs: declaration.hfs,
394
336
  kind: APP_KIND,
395
337
  project: declaration.project,
338
+ edition,
396
339
  side: name,
397
340
  profile: name,
398
341
  apps: Object.freeze(s.apps.map((a) => Object.freeze({ name: a.name, kind: a.kind }))),
399
342
  optionalSlots: Object.freeze([...(s.optionalSlots ?? [])]),
400
- connections: Object.freeze((s.connections ?? []).map((c) => Object.freeze({ name: c.name, envPrefix: c.envPrefix }))),
343
+ patterns: Object.freeze([...(s.patterns ?? [])]), kinds: Object.freeze([...(s.kinds ?? [])]),
344
+ connections: Object.freeze((s.connections ?? []).map((c) => Object.freeze({ name: c.name, envPrefix: c.envPrefix, owner: c.owner, isolation: c.isolation, ...(c.provider !== undefined ? { provider: c.provider } : {}) }))),
345
+ providers,
401
346
  reads: Object.freeze([...(s.reads ?? [])]),
402
347
  manifestVersion: manifest.version,
403
348
  })];
@@ -407,12 +352,14 @@ export function resolveRepoDeclaration(manifest, declaration, { file = HFS_DECLA
407
352
  hfs: declaration.hfs,
408
353
  kind: APP_KIND,
409
354
  project: declaration.project,
355
+ edition,
410
356
  side: null,
411
357
  profile: APP_SCOPE,
412
- // The root declares no app, opt-in slot or connection of its own: each side does.
358
+ // The root declares no app or connection of its own (each side does); its one opt-in slot is the browser journey (`browser: true`).
413
359
  apps: Object.freeze([]),
414
- optionalSlots: Object.freeze([]),
360
+ optionalSlots: Object.freeze(declaration.browser === true ? ['app.browser'] : []),
415
361
  connections: Object.freeze([]),
362
+ providers,
416
363
  reads: Object.freeze([]),
417
364
  sides: Object.freeze(sides),
418
365
  manifestVersion: manifest.version,
@@ -454,14 +401,18 @@ const isEntryFile = (name) => name === 'index.ts' || name === 'index.tsx';
454
401
  */
455
402
  function createScopeResolver(manifest, repo) {
456
403
  const profile = repo.profile;
457
- const slots = manifest.slots.filter((s) => s.profiles.includes(profile));
404
+ const edition = repo.edition ?? 'full';
405
+ const slots = manifest.slots.filter((s) => s.profiles.includes(profile) && slotInEdition(manifest, s, edition)).map((s) => effectiveSlot(s, profile, edition));
458
406
  const byId = new Map(slots.map((s) => [s.id, s]));
459
407
  const variants = slots.flatMap(variantsOf);
460
408
  const appKind = new Map(repo.apps.map((a) => [a.name, a.kind]));
461
409
 
462
410
  const slotEnabled = (slot) => {
411
+ // A provider slot is gated by its provider in every edition: litePresence may lift it to required, but it still
412
+ // exists for this repository only while a connection declares the provider.
413
+ if (slot.provider !== undefined) return declaredSlotEnabled(slot, repo);
463
414
  if (slot.presence !== 'opt-in') return true;
464
- return slot.appKind !== undefined ? repo.apps.some((a) => a.kind === slot.appKind) : repo.optionalSlots.includes(slot.id);
415
+ return declaredSlotEnabled(slot, repo);
465
416
  };
466
417
  const clean = (p) => posixPath(p).replace(/\/+$/, '');
467
418
 
@@ -599,10 +550,11 @@ function createScopeResolver(manifest, repo) {
599
550
  const b = layerIndex(byId.get((toOwner ?? to).slot), toOwner?.root ?? to.root);
600
551
  if (a >= 0 && b >= 0 && b <= a) return { allowed: false, reason: 'layerOrder', fromLayer: fromSlot.layers[a], toLayer: fromSlot.layers[b] };
601
552
  }
602
- if (toOwner) {
553
+ // A runtime owner is imported file by file (crossOwner of knowledge/hfs/runtime-slots.yaml): no public entry.
554
+ if (toOwner && repo.kind !== RUNTIME_KIND) {
603
555
  const relative = to.path === toOwner.root ? '' : to.path.slice(toOwner.root.length + 1);
604
- const ownerTier = byId.get(toOwner.slot).tier;
605
- const entry = isEntryFile(relative) || (ownerTier === 'package' && relative === 'src/index.ts') || (ownerTier === 'app' && relative === 'app.module.ts');
556
+ const ownerSlot = byId.get(toOwner.slot);
557
+ const entry = isEntryFile(relative) || (byId.get(to.slot).entries ?? []).includes(to.path.slice(to.root.length + 1)) || (ownerSlot.tier === 'package' && relative === 'src/index.ts') || (ownerSlot.tier === 'app' && relative === 'app.module.ts');
606
558
  if (!entry) return { allowed: false, reason: 'notPublicEntry', owner: toOwner.root, path: to.path };
607
559
  }
608
560
  return { allowed: true, reason: 'allowed', fromTier, toTier };
@@ -631,7 +583,7 @@ function createScopeResolver(manifest, repo) {
631
583
  const minimums = [];
632
584
  const expandApps = (slot) => repo.apps.filter((a) => slot.appKind === undefined || a.kind === slot.appKind);
633
585
  for (const slot of slots) {
634
- if (slot.presence !== 'required') continue;
586
+ if (slot.presence !== 'required' || !slotEnabled(slot)) continue;
635
587
  if (slot.requiredWhen === 'connections' && !repo.connections.length) continue;
636
588
  if (slot.minInstances) minimums.push({ slot: slot.id, min: slot.minInstances, ...(slot.appKind ? { appKind: slot.appKind } : {}) });
637
589
  for (const variant of braceVariants(slot.path)) {
@@ -679,7 +631,7 @@ function createScopeResolver(manifest, repo) {
679
631
  requiredPaths,
680
632
  trackingOf,
681
633
  isTracked,
682
- ruleParams: () => ruleParams(manifest, profile),
634
+ ruleParams: () => ruleParams(manifest, profile, edition),
683
635
  allowedImports: (tier) => manifest.tiers[profile]?.[tier]?.mayImport ?? null,
684
636
  });
685
637
  }
@@ -693,7 +645,9 @@ function createScopeResolver(manifest, repo) {
693
645
  */
694
646
  export function createSlotResolver(manifest, repo) {
695
647
  if (repo.profile !== APP_SCOPE) return createScopeResolver(manifest, repo);
696
- const root = createScopeResolver(manifest, repo);
648
+ // The root declares no connection of its own, yet a provider slot of the app profile (app.supabase*) is enabled by a
649
+ // side's connection with that provider: the root scope reads the union of the sides' connections.
650
+ const root = createScopeResolver(manifest, { ...repo, connections: SIDES.flatMap((side) => repo.sides[side].connections) });
697
651
  const sides = Object.fromEntries(PROFILES.map((side) => [side, createScopeResolver(manifest, repo.sides[side])]));
698
652
  const clean = (p) => posixPath(p).replace(/\/+$/, '');
699
653
  /** { side, rest } when `p` lies below a side folder, else null. */
@@ -740,6 +694,10 @@ export function createSlotResolver(manifest, repo) {
740
694
  if (from && to && from.side !== to.side) {
741
695
  return reads(from.side, toPath) ? { allowed: true, reason: 'sideRead', fromSide: from.side, toSide: to.side } : { allowed: false, reason: 'crossSide', fromSide: from.side, toSide: to.side, reads: repo.sides[from.side].reads };
742
696
  }
697
+ // A side file may read an app-root path (supabase/types/) only through a declared read, exactly as it reads the other side.
698
+ if (from && !to) {
699
+ return reads(from.side, toPath) ? { allowed: true, reason: 'sideRead', fromSide: from.side, toSide: null } : { allowed: false, reason: 'crossSide', fromSide: from.side, toSide: null, reads: repo.sides[from.side].reads };
700
+ }
743
701
  if (from && to) return sides[from.side].importAllowed(from.rest, to.rest);
744
702
  return root.importAllowed(fromPath, toPath);
745
703
  },
@@ -763,11 +721,11 @@ export function createSlotResolver(manifest, repo) {
763
721
  });
764
722
  }
765
723
 
766
- /** The rule parameters of one profile (be: fileLines, duplicateBlock, infraOwners, suffixes, bannedSuffixes; fe: fileLines, duplicateBlock), as a frozen deep copy. */
767
- export function ruleParams(manifest, profile) {
768
- if (!PROFILES.includes(profile)) fail('HFS_MANIFEST_INVALID', `ruleParams has no profile ${profile}`, { profile });
769
- const deepFreeze = (v) => { if (v && typeof v === 'object') Object.values(v).forEach(deepFreeze); return Object.freeze(v); };
770
- return deepFreeze(structuredClone(manifest.ruleParams[profile]));
724
+ /** The rule parameters of one profile (be: infraOwners, suffixes, bannedSuffixes and the rest over the shared ruleParams.common fileLines and duplicateBlock; fe: common alone or with its overrides; runtime: ruleParams.runtime of a runtime manifest), as a frozen deep copy. Under `edition` lite the `lite` overrides of ruleParams.<profile> merge over the base (the `lite` key itself is never returned). */
725
+ export function ruleParams(manifest, profile, edition = 'full') {
726
+ const profiles = manifestKind(manifest) === RUNTIME_KIND ? [RUNTIME_KIND] : PROFILES;
727
+ if (!profiles.includes(profile)) fail('HFS_MANIFEST_INVALID', `ruleParams has no profile ${profile}`, { profile });
728
+ return editionRuleParams({ ...manifest.ruleParams.common, ...manifest.ruleParams[profile] }, edition);
771
729
  }
772
730
 
773
731
  /**
@@ -781,19 +739,19 @@ export function openHfs({ root = skillRoot, repoRoot, declaration, side = null,
781
739
 
782
740
  // ------------------------------------------------------------------------------------------ rule catalog
783
741
 
784
- export const HFS_RULES_FILE = 'knowledge/hfs/rules.yaml';
785
- export const RULE_GATES = Object.freeze(['pre-commit', 'pre-push', 'settle', 'land', 'ci', 'sonar']);
786
- export const ENFORCER_FAMILIES = Object.freeze(['eslint-be', 'eslint-fe', 'stylelint', 'machine', 'hfs', 'work-validate', 'sonar']);
787
- export const RULE_KINDS = Object.freeze(['codemod', 'lint', 'check', 'design']);
742
+ const HFS_RULES_FILE = 'knowledge/hfs/rules.yaml';
743
+ const RULE_GATES = Object.freeze(['pre-commit', 'pre-push', 'settle', 'land', 'ci', 'sonar', 'runtime']);
744
+ const ENFORCER_FAMILIES = Object.freeze(['eslint-be', 'eslint-fe', 'stylelint', 'machine', 'hfs', 'work-validate', 'sonar', 'runtime']);
745
+ const RULE_KINDS = Object.freeze(['codemod', 'lint', 'check', 'design']);
788
746
  const FINDING_CODE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/;
789
747
  const ENFORCER_ID = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
790
- const FILE_ENFORCERS = ['machine', 'hfs', 'work-validate', 'sonar'];
748
+ const FILE_ENFORCERS = ['machine', 'hfs', 'work-validate', 'sonar', 'runtime'];
791
749
 
792
750
  /** Shape and semantic problems of a parsed knowledge/hfs/rules.yaml, in the words of modules/schemas/hfs-rules.schema.yaml. */
793
751
  function ruleCatalogProblems(d) {
794
752
  const bad = [];
795
753
  if (!isPlainObject(d)) return ['the rule catalog is not a map'];
796
- for (const key of Object.keys(d)) if (!['schema', 'version', 'gates', 'enforcerKinds', 'rules'].includes(key)) bad.push(`unknown top-level key ${key}`);
754
+ for (const key of Object.keys(d)) if (!['schema', 'version', 'gates', 'enforcerKinds', 'rules', 'retired'].includes(key)) bad.push(`unknown top-level key ${key}`);
797
755
  const schemaOk = /^starci\/hfs-rules@\d+$/.test(String(d.schema));
798
756
  if (!schemaOk) bad.push('schema must be starci/hfs-rules@<major>');
799
757
  if (!SEMVER.test(String(d.version))) bad.push('version must be MAJOR.MINOR.PATCH');
@@ -811,13 +769,14 @@ function ruleCatalogProblems(d) {
811
769
  const at = `rules[${index}]`;
812
770
  if (!isPlainObject(r)) { bad.push(`${at} is not a map`); return; }
813
771
  const label = typeof r.id === 'string' ? r.id : at;
814
- for (const key of Object.keys(r)) if (!['id', 'code', 'law', 'kinds', 'gates', 'failureCodes', 'enforcers'].includes(key)) bad.push(`${label} has unknown key ${key}`);
772
+ for (const key of Object.keys(r)) if (!['id', 'code', 'law', 'scope', 'kinds', 'gates', 'failureCodes', 'editions', 'enforcers'].includes(key)) bad.push(`${label} has unknown key ${key}`);
815
773
  // A retired rule leaves its id unused for good (never reused), so ids only have to increase.
816
774
  if (!/^R\d{2,3}$/.test(String(r.id))) bad.push(`${at}.id must be R<two or three digits>`);
817
775
  else if (index > 0 && typeof d.rules[index - 1]?.id === 'string' && Number(r.id.slice(1)) <= Number(d.rules[index - 1].id.slice(1))) bad.push(`${label} is out of order: ids must increase, and ${d.rules[index - 1].id} comes before it`);
818
776
  if (!FINDING_CODE.test(String(r.code))) bad.push(`${label}.code must be an UPPER_SNAKE finding code`);
819
777
  if (typeof r.law !== 'string' || !r.law.trim()) bad.push(`${label}.law is missing`);
820
778
  if (typeof r.law === 'string' && r.law.includes('\n')) bad.push(`${label}.law must be one line`);
779
+ if (r.scope !== undefined && r.scope !== 'runtime') bad.push(`${label}.scope is absent or runtime`);
821
780
  const enumList = (key, allowed) => {
822
781
  if (!Array.isArray(r[key]) || !r[key].length) { bad.push(`${label}.${key} must be a non-empty list`); return []; }
823
782
  for (const v of r[key]) if (!allowed.includes(v)) bad.push(`${label}.${key} has ${JSON.stringify(v)}, not one of ${allowed.join(', ')}`);
@@ -825,6 +784,7 @@ function ruleCatalogProblems(d) {
825
784
  return r[key];
826
785
  };
827
786
  enumList('kinds', RULE_KINDS);
787
+ bad.push(...ruleEditionProblems(r, label));
828
788
  const gates = enumList('gates', RULE_GATES);
829
789
  if (gates.length) {
830
790
  if (!gates.includes('land')) bad.push(`${label} must run at the land gate (every rule does)`);
@@ -844,7 +804,8 @@ function ruleCatalogProblems(d) {
844
804
  r.enforcers.forEach((e, n) => {
845
805
  const eat = `${label}.enforcers[${n}]`;
846
806
  if (!isPlainObject(e)) { bad.push(`${eat} is not a map`); return; }
847
- for (const key of Object.keys(e)) if (!['kind', 'id', 'status', 'at'].includes(key)) bad.push(`${eat} has unknown key ${key}`);
807
+ for (const key of Object.keys(e)) if (!['kind', 'id', 'status', 'at', 'editions'].includes(key)) bad.push(`${eat} has unknown key ${key}`);
808
+ bad.push(...ruleEditionProblems(e, eat));
848
809
  if (!ENFORCER_FAMILIES.includes(e.kind)) bad.push(`${eat}.kind must be one of ${ENFORCER_FAMILIES.join(', ')}`);
849
810
  if (!ENFORCER_ID.test(String(e.id))) bad.push(`${eat}.id must be kebab-case`);
850
811
  if (seen.has(`${e.kind}:${e.id}`)) bad.push(`${eat} repeats ${e.kind}:${e.id}`);
@@ -853,7 +814,7 @@ function ruleCatalogProblems(d) {
853
814
  if (e.at !== undefined) {
854
815
  if (typeof e.at !== 'string' || !e.at.trim() || e.at.startsWith('/') || e.at.includes('..')) bad.push(`${eat}.at must be a repository-relative path`);
855
816
  if (e.status === 'planned') bad.push(`${eat} is planned, so it has no file yet (at)`);
856
- if (!FILE_ENFORCERS.includes(e.kind)) bad.push(`${eat}.at belongs to a machine, hfs, work-validate or sonar enforcer only`);
817
+ if (!FILE_ENFORCERS.includes(e.kind)) bad.push(`${eat}.at belongs to a machine, hfs, work-validate, sonar or runtime enforcer only`);
857
818
  } else if (FILE_ENFORCERS.includes(e.kind) && e.status !== 'planned') bad.push(`${eat} exists, so it names the file (at) that emits its code`);
858
819
  });
859
820
  if (Array.isArray(r.gates)) {
@@ -886,11 +847,14 @@ export function loadRuleCatalog({ root = skillRoot, file = path.join(root, HFS_R
886
847
  version: doc.version, major, minor, patch,
887
848
  gates: deepFreeze(structuredClone(doc.gates)),
888
849
  enforcerKinds: deepFreeze(structuredClone(doc.enforcerKinds)),
889
- rules: list,
850
+ rules: list, retired: deepFreeze(structuredClone(doc.retired ?? [])),
890
851
  /** The rule with this id (R01..), or null. */
891
852
  rule: (id) => byId.get(id) ?? null,
892
853
  /** The rule that owns this failure code (its own or a sub-check code), or null. */
893
854
  byCode: (code) => byCode.get(code) ?? null,
855
+ /** Whether a finding code is judged under `edition`: a code of a rule that names `editions` without it is not (a code outside the catalog always is). */
856
+ judgedIn: (code, edition = 'full') => judgedInEdition(byCode.get(code), edition),
857
+ enforcerJudgedIn: (kind, id, edition = 'full') => enforcerJudgedInEdition(list, kind, id, edition),
894
858
  /** The rules that run at a gate. */
895
859
  forGate: (gate) => list.filter((r) => r.gates.includes(gate)),
896
860
  /** The catalogued why code of a lint finding's rule id (`starci-be/<id>`, `starci-fe/<id>`), or undefined. */
@@ -0,0 +1,46 @@
1
+ // pg-parse.mjs - the one SQL parse entry of the hfs database rules (scripts/hfs/rules/database.mjs): the WASM build
2
+ // of libpg-query, the real PostgreSQL parser compiled to WebAssembly (no native build, no postgres server). It is
3
+ // loaded lazily: a check of an app with no supabase/ tree never pays the module init. parseSql gives the statement
4
+ // tree of a migration (`{version, stmts}`, each stmt `{stmt, stmt_location, stmt_len}` with byte offsets); a
5
+ // plpgsql body (a DO block's or a function's `as` source) is judged through parsePlpgsqlBody, which wraps it in a
6
+ // probe CREATE FUNCTION because libpg-query parses plpgsql only in that form. A parse failure throws the
7
+ // library's SqlError, whose sqlDetails.cursorPosition is the 1-based byte offset of the error.
8
+
9
+ let loading = null;
10
+
11
+ /**
12
+ * The libpg-query module (wasm build), initialized once per process. A bare dynamic import so the package can be
13
+ * absent until a supabase/ tree exists to judge; the caller reports the load failure, not this module.
14
+ */
15
+ function loadPgParser() {
16
+ loading ??= (async () => {
17
+ const module = await import('libpg-query');
18
+ const lib = module.default?.parseSync ? module.default : module;
19
+ await lib.loadModule();
20
+ return lib;
21
+ })();
22
+ return loading;
23
+ }
24
+
25
+ /** The parse tree of `text` ({version, stmts}); throws SqlError on invalid SQL. */
26
+ export async function parseSql(text) {
27
+ return (await loadPgParser()).parseSync(text);
28
+ }
29
+
30
+ /**
31
+ * The plpgsql AST of a function/DO body: libpg-query's plpgsql parser only accepts a whole CREATE FUNCTION, so
32
+ * `body` is wrapped in a probe function under a dollar tag that does not collide with the body's own tags.
33
+ * Answers the PLpgSQL_function node; throws on a body that is not valid plpgsql.
34
+ */
35
+ export async function parsePlpgsqlBody(body, parameters = [], returnKind = 'void') {
36
+ let i = 0;
37
+ let tag = `$hfs_probe_${i}$`;
38
+ while (body.includes(tag)) { i += 1; tag = `$hfs_probe_${i}$`; }
39
+ const args = parameters.map((parameter) => parameter?.FunctionParameter?.name).filter(Boolean)
40
+ .map((name) => `"${name.replace(/"/g, '""')}" text`).join(', ');
41
+ const returns = returnKind === 'setof' ? 'setof text' : returnKind === 'trigger' ? 'trigger' : returnKind === 'scalar' ? 'text' : 'void';
42
+ const result = await (await loadPgParser()).parsePlPgSQLSync(
43
+ `create function hfs_probe(${args}) returns ${returns} language plpgsql as ${tag}${body}${tag};`,
44
+ );
45
+ return result?.plpgsql_funcs?.[0]?.PLpgSQL_function ?? null;
46
+ }
@@ -0,0 +1,6 @@
1
+ // test-world-slot.mjs - the one answer of the machine checks to "is this slot the test world": the slot `be.tests.world` or one of its
2
+ // sub-slots (`be.tests.world.kit`, the world's inlined helpers), which form one test composition root. The lint side asks the same of
3
+ // the slot view through `inTestWorld` of packages/eslint/be/lib/hfs.mjs.
4
+
5
+ /** True when `slot` (a slot id, or a file's slot) belongs to the test world family. */
6
+ export const isTestWorldSlot = (slot) => typeof slot === 'string' && (slot === 'be.tests.world' || slot.startsWith('be.tests.world.'));
@@ -4,16 +4,17 @@
4
4
  // Read-only: it walks the file system and asks git, nothing else.
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
- import { gitOutput } from '../api/git/lib.mjs';
8
- import { posixPath } from './path-key.mjs';
7
+ import { lsFiles } from '../api/git/ls-files.mjs';
8
+ import { gitOutputOf } from '../lib/git.mjs';
9
+ import { posixPath } from '../lib/path-key.mjs';
9
10
 
10
11
  /** Directory names no tree check enters: git's own store and installed packages. */
11
12
  const NEVER_WALKED = new Set(['.git', 'node_modules']);
12
13
  /** Two sibling names this close (Levenshtein) are the same name misspelt. */
13
- export const GHOST_DISTANCE = 2;
14
+ const GHOST_DISTANCE = 2;
14
15
 
15
16
  /** Levenshtein distance of two names. */
16
- export function editDistance(a, b) {
17
+ function editDistance(a, b) {
17
18
  let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
18
19
  for (let i = 1; i <= a.length; i += 1) {
19
20
  const row = [i];
@@ -75,6 +76,6 @@ export function treeFacts(tree) {
75
76
 
76
77
  /** Entries git neither tracks nor ignores, as it prints them (a wholly untracked directory is one entry ending in '/'). */
77
78
  export function untrackedEntries(repoRoot) {
78
- const out = gitOutput(['ls-files', '-z', '--others', '--exclude-standard', '--directory', '--no-empty-directory'], { dir: repoRoot, maxBuffer: 256 * 1024 * 1024 });
79
+ const out = gitOutputOf(lsFiles(['-z', '--others', '--exclude-standard', '--directory', '--no-empty-directory'], { dir: repoRoot, maxBuffer: 256 * 1024 * 1024 }), 'git ls-files');
79
80
  return out.split('\0').filter(Boolean).map(posixPath);
80
81
  }