@starci/hfs 4.0.8 → 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 (667) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +81 -39
  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 +53 -19
  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/knowledge/hfs/canon-pins.yaml +160 -29
  17. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
  18. package/runtime/knowledge/hfs/rules.yaml +2059 -0
  19. package/runtime/knowledge/hfs/slots.yaml +912 -120
  20. package/runtime/knowledge/patterns/be/api.yaml +369 -0
  21. package/runtime/knowledge/patterns/be/cli.yaml +297 -0
  22. package/runtime/knowledge/patterns/be/domain.yaml +113 -0
  23. package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
  24. package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
  25. package/runtime/knowledge/patterns/be/projections.yaml +178 -0
  26. package/runtime/knowledge/patterns/be/queues.yaml +236 -0
  27. package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
  28. package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
  29. package/runtime/knowledge/patterns/be/saga.yaml +248 -0
  30. package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
  31. package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
  32. package/runtime/knowledge/sonar-gate.yaml +52 -18
  33. package/runtime/modules/kernel/failure-codes.yaml +571 -51
  34. package/runtime/modules/models/registry.yaml +591 -0
  35. package/runtime/modules/models/runtimes.yaml +701 -0
  36. package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
  37. package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
  38. package/runtime/scripts/api/fs/lib.mjs +10 -2
  39. package/runtime/scripts/api/fs/safe-remove.mjs +12 -132
  40. package/runtime/scripts/api/git/config-get.mjs +10 -0
  41. package/runtime/scripts/api/git/diff.mjs +5 -0
  42. package/runtime/scripts/api/git/lib.mjs +16 -26
  43. package/runtime/scripts/api/git/log.mjs +5 -0
  44. package/runtime/scripts/api/git/ls-files.mjs +6 -0
  45. package/runtime/scripts/api/git/ls-tree.mjs +5 -0
  46. package/runtime/scripts/api/git/merge-base.mjs +9 -0
  47. package/runtime/scripts/api/git/remote.mjs +5 -0
  48. package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
  49. package/runtime/scripts/api/git/show.mjs +6 -0
  50. package/runtime/scripts/api/sops/decrypt.mjs +9 -5
  51. package/runtime/scripts/api/sops/lib.mjs +35 -0
  52. package/runtime/scripts/api/sops/seal.mjs +18 -0
  53. package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
  54. package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
  55. package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
  56. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
  57. package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
  58. package/runtime/scripts/hfs/architecture/config.mjs +9 -6
  59. package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
  60. package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
  61. package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
  62. package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
  63. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
  64. package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
  65. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
  66. package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
  67. package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
  68. package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
  69. package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
  70. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
  72. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  73. package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
  74. package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
  75. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
  76. package/runtime/scripts/hfs/architecture/index.mjs +17 -4
  77. package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
  78. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
  79. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
  80. package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
  81. package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
  82. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
  83. package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
  84. package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
  85. package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
  86. package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
  87. package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
  88. package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
  89. package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
  90. package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
  91. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
  92. package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
  93. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
  94. package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
  95. package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
  96. package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
  97. package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
  98. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
  99. package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
  100. package/runtime/scripts/hfs/architecture.mjs +4 -4
  101. package/runtime/scripts/hfs/check.mjs +75 -72
  102. package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
  103. package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
  104. package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
  105. package/runtime/scripts/hfs/edition-slots.mjs +66 -0
  106. package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
  107. package/runtime/scripts/hfs/param-names.mjs +18 -0
  108. package/runtime/scripts/hfs/path-findings.mjs +1 -1
  109. package/runtime/scripts/hfs/repo-identity.mjs +9 -6
  110. package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
  111. package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
  112. package/runtime/scripts/hfs/rules/cli.mjs +37 -0
  113. package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
  114. package/runtime/scripts/hfs/rules/contract.mjs +10 -9
  115. package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
  116. package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
  117. package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
  118. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
  119. package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
  120. package/runtime/scripts/hfs/rules/database.mjs +68 -0
  121. package/runtime/scripts/hfs/rules/deps.mjs +1 -1
  122. package/runtime/scripts/hfs/rules/docker.mjs +259 -0
  123. package/runtime/scripts/hfs/rules/edition.mjs +111 -0
  124. package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
  125. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -1
  126. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
  127. package/runtime/scripts/hfs/rules/frontend-tree.mjs +9 -9
  128. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -6
  129. package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
  130. package/runtime/scripts/hfs/rules/lint-suppression.mjs +1 -1
  131. package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
  132. package/runtime/scripts/hfs/rules/peer-integrations.mjs +3 -3
  133. package/runtime/scripts/hfs/rules/pipeline.mjs +11 -21
  134. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -2
  135. package/runtime/scripts/hfs/rules/read.mjs +1 -1
  136. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +2 -2
  137. package/runtime/scripts/hfs/rules/saga.mjs +170 -0
  138. package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
  139. package/runtime/scripts/hfs/rules/services.mjs +220 -0
  140. package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
  141. package/runtime/scripts/hfs/rules/stacks.mjs +3 -3
  142. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
  143. package/runtime/scripts/hfs/rules/test-topology.mjs +2 -2
  144. package/runtime/scripts/hfs/secret.mjs +175 -0
  145. package/runtime/scripts/hfs/slots.mjs +78 -108
  146. package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
  147. package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
  148. package/runtime/scripts/hfs/tree.mjs +5 -4
  149. package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
  150. package/runtime/scripts/hfs/view.mjs +3 -2
  151. package/runtime/scripts/lib/dockerfile.mjs +124 -0
  152. package/runtime/scripts/lib/env.mjs +10 -0
  153. package/runtime/scripts/lib/event-contract.mjs +103 -0
  154. package/runtime/scripts/lib/git.mjs +36 -0
  155. package/runtime/scripts/lib/graphql-contract.mjs +1 -1
  156. package/runtime/scripts/lib/i18n.mjs +86 -0
  157. package/runtime/scripts/lib/json.mjs +13 -3
  158. package/runtime/scripts/lib/language.mjs +24 -5
  159. package/runtime/scripts/lib/list.mjs +20 -0
  160. package/runtime/scripts/lib/package-at.mjs +37 -0
  161. package/runtime/scripts/lib/path-key.mjs +57 -2
  162. package/runtime/scripts/lib/read-text.mjs +34 -0
  163. package/runtime/scripts/lib/regex.mjs +4 -0
  164. package/runtime/scripts/lib/same-text.mjs +5 -0
  165. package/runtime/scripts/lib/secret-patterns.mjs +4 -3
  166. package/runtime/scripts/lib/sleep-sync.mjs +2 -1
  167. package/runtime/scripts/lib/sops-envelope.mjs +58 -0
  168. package/runtime/scripts/lib/stack-services.mjs +7 -31
  169. package/runtime/scripts/lib/ts-ast.mjs +35 -0
  170. package/runtime/scripts/lib/walk.mjs +4 -3
  171. package/scaffold/add-app.mjs +174 -0
  172. package/scaffold/add-cli-lite.mjs +257 -0
  173. package/scaffold/add-inbox-lite.mjs +314 -0
  174. package/scaffold/add-table.mjs +344 -0
  175. package/scaffold/add.mjs +299 -0
  176. package/scaffold/app.mjs +358 -69
  177. package/scaffold/edition-gate.mjs +25 -0
  178. package/scaffold/image.mjs +19 -0
  179. package/scaffold/lite-exports.mjs +186 -0
  180. package/scaffold/service.mjs +32 -26
  181. package/scaffold/supabase-ports.mjs +26 -0
  182. package/src/main.mjs +327 -0
  183. package/sync/cli.mjs +6 -9
  184. package/sync/format.mjs +1 -1
  185. package/sync/hygiene.mjs +10 -10
  186. package/sync/index.mjs +167 -104
  187. package/sync/managed.mjs +19 -11
  188. package/sync/sonar-key.mjs +5 -0
  189. package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
  190. package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
  191. package/templates/app/ci-workflows/sonar-steps.yml +9 -0
  192. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
  193. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
  194. package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
  195. package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
  196. package/templates/app/docker-ignore/dockerignore +50 -0
  197. package/templates/app/gitignore-lite +35 -0
  198. package/templates/app/hooks/husky/pre-commit +3 -8
  199. package/templates/app/hooks/husky/pre-push +45 -6
  200. package/templates/app/package-scripts/core.json +8 -0
  201. package/templates/app/package-scripts/package.json +3 -10
  202. package/templates/app/package-scripts-lite/package.json +15 -0
  203. package/templates/app/quality-config/codecov.yml +15 -3
  204. package/templates/app/quality-config/sonar-base.properties +4 -0
  205. package/templates/app/quality-config/sonar-project.properties +1 -4
  206. package/templates/app/quality-config-lite/sonar-project.properties +5 -0
  207. package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
  208. package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
  209. package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
  210. package/templates/app/skeleton/README.md +7 -5
  211. package/templates/app/skeleton-lite/.sops.yaml +3 -0
  212. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
  213. package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
  214. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
  215. package/templates/app/skeleton-lite/README.md +40 -0
  216. package/templates/app/skeleton-lite/supabase/config.toml +41 -0
  217. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
  218. package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
  219. package/templates/app/task-graph/turbo.json +23 -0
  220. package/templates/app/tool-config-lite/prettierignore +2 -0
  221. package/templates/app/tool-config-lite/prettierrc +1 -0
  222. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
  223. package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
  224. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
  225. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
  226. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
  227. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
  228. package/templates/be/common/main-listen.ts.partial +14 -0
  229. package/templates/be/common/sql-ident.ts.partial +7 -0
  230. package/templates/be/common/sql-tag.ts.partial +5 -0
  231. package/templates/be/image/api/Dockerfile +38 -0
  232. package/templates/be/image/cli/Dockerfile +38 -0
  233. package/templates/be/image/worker/Dockerfile +36 -0
  234. package/templates/be/inbox/database-client.ts.tpl +50 -0
  235. package/templates/be/inbox/index.ts.tpl +2 -0
  236. package/templates/be/inbox/migration.sql.tpl +29 -0
  237. package/templates/be/inbox/module-definition.ts.tpl +6 -0
  238. package/templates/be/inbox/module.ts.tpl +14 -0
  239. package/templates/be/inbox/options.ts.tpl +2 -0
  240. package/templates/be/inbox/service.ts.tpl +33 -0
  241. package/templates/be/inbox/sql.ts.tpl +16 -0
  242. package/templates/be/inbox/webhook-door.ts.tpl +36 -0
  243. package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
  244. package/templates/be/patterns/api/application.command.ts.tpl +10 -0
  245. package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
  246. package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
  247. package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
  248. package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
  249. package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
  250. package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
  251. package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
  252. package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
  253. package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
  254. package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
  255. package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
  256. package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
  257. package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
  258. package/templates/be/patterns/api/http.module.ts.tpl +7 -0
  259. package/templates/be/patterns/api/http.request.ts.tpl +10 -0
  260. package/templates/be/patterns/api/http.response.ts.tpl +5 -0
  261. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
  262. package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
  263. package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
  264. package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
  265. package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
  266. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
  267. package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
  268. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
  269. package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
  270. package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
  271. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
  272. package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
  273. package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
  274. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
  275. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
  276. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
  277. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
  278. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
  279. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
  280. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
  281. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
  282. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
  283. package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
  284. package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
  285. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
  286. package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
  287. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
  288. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
  289. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
  290. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
  291. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
  292. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
  293. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
  294. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
  295. package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
  296. package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
  297. package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
  298. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
  299. package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
  300. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
  301. package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
  302. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
  303. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
  304. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
  305. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
  306. package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
  307. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
  308. package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
  309. package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
  310. package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
  311. package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
  312. package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
  313. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
  314. package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
  315. package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
  316. package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
  317. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
  318. package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
  319. package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
  320. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
  321. package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
  322. package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
  323. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
  324. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
  325. package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
  326. package/templates/be/patterns/projections/migration.ts.tpl +18 -0
  327. package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
  328. package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
  329. package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
  330. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
  331. package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
  332. package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
  333. package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
  334. package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
  335. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
  336. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
  337. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
  338. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
  339. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
  340. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
  341. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
  342. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
  343. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
  344. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
  345. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
  346. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
  347. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
  348. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
  349. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
  350. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
  351. package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
  352. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
  353. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
  354. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
  355. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
  356. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
  357. package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
  358. package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
  359. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
  360. package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
  361. package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
  362. package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
  363. package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
  364. package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
  365. package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
  366. package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
  367. package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
  368. package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
  369. package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
  370. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
  371. package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
  372. package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
  373. package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
  374. package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
  375. package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
  376. package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
  377. package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
  378. package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
  379. package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
  380. package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
  381. package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
  382. package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
  383. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
  384. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
  385. package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
  386. package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
  387. package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
  388. package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
  389. package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
  390. package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
  391. package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
  392. package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
  393. package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
  394. package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
  395. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
  396. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
  397. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
  398. package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
  399. package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
  400. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
  401. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
  402. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
  403. package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
  404. package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
  405. package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
  406. package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
  407. package/templates/be/skeleton/src/features/cli/index.ts +1 -0
  408. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
  409. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
  410. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
  411. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
  412. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
  413. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
  414. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
  415. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
  416. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
  417. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
  418. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
  419. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
  420. package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
  421. package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
  422. package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
  423. package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
  424. package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
  425. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
  426. package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
  427. package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
  428. package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
  429. package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
  430. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
  431. package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
  432. package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
  433. package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
  434. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
  435. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
  436. package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
  437. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
  438. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
  439. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
  440. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
  441. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
  442. package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
  443. package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
  444. package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
  445. package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
  446. package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
  447. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
  448. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
  449. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
  450. package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
  451. package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
  452. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
  453. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
  454. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
  455. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
  456. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
  457. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
  458. package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
  459. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
  460. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
  461. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
  462. package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
  463. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
  464. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
  465. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
  466. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
  468. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
  469. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
  471. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
  472. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
  473. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
  474. package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
  475. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
  476. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
  477. package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
  478. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
  479. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
  480. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
  481. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
  482. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
  483. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
  484. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
  485. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
  486. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
  487. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
  488. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
  489. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
  490. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
  491. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
  492. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
  493. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
  494. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
  495. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
  496. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
  497. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
  498. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
  499. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
  500. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
  501. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
  502. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
  503. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
  504. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
  505. package/templates/be/table/error.ts.tpl +16 -0
  506. package/templates/be/table/index.ts.tpl +2 -0
  507. package/templates/be/table/migration.sql.tpl +51 -0
  508. package/templates/be/table/module-definition.ts.tpl +7 -0
  509. package/templates/be/table/module.ts.tpl +14 -0
  510. package/templates/be/table/options.ts.tpl +2 -0
  511. package/templates/be/table/rows.ts.tpl +4 -0
  512. package/templates/be/table/service.ts.tpl +23 -0
  513. package/templates/be/table/sql.ts.tpl +4 -0
  514. package/templates/be/tool-config/jest.config.js +3 -1
  515. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
  516. package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
  517. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
  518. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
  519. package/templates/common/gitignore.base +1 -2
  520. package/templates/fe/app/package.json.tpl +1 -0
  521. package/templates/fe/app/tsconfig.json +12 -0
  522. package/templates/fe/common/app-layout.tsx.partial +8 -0
  523. package/templates/fe/common/site-shell.tsx.partial +9 -0
  524. package/templates/fe/image/next/Dockerfile +42 -0
  525. package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
  526. package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
  527. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +2 -2
  528. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
  529. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
  530. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
  531. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
  532. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
  533. package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
  534. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
  535. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
  536. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
  537. package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
  538. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
  539. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
  540. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
  541. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  542. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
  543. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
  544. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
  545. package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
  546. package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
  547. package/templates/fe/skeleton/apps/{__app__ → landing}/next.config.ts +4 -2
  548. package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
  549. package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
  550. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
  551. package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
  552. package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
  553. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
  554. package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
  555. package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
  556. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
  557. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
  558. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
  559. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
  560. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
  561. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
  562. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
  563. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
  564. package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
  565. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
  566. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
  567. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
  568. package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
  569. package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
  570. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
  571. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
  572. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
  573. package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
  574. package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
  575. package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
  576. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
  577. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
  578. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
  579. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
  580. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
  581. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
  582. package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
  583. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
  584. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
  585. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
  586. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
  587. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
  588. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
  589. package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
  590. package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
  591. package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
  592. package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
  593. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
  594. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
  595. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
  596. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
  597. package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
  598. package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
  599. package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
  600. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
  601. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
  602. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
  603. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
  604. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
  605. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
  606. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
  607. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
  608. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
  609. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
  610. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  611. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
  612. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
  613. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
  614. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
  615. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
  616. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
  617. package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
  618. package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
  619. package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
  620. package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
  621. package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
  622. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
  623. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
  624. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
  625. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +16 -0
  626. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
  627. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
  628. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
  629. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
  630. package/templates/fe/table/read.ts.tpl +16 -0
  631. package/templates/fe/table/write.ts.tpl +16 -0
  632. package/upgrade/check.mjs +137 -0
  633. package/upgrade/index.mjs +524 -0
  634. package/bin/hfs.mjs +0 -233
  635. package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
  636. package/runtime/scripts/lib/test-secrets.mjs +0 -116
  637. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
  638. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
  639. package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
  640. package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
  641. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -19
  642. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -11
  643. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -24
  644. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -14
  645. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -21
  646. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
  647. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -23
  648. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
  649. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -28
  650. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
  651. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
  652. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
  653. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
  654. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/request.ts +0 -19
  655. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
  656. package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
  657. package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
  658. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
  659. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
  660. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
  661. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
  662. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
  663. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
  664. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
  665. /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
  666. /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
  667. /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
@@ -0,0 +1,2059 @@
1
+ # knowledge/hfs/rules.yaml - the machine-readable catalog of the HFS rules (the ids run R01 up; an id with no rule is listed under `retired` with its reason, never reused; count the rules, never type the count).
2
+ #
3
+ # One entry per rule: its id, its finding code, the law in one line, how a violation is fixed
4
+ # (`kinds`), the gates it runs at, the failure codes it reports, and every enforcer that judges it. The prose of each
5
+ # rule is knowledge/hfs/README.md (section 12 lists the same ids and codes); this file is the data behind it. Read it only
6
+ # through scripts/hfs/slots.mjs (loadRuleCatalog, rules); its shape is modules/schemas/hfs-rules.schema.yaml.
7
+ #
8
+ # An enforcer is a check that exists or one still owed. `status: planned` marks one owed: the architecture machine, the
9
+ # hfs CLI, stylelint and Sonar lanes fill them, and the entry's `status` is removed the day the check ships (the check
10
+ # refuses a planned enforcer that already exists). An existing eslint enforcer is a rule id of the plugin
11
+ # `@starci/eslint-canon-be` or `@starci/eslint-canon-fe`; an existing machine, hfs or work-validate enforcer names the file
12
+ # (`at`) that emits one of the rule's codes. scripts/checks/check-hfs-rules.mjs holds all of this to the packages, the
13
+ # scripts and modules/kernel/failure-codes.yaml.
14
+ #
15
+ # Every rule is an error from 2.0. There is no baseline, allowlist or suppression file for a product. The runtime gate
16
+ # (the StarCi runtime repository judged by knowledge/hfs/runtime-slots.yaml) has exactly one, the shrink-only sections of
17
+ # modules/kernel/allowlist.yaml, held by RT_ALLOWLIST_SPRAWL. Codes are quoted: the failure-code
18
+ # scanner reads this file as an emitter of every code it names.
19
+ schema: starci/hfs-rules@2
20
+ version: 2.0.0
21
+ gates:
22
+ pre-commit: the managed .husky/pre-commit at the app root - starci app hygiene (staged .starciwork and .starcistacks paths, secrets guard), typecheck, ESLint per side and stylelint on the staged files, prettier --check, the unit specs the staged files touch
23
+ pre-push: typecheck, lint (starci app lint), format:check and the unit specs affected since origin/main; runs every pre-commit rule again
24
+ settle: the op gate scripts/gates/gate.mjs over the op's changed files (merge guard, starci app lint --changed, codegen, tsc, the slice's specs; only findings new against the workflow's previous checkpoint block), re-read at starci kernel settle, which refuses a red done; a green settle commits the op's checkpoint on wf-<workflowId>
25
+ land: the workflow's finish, the only time main is touched - gate.mjs over the whole workflow branch against its merge-base with main, the merge guard, review.verify of the exact head that lands, the branch rebased onto main, then main fast-forwarded and pushed
26
+ ci: GitHub Actions runs the same pinned starci app lint, format:check, typecheck, unit with the per-file coverage threshold, build:be and build:fe, then the Sonar scan and gate
27
+ sonar: Sonar quality gate (knowledge/sonar-gate.yaml) - the imported lint findings, duplication, cognitive complexity and the coverage of the logic of be/src/modules at 100
28
+ runtime: "`starci runtime check` (npm run check) of the StarCi runtime repository itself: scripts/checks/check-runtime.mjs runs node --check, scripts/hfs/runtime-check.mjs with knowledge/hfs/runtime-slots.yaml, and the retained self-checks"
29
+ enforcerKinds:
30
+ eslint-be: a rule of the plugin @starci/eslint-canon-be (id without the plugin prefix)
31
+ eslint-fe: a rule of the plugin @starci/eslint-canon-fe (id without the plugin prefix)
32
+ stylelint: a rule of the @starci/stylelint-canon package
33
+ machine: a check of the architecture machine (scripts/hfs/architecture), which reads the import graph
34
+ hfs: a check of the hfs CLI that reads the repository tree, configuration, contracts or catalogs
35
+ work-validate: a check of scripts/work/validate/work-validate.mjs over a .starciwork tree
36
+ sonar: a condition of the Sonar quality gate
37
+ runtime: a rule module of the runtime check (scripts/hfs/runtime-check.mjs, scripts/hfs/runtime-rules/*) or an emitter whose findings it judges, over the runtime repository
38
+ rules:
39
+ - id: R01
40
+ code: "HFS_SLOT_UNDECLARED"
41
+ law: "Every tracked path matches exactly one slot."
42
+ kinds: [check, lint]
43
+ gates: [pre-push, settle, land, ci, runtime]
44
+ failureCodes: ["HFS_SLOT_UNDECLARED", "HFS_SLOT_AMBIGUOUS", "HFS_SLOT_NOT_ENABLED", "HFS_FORBIDDEN_PRESENT", "HFS_TRACKED_MUST_BE_IGNORED", "HFS_ROOT_ENTRY_FORBIDDEN", "HFS_ROOT_MARKDOWN_FORBIDDEN", "HFS_ROOT_SRC_FORBIDDEN_FE", "HFS_SRC_LAYOUT_INVALID", "HFS_MODULE_TIER_INVALID", "HFS_WORK_IN_FE", "HFS_STACKS_IN_SIDE"]
45
+ enforcers:
46
+ - {kind: hfs, id: slot-undeclared, at: scripts/hfs/check.mjs}
47
+ - {kind: eslint-be, id: slot-undeclared}
48
+ - {kind: eslint-fe, id: slot-undeclared}
49
+ - {kind: machine, id: repository-tree-layout, at: scripts/hfs/architecture/hfs.mjs}
50
+ - {kind: runtime, id: runtime-tree, at: scripts/hfs/runtime-check.mjs}
51
+ - id: R02
52
+ code: "HFS_SLOT_REQUIRED_MISSING"
53
+ law: "A slot is missing a required file (feature `index.ts`, app `main.ts`, FE `global-error.tsx`), or the root README breaks its standard shape (title, description, standard sections in order, Work pointer, live badges only, no private URL)."
54
+ kinds: [check, codemod]
55
+ gates: [settle, land, ci, runtime]
56
+ failureCodes: ["HFS_SLOT_REQUIRED_MISSING", "HFS_MIN_INSTANCES", "HFS_REQUIRED_FILE_MISSING", "HFS_ROOT_ENTRY_MISSING", "HFS_APPS_REQUIRED", "HFS_APP_LAYOUT_INVALID", "HFS_README_TITLE_INVALID", "HFS_README_DESCRIPTION_INVALID", "HFS_README_SECTION_MISSING", "HFS_README_SECTION_ORDER", "HFS_README_DEVELOPMENT_INCOMPLETE", "HFS_README_WORK_POINTER_MISSING", "HFS_README_PRIVATE_URL", "HFS_README_BADGE_NOT_LIVE"]
57
+ enforcers:
58
+ - {kind: hfs, id: slot-required-missing, at: scripts/hfs/check.mjs}
59
+ - {kind: machine, id: required-files, at: scripts/hfs/architecture/required-files.mjs}
60
+ - {kind: machine, id: repository-tree-required, at: scripts/hfs/architecture/hfs.mjs}
61
+ - {kind: runtime, id: runtime-required, at: scripts/hfs/runtime-check.mjs}
62
+ - id: R03
63
+ code: "HFS_UNTRACKED_ROOT_ENTRY"
64
+ law: "No untracked or ignored entry outside an `ignored` slot."
65
+ kinds: [check, codemod]
66
+ gates: [pre-push, land, ci, runtime]
67
+ failureCodes: ["HFS_UNTRACKED_ROOT_ENTRY", "HFS_EMPTY_DIR", "HFS_GHOST_TREE"]
68
+ enforcers:
69
+ - {kind: hfs, id: untracked-root-entry, at: scripts/hfs/check.mjs}
70
+ - {kind: runtime, id: runtime-untracked, at: scripts/hfs/runtime-check.mjs}
71
+ - id: R04
72
+ code: "HFS_GITIGNORE_BLOCK_DRIFT"
73
+ law: "The managed `.gitignore` block equals its render."
74
+ kinds: [check, codemod]
75
+ gates: [pre-push, land, ci]
76
+ failureCodes: ["HFS_GITIGNORE_BLOCK_DRIFT"]
77
+ enforcers:
78
+ - {kind: hfs, id: gitignore-block-drift, at: packages/hfs/sync/managed.mjs}
79
+ - id: R05
80
+ code: "HFS_MANAGED_FILE_DRIFT"
81
+ law: "A managed file equals its template render."
82
+ kinds: [check, codemod]
83
+ gates: [pre-push, land, ci]
84
+ failureCodes: ["HFS_MANAGED_FILE_DRIFT"]
85
+ enforcers:
86
+ - {kind: hfs, id: managed-file-drift, at: packages/hfs/sync/managed.mjs}
87
+ - id: R06
88
+ code: "HFS_PLAINTEXT_SECRET"
89
+ law: "Secrets exist only as `.starcistacks/<env>/secrets/<slug>.enc`."
90
+ kinds: [check, design]
91
+ gates: [pre-commit, pre-push, land, ci]
92
+ failureCodes: ["HFS_PLAINTEXT_SECRET"]
93
+ enforcers:
94
+ - {kind: hfs, id: plaintext-secret, at: scripts/hfs/rules/secrets.mjs}
95
+ - {kind: hfs, id: plaintext-secret-staged, at: packages/hfs/sync/hygiene.mjs}
96
+ - id: R07
97
+ code: "HFS_AGENT_DATA_TRACKED"
98
+ law: "`.starciwork` holds product records only."
99
+ kinds: [check, codemod]
100
+ gates: [pre-commit, pre-push, settle, land, ci]
101
+ failureCodes: ["HFS_AGENT_DATA_TRACKED"]
102
+ enforcers:
103
+ - {kind: work-validate, id: agent-data-tracked, at: scripts/work/validate/check-example-work.mjs}
104
+ - id: R08
105
+ code: "HFS_WORK_NODE_RETIRED"
106
+ law: "Only flat family records, never `work/node`."
107
+ kinds: [check, codemod]
108
+ gates: [settle, land, ci]
109
+ failureCodes: ["HFS_WORK_NODE_RETIRED"]
110
+ enforcers:
111
+ - {kind: work-validate, id: work-node-retired, at: scripts/work/validate/check-example-work.mjs}
112
+ - id: R09
113
+ code: "HFS_IDENTITY_CUSTODY"
114
+ law: "An identity points its secret at `secrets/identity-<slug>.enc`; UAT chooses by role."
115
+ kinds: [check, codemod]
116
+ gates: [settle, land, ci]
117
+ failureCodes: ["HFS_IDENTITY_CUSTODY", "SEALED_CUSTODY_LOCATION", "SEALED_FILE_IN_WORK"]
118
+ enforcers:
119
+ - {kind: work-validate, id: sealed-custody-location, at: scripts/work/validate/check-work-artifacts.mjs}
120
+ - {kind: work-validate, id: identity-custody-role, at: scripts/work/validate/check-example-work.mjs}
121
+ - id: R10
122
+ code: "HFS_STACKS_SHAPE"
123
+ law: "`.starcistacks` has the standard shape and the host Sonar owner."
124
+ kinds: [check, codemod]
125
+ gates: [settle, land, ci]
126
+ failureCodes: ["HFS_STACKS_SHAPE"]
127
+ enforcers:
128
+ - {kind: hfs, id: stacks-shape, at: scripts/hfs/rules/stacks.mjs}
129
+ - id: R11
130
+ code: "HFS_SONAR_CONFIG"
131
+ law: "Sonar config is generated, with no host URL, importing the be lcov with the logic of be/src/modules (the coverage scope derived from the slot manifest) as the only coverage scope."
132
+ kinds: [check, codemod]
133
+ gates: [land, ci]
134
+ failureCodes: ["HFS_SONAR_CONFIG"]
135
+ enforcers:
136
+ - {kind: hfs, id: sonar-config, at: packages/hfs/sync/managed.mjs}
137
+ - id: R12
138
+ code: "HFS_E2E_IN_AUTOMATIC_GATE"
139
+ law: "e2e never joins husky, coverage or automatic CI."
140
+ kinds: [check]
141
+ gates: [pre-push, land, ci]
142
+ failureCodes: ["HFS_E2E_IN_AUTOMATIC_GATE"]
143
+ enforcers:
144
+ - {kind: machine, id: e2e-in-automatic-gate, at: scripts/hfs/architecture/hfs.mjs}
145
+ - id: R13
146
+ code: "HFS_CI_MISSING_CANON"
147
+ law: "CI runs the pinned `starci app lint` (`npm run lint`), and a clone never redirects `core.hooksPath` away from husky."
148
+ kinds: [check, codemod]
149
+ gates: [pre-push, land, ci]
150
+ failureCodes: ["HFS_CI_MISSING_CANON", "HFS_HOOKS_PATH_REDIRECTED"]
151
+ enforcers:
152
+ - {kind: machine, id: hooks-path-not-redirected, at: scripts/hfs/architecture/hfs.mjs}
153
+ - {kind: hfs, id: ci-missing-canon, at: scripts/hfs/rules/pipeline.mjs}
154
+ - id: R14
155
+ code: "HFS_DEP_VERSION_SKEW"
156
+ law: "One version per dependency in the workspace, a root `overrides` pin included, and npm is the only package manager."
157
+ kinds: [check, codemod]
158
+ gates: [pre-push, land, ci]
159
+ failureCodes: ["HFS_DEP_VERSION_SKEW", "HFS_PACKAGE_MANAGER_MIXED"]
160
+ enforcers:
161
+ - {kind: hfs, id: dep-version-skew, at: scripts/hfs/rules/deps.mjs}
162
+ - {kind: machine, id: package-manager, at: scripts/hfs/architecture/hfs.mjs}
163
+ - id: R15
164
+ code: "HFS_CANON_PIN_DRIFT"
165
+ law: "Canon packages and frameworks match `canon-pins.yaml`."
166
+ kinds: [check, codemod]
167
+ gates: [pre-push, land, ci]
168
+ failureCodes: ["HFS_CANON_PIN_DRIFT"]
169
+ enforcers:
170
+ - {kind: hfs, id: canon-pin-drift, at: scripts/hfs/check.mjs}
171
+ - id: R16
172
+ code: "HFS_TOOL_CONFIG_LOCAL"
173
+ law: "Configs only call the factories; no local rules."
174
+ kinds: [check, codemod]
175
+ gates: [land, ci]
176
+ failureCodes: ["HFS_TOOL_CONFIG_LOCAL"]
177
+ enforcers:
178
+ - {kind: hfs, id: tool-config-local, at: packages/hfs/sync/managed.mjs}
179
+ - {kind: hfs, id: tool-config-forbidden, at: scripts/hfs/check.mjs}
180
+ - id: R17
181
+ code: "HFS_RULE_OFF_WITHOUT_REPLACEMENT"
182
+ law: "The ESLint configuration (and a front end's stylelint configuration) is the rendered one-liner, so no rule is off, warned or redefined in a repository."
183
+ kinds: [check]
184
+ gates: [land, ci]
185
+ failureCodes: ["HFS_RULE_OFF_WITHOUT_REPLACEMENT"]
186
+ enforcers:
187
+ - {kind: hfs, id: rule-off-without-replacement, at: packages/hfs/sync/managed.mjs}
188
+ - id: R18
189
+ code: "HFS_INLINE_SUPPRESSION"
190
+ law: "No inline suppression of any kind."
191
+ kinds: [lint, design]
192
+ gates: [pre-commit, pre-push, land, ci]
193
+ failureCodes: ["HFS_INLINE_SUPPRESSION"]
194
+ enforcers:
195
+ - {kind: eslint-be, id: no-inline-suppression}
196
+ - {kind: eslint-fe, id: no-inline-lint-config}
197
+ - {kind: stylelint, id: no-inline-lint-config}
198
+ - id: R19
199
+ code: "HFS_FORMAT"
200
+ law: "Prettier is the only formatter."
201
+ kinds: [codemod]
202
+ gates: [pre-commit, pre-push, land, ci]
203
+ failureCodes: ["HFS_FORMAT"]
204
+ enforcers:
205
+ - {kind: hfs, id: format, at: packages/hfs/sync/format.mjs}
206
+ - id: R20
207
+ code: "HFS_SIZE_GROWTH"
208
+ law: "Over-budget files do not grow; new files are inside budget."
209
+ kinds: [lint, design]
210
+ gates: [land, ci, sonar, runtime]
211
+ failureCodes: ["HFS_SIZE_GROWTH", "HFS_SIZE_SOFT_BACKLOG"]
212
+ enforcers:
213
+ - {kind: eslint-be, id: file-size-growth}
214
+ - {kind: eslint-fe, id: file-size-growth}
215
+ - {kind: sonar, id: cognitive-complexity, at: knowledge/sonar-gate.yaml}
216
+ - {kind: runtime, id: runtime-size, at: scripts/hfs/runtime-rules/size.mjs}
217
+ - id: R21
218
+ code: "HFS_DUPLICATE_CODE"
219
+ law: "No duplicated blocks or twice-defined helpers."
220
+ kinds: [lint, design]
221
+ gates: [land, ci, sonar]
222
+ failureCodes: ["HFS_DUPLICATE_CODE", "HFS_DUPLICATE_SYMBOL", "FE_CROSS_APP_DUPLICATE"]
223
+ enforcers:
224
+ - {kind: eslint-be, id: duplicate-code}
225
+ - {kind: eslint-fe, id: duplicate-code}
226
+ - {kind: eslint-be, id: duplicate-symbol}
227
+ - {kind: eslint-fe, id: duplicate-symbol}
228
+ - {kind: eslint-fe, id: cross-app-duplicate}
229
+ - {kind: sonar, id: duplicated-lines-density, at: knowledge/sonar-gate.yaml}
230
+ - id: R22
231
+ code: "HFS_TS_STRICT"
232
+ law: "tsconfig extends `@starci/tsconfig` and lowers no flag; no assertion, `!` or `any` in product source."
233
+ kinds: [codemod, design, lint]
234
+ gates: [pre-commit, pre-push, settle, land, ci]
235
+ failureCodes: ["HFS_TS_STRICT", "FE_TYPE_ESCAPE"]
236
+ enforcers:
237
+ - {kind: eslint-fe, id: no-type-assertion}
238
+ - {kind: eslint-fe, id: no-non-null-assertion}
239
+ - {kind: eslint-fe, id: no-explicit-any}
240
+ - {kind: hfs, id: ts-strict, at: packages/hfs/sync/ts-strict.mjs}
241
+ - {kind: eslint-fe, id: no-double-cast}
242
+ - id: R23
243
+ code: "HFS_CONTRACT_SNAPSHOT_DRIFT"
244
+ law: "The be side commits its contract snapshot and it equals the emit; the fe side reads it in place (be/contracts/)."
245
+ kinds: [check]
246
+ gates: [land, ci]
247
+ failureCodes: ["HFS_CONTRACT_SNAPSHOT_DRIFT"]
248
+ enforcers:
249
+ - {kind: hfs, id: contract-snapshot-drift, at: scripts/hfs/rules/contract.mjs}
250
+ - id: R24
251
+ code: "HFS_ARCH_CONFIG_UNREAD"
252
+ law: "The machine reads `hfs.json`; zero files analysed is red."
253
+ kinds: [check]
254
+ gates: [land, ci]
255
+ failureCodes: ["HFS_ARCH_CONFIG_UNREAD"]
256
+ enforcers:
257
+ - {kind: machine, id: arch-config-unread, at: scripts/hfs/architecture/config-unread.mjs}
258
+ - id: R25
259
+ code: "HFS_UNUSED_EXPORT"
260
+ law: "No public export without a consumer; no source file nothing reaches."
261
+ kinds: [lint, codemod]
262
+ gates: [pre-push, settle, land, ci]
263
+ failureCodes: ["HFS_UNUSED_EXPORT", "HFS_UNUSED_FILE"]
264
+ enforcers:
265
+ - {kind: eslint-be, id: dead-exports}
266
+ - {kind: eslint-fe, id: dead-exports}
267
+ - id: R26
268
+ code: "BE_TIER_DIRECTION"
269
+ law: "The tier direction matrix; a shared package never imports an app."
270
+ kinds: [lint, design]
271
+ gates: [pre-push, settle, land, ci]
272
+ failureCodes: ["BE_TIER_DIRECTION", "FE_TIER_DIRECTION", "ARCH_PACKAGE_IMPORTS_APP"]
273
+ enforcers:
274
+ - {kind: eslint-be, id: tier-direction}
275
+ - {kind: eslint-fe, id: tier-direction}
276
+ - {kind: eslint-be, id: package-imports-app}
277
+ - {kind: eslint-fe, id: package-imports-app}
278
+ - id: R27
279
+ code: "ARCH_OWNER_CYCLE"
280
+ law: "No owner cycles, type-only included."
281
+ kinds: [lint, design]
282
+ gates: [pre-push, settle, land, ci, runtime]
283
+ failureCodes: ["ARCH_OWNER_CYCLE"]
284
+ enforcers:
285
+ - {kind: eslint-be, id: owner-cycle}
286
+ - {kind: eslint-fe, id: owner-cycle}
287
+ - {kind: runtime, id: runtime-cycles, at: scripts/hfs/runtime-rules/tier-direction.mjs}
288
+ - id: R28
289
+ code: "BE_FEATURE_IMPORTS_FEATURE"
290
+ law: "A feature never imports a feature."
291
+ kinds: [lint, design]
292
+ gates: [settle, land, ci]
293
+ failureCodes: ["BE_FEATURE_IMPORTS_FEATURE"]
294
+ enforcers:
295
+ - {kind: eslint-be, id: feature-imports-feature}
296
+ - id: R29
297
+ code: "BE_FEATURE_SHAPE"
298
+ law: "Feature root is `index.ts`, module, `application/`, `transport/<protocol>/`; `application/` never imports `transport/` or a protocol framework."
299
+ kinds: [check, codemod, design, lint]
300
+ gates: [settle, land, ci]
301
+ failureCodes: ["BE_FEATURE_SHAPE", "BE_FEATURE_LAYOUT_INVALID", "BE_APPLICATION_IMPORTS_TRANSPORT", "BE_APPLICATION_TRANSPORT_FRAMEWORK"]
302
+ enforcers:
303
+ - {kind: machine, id: feature-shape, at: scripts/hfs/architecture/feature-shape.mjs}
304
+ - {kind: eslint-be, id: feature-layout}
305
+ - {kind: eslint-be, id: application-never-imports-transport}
306
+ - id: R30
307
+ code: "BE_PUBLIC_SURFACE"
308
+ law: "Every owner has one `index.ts`; cross-owner imports use it, same-owner imports are relative and never go through it; it holds only named `export { }` lines, no `export *`, no alias re-export (`export { X as Y }`, `export const Y = X`, `export type Y = X`, `export interface Y extends X {}`: one declaration has one name), at most 60 exports; cross-package imports use the package name and a declared export."
309
+ kinds: [lint, codemod]
310
+ gates: [pre-push, settle, land, ci]
311
+ failureCodes: ["BE_PUBLIC_SURFACE", "ARCH_OWNER_EXPORT_BYPASS", "ARCH_OWNER_EXPORT_STAR", "HFS_ALIAS_REEXPORT", "ARCH_PACKAGE_EXPORT_BYPASS"]
312
+ enforcers:
313
+ - {kind: eslint-be, id: import-owner-entry}
314
+ - {kind: eslint-be, id: no-folder-reexport}
315
+ - {kind: eslint-be, id: owner-export-bypass}
316
+ - {kind: eslint-fe, id: owner-export-bypass}
317
+ - {kind: eslint-be, id: alias-reexport}
318
+ - {kind: eslint-fe, id: alias-reexport}
319
+ - {kind: eslint-be, id: no-relative-capability-escape}
320
+ - {kind: eslint-be, id: no-self-module-alias}
321
+ - {kind: eslint-be, id: package-export-bypass}
322
+ - {kind: eslint-fe, id: package-export-bypass}
323
+ - id: R31
324
+ code: "BE_FEATURE_NOT_COMPOSED"
325
+ law: "Every feature, transport module and capability module is composed by an app."
326
+ kinds: [lint, design]
327
+ gates: [land, ci]
328
+ failureCodes: ["BE_FEATURE_NOT_COMPOSED"]
329
+ enforcers:
330
+ - {kind: eslint-be, id: feature-not-composed}
331
+ - id: R32
332
+ code: "BE_APP_COMPOSITION_ONLY"
333
+ law: "Apps compose only; an app is proven by the e2e world (`useTestWorld({ apps })`), never by a unit spec."
334
+ kinds: [lint, codemod]
335
+ gates: [settle, land, ci]
336
+ failureCodes: ["BE_APP_COMPOSITION_ONLY", "BE_APP_BUSINESS_ROLE"]
337
+ enforcers:
338
+ - {kind: eslint-be, id: app-composition-only}
339
+ - id: R33
340
+ code: "BE_ENTRYPOINT_ONLY_IN_APPS"
341
+ law: "Entrypoints only in `be/apps/*/src`."
342
+ kinds: [lint, codemod]
343
+ gates: [land, ci]
344
+ failureCodes: ["BE_ENTRYPOINT_ONLY_IN_APPS"]
345
+ enforcers:
346
+ - {kind: eslint-be, id: entrypoint-only-in-apps}
347
+ - id: R34
348
+ code: "BE_SCHEMA_AUTHORITY"
349
+ law: "Migrations are the only schema authority, run only by the cli migrate command and the test world; `synchronize` is `false`."
350
+ kinds: [lint, codemod, design]
351
+ gates: [pre-commit, pre-push, settle, land, ci]
352
+ failureCodes: ["BE_SCHEMA_AUTHORITY"]
353
+ enforcers:
354
+ - {kind: eslint-be, id: no-runtime-schema}
355
+ - id: R35
356
+ code: "BE_SCHEMA_OWNER"
357
+ law: "Entities and migrations live in the owning capability's `persistence/`; its `<c>Entities` and `<c>Migrations` are registered under exactly one declared connection, which every `Inject<Conn>EntityManager` of the capability names (no `CONNECTION` alias); only a platform capability the manifest lists as `perConnection` (the event-bus outbox, the job table) registers on several."
358
+ kinds: [lint, codemod, design]
359
+ gates: [settle, land, ci]
360
+ failureCodes: ["BE_SCHEMA_OWNER"]
361
+ enforcers:
362
+ - {kind: eslint-be, id: schema-owner}
363
+ - id: R36
364
+ code: "BE_SQL_OUTSIDE_PERSISTENCE"
365
+ law: "Raw SQL is `sql`-tagged `SqlText` in `persistence/<name>.sql.ts` of the owning capability; `.query()` takes only `SqlText`; no QueryBuilder."
366
+ kinds: [lint, design]
367
+ gates: [settle, land, ci]
368
+ failureCodes: ["BE_SQL_OUTSIDE_PERSISTENCE"]
369
+ enforcers:
370
+ - {kind: eslint-be, id: sql-text-only}
371
+ - {kind: eslint-be, id: no-query-builder}
372
+ - id: R37
373
+ code: "BE_ENTITY_IN_CONTRACT"
374
+ law: "No ORM entity in a contract or transport type."
375
+ kinds: [lint, design]
376
+ gates: [settle, land, ci]
377
+ failureCodes: ["BE_ENTITY_IN_CONTRACT"]
378
+ enforcers:
379
+ - {kind: eslint-be, id: no-entity-in-contract}
380
+ - id: R38
381
+ code: "BE_ERROR_HOME"
382
+ law: "Errors live in the owning capability's `errors/` and extend `DomainError`."
383
+ kinds: [lint, codemod]
384
+ gates: [settle, land, ci]
385
+ failureCodes: ["BE_ERROR_HOME"]
386
+ enforcers:
387
+ - {kind: eslint-be, id: error-home}
388
+ - {kind: eslint-be, id: throw-domain-error}
389
+ - {kind: eslint-be, id: error-family-shape}
390
+ - {kind: eslint-be, id: error-code-unique}
391
+ - id: R39
392
+ code: "BE_ERROR_MASKED"
393
+ law: "One filter per app; undeclared errors are masked."
394
+ kinds: [lint, codemod]
395
+ gates: [land, ci]
396
+ failureCodes: ["BE_ERROR_MASKED"]
397
+ enforcers:
398
+ - {kind: eslint-be, id: error-masked}
399
+ - id: R40
400
+ code: "BE_LOGGER_REQUIRED"
401
+ law: "`platform/logging` exists and its `Logger` port is the only logger; every `catch` logs, rethrows or returns a reasoned outcome, and a log call names its event with a member of an owner's `<owner>.log-events.ts` enum."
402
+ kinds: [lint, design]
403
+ gates: [pre-commit, pre-push, settle, land, ci]
404
+ failureCodes: ["BE_LOGGER_REQUIRED"]
405
+ enforcers:
406
+ - {kind: eslint-be, id: catch-must-account}
407
+ - {kind: eslint-be, id: no-error-wording-as-log-identity}
408
+ - {kind: eslint-be, id: no-interpolated-log-message}
409
+ - {kind: eslint-be, id: no-framework-logger}
410
+ - id: R41
411
+ code: "BE_DEFAULT_DENY"
412
+ law: "APP_GUARD plus `@Public({ reason: PublicReason.X })`; no `@UseGuards`; typed bodies; `timingSafeEqual`."
413
+ kinds: [lint, codemod]
414
+ gates: [pre-commit, pre-push, settle, land, ci]
415
+ failureCodes: ["BE_DEFAULT_DENY"]
416
+ enforcers:
417
+ - {kind: eslint-be, id: no-untyped-body}
418
+ - {kind: eslint-be, id: public-needs-reason}
419
+ - {kind: eslint-be, id: secret-compare-timing-safe}
420
+ - {kind: eslint-be, id: no-auth-use-guards}
421
+ - {kind: eslint-be, id: default-deny-app-guard}
422
+ - id: R42
423
+ code: "BE_INPUT_BOUNDED"
424
+ law: "Bounded input, cursor-only pagination, depth limits, rate limits; every property of an input class carries the `class-validator` decorators its type calls for; a door opened for `PublicReason.AuthHandshake` or `PublicReason.SignedWebhook` carries `RateTier.Strict` of `platform/http-security`."
425
+ kinds: [lint, design]
426
+ gates: [settle, land, ci]
427
+ failureCodes: ["BE_INPUT_BOUNDED"]
428
+ enforcers:
429
+ - {kind: eslint-be, id: input-bounded}
430
+ - {kind: eslint-be, id: no-offset-pagination}
431
+ - {kind: eslint-be, id: auth-door-strict-rate-tier}
432
+ - id: R43
433
+ code: "BE_CONFIG_OWNER"
434
+ law: "Only `platform/config` reads `process.env`; config per capability."
435
+ kinds: [lint, design]
436
+ gates: [pre-commit, pre-push, settle, land, ci]
437
+ failureCodes: ["BE_CONFIG_OWNER"]
438
+ enforcers:
439
+ - {kind: eslint-be, id: no-direct-env-read}
440
+ - {kind: eslint-be, id: config-parsed-in-main}
441
+ - id: R44
442
+ code: "BE_SECRET_DEFAULT"
443
+ law: "No default for a secret key or infrastructure URL."
444
+ kinds: [lint, codemod]
445
+ gates: [pre-commit, pre-push, land, ci]
446
+ failureCodes: ["BE_SECRET_DEFAULT"]
447
+ enforcers:
448
+ - {kind: eslint-be, id: no-secret-default}
449
+ - id: R45
450
+ code: "BE_MODULE_SHAPE"
451
+ law: "`@Global` nowhere and `isGlobal: true` only at app roots; no cross-owner module imports; typed options; one module per transport; every handler and provider registered exactly once."
452
+ kinds: [lint, codemod]
453
+ gates: [pre-commit, pre-push, settle, land, ci]
454
+ failureCodes: ["BE_MODULE_SHAPE", "BE_MODULE_HANDLER_REGISTRATION", "BE_MODULE_PROVIDER_REREGISTRATION"]
455
+ enforcers:
456
+ - {kind: eslint-be, id: no-global-decorator}
457
+ - {kind: eslint-be, id: is-global-only-in-app}
458
+ - {kind: eslint-be, id: no-cross-owner-module-import}
459
+ - {kind: eslint-be, id: capability-module-shape}
460
+ - {kind: eslint-be, id: typed-module-definition}
461
+ - {kind: eslint-be, id: static-module-register}
462
+ - {kind: eslint-be, id: no-new-injectable}
463
+ - {kind: eslint-be, id: no-module-let}
464
+ - {kind: eslint-be, id: one-module-per-file}
465
+ - {kind: eslint-be, id: register-once}
466
+ - {kind: eslint-be, id: module-per-transport}
467
+ - {kind: eslint-be, id: module-registration}
468
+ - id: R46
469
+ code: "BE_BACKGROUND_UNOWNED"
470
+ law: "Every sweep, outbox or retry has a job or consumer composed by an app: the service that owns it (an api app) or a worker app scaled apart."
471
+ kinds: [lint, design]
472
+ gates: [land, ci]
473
+ failureCodes: ["BE_BACKGROUND_UNOWNED"]
474
+ editions: [full]
475
+ enforcers:
476
+ - {kind: eslint-be, id: background-unowned}
477
+ - id: R47
478
+ code: "BE_TEST_TOPOLOGY"
479
+ law: "One jest config, projects `unit`, `integration`, `e2e` and `contract`, live by folder, `diagnostics: false`; unit specs are `<name>.<role>.spec.ts` beside the file they test: required for the roles of `ruleParams.be.unitRoles` (`<name>.service.spec.ts` beside a `<name>.service.ts`, `<name>.cli.spec.ts` beside a cli command `<name>.cli.ts` of `src/features/cli`), optional for any other logic role (`ruleParams.be.logicRoles`) inside a slot whose `coverage` is required, and nowhere else but beside a webhook or realtime door that has no service of its own (`<provider>.webhook.spec.ts`, `<channel>.gateway.spec.ts`, `<x>.subscription.spec.ts`) (every unit-tested subject has exactly one; any other `*.spec.ts` outside `be/src/tests/{world,integration,e2e,contract}` is a finding, including specs in apps), and the unit project collects coverage from the logic roles of the measured roots only (the scope derived from the slot manifest, R204) with a per-file threshold of 100 on lines, branches, functions and statements (`test` runs `--coverage`; `importHelpers` and `tslib`); `be/src/tests/world/` is the ONLY test infrastructure: `global-setup.ts` starts the app's own stack (`.starcistacks/<env>`, named by `stack` in `test-world.config.ts`, the declaration of the test-world library in its one named form `export const { useTestWorld, useSandbox } = defineTestWorld({...})` (a default export is not read): every service it declares runs REAL, behind toxiproxy, images and versions read from the stack at run time, never spelled in test source) or attaches to a warm one that answers, and runs the migrations once over the cli app's connections (the runner of the cli migrate command); `global-teardown.ts` stops only what its setup started; `use-test-world.ts` exports `useTestWorld({ apps } | { modules })` (`world.apps.<name>.api`, `world.db.<connection>`, `world.infra.<service>` with `latency(ms)`, `cut()` and `restore()` on the real service, `world.fake.<provider>`, `world.waitFor`) and `fakes/<provider>/` holds the network-edge fakes (`failNext`, `replayWebhook`, `delay`, payload fixtures) of external SaaS the team does not operate ONLY: a fake of a service the stack declares is refused, except stateless compute that needs special hardware or an external model (GPU inference, a self-hosted embedding model), whose `stacks` entry in `test-world.config.ts` declares `{ fakedBy: <fake>, reason }` with a non-empty `reason` (a stateful service, or one with a persistent volume, is never accepted); it alone owns containers, DataSource, migrations and `process.env`; nothing under `be/src/tests` overrides a provider or DI token; `be/src/tests/integration/<capability>/*.integration-spec.ts` (`{ modules }`, with the real peer apps its client calls beside them as `apps`; real infrastructure, no HTTP door of ours), `be/src/tests/e2e/<area>/*.e2e-spec.ts` (`{ apps }`) and `be/src/tests/contract/<provider>/*.contract-spec.ts` (provider sandboxes, skipped without sandbox config, run only by `test:contract`, never by `test` or `test:e2e`) agree folder with suffix; an integration or e2e spec passes `useTestWorld` one object literal with plain keys (no variable, spread or computed key) and calls it; the contract layer skips only through the test-world library's `useSandbox(...)` when the sandbox config is absent (`sandbox.describe(...)`), and every `fakes/<provider>/` that serves payload fixtures (`payloads/*.json`) has a `contract/<provider>/*.contract-spec.ts` that references those fixtures and asserts `shapeOf(real)` equals `shapeOf(fixture)` (the helper `shapeOf` is named by `ruleParams.be.contractShape` of the slot manifest; fakes the test-world library provides are guarded by the library); no `.test.ts`, `int-spec` or `harness-spec`, no `live/` and no `be/src/tests/e2e/world`."
480
+ kinds: [check, codemod, lint]
481
+ gates: [land, ci]
482
+ failureCodes: ["BE_TEST_TOPOLOGY", "BE_CONTRACT_UNGUARDED"]
483
+ editions: [full]
484
+ enforcers:
485
+ - {kind: hfs, id: test-topology, at: scripts/hfs/rules/test-topology.mjs}
486
+ - {kind: machine, id: test-kind-retired, at: scripts/hfs/architecture/hfs.mjs}
487
+ - {kind: eslint-be, id: test-world-files}
488
+ - {kind: machine, id: contract-fixture-guard, at: scripts/hfs/architecture/contract-fixture-guard.mjs}
489
+ - {kind: eslint-be, id: unit-test-colocated}
490
+ - {kind: eslint-be, id: tests-infra-only-in-world}
491
+ - {kind: eslint-be, id: tests-no-override}
492
+ - {kind: eslint-be, id: test-world-shape}
493
+ - id: R48
494
+ code: "BE_SPEC_QUALITY"
495
+ law: "No source-reading specs; a spec asserts results or state, not only calls; no `as` and no `x!` in a spec (the borrowed rules of R72), no return-only generic (`<T>(value: unknown): T`), `unknown` handed back as a concrete type, `JSON.parse(JSON.stringify(x))` or `Object.assign(new X(), y)` returned as another type in a spec or a test fixture, and no `Date.now()`, argless `new Date()` or `process.env` (R79, R43); a unit spec (`<name>.service.spec.ts`) builds its subject with `Test.createTestingModule({ providers: [...] }).compile()` and `moduleRef.get(Subject)`: no `new` of the service, no `imports` key, no `override*` call, and no `jest.mock`, `jest.doMock`, `jest.unstable_mockModule` or `jest.requireMock` of an own module or a third-party library; it provides each infrastructure token from its `@starci/jest-preset` double (`mockEntityManager(...)` for an EntityManager token, `new FakeClock(...)`, `recordingEventBus()`, `recordingQueueOutbox()`, `fakeCache(...)`, `fakeLock(...)`, `fakeIds()`, `builder(...)(...)` or a plain literal of real values for an options token, `mock<T>()` for everything else; the token table is `ruleParams.be.specDoubles` of the slot manifest) and takes its EntityManager only from the kit's `mockEntityManager()` or `fakeTransaction()`, never an ad-hoc `jest.fn` object, and asserts exact values: ids from `fakeIds()` and dates from `FakeClock` are compared as themselves, never `expect.any(String)`, `expect.any(Number)` or `expect.any(Date)`; an e2e enters through transport, waits with `waitFor`, boots through `useTestWorld`, reads persisted state back and reaches no model provider. Test data is arranged by builders, which R98 to R101 hold. No spec of any layer skips, focuses or marks a test todo (`skip`, `skipIf`, `runIf`, `todo`, `only`, `xit`, `xdescribe`, `xtest`) and none selects its runner conditionally, and no file of the test tree (world, fixtures, kit) writes one either; the only skip is the test-world library's `useSandbox(...)` (`@starci/test-world`), which skips a contract spec when the sandbox config is absent."
496
+ kinds: [lint, design]
497
+ gates: [settle, land, ci]
498
+ failureCodes: ["BE_SPEC_QUALITY"]
499
+ editions: [full]
500
+ enforcers:
501
+ - {kind: eslint-be, id: spec-no-source-read}
502
+ - {kind: eslint-be, id: e2e-uses-production-transport}
503
+ - {kind: eslint-be, id: no-branch-in-flow-step}
504
+ - {kind: eslint-be, id: no-sleep-in-flow}
505
+ - {kind: eslint-be, id: no-wiring-in-flow-spec}
506
+ - {kind: eslint-be, id: e2e-asserts-persisted-state}
507
+ - {kind: eslint-be, id: no-api-shaped-e2e-filename}
508
+ - {kind: eslint-be, id: no-call-only-spec}
509
+ - {kind: eslint-be, id: no-marker-model-stub}
510
+ - {kind: eslint-be, id: no-model-call-in-e2e}
511
+ - {kind: eslint-be, id: spec-typed-entity-manager}
512
+ - {kind: eslint-be, id: spec-builds-with-testing-module}
513
+ - {kind: eslint-be, id: spec-no-new-subject}
514
+ - {kind: eslint-be, id: spec-module-definition-only-providers}
515
+ - {kind: eslint-be, id: spec-no-module-mock}
516
+ - {kind: eslint-be, id: no-return-only-generic}
517
+ - {kind: eslint-be, id: spec-infra-double-from-kit}
518
+ - {kind: eslint-be, id: spec-exact-values}
519
+ - {kind: eslint-be, id: spec-no-skip}
520
+ - {kind: eslint-be, id: unit-spec-providers}
521
+ - id: R49
522
+ code: "FE_ENV_OWNER"
523
+ law: "Only `modules/config` reads the environment; no localhost fallback."
524
+ kinds: [lint, codemod]
525
+ gates: [pre-commit, pre-push, settle, land, ci]
526
+ failureCodes: ["FE_ENV_OWNER"]
527
+ enforcers:
528
+ - {kind: eslint-fe, id: no-env-outside-config}
529
+ - {kind: eslint-fe, id: no-hardcoded-endpoint-fallback}
530
+ - id: R50
531
+ code: "FE_TRANSPORT_OWNER"
532
+ law: "One `fetch` per front end, in the transport client: `fe/apps/<app>/src/modules/api/client.ts` of a one-app front end or `fe/packages/<family>-api/src/client.ts` shared by every app; timeout and abort; a client read is SWR whose key carries the identity of its result, and a mutation is tied to its resource."
533
+ kinds: [lint, design]
534
+ gates: [settle, land, ci]
535
+ failureCodes: ["FE_TRANSPORT_OWNER", "FE_EFFECT_FETCH", "FE_SWR_KEY_IDENTITY", "FE_SWR_MUTATION_RESOURCE_IDENTITY"]
536
+ enforcers:
537
+ - {kind: eslint-fe, id: fetch-only-in-api-client}
538
+ - {kind: eslint-fe, id: client-fetch-has-signal}
539
+ - {kind: eslint-fe, id: no-shared-transport-state}
540
+ - {kind: eslint-fe, id: no-data-fetch-in-effect}
541
+ - {kind: eslint-fe, id: transport-owner}
542
+ - {kind: eslint-fe, id: api-client-attaches-the-locale}
543
+ - {kind: eslint-fe, id: locale-header-belongs-to-the-link}
544
+ - {kind: eslint-fe, id: swr-data-lifecycle}
545
+ - id: R51
546
+ code: "FE_HTTP_STATUS_COLLAPSE"
547
+ law: "One `Outcome<T>` union per front end (the api slot's `outcome.ts`); 401 and 403 become `refused`."
548
+ kinds: [lint, design]
549
+ gates: [settle, land, ci]
550
+ failureCodes: ["FE_HTTP_STATUS_COLLAPSE", "FE_OUTCOME_KIND_UNHANDLED"]
551
+ enforcers:
552
+ - {kind: eslint-fe, id: client-maps-auth-to-refused}
553
+ - {kind: eslint-fe, id: no-failure-collapse}
554
+ - {kind: eslint-fe, id: outcome-kinds-exhaustive}
555
+ - {kind: eslint-fe, id: one-outcome-union}
556
+ - id: R52
557
+ code: "FE_WIRE_GENERATED"
558
+ law: "Wire types generated from the be contract snapshots the fe side reads."
559
+ kinds: [lint, check, design]
560
+ gates: [settle, land, ci]
561
+ failureCodes: ["FE_WIRE_GENERATED"]
562
+ enforcers:
563
+ - {kind: eslint-fe, id: no-hand-typed-wire}
564
+ - {kind: hfs, id: wire-generated, at: scripts/hfs/rules/frontend-tree.mjs}
565
+ - id: R53
566
+ code: "FE_ERROR_BOUNDARY_MISSING"
567
+ law: "Global, locale error, not-found and loading boundaries exist."
568
+ kinds: [check, codemod, lint]
569
+ gates: [settle, land, ci]
570
+ failureCodes: ["FE_ERROR_BOUNDARY_MISSING", "FE_SUSPENSE_NULL_FALLBACK"]
571
+ enforcers:
572
+ - {kind: eslint-fe, id: no-null-suspense-fallback}
573
+ - {kind: machine, id: error-boundary-missing, at: scripts/hfs/architecture/required-files.mjs}
574
+ - id: R54
575
+ code: "FE_ROUTE_FILES_THIN"
576
+ law: "Route files mount one owner; Next conventions (`proxy.ts`, metadata)."
577
+ kinds: [lint]
578
+ gates: [settle, land, ci]
579
+ failureCodes: ["FE_ROUTE_FILES_THIN", "FE_NEXT_CONVENTIONS", "FE_PAGE_METADATA_MISSING", "FE_ROUTE_ONE_PAGE", "FE_ROUTE_DRAWING_DECISION", "FE_ROUTE_CLIENT_BOUNDARY", "FE_ROUTE_CLIENT_HOOK", "FE_ROUTE_DEFAULT_EXPORT"]
580
+ enforcers:
581
+ - {kind: eslint-fe, id: no-middleware-file}
582
+ - {kind: eslint-fe, id: page-exports-metadata}
583
+ - {kind: eslint-fe, id: route-files-thin}
584
+ - {kind: eslint-fe, id: route-adapter}
585
+ - id: R55
586
+ code: "FE_CLIENT_BOUNDARY"
587
+ law: "Server first; `\"use client\"` only where allowed."
588
+ kinds: [lint]
589
+ gates: [pre-commit, pre-push, settle, land, ci]
590
+ failureCodes: ["FE_CLIENT_BOUNDARY", "FE_CLIENT_SERVER_IMPORT", "FE_CLIENT_REACHES_SERVER", "FE_SERVER_ONLY_MARK", "FE_STORAGE_OUTSIDE_MODULES", "FE_DANGEROUS_HTML"]
591
+ enforcers:
592
+ - {kind: eslint-fe, id: use-client-only-at-boundary}
593
+ - {kind: eslint-fe, id: client-no-server-import}
594
+ - {kind: eslint-fe, id: server-module-marks-server-only}
595
+ - {kind: eslint-fe, id: web-storage-only-in-modules}
596
+ - {kind: eslint-fe, id: no-dangerous-html}
597
+ - {kind: eslint-fe, id: client-reaches-server}
598
+ - id: R56
599
+ code: "FE_HOOKS_ARE_HOOKS"
600
+ law: "`hooks/` hold hooks and one shared file per domain; a product hook is defined only there and imported through its index."
601
+ kinds: [lint, codemod]
602
+ gates: [settle, land, ci]
603
+ failureCodes: ["FE_HOOKS_ARE_HOOKS", "FE_CUSTOM_HOOK_LOCATION", "FE_BLOCK_PRODUCT_HOOK_DEFINITION", "FE_COMPONENT_DEEP_HOOK_IMPORT"]
604
+ enforcers:
605
+ - {kind: eslint-fe, id: hooks-folder-holds-hooks-only}
606
+ - {kind: eslint-fe, id: hooks-are-hooks}
607
+ - {kind: eslint-fe, id: hook-location}
608
+ - id: R57
609
+ code: "FE_OWNER_REACHABLE"
610
+ law: "Every owner is mounted; every href resolves to a route."
611
+ kinds: [lint, design]
612
+ gates: [settle, land, ci]
613
+ failureCodes: ["FE_OWNER_REACHABLE", "FE_ROUTE_HARDCODED", "FE_HREF_RESOLVES"]
614
+ enforcers:
615
+ - {kind: eslint-fe, id: no-hardcoded-route}
616
+ - {kind: eslint-fe, id: owner-reachable}
617
+ - id: R58
618
+ code: "FE_I18N_LITERAL"
619
+ law: "No display text at any tier, no escape comment."
620
+ kinds: [lint, codemod]
621
+ gates: [pre-commit, pre-push, settle, land, ci]
622
+ failureCodes: ["FE_I18N_LITERAL"]
623
+ enforcers:
624
+ - {kind: eslint-fe, id: no-hardcoded-copy}
625
+ - {kind: eslint-fe, id: html-lang-from-locale}
626
+ - {kind: eslint-fe, id: no-copy-resolution-below-block}
627
+ - id: R59
628
+ code: "FE_I18N_PLACEMENT"
629
+ law: "`next-intl` with `[locale]`; the next-intl stack is written once per front end: `fe/packages/<family>-i18n` (`createAppI18n`) called by each app's `modules/i18n/index.ts`, or the only app's `modules/i18n/`."
630
+ kinds: [check, codemod, lint]
631
+ gates: [settle, land, ci]
632
+ failureCodes: ["FE_I18N_PLACEMENT", "FE_I18N_NAVIGATION", "FE_I18N_FORMATTER"]
633
+ enforcers:
634
+ - {kind: eslint-fe, id: locale-segment-is-locale}
635
+ - {kind: eslint-fe, id: no-second-i18n-stack}
636
+ - {kind: eslint-fe, id: i18n-stack-in-one-module}
637
+ - {kind: eslint-fe, id: navigation-from-intl}
638
+ - {kind: eslint-fe, id: no-native-anchor}
639
+ - {kind: eslint-fe, id: use-intl-formatter}
640
+ - {kind: hfs, id: i18n-placement, at: scripts/hfs/rules/frontend-tree.mjs}
641
+ - id: R60
642
+ code: "FE_I18N_CATALOG"
643
+ law: "Same keys in every catalog, `pick` for clients."
644
+ kinds: [check]
645
+ gates: [land, ci]
646
+ failureCodes: ["FE_I18N_CATALOG"]
647
+ enforcers:
648
+ - {kind: hfs, id: i18n-catalog, at: scripts/hfs/rules/frontend-tree.mjs}
649
+ - id: R61
650
+ code: "FE_STYLE_TOKEN_ONLY"
651
+ law: "Token-only CSS; colour only in `brand.css`; every `@source` resolves (sub-check `FE_STYLE_SOURCE_UNRESOLVED`). Status colours follow HeroUI soft pairs: every status tone has `--<tone>-soft` and `--<tone>-soft-foreground` in light and dark, the soft foreground reaches 3:1 on its tint and on `--background`, body text 4.5:1, a solid tone is never text below 4.5:1, and text and icons take the soft foreground, never the solid tone."
652
+ kinds: [lint, design]
653
+ gates: [pre-commit, pre-push, settle, land, ci]
654
+ failureCodes: ["FE_STYLE_TOKEN_ONLY", "FE_STYLE_SOURCE_UNRESOLVED"]
655
+ enforcers:
656
+ - {kind: stylelint, id: token-only}
657
+ - {kind: stylelint, id: raw-brand-value}
658
+ - {kind: stylelint, id: no-apply-raw}
659
+ - {kind: stylelint, id: no-important}
660
+ - {kind: stylelint, id: globals-shape}
661
+ - {kind: stylelint, id: globals-import-order}
662
+ - {kind: stylelint, id: no-token-redefinition}
663
+ - {kind: stylelint, id: brand-layer-shape}
664
+ - {kind: stylelint, id: status-contrast}
665
+ - {kind: stylelint, id: no-css-module}
666
+ - {kind: stylelint, id: no-class-selector}
667
+ - {kind: stylelint, id: breakpoint-scale}
668
+ - {kind: stylelint, id: source-resolves}
669
+ - {kind: eslint-fe, id: no-raw-brand-value}
670
+ - {kind: eslint-fe, id: no-fractional-step}
671
+ - {kind: eslint-fe, id: no-arbitrary-value}
672
+ - {kind: eslint-fe, id: no-hand-rolled-heading}
673
+ - {kind: eslint-fe, id: no-unresolved-token-class}
674
+ - {kind: eslint-fe, id: no-heading-tag-outside-heading-component}
675
+ - {kind: eslint-fe, id: status-text-uses-soft-foreground}
676
+ - id: R62
677
+ code: "FE_NATIVE_FORM_CONTROL"
678
+ law: "No raw form controls, native images or raw structural tags in product tiers; the grammar renders them."
679
+ kinds: [lint, design]
680
+ gates: [pre-commit, pre-push, settle, land, ci]
681
+ failureCodes: ["FE_NATIVE_FORM_CONTROL", "FE_NATIVE_IMAGE"]
682
+ enforcers:
683
+ - {kind: eslint-fe, id: no-native-form-control}
684
+ - {kind: eslint-fe, id: no-native-img}
685
+ - {kind: eslint-fe, id: no-raw-structural-element}
686
+ - {kind: eslint-fe, id: image-has-size}
687
+ - id: R63
688
+ code: "FE_PACKAGE_SHAPE"
689
+ law: "Packages build to `dist` with explicit exports and no dead unit."
690
+ kinds: [check, design]
691
+ gates: [land, ci]
692
+ failureCodes: ["FE_PACKAGE_SHAPE"]
693
+ enforcers:
694
+ - {kind: machine, id: package-shape, at: scripts/hfs/architecture/package-shape.mjs}
695
+ - id: R64
696
+ code: "FE_APP_ISOLATION"
697
+ law: "Apps never import apps; ui-screen declares `app`."
698
+ kinds: [lint, codemod]
699
+ gates: [settle, land, ci]
700
+ failureCodes: ["FE_APP_ISOLATION"]
701
+ enforcers:
702
+ - {kind: eslint-fe, id: app-isolation}
703
+ - id: R65
704
+ code: "FE_SIZE_AND_STATE_BUDGET"
705
+ law: "Hook and state budgets; no hand-written poll loop; keyed lists; everything an effect starts is released by its cleanup; no swallowed error or console."
706
+ kinds: [lint, design]
707
+ gates: [settle, land, ci]
708
+ failureCodes: ["FE_SIZE_AND_STATE_BUDGET", "FE_LIST_KEY", "FE_EFFECT_CLEANUP", "FE_SWALLOWED_ERROR", "FE_CONSOLE_CALL"]
709
+ enforcers:
710
+ - {kind: eslint-fe, id: component-line-budget}
711
+ - {kind: eslint-fe, id: unit-hook-budget}
712
+ - {kind: eslint-fe, id: no-hand-rolled-polling}
713
+ - {kind: eslint-fe, id: list-item-has-key}
714
+ - {kind: eslint-fe, id: no-index-key}
715
+ - {kind: eslint-fe, id: no-inline-literal-prop-in-list}
716
+ - {kind: eslint-fe, id: effect-subscription-needs-cleanup}
717
+ - {kind: eslint-fe, id: no-empty-catch}
718
+ - {kind: eslint-fe, id: no-console}
719
+ - id: R68
720
+ code: "BE_SQL_INTERPOLATED"
721
+ law: "SQL text carries no runtime substitution; values are numbered parameters."
722
+ kinds: [lint, codemod]
723
+ gates: [pre-commit, pre-push, settle, land, ci]
724
+ failureCodes: ["BE_SQL_INTERPOLATED"]
725
+ enforcers:
726
+ - {kind: eslint-be, id: no-interpolated-sql}
727
+ - id: R69
728
+ code: "BE_QUERY_UNBOUNDED"
729
+ law: "A read that can return many rows states `take`, `limit` or `LIMIT`, or pages by cursor."
730
+ kinds: [lint, codemod]
731
+ gates: [pre-commit, pre-push, settle, land, ci]
732
+ failureCodes: ["BE_QUERY_UNBOUNDED"]
733
+ enforcers:
734
+ - {kind: eslint-be, id: query-needs-limit}
735
+ - id: R70
736
+ code: "BE_HTTP_TIMEOUT"
737
+ law: "Every outbound `fetch`, axios or HttpService call states a timeout or an abort signal."
738
+ kinds: [lint, codemod]
739
+ gates: [pre-commit, pre-push, settle, land, ci]
740
+ failureCodes: ["BE_HTTP_TIMEOUT"]
741
+ enforcers:
742
+ - {kind: eslint-be, id: http-needs-timeout}
743
+ - id: R71
744
+ code: "BE_LOG_SECRET"
745
+ law: "A `Logger` call carries no `Secret` or `Pii` value and no credential or personal identifier by name; log an id or a masked form. A constructed error carries no `Secret` value and no text value named like a credential."
746
+ kinds: [lint, codemod]
747
+ gates: [pre-commit, pre-push, settle, land, ci]
748
+ failureCodes: ["BE_LOG_SECRET"]
749
+ enforcers:
750
+ - {kind: eslint-be, id: no-secret-in-log}
751
+ - {kind: eslint-be, id: no-secret-in-error}
752
+ - id: R72
753
+ code: "BE_TYPE_ESCAPE"
754
+ law: "No type escape in any file, specs included: no `as X` (only `as const`), `<X>y`, `x!` or `any` (the factory turns on the typescript-eslint rules `consistent-type-assertions` with `never`, `no-non-null-assertion` and `no-explicit-any`), no `Function` type, no `eval`."
755
+ kinds: [lint, codemod]
756
+ gates: [pre-commit, pre-push, settle, land, ci]
757
+ failureCodes: ["BE_TYPE_ESCAPE"]
758
+ enforcers:
759
+ - {kind: eslint-be, id: no-function-or-eval}
760
+ - id: R73
761
+ code: "BE_ASYNC_NO_AWAIT"
762
+ law: "An `async` function contains an `await`; otherwise it is not `async`."
763
+ kinds: [lint, codemod]
764
+ gates: [pre-commit, pre-push, settle, land, ci]
765
+ failureCodes: ["BE_ASYNC_NO_AWAIT"]
766
+ enforcers:
767
+ - {kind: eslint-be, id: async-needs-await}
768
+ - id: R74
769
+ code: "BE_MIGRATION_REVERSIBLE"
770
+ law: "Every migration declares a `down()` that reverses its `up()`; never empty, never a bare throw."
771
+ kinds: [lint, codemod]
772
+ gates: [pre-commit, pre-push, settle, land, ci]
773
+ failureCodes: ["BE_MIGRATION_REVERSIBLE"]
774
+ enforcers:
775
+ - {kind: eslint-be, id: migration-down-reversible}
776
+ - id: R75
777
+ code: "BE_RETURN_TYPE"
778
+ law: "Handlers and public methods of an Injectable, Resolver or Controller declare their return type."
779
+ kinds: [lint, codemod]
780
+ gates: [pre-commit, pre-push, settle, land, ci]
781
+ failureCodes: ["BE_RETURN_TYPE"]
782
+ enforcers:
783
+ - {kind: eslint-be, id: explicit-handler-return-type}
784
+ - id: R76
785
+ code: "BE_JSON_PARSE_UNGUARDED"
786
+ law: "`JSON.parse` sits inside a `try` in its own function and fails as a typed outcome."
787
+ kinds: [lint, codemod]
788
+ gates: [pre-commit, pre-push, settle, land, ci]
789
+ failureCodes: ["BE_JSON_PARSE_UNGUARDED"]
790
+ enforcers:
791
+ - {kind: eslint-be, id: json-parse-needs-guard}
792
+ - id: R77
793
+ code: "BE_QUERY_IN_LOOP"
794
+ law: "A read through the shared EntityManager does not run once per element of a loop; read once before the loop by key list."
795
+ kinds: [lint, codemod]
796
+ gates: [pre-commit, pre-push, settle, land, ci]
797
+ failureCodes: ["BE_QUERY_IN_LOOP"]
798
+ enforcers:
799
+ - {kind: eslint-be, id: no-query-in-loop}
800
+ - id: R78
801
+ code: "BE_USER_COPY_LITERAL"
802
+ law: "A literal exception message, notification text or response copy comes from the per-capability messages catalog through the typed `MessageCatalog` port, not from source."
803
+ kinds: [lint, codemod]
804
+ gates: [pre-commit, pre-push, settle, land, ci]
805
+ failureCodes: ["BE_USER_COPY_LITERAL"]
806
+ enforcers:
807
+ - {kind: eslint-be, id: user-copy-through-catalog}
808
+ - id: R79
809
+ code: "BE_AMBIENT_CLOCK"
810
+ law: "`Date.now`, a bare `new Date()`, `performance.now`, `process.hrtime` and `Temporal.Now` are referenced only inside `platform/clock`, specs included; code asks the injected `Clock` port and specs use `FakeClock`."
811
+ kinds: [lint, codemod]
812
+ gates: [pre-commit, pre-push, settle, land, ci]
813
+ failureCodes: ["BE_AMBIENT_CLOCK"]
814
+ enforcers:
815
+ - {kind: eslint-be, id: no-ambient-clock}
816
+ - id: R80
817
+ code: "BE_INBOX_DEDUPE_MISSING"
818
+ law: "Every service method that takes a delivery (a public method of a `*.service.ts` whose first parameter has an `eventId`) makes `claim(source, eventId)` on the `Inbox` port its first awaited expression and returns early when the claim answers `false`; consumers and `SignedWebhook` controllers stay thin doors (R88) and dispatch to the handler whose service claims."
819
+ kinds: [lint, design]
820
+ gates: [pre-commit, pre-push, settle, land, ci]
821
+ failureCodes: ["BE_INBOX_DEDUPE_MISSING"]
822
+ enforcers:
823
+ - {kind: eslint-be, id: inbox-dedupe-required}
824
+ - id: R81
825
+ code: "BE_HAND_ROLLED_RETRY"
826
+ law: "A loop that catches an error and waits before trying again goes through the shared `platform/retry` helper, never a hand-written loop."
827
+ kinds: [lint, codemod]
828
+ gates: [pre-commit, pre-push, settle, land, ci]
829
+ failureCodes: ["BE_HAND_ROLLED_RETRY"]
830
+ enforcers:
831
+ - {kind: eslint-be, id: no-hand-rolled-retry}
832
+ - id: R82
833
+ code: "BE_TRANSACTION_EXTERNAL_CALL"
834
+ law: "No transaction spans an external call; commit first and call out after, or write an outbox message inside the transaction."
835
+ kinds: [lint, design]
836
+ gates: [pre-commit, pre-push, settle, land, ci]
837
+ failureCodes: ["BE_TRANSACTION_EXTERNAL_CALL"]
838
+ enforcers:
839
+ - {kind: eslint-be, id: no-external-call-in-transaction}
840
+ - id: R83
841
+ code: "BE_UNNAMED_DATA_ACCESS"
842
+ law: "The database is reached through the shared EntityManager, injected as a constructor parameter by the `Inject<Conn>EntityManager()` of a declared connection and called directly; no bare `@InjectEntityManager()`, `getRepository`, repository, QueryBuilder or property injection; a `DataSource` or `QueryRunner` only in `platform/database`, the cli (`be/apps/cli`, `be/src/features/cli`) and the test world `be/src/tests/world`, whose `world.db.<connection>` EntityManager the specs use. No class outside an application handler, a domain service or a platform persistence capability takes, holds or returns an `EntityManager`, `Repository`, `DataSource` or `QueryRunner` (a repository under any name, a store, dao, gateway or persistence wrapper included), no exported function whose first parameter is an `EntityManager` (a statement module), and no wrapper return type, awaited value or `provide:` token hands out a connection object."
843
+ kinds: [lint, design]
844
+ gates: [pre-commit, pre-push, settle, land, ci]
845
+ failureCodes: ["BE_UNNAMED_DATA_ACCESS"]
846
+ enforcers:
847
+ - {kind: eslint-be, id: named-entity-manager-only}
848
+ - {kind: eslint-be, id: no-injected-repository}
849
+ - {kind: eslint-be, id: must-inject-entity-manager}
850
+ - {kind: eslint-be, id: no-repository-class}
851
+ - {kind: eslint-be, id: no-eager-relation}
852
+ - {kind: eslint-be, id: no-outer-manager-in-transaction}
853
+ - {kind: eslint-be, id: require-entity-table-name}
854
+ - id: R84
855
+ code: "BE_CONNECTION_DUPLICATE"
856
+ law: "One physical database is one connection and one `Inject<Conn>EntityManager()` injector declared once in `platform/database`; `hfs.json` connections, connection files, injectors and module registrations correspond one to one."
857
+ kinds: [lint]
858
+ gates: [pre-commit, pre-push, settle, land, ci]
859
+ failureCodes: ["BE_CONNECTION_DUPLICATE"]
860
+ enforcers:
861
+ - {kind: eslint-be, id: one-connection-per-database}
862
+ - {kind: eslint-be, id: connection-map}
863
+ - id: R85
864
+ code: "BE_RAW_INJECT"
865
+ law: "Every injected infrastructure dependency arrives through a zero-argument `Inject<Thing>()` from its owner's `<owner>.decorators.ts` over a `unique symbol` token; raw `@Inject(` exists only there, and every such token is exported so a spec can provide it; a constructor parameter of a provider is typed by a class or carries an `Inject<Thing>()`, never a bare primitive, `Map`, union or interface; the parameter that injects a port of the table `ruleParams.be.paramNames` of `slots.yaml` (`Clock`, `Logger`, `Cache`, `CommandBus`, `QueryBus`, `EntityManager`, an `*Options`) carries the name that table gives it."
866
+ kinds: [lint, codemod]
867
+ gates: [pre-commit, pre-push, settle, land, ci]
868
+ failureCodes: ["BE_RAW_INJECT"]
869
+ enforcers:
870
+ - {kind: eslint-be, id: injector-only}
871
+ - {kind: eslint-be, id: injector-shape}
872
+ - {kind: eslint-be, id: injector-type-match}
873
+ - {kind: eslint-be, id: infra-needs-injector}
874
+ - {kind: eslint-be, id: provider-param-token}
875
+ - {kind: eslint-be, id: injected-param-name}
876
+ - {kind: eslint-be, id: no-module-ref}
877
+ - {kind: eslint-be, id: no-forward-ref}
878
+ - {kind: eslint-be, id: no-string-token}
879
+ - {kind: eslint-be, id: injection-token-exported}
880
+ - id: R86
881
+ code: "BE_SQL_TABLE_OWNER"
882
+ law: "SQL writes only the tables of its own capability's entities, reads only tables of its own context (its own capability's, or a same-context capability it may import; a cross-context JOIN is refused), and every multi-row SELECT is bounded."
883
+ kinds: [lint, design]
884
+ gates: [settle, land, ci]
885
+ failureCodes: ["BE_SQL_TABLE_OWNER"]
886
+ enforcers:
887
+ - {kind: eslint-be, id: sql-owner}
888
+ - id: R87
889
+ code: "BE_CQRS_SHAPE"
890
+ law: "The application layer is CQRS: typed `Command<R>`/`Query<R>` messages carrying one `params`, handlers extending `ICQRSHandler` that override `process`, where `process` is one `return this.<service>.<method>(...)` and the handler injects only `*Service` classes and the Logger (no EntityManager, no branch, no loop, no second call); no use-case classes, forwarder services or in-process events (`EventBus`, `EventEmitter`, an RxJS `Subject`, a stored listener list); a message and an injected dependency are `readonly`."
891
+ kinds: [lint, codemod]
892
+ gates: [pre-commit, pre-push, settle, land, ci]
893
+ failureCodes: ["BE_CQRS_SHAPE", "BE_READONLY_BOUNDARY"]
894
+ enforcers:
895
+ - {kind: eslint-be, id: handler-overrides-process}
896
+ - {kind: eslint-be, id: handler-is-thin}
897
+ - {kind: eslint-be, id: message-carries-params-only}
898
+ - {kind: eslint-be, id: message-typed-result}
899
+ - {kind: eslint-be, id: no-use-case}
900
+ - {kind: eslint-be, id: no-event-bus}
901
+ - {kind: eslint-be, id: execute-params-shape}
902
+ - {kind: eslint-be, id: readonly-boundary}
903
+ - id: R88
904
+ code: "BE_TRANSPORT_SHAPE"
905
+ law: "A transport handler (a controller, resolver, gateway, consumer or job; a cli command is an action runner, R149) maps its input, dispatches exactly one command or query through the injected bus and maps the result; it injects nothing else (no EntityManager, no Inbox), holds no branch, loop or other call besides pure mapper functions and `unwrapOutcome` of `platform/primitives`, returns no envelope and takes no `GraphQLJSON`."
906
+ kinds: [lint, codemod]
907
+ gates: [pre-commit, pre-push, settle, land, ci]
908
+ failureCodes: ["BE_TRANSPORT_SHAPE"]
909
+ enforcers:
910
+ - {kind: eslint-be, id: door-lives-in-features}
911
+ - {kind: eslint-be, id: no-capability-imports-features}
912
+ - {kind: eslint-be, id: rest-door-needs-a-reason, editions: [full]}
913
+ - {kind: eslint-be, id: transport-is-thin}
914
+ - {kind: eslint-be, id: no-response-envelope}
915
+ - {kind: eslint-be, id: no-graphql-json}
916
+ - {kind: eslint-be, id: em-injection-slots}
917
+ - id: R89
918
+ code: "BE_SOURCE_FORM"
919
+ law: "Files use the closed role-suffix vocabulary of the slot manifest, `ruleParams.be.suffixes`; named exports only; every export has English JSDoc (its public members: R109); no emoji, and no Vietnamese in identifiers, string literals, comments or test titles outside message catalogs and the i18n fixtures slot; a public input or output is a named contract, never an inline object type; no `Mock*`, `Fake*` or `Stub*` class, function or constant in production source."
920
+ kinds: [lint, check, codemod]
921
+ gates: [pre-commit, pre-push, settle, land, ci]
922
+ failureCodes: ["BE_SOURCE_FORM", "BE_PUBLIC_CONTRACT_FORM"]
923
+ enforcers:
924
+ - {kind: eslint-be, id: require-export-jsdoc}
925
+ - {kind: eslint-be, id: require-enum-member-jsdoc}
926
+ - {kind: eslint-be, id: no-non-ascii-source}
927
+ - {kind: eslint-be, id: no-restated-name-jsdoc}
928
+ - {kind: eslint-be, id: no-bare-verb-export}
929
+ - {kind: eslint-be, id: no-vendor-module-factory-name}
930
+ - {kind: eslint-be, id: no-version-in-name}
931
+ - {kind: eslint-be, id: no-const-enum}
932
+ - {kind: eslint-be, id: no-inline-object-type}
933
+ - {kind: eslint-be, id: no-inline-param-type}
934
+ - {kind: eslint-be, id: no-test-double-in-source}
935
+ - {kind: eslint-be, id: no-default-export}
936
+ - {kind: hfs, id: source-suffix, at: scripts/hfs/check.mjs}
937
+ - {kind: eslint-be, id: source-suffix}
938
+ - {kind: eslint-be, id: source-names}
939
+ - {kind: eslint-be, id: public-contract-form}
940
+ - id: R90
941
+ code: "BE_INFRA_OWNER"
942
+ law: "Each raw infrastructure library (HTTP, cache, queue, scheduler, logger, date, config, events) is imported or referenced only by its one owning platform or integration capability, and the `HttpClient` port of `platform/http` is called only by an `integrations` capability."
943
+ kinds: [lint, codemod]
944
+ gates: [pre-commit, pre-push, settle, land, ci]
945
+ failureCodes: ["BE_INFRA_OWNER"]
946
+ enforcers:
947
+ - {kind: eslint-be, id: infra-import-owner}
948
+ - {kind: eslint-be, id: http-client-only-in-integrations}
949
+ - id: R91
950
+ code: "FE_SOURCE_FORM"
951
+ law: "Front-end files sit in their tier folder with the fixed names, export arrow functions named after the folder, carry English JSDoc, no emoji, and no Vietnamese in identifiers, string literals, comments or test titles outside the i18n catalogs and the i18n e2e fixtures slot."
952
+ kinds: [lint, codemod]
953
+ gates: [pre-commit, pre-push, settle, land, ci]
954
+ failureCodes: ["FE_SOURCE_FORM", "FE_SOURCE_LAYOUT_INVALID"]
955
+ enforcers:
956
+ - {kind: eslint-fe, id: require-export-jsdoc}
957
+ - {kind: eslint-fe, id: no-emoji-in-source}
958
+ - {kind: eslint-fe, id: no-vietnamese-in-source}
959
+ - {kind: eslint-fe, id: no-shell-tier}
960
+ - {kind: eslint-fe, id: source-tier-marker-matches-folder}
961
+ - {kind: eslint-fe, id: surface-folder-two-files-only}
962
+ - {kind: eslint-fe, id: route-tree-holds-routes-only}
963
+ - {kind: eslint-fe, id: route-slot-fixed-name}
964
+ - {kind: eslint-fe, id: no-helper-folder-in-components}
965
+ - {kind: eslint-fe, id: export-matches-folder}
966
+ - {kind: eslint-fe, id: no-runtime-namespace}
967
+ - {kind: eslint-fe, id: monorepo-tier-belongs-to-its-side}
968
+ - {kind: eslint-fe, id: prefer-arrow-export}
969
+ - {kind: eslint-fe, id: handler-on-prefix}
970
+ - {kind: eslint-fe, id: no-second-language-in-path}
971
+ - {kind: eslint-fe, id: no-direct-const-alias}
972
+ - {kind: eslint-fe, id: frontend-source-layout}
973
+ - id: R92
974
+ code: "FE_COMPONENT_API"
975
+ law: "A component exposes the canon surface: typed named props and slots, a presentational twin for every connected block, status through the slot view, no className or CSS doors, no resting twin or placeholder prop, class names in the colocated file; a pure component reaches no hook, router, locale or API."
976
+ kinds: [lint, design]
977
+ gates: [pre-commit, pre-push, settle, land, ci]
978
+ failureCodes: ["FE_COMPONENT_API", "FE_COMPONENT_WORLD_OWNERSHIP", "FE_WORLD_OWNER_RENDER_BOUNDARY", "FE_CONNECTED_BLOCK_RENDER_PAIR", "FE_PURE_REACHES_DATA", "FE_PURE_WORLD_HOOK", "FE_PURE_WORLD_IMPORT"]
979
+ enforcers:
980
+ - {kind: eslint-fe, id: no-inline-parameter-type}
981
+ - {kind: eslint-fe, id: public-component-signature}
982
+ - {kind: eslint-fe, id: no-per-part-classname-prop}
983
+ - {kind: eslint-fe, id: no-public-classname-prop}
984
+ - {kind: eslint-fe, id: no-public-frame-css-props}
985
+ - {kind: eslint-fe, id: no-css-door-type-laundering}
986
+ - {kind: eslint-fe, id: base-props-atom}
987
+ - {kind: eslint-fe, id: base-import-pair}
988
+ - {kind: eslint-fe, id: no-data-status-shape}
989
+ - {kind: eslint-fe, id: slot-status-through-slotview}
990
+ - {kind: eslint-fe, id: presentational-purity}
991
+ - {kind: eslint-fe, id: connected-block-has-presentational-twin}
992
+ - {kind: eslint-fe, id: no-resting-twin-component}
993
+ - {kind: eslint-fe, id: no-placeholder-prop}
994
+ - {kind: eslint-fe, id: no-resting-branch-at-call-site}
995
+ - {kind: eslint-fe, id: no-inline-class-name}
996
+ - {kind: eslint-fe, id: class-names-in-colocated-file}
997
+ - {kind: eslint-fe, id: cn-arguments-are-single-tokens}
998
+ - {kind: eslint-fe, id: component-purity}
999
+ - id: R93
1000
+ code: "FE_VENDOR_BOUNDARY"
1001
+ law: "Vendor primitives and icons reach components only through their named owner: heroicons through the icon leaf at the glyph scale, every vendor primitive behind a named owner; the Grammar package is imported only through its selected code and style entry and keeps its declared contract."
1002
+ kinds: [lint, codemod]
1003
+ gates: [pre-commit, pre-push, settle, land, ci]
1004
+ failureCodes: ["FE_VENDOR_BOUNDARY", "ARCH_GRAMMAR_CONTRACT_INVALID", "ARCH_GRAMMAR_EXPORT_BYPASS"]
1005
+ enforcers:
1006
+ - {kind: eslint-fe, id: no-vendor-icon-outside-icon-leaf}
1007
+ - {kind: eslint-fe, id: heroicons-is-the-glyph-vendor}
1008
+ - {kind: eslint-fe, id: no-off-scale-glyph-size}
1009
+ - {kind: eslint-fe, id: no-decorative-icon-in-metric-cell}
1010
+ - {kind: eslint-fe, id: vendor-primitive-has-named-owner}
1011
+ - {kind: eslint-fe, id: grammar-entry}
1012
+ - id: R94
1013
+ code: "FE_SLOT_FILE_ROLE"
1014
+ law: "A front-end file is owned by a slot and named by it: a slot that owns a directory (route, feature, components, hooks, api module) holds only the files its `allows` list names, and holds them inside a folder of its own."
1015
+ kinds: [check, design]
1016
+ gates: [settle, land, ci]
1017
+ failureCodes: ["FE_SLOT_FILE_ROLE"]
1018
+ enforcers:
1019
+ - {kind: machine, id: fe-slot-allows, at: scripts/hfs/architecture/fe-slot-allows.mjs}
1020
+ - id: R95
1021
+ code: "BE_OPERATION_CONTRACT"
1022
+ law: "A versioned operation is declared once in the app's typed operation table (`OperationContract<Input, Output, RefusalCode>` registered by `defineOperations`): its input and output are closed types (never `any`, `unknown` or `Record<string, unknown>`), its refusal codes a closed union of string literals, and the route that serves the table takes `OperationRequest<Table>` and answers `Promise<OperationReply<Table>>` of that one table."
1023
+ kinds: [lint]
1024
+ gates: [pre-commit, pre-push, settle, land, ci]
1025
+ failureCodes: ["BE_OPERATION_CONTRACT"]
1026
+ enforcers:
1027
+ - {kind: eslint-be, id: operation-contract-decidable}
1028
+ - {kind: eslint-be, id: operation-route-driven-by-table}
1029
+ - id: R96
1030
+ code: "HFS_DOC_NOT_ENGLISH"
1031
+ law: "Every Markdown and YAML document under a side's docs/, src/ and apps/ (be/, fe/) and under the runtime's knowledge/ and docs/ is English, code fences included; only YAML data in a message-catalog or i18n-fixtures slot may hold another language."
1032
+ kinds: [check]
1033
+ gates: [settle, land, ci]
1034
+ failureCodes: ["HFS_DOC_NOT_ENGLISH"]
1035
+ enforcers:
1036
+ - {kind: machine, id: doc-language, at: scripts/hfs/architecture/doc-language.mjs}
1037
+ - id: R97
1038
+ code: "FE_NO_TESTS"
1039
+ law: "The front end (`fe/`) has no tests by standard: no `*.spec.*`, `*.test.*` or `*-spec.*` file, no `e2e/`, `__tests__/`, `__mocks__/` or `test-support/` directory, no vitest, Playwright, jest or Cypress configuration or setup file, no test script, and no test-runner, test-environment, testing-library or axe dependency in any `package.json`; there is no exception, `scripts/` included."
1040
+ kinds: [check]
1041
+ gates: [pre-push, settle, land, ci]
1042
+ failureCodes: ["FE_NO_TESTS"]
1043
+ enforcers:
1044
+ - {kind: hfs, id: fe-no-tests, at: scripts/hfs/rules/fe-no-tests.mjs}
1045
+ - id: R98
1046
+ code: "BE_TEST_BUILDER_HOME"
1047
+ law: "A test data builder lives only in `be/src/tests/fixtures/builders/<area>.builder.ts` (slot `be.tests.fixtures.builders`, its SQL text in `<area>.sql.ts` beside it): a `*.repository.ts`, `*.factory.ts` or `*.fixture.ts` under `be/src/tests` and a `*.builder.ts` elsewhere are BE_SOURCE_FORM findings, and any other module of the test tree that exports a function creating persisted rows through an EntityManager, DataSource or QueryRunner is a builder in the wrong place."
1048
+ kinds: [lint]
1049
+ gates: [pre-push, settle, land, ci]
1050
+ failureCodes: ["BE_TEST_BUILDER_HOME"]
1051
+ editions: [full]
1052
+ enforcers:
1053
+ - {kind: eslint-be, id: persisting-builder-in-builders-slot}
1054
+ - id: R99
1055
+ code: "BE_TEST_ROW_BY_BUILDER"
1056
+ law: "A unit, integration or e2e spec never hand-builds rows: no raw INSERT/UPDATE/DELETE text, no `save`, `insert`, `upsert`, `update`, `delete` or `remove` on an EntityManager, DataSource or QueryRunner, and no persistence entity built as an object literal more than once in the spec; rows come from the area's builder."
1057
+ kinds: [lint]
1058
+ gates: [pre-push, settle, land, ci]
1059
+ failureCodes: ["BE_TEST_ROW_BY_BUILDER"]
1060
+ editions: [full]
1061
+ enforcers:
1062
+ - {kind: eslint-be, id: spec-no-raw-insert}
1063
+ - {kind: eslint-be, id: spec-no-repeated-row-literal}
1064
+ - id: R100
1065
+ code: "BE_TEST_CONSTRAINTS_ON"
1066
+ law: "Nothing under `be/src/tests` disables or drops a database constraint: no `session_replication_role`, `SET CONSTRAINTS ... DEFERRED`, `DEFERRABLE INITIALLY DEFERRED`, `ALTER TABLE ... DISABLE`, `DISABLE TRIGGER`, `DROP CONSTRAINT`, and no `dropForeignKey(s)`, `dropCheckConstraint(s)` or `dropUniqueConstraint(s)` on a QueryRunner; a builder creates the parent chain instead."
1067
+ kinds: [lint]
1068
+ gates: [pre-push, settle, land, ci]
1069
+ failureCodes: ["BE_TEST_CONSTRAINTS_ON"]
1070
+ editions: [full]
1071
+ enforcers:
1072
+ - {kind: eslint-be, id: tests-keep-constraints}
1073
+ - id: R101
1074
+ code: "BE_TEST_BUILDER_ARRANGES"
1075
+ law: "A test data builder arranges data only: it imports no assertion or test-framework global (`expect`, `@jest/globals` or the Jest namespace) and its defaults are deterministic (no `Date.now()`, argless `new Date()`, `Math.random()`, `randomUUID()` or `crypto.random*`)."
1076
+ kinds: [lint]
1077
+ gates: [pre-push, settle, land, ci]
1078
+ failureCodes: ["BE_TEST_BUILDER_ARRANGES"]
1079
+ editions: [full]
1080
+ enforcers:
1081
+ - {kind: eslint-be, id: builder-arranges-only}
1082
+ - id: R102
1083
+ code: "BE_SPEC_PLACEMENT"
1084
+ law: "A back-end spec or test file lives in one of the four test layers and nowhere else: a unit spec of a unit-tested role (`ruleParams.be.unitRoles`: `<name>.service.spec.ts` beside its service, `<name>.cli.spec.ts` beside its cli command), or an integration, e2e or contract spec under `be/src/tests/{integration,e2e,contract}`; a `*.spec.*`, `*.test.*` or `*-spec.*` file anywhere else, `scripts/` and `tools/` included, is a finding."
1085
+ kinds: [check, lint]
1086
+ gates: [land, ci]
1087
+ failureCodes: ["BE_SPEC_PLACEMENT"]
1088
+ editions: [full]
1089
+ enforcers:
1090
+ - {kind: hfs, id: spec-placement, at: scripts/hfs/rules/spec-placement.mjs}
1091
+ - {kind: eslint-be, id: spec-placement}
1092
+ - id: R103
1093
+ code: "HFS_REPO_LOCAL_CHECK"
1094
+ law: "A repository keeps no check, lint rule or lint plugin of its own: no `check-*` file in `scripts/` or `tools/`, no `eslint-local-rules*` or local eslint plugin, no script that runs one; every check lives in the `.claude` runtime and the canons."
1095
+ kinds: [check]
1096
+ gates: [land, ci]
1097
+ failureCodes: ["HFS_REPO_LOCAL_CHECK"]
1098
+ enforcers:
1099
+ - {kind: hfs, id: repo-local-check, at: scripts/hfs/rules/repo-local-checks.mjs}
1100
+ - id: R104
1101
+ code: "HFS_LINT_SUPPRESSION_FILE"
1102
+ law: "A repository keeps no lint-suppression file, script or option: no `eslint.suppressions*`, no `lint:suppressions` script, no eslint `--suppress-all` or `--suppressions-location` flag and no suppressions configuration passed to eslint; a finding is fixed in the code."
1103
+ kinds: [check]
1104
+ gates: [land, ci]
1105
+ failureCodes: ["HFS_LINT_SUPPRESSION_FILE"]
1106
+ enforcers:
1107
+ - {kind: hfs, id: lint-suppression, at: scripts/hfs/rules/lint-suppression.mjs}
1108
+ - id: R105
1109
+ code: "HFS_PROOF_COMMAND_FILE_MISSING"
1110
+ law: "A proof command of a `.starciwork` record (`requiresProof.<kind>.command`) is runnable as written: every repository file it names exists."
1111
+ kinds: [check]
1112
+ gates: [land, ci]
1113
+ failureCodes: ["HFS_PROOF_COMMAND_FILE_MISSING"]
1114
+ enforcers:
1115
+ - {kind: hfs, id: proof-command-files, at: scripts/hfs/rules/proof-commands.mjs}
1116
+ - id: R106
1117
+ code: "FE_I18N_KEYS"
1118
+ law: "The catalogs of a front-end app and its source agree both ways: a literal key read through `next-intl` (`useTranslations`, `getTranslations`, `t`, `t.rich`, `t.raw`, `t.markup`) exists in every locale, and a catalog key that no string of the app's or the shared packages' source can be reading is deleted."
1119
+ kinds: [lint]
1120
+ gates: [land, ci]
1121
+ failureCodes: ["FE_I18N_KEYS"]
1122
+ enforcers:
1123
+ - {kind: eslint-fe, id: i18n-keys}
1124
+ - id: R107
1125
+ code: "FE_COOKIE_ATTRIBUTES"
1126
+ law: "A cookie the front end writes through Next's response cookies (`response.cookies.set`, `cookies().set`) states `httpOnly` as a literal (`true` for a session, `false` only for a preference page script reads), carries `secure` and has `sameSite` `lax` or `strict`."
1127
+ kinds: [lint]
1128
+ gates: [pre-commit, pre-push, settle, land, ci]
1129
+ failureCodes: ["FE_COOKIE_ATTRIBUTES"]
1130
+ enforcers:
1131
+ - {kind: eslint-fe, id: response-cookie-attributes}
1132
+ - id: R108
1133
+ code: "BE_ERROR_CAUSE_DROPPED"
1134
+ law: "A `throw` that escapes a `catch` (or a promise `.catch` handler) rethrows the caught value or carries it: the replacement capability error takes the exact caught value as `cause`; a `catch` with no binding throws nothing new."
1135
+ kinds: [lint]
1136
+ gates: [pre-commit, pre-push, settle, land, ci]
1137
+ failureCodes: ["BE_ERROR_CAUSE_DROPPED"]
1138
+ enforcers:
1139
+ - {kind: eslint-be, id: replacement-throw-carries-cause}
1140
+ - id: R109
1141
+ code: "BE_MEMBER_DOC_MISSING"
1142
+ law: "Every public member of an exported class, interface or object type alias in product source opens with English JSDoc: methods, accessors, fields and signatures; a private, protected or `#` member, the constructor, an index signature and a property set to a literal or a named constant are not judged, and overloads of one name share one doc."
1143
+ kinds: [lint]
1144
+ gates: [pre-commit, pre-push, settle, land, ci]
1145
+ failureCodes: ["BE_MEMBER_DOC_MISSING"]
1146
+ enforcers:
1147
+ - {kind: eslint-be, id: require-public-member-jsdoc}
1148
+ - id: R110
1149
+ code: "FE_PROPS_MUTABLE"
1150
+ law: "The props type of an exported rendering function in front-end product source is readonly all the way down: every field and index signature is `readonly`, and every collection it holds, directly or in a nested object, is `readonly T[]`, `readonly [A, B]` or `ReadonlyArray<T>`."
1151
+ kinds: [lint]
1152
+ gates: [pre-commit, pre-push, settle, land, ci]
1153
+ failureCodes: ["FE_PROPS_MUTABLE"]
1154
+ enforcers:
1155
+ - {kind: eslint-fe, id: props-fields-readonly}
1156
+ - id: R111
1157
+ code: "HFS_PEER_INTEGRATION_MISSING"
1158
+ law: "An app declares the runtime peer every driver integration it uses needs: when the app root package.json depends on every package of a pair of knowledge/hfs/peer-integrations.yaml (at the named major), it declares the pair's `requires` in its dependencies (`@nestjs/apollo` on `@nestjs/platform-express` 11, Express 5, needs `@as-integrations/express5`)."
1159
+ kinds: [check]
1160
+ gates: [land, ci]
1161
+ failureCodes: ["HFS_PEER_INTEGRATION_MISSING"]
1162
+ enforcers:
1163
+ - {kind: hfs, id: peer-integrations, at: scripts/hfs/rules/peer-integrations.mjs}
1164
+ - id: R112
1165
+ code: "BE_INTEGRATION_SPEC_MISSING"
1166
+ law: "Every integration of a back end (`src/modules/integrations/<provider>/` holding `<provider>.config.ts`) has at least one `src/tests/integration/<provider>/*.integration-spec.ts` that, read as a syntax tree, calls `useTestWorld({ modules })` with a factory registering the integration's own module (`<Provider>Module.register`, imported from the integration folder; an inline array, a local const or an exported const of another file), references the ErrorCode enum the integration exports, and drives an outage through the world (`world.infra.<service>.cut|latency|during|connection`, `world.fake.<name>.failNext`, `world.apps.<peer>.during`, `world.interruptDatabase`); the finding sits on the integration folder."
1167
+ kinds: [check]
1168
+ gates: [land, ci]
1169
+ failureCodes: ["BE_INTEGRATION_SPEC_MISSING"]
1170
+ editions: [full]
1171
+ enforcers:
1172
+ - {kind: hfs, id: integration-specs, at: scripts/hfs/rules/integration-specs.mjs}
1173
+ - id: R113
1174
+ code: "FE_GRAPHQL_CONTRACT"
1175
+ law: "Every GraphQL operation a front end keeps in a `.graphql` file under `fe/` is one its back end serves: it is judged against the contract snapshot (`be/contracts/<service>/schema.graphql`) whose root type declares its first root field, and every selected field, argument and input field must be declared there, every required argument and required input field given, every variable passed with the argument's named type, every leaf selected without and every object with a selection, and every declared variable used; a root field no contract declares is a finding too."
1176
+ kinds: [check]
1177
+ gates: [land, ci]
1178
+ failureCodes: ["FE_GRAPHQL_CONTRACT"]
1179
+ enforcers:
1180
+ - {kind: hfs, id: fe-contract-documents, at: scripts/hfs/rules/fe-contract-documents.mjs}
1181
+ - id: R114
1182
+ code: "RT_EXTERNAL_OWNER"
1183
+ law: "In the runtime repository a process, the network, SQLite or an external binary is reached only by its owner in ruleParams.runtime.infraOwners of knowledge/hfs/runtime-slots.yaml: an owned module specifier, global or spawned program word is used only by the files of scripts/api/<system> or engine/db that the map names."
1184
+ scope: runtime
1185
+ kinds: [check]
1186
+ gates: [land, runtime]
1187
+ failureCodes: ["RT_EXTERNAL_OWNER"]
1188
+ enforcers:
1189
+ - {kind: runtime, id: external-owner, at: scripts/hfs/runtime-rules/external-owner.mjs}
1190
+ - id: R115
1191
+ code: "RT_TIER_DIRECTION"
1192
+ law: "Every relative import between two runtime owners goes from a tier to a tier its mayImport lists in tiers.runtime of knowledge/hfs/runtime-slots.yaml; owner cycles are ARCH_OWNER_CYCLE."
1193
+ scope: runtime
1194
+ kinds: [check]
1195
+ gates: [land, runtime]
1196
+ failureCodes: ["RT_TIER_DIRECTION"]
1197
+ enforcers:
1198
+ - {kind: runtime, id: runtime-tiers, at: scripts/hfs/runtime-rules/tier-direction.mjs}
1199
+ - id: R116
1200
+ code: "RT_BASE_IMPURE"
1201
+ law: "The runtime base tier (the engine foundation and scripts/lib) never writes the filesystem and reads process.env only in a declared seam of ruleParams.runtime.baseEnvSeams."
1202
+ scope: runtime
1203
+ kinds: [check]
1204
+ gates: [land, runtime]
1205
+ failureCodes: ["RT_BASE_IMPURE"]
1206
+ enforcers:
1207
+ - {kind: runtime, id: base-pure, at: scripts/hfs/runtime-rules/base-pure.mjs}
1208
+ - id: R117
1209
+ code: "RT_API_SHAPE"
1210
+ law: "A call file of scripts/api/<system>/ exports exactly one function named in camelCase after the file; only the files of its own system import its lib.mjs; no api system imports another; a system with a calls contract has a call file only for a call id the contract declares."
1211
+ scope: runtime
1212
+ kinds: [check]
1213
+ gates: [land, runtime]
1214
+ failureCodes: ["RT_API_SHAPE"]
1215
+ enforcers:
1216
+ - {kind: runtime, id: api-shape, at: scripts/hfs/runtime-rules/api-shape.mjs}
1217
+ - id: R118
1218
+ code: "RT_SPEC_PLACEMENT"
1219
+ law: "A runtime spec is tests/<area>/<module>[.<topic>].spec.mjs, shared spec code is tests/helpers/<name>.mjs, preloads tests/setup/<name>.mjs and data tests/fixtures/**; a package spec sits beside its source as <name>.spec.*; no file uses the .test. suffix."
1220
+ scope: runtime
1221
+ kinds: [check]
1222
+ gates: [land, runtime]
1223
+ failureCodes: ["RT_SPEC_PLACEMENT"]
1224
+ enforcers:
1225
+ - {kind: runtime, id: spec-placement, at: scripts/hfs/runtime-rules/test-layout.mjs}
1226
+ - id: R119
1227
+ code: "RT_SOURCE_NAME"
1228
+ law: "A runtime source file is named <kebab-name>.mjs, never a one-off of ruleParams.runtime.oneOffNames (_*, tmp-*, fix-*, *-backfill, migrate-*, *-old, *.bak*), and its basename is unique inside its tier except lib.mjs and index.mjs."
1229
+ scope: runtime
1230
+ kinds: [check]
1231
+ gates: [land, runtime]
1232
+ failureCodes: ["RT_SOURCE_NAME"]
1233
+ enforcers:
1234
+ - {kind: runtime, id: source-name, at: scripts/hfs/runtime-rules/source-name.mjs}
1235
+ - id: R120
1236
+ code: "RT_RETIRED_PRESENT"
1237
+ law: "No path of retired[] and no `from` of moved[] in modules/kernel/retired-paths.yaml is tracked again, and no symbol of its retiredSymbols[] is declared in runtime production code."
1238
+ scope: runtime
1239
+ kinds: [check]
1240
+ gates: [land, runtime]
1241
+ failureCodes: ["RT_RETIRED_PRESENT"]
1242
+ enforcers:
1243
+ - {kind: runtime, id: retired-present, at: scripts/hfs/runtime-rules/retired.mjs}
1244
+ - id: R121
1245
+ code: "RT_GENERATED_DRIFT"
1246
+ law: "A generated root of ruleParams.runtime.generated is git-ignored output of its declared generatedBy: the repo's check regenerates every root, the runtime copies (packages/hfs/runtime, packages/eslint/be/runtime, packages/eslint/fe/runtime) are judged equal to what scripts/hfs/sync-runtime.mjs writes from the single source, and no git-tracked path lies under a generated root."
1247
+ scope: runtime
1248
+ kinds: [check, codemod]
1249
+ gates: [land, runtime]
1250
+ failureCodes: ["RT_GENERATED_DRIFT", "GENERATED_UNTRACKED"]
1251
+ enforcers:
1252
+ - {kind: runtime, id: generated-drift, at: scripts/hfs/sync-runtime.mjs}
1253
+ - {kind: runtime, id: generated-untracked, at: scripts/hfs/runtime-rules/generated-untracked.mjs}
1254
+ - id: R122
1255
+ code: "RT_CITED_PATH_MISSING"
1256
+ law: "A runtime path cited by live prose or contracts (modules outside the contract history, docs, skills, knowledge, CONTEXT.md, README.md, CONTRIBUTING.md, init, the ui docs) exists; a retired or moved path is cited only by history."
1257
+ scope: runtime
1258
+ kinds: [check]
1259
+ gates: [land, runtime]
1260
+ failureCodes: ["RT_CITED_PATH_MISSING"]
1261
+ enforcers:
1262
+ - {kind: runtime, id: cited-path, at: scripts/checks/check-contract-cites.mjs}
1263
+ - id: R124
1264
+ code: "RT_PINNED_PATH_MOVED"
1265
+ law: "A pinned path of ruleParams.runtime.pinned (persisted outside git) exists, or moved through a modules/kernel/retired-paths.yaml moved[] entry marked quiesced: true, landed with the workers stopped."
1266
+ scope: runtime
1267
+ kinds: [check]
1268
+ gates: [land, runtime]
1269
+ failureCodes: ["RT_PINNED_PATH_MOVED"]
1270
+ enforcers:
1271
+ - {kind: runtime, id: pinned-path, at: scripts/hfs/runtime-rules/retired.mjs}
1272
+ - id: R125
1273
+ code: "RT_NODE_MODULES_LINK"
1274
+ law: "No runtime source creates a junction or a symlink for a node_modules folder, through node:fs (symlink, symlinkSync, directly or through a local wrapper) or a spawned link command (mklink /J or /D, New-Item -ItemType Junction or SymbolicLink, ln -s): every checkout installs its own dependencies with a real npm ci from the cache."
1275
+ scope: runtime
1276
+ kinds: [check]
1277
+ gates: [land, runtime]
1278
+ failureCodes: ["RT_NODE_MODULES_LINK"]
1279
+ enforcers:
1280
+ - {kind: runtime, id: node-modules-link, at: scripts/hfs/runtime-rules/node-modules-link.mjs}
1281
+ - id: R126
1282
+ code: "RT_CONTROL_CHARACTER"
1283
+ law: "Tracked text source of the runtime (*.mjs, *.cjs, *.js, *.ts, *.tsx, *.yaml, *.yml, *.md, *.json outside the generated copies) holds no raw control character (U+0000 to U+001F except tab, LF and CR, and U+007F); the character is written as its escape."
1284
+ scope: runtime
1285
+ kinds: [check]
1286
+ gates: [land, runtime]
1287
+ failureCodes: ["RT_CONTROL_CHARACTER"]
1288
+ enforcers:
1289
+ - {kind: runtime, id: control-characters, at: scripts/hfs/runtime-rules/control-chars.mjs}
1290
+ - id: R127
1291
+ code: "RT_ABSOLUTE_PATH"
1292
+ law: "No tracked file of the runtime holds a hard-coded absolute host path: a drive-letter path, a user-profile path (/Users/<name>/, /home/<name>/) or an expanded AppData path. Paths resolve from the runtime root, os.tmpdir(), the config or a state-root helper, and a spec builds every path and prompt fixture from its temp dir; %LOCALAPPDATA% as an unexpanded name is clean. In source the rule reads string and template literals and comments through the syntax tree (a URL and a generic drive-pattern regular expression are not paths); in yaml, json and md it reads line text."
1293
+ scope: runtime
1294
+ kinds: [check]
1295
+ gates: [land, runtime]
1296
+ failureCodes: ["RT_ABSOLUTE_PATH"]
1297
+ enforcers:
1298
+ - {kind: runtime, id: absolute-path, at: scripts/hfs/runtime-rules/absolute-path.mjs}
1299
+ - id: R141
1300
+ code: "BE_COOKIE_ATTRIBUTES"
1301
+ law: "A cookie a back-end door writes through the response of Express or Fastify (`cookie`, `setCookie`) states `httpOnly: true`, `secure: true` and `sameSite` `lax` or `strict` in options typed with those literals, and `Set-Cookie` is never written as a header (`setHeader`, `append`, `header`); the call is recognised by where its signature is declared, never by the receiver's name."
1302
+ kinds: [lint]
1303
+ gates: [pre-commit, pre-push, settle, land, ci]
1304
+ failureCodes: ["BE_COOKIE_ATTRIBUTES"]
1305
+ enforcers:
1306
+ - {kind: eslint-be, id: response-cookie-attributes}
1307
+ - id: R142
1308
+ code: "BE_AMBIENT_ID"
1309
+ law: "`randomUUID` of Node's crypto (named, namespace or global), the `v1`, `v4`, `v6` and `v7` generators of `uuid`, `nanoid` and `ulid` are used only inside `platform/ids`, specs included; code asks the injected `Ids` port (`ids.next()`) and specs use `fakeIds()`."
1310
+ kinds: [lint, codemod]
1311
+ gates: [pre-commit, pre-push, settle, land, ci]
1312
+ failureCodes: ["BE_AMBIENT_ID"]
1313
+ enforcers:
1314
+ - {kind: eslint-be, id: no-ambient-id}
1315
+ - id: R143
1316
+ code: "HFS_MONO_WORKSPACES"
1317
+ law: "Every app is a monorepo, even with one service: the app root package.json declares `workspaces` exactly `[\"fe/apps/*\", \"fe/packages/*\"]`, `packageManager` as `npm@<exact version>` and `turbo` in its devDependencies (at the canon pin), the app root holds the one `package-lock.json` and the managed `turbo.json` (app.task-graph: build, dev, lint and typecheck, build depending on `^build` with cached `.next` and `dist` outputs)."
1318
+ kinds: [check]
1319
+ gates: [settle, land, ci]
1320
+ failureCodes: ["HFS_MONO_WORKSPACES"]
1321
+ enforcers:
1322
+ - {kind: hfs, id: monorepo, at: scripts/hfs/rules/monorepo.mjs}
1323
+ - id: R144
1324
+ code: "HFS_MONO_FE_WORKSPACE"
1325
+ law: "Every fe app `fe/apps/<app>` is an npm workspace: its `package.json` is named `@<project>/<app>`, is private, declares its own runtime dependencies and has exactly the scripts `build: next build`, `dev: next dev`, `lint: starci app lint --workspace .`, `start: next start` and `typecheck: tsc --noEmit`; every fe package `fe/packages/<pkg>` is private with `build`, `typecheck` and the same workspace `lint` script."
1326
+ kinds: [check]
1327
+ gates: [settle, land, ci]
1328
+ failureCodes: ["HFS_MONO_FE_WORKSPACE"]
1329
+ enforcers:
1330
+ - {kind: hfs, id: monorepo, at: scripts/hfs/rules/monorepo.mjs}
1331
+ - id: R145
1332
+ code: "HFS_MONO_NEST_PROJECTS"
1333
+ law: "The back end is a Nest monorepo of `be/apps/<app>`, even with one service: `be/nest-cli.json` is `\"monorepo\": true`, its default project (`root`, `sourceRoot`) is an api app, and its `projects` are exactly the be apps hfs.json declares, each `{\"type\": \"application\", \"root\": \"apps/<app>\", \"sourceRoot\": \"apps/<app>/src\"}`."
1334
+ kinds: [check]
1335
+ gates: [settle, land, ci]
1336
+ failureCodes: ["HFS_MONO_NEST_PROJECTS"]
1337
+ enforcers:
1338
+ - {kind: hfs, id: monorepo, at: scripts/hfs/rules/monorepo.mjs}
1339
+ - id: R146
1340
+ code: "HFS_MONO_WORKSPACE_DEP"
1341
+ law: "An fe workspace declares every package its files import (read with TypeScript's import pre-processor, its tsconfig path aliases excluded) in its own `package.json`, a sibling workspace package at `\"*\"`; the app root `package.json` never declares a workspace package."
1342
+ kinds: [check]
1343
+ gates: [settle, land, ci]
1344
+ failureCodes: ["HFS_MONO_WORKSPACE_DEP"]
1345
+ enforcers:
1346
+ - {kind: hfs, id: monorepo, at: scripts/hfs/rules/monorepo.mjs}
1347
+ - id: R147
1348
+ code: "BE_CLI_REQUIRED"
1349
+ law: "Every one-off action of a back end lives in ONE app, `be/apps/cli` (kind cli, named cli): a back end that declares a connection (its migrations run as `cli migrate run`) or tracks a command under `src/features/cli/` declares exactly that app, and tracks its image `be/apps/cli/Dockerfile` (one image, run as `cli <group> <command>`)."
1350
+ kinds: [check]
1351
+ gates: [settle, land, ci]
1352
+ failureCodes: ["BE_CLI_REQUIRED"]
1353
+ enforcers:
1354
+ - {kind: hfs, id: cli, at: scripts/hfs/rules/cli.mjs}
1355
+ - id: R148
1356
+ code: "BE_CLI_BOOTSTRAP"
1357
+ law: "The cli app boots in its `main.ts` with `CommandFactory.run(...)` of nest-commander and never serves: `NestFactory.create*` of `@nestjs/core`, a `.listen()`, a microservice connection, an `@nestjs/platform-*` adapter or `@nestjs/microservices` in the cli app is refused (by the import that binds each name)."
1358
+ kinds: [lint]
1359
+ gates: [pre-commit, pre-push, settle, land, ci]
1360
+ failureCodes: ["BE_CLI_BOOTSTRAP"]
1361
+ enforcers:
1362
+ - {kind: eslint-be, id: cli-bootstrap}
1363
+ - id: R149
1364
+ code: "BE_CLI_COMMAND_SHAPE"
1365
+ law: "A nest-commander command (`@Command` or `@SubCommand`, by the import that binds the decorator) is declared only in the cli feature root `src/features/cli/`: a group `@Command` in `<group>/<group>.cli.ts`, a `@SubCommand` extending `CommandRunner` in `<group>/subs/<name>.cli.ts`, one command per file, and every sub-command has its unit spec `<name>.cli.spec.ts` beside it (a command is an action runner, not a thin transport: R88 does not apply to it). `.cli.ts`, because `.command.ts` is the CQRS message."
1366
+ kinds: [lint]
1367
+ gates: [pre-commit, pre-push, settle, land, ci]
1368
+ failureCodes: ["BE_CLI_COMMAND_SHAPE"]
1369
+ enforcers:
1370
+ - {kind: eslint-be, id: cli-command-shape}
1371
+ - {kind: eslint-be, id: unit-test-colocated}
1372
+ - id: R150
1373
+ code: "BE_CLI_OWNER"
1374
+ law: "`nest-commander` is imported only by the cli app and the cli feature root, and no back-end source parses command-line arguments itself: `process.argv` and the parsers commander, yargs and minimist are refused everywhere; a one-off action anywhere but a cli command is a door the rule closes."
1375
+ kinds: [lint]
1376
+ gates: [pre-commit, pre-push, settle, land, ci]
1377
+ failureCodes: ["BE_CLI_OWNER"]
1378
+ enforcers:
1379
+ - {kind: eslint-be, id: cli-owner}
1380
+ - id: R151
1381
+ code: "BE_EVENT_CLASS_CONTRACT"
1382
+ law: "Every typed event class `be/src/modules/events/<service>/<event>.event.ts` (one class with literal `static readonly eventName` and `version`, in a file named after its event: dots become dashes) equals an entry of the vendored contract `be/contracts/<service>/events.json` at the same name and version, and every entry of a vendored contract has its class; a class the contract does not list, a version mismatch, a contract entry with no class or a misnamed file is a finding (pattern event-bus)."
1383
+ kinds: [check, codemod]
1384
+ gates: [settle, land, ci]
1385
+ failureCodes: ["BE_EVENT_CLASS_CONTRACT"]
1386
+ editions: [full]
1387
+ enforcers:
1388
+ - {kind: hfs, id: event-class-contract, at: scripts/hfs/rules/event-bus.mjs}
1389
+ - id: R152
1390
+ code: "BE_OUTBOX_WRITE_TX"
1391
+ law: "The outbox is written only by a domain service, with the manager of its own transaction: `eventBus.publish(event, tx)` and a queue producer's `enqueueX(payload, tx)` are called only in a file of `modules/domain` (the test world, e2e and fixtures aside), and `tx` is the `manager` parameter of a `.transaction(async (manager) => ...)` callback or a function parameter typed `EntityManager`; the injected shared manager, an alias, an outer manager or a missing argument is a finding. The call is found by the receiver's TYPE (the `EventBus` port of platform/event-bus) or by the resolved signature's declaring tier (queues), never by a name."
1392
+ kinds: [lint]
1393
+ gates: [pre-commit, pre-push, settle, land, ci]
1394
+ failureCodes: ["BE_OUTBOX_WRITE_TX"]
1395
+ editions: [full]
1396
+ enforcers:
1397
+ - {kind: eslint-be, id: outbox-write-tx}
1398
+ - id: R153
1399
+ code: "BE_EVENT_CONSUMER_SHAPE"
1400
+ law: "A class that implements the `EventConsumer` port of platform/event-bus (by type origin) is `transport/message/<event>.consumer.ts` (slot be.transport.message), its file stem is the kebab form of its event class's literal `eventName` (dots become dashes), and its `handle` reads `delivery.eventId` so the dispatched command carries the id the domain service claims in platform/inbox; a `.consumer.ts` that implements no `EventConsumer` is a finding."
1401
+ kinds: [lint]
1402
+ gates: [pre-commit, pre-push, settle, land, ci]
1403
+ failureCodes: ["BE_EVENT_CONSUMER_SHAPE"]
1404
+ editions: [full]
1405
+ enforcers:
1406
+ - {kind: eslint-be, id: event-consumer-shape}
1407
+ - id: R154
1408
+ code: "BE_QUEUE_PRODUCER_SHAPE"
1409
+ law: "In `modules/queues/<queue>/<queue>.queue.ts` every exported function or class method that writes the `QueueOutbox` port of platform/queue declares a `tx` parameter typed `EntityManager` and passes that parameter as the first argument of the write; a write with another manager, an outer manager or none is a finding (pattern queue)."
1410
+ kinds: [lint]
1411
+ gates: [pre-commit, pre-push, settle, land, ci]
1412
+ failureCodes: ["BE_QUEUE_PRODUCER_SHAPE"]
1413
+ editions: [full]
1414
+ enforcers:
1415
+ - {kind: eslint-be, id: queue-producer-shape}
1416
+ - id: R155
1417
+ code: "BE_PATTERN_SPEC_MISSING"
1418
+ law: "Every pattern a back end declares (hfs.json sides.be.patterns) is proven on the test world: each scenario id of ruleParams.be.patternScenarios.<pattern> has a test titled `<pattern>/<scenario>: ...` (the first argument of `it(` or `test(`) in an e2e or integration spec under be/src/tests; a declared pattern with a missing scenario is a finding."
1419
+ kinds: [check]
1420
+ gates: [land, ci]
1421
+ failureCodes: ["BE_PATTERN_SPEC_MISSING"]
1422
+ editions: [full]
1423
+ enforcers:
1424
+ - {kind: hfs, id: pattern-spec-missing, at: scripts/hfs/rules/event-bus.mjs}
1425
+ - id: R156
1426
+ code: "BE_KIND_ISOLATION"
1427
+ law: "No trigger kind imports another: a feature whose owner slot names one `trigger` (api, webhooks, realtime, saga, reactors, jobs, cli) never imports (static, dynamic, re-export or type-only) a feature of another kind; each kind imports only `modules/*` and only be/apps/* compose features. The kinds meet through the event bus: a domain service publishes `eventBus.publish(event, tx)` and the other kind consumes it. The importing and imported owners are read from the slot view (slot field `trigger`), never from a folder name; the finding replaces BE_FEATURE_IMPORTS_FEATURE when both features name different kinds."
1428
+ kinds: [lint, design]
1429
+ gates: [settle, land, ci]
1430
+ failureCodes: ["BE_KIND_ISOLATION"]
1431
+ enforcers:
1432
+ - {kind: eslint-be, id: kind-isolation}
1433
+ - {kind: machine, id: tiers, at: scripts/hfs/architecture/tiers.mjs}
1434
+ - id: R157
1435
+ code: "BE_JOB_WRITE_OUTSIDE_OWNER"
1436
+ law: "The job entity (the class `platform/jobs` declares in its persistence) is written only by `platform/jobs`: an `update`, `increment`, `decrement`, `save`, `insert`, `upsert`, `delete`, `remove`, `softDelete` or a query-builder write on it in any other file (the test world, e2e and fixtures aside) is a finding. The entity is found by the type origin of the call's arguments or builder chain, never by a name; raw SQL is judged by BE_SQL_TABLE_OWNER (pattern fenced-job)."
1437
+ kinds: [lint]
1438
+ gates: [pre-commit, pre-push, settle, land, ci]
1439
+ failureCodes: ["BE_JOB_WRITE_OUTSIDE_OWNER"]
1440
+ editions: [full]
1441
+ enforcers:
1442
+ - {kind: eslint-be, id: job-write-owner}
1443
+ - id: R158
1444
+ code: "BE_JOB_FENCE_REQUIRED"
1445
+ law: "Every `JobClaims` method that takes a job (a parameter type with `jobId`) declares a required, non-nullable `expectedFencingToken: number`; no call on `JobClaims` outside `platform/jobs` casts around it (`as`, a type assertion or `!` in an argument); and a `catch` around a `JobClaims` write rethrows, so a `JobFencedOut` stops the zombie with no further effect (pattern fenced-job)."
1446
+ kinds: [lint]
1447
+ gates: [pre-commit, pre-push, settle, land, ci]
1448
+ failureCodes: ["BE_JOB_FENCE_REQUIRED"]
1449
+ editions: [full]
1450
+ enforcers:
1451
+ - {kind: eslint-be, id: job-fence-required}
1452
+ - id: R159
1453
+ code: "BE_JOB_RUN_KEY"
1454
+ law: "In a job step (slot be.feature.jobs.steps) every call on a receiver typed by an `integrations` owner passes an argument of the `RunKey` type of `platform/jobs`, produced by `JobClaims.runKey(job, step)` (it includes the fencing token), never built with a cast; a read belongs in the application layer, not in a step (pattern fenced-job)."
1455
+ kinds: [lint]
1456
+ gates: [pre-commit, pre-push, settle, land, ci]
1457
+ failureCodes: ["BE_JOB_RUN_KEY"]
1458
+ editions: [full]
1459
+ enforcers:
1460
+ - {kind: eslint-be, id: job-run-key}
1461
+ - id: R160
1462
+ code: "BE_JOB_SHAPE"
1463
+ law: "A `<job>.processor.ts` exists only in `features/jobs/<job>/transport/queue/`, is named after its job and declares a class extending the `FencedProcessor` of `platform/jobs`; a `steps/<step>.step.ts` exists only in `features/jobs/<job>/steps/` and declares a class implementing `JobStep`; a class implementing `JobStep` exists nowhere else (pattern fenced-job)."
1464
+ kinds: [lint]
1465
+ gates: [pre-commit, pre-push, settle, land, ci]
1466
+ failureCodes: ["BE_JOB_SHAPE"]
1467
+ editions: [full]
1468
+ enforcers:
1469
+ - {kind: eslint-be, id: job-shape}
1470
+ - id: R161
1471
+ code: "BE_PROJECTION_WRITE_OWNER"
1472
+ law: "A projection's entity (a class of `<name>.projection-entity.ts` in `modules/projections/<name>/`) is written only by the `<name>.projection.ts` of the same folder (the test world, e2e and fixtures aside): a write on it from a domain service, a handler, another projection or another file of the folder is a finding. The entity is found by the type origin of the write's arguments or builder chain (pattern projection)."
1473
+ kinds: [lint]
1474
+ gates: [pre-commit, pre-push, settle, land, ci]
1475
+ failureCodes: ["BE_PROJECTION_WRITE_OWNER"]
1476
+ editions: [full]
1477
+ enforcers:
1478
+ - {kind: eslint-be, id: projection-write-owner}
1479
+ - id: R162
1480
+ code: "BE_PROJECTION_SHAPE"
1481
+ law: "Every public method of a `<name>.projection.ts` class is `recompute*` (idempotent write from the source facts) or `get*` (read); the `index.ts` of a projection exports nothing from a `.projection-entity` file; and a feature whose kind is api calls no `recompute*` method of a projection (it reads through `get*`). The kind is read from the owner slot's `trigger` (pattern projection)."
1482
+ kinds: [lint]
1483
+ gates: [pre-commit, pre-push, settle, land, ci]
1484
+ failureCodes: ["BE_PROJECTION_SHAPE"]
1485
+ editions: [full]
1486
+ enforcers:
1487
+ - {kind: eslint-be, id: projection-shape}
1488
+ - id: R163
1489
+ code: "HFS_SERVICE_PLACEMENT"
1490
+ law: "A back-end service lives only at `be/apps/<service>/`: a folder with a `Dockerfile` of its own, or with a `package.json` other than the app root's and those of the fe workspaces (`fe/apps/<app>`, `fe/packages/<pkg>`), outside `be/apps/<service>/` (and outside a front end's `fe/apps/<app>/Dockerfile`) is a second service root and a finding; a service is a Nest app of the one repository that shares the `be/src` libraries and the one package.json, and its image is built from its own Dockerfile."
1491
+ kinds: [check]
1492
+ gates: [land, ci]
1493
+ failureCodes: ["HFS_SERVICE_PLACEMENT"]
1494
+ enforcers:
1495
+ - {kind: hfs, id: services-placement, at: scripts/hfs/rules/services.mjs}
1496
+ - id: R164
1497
+ code: "HFS_IMAGE_UNPINNED"
1498
+ law: "In a product with more than one service (two or more be apps of kind api or worker) every component `image` of `.starcistacks/application-stacks.yaml` is pinned: a digest (`@sha256:<64 hex>`) or an exact version tag (`x.y.z`, optionally with a suffix); no tag, `latest`, a branch word or a major or minor-only tag is a finding. An own service image is `<repo>/<service>:<x.y.z>`, built from `be/apps/<service>/Dockerfile`; a service the product does not own stays a pinned third-party image."
1499
+ kinds: [check]
1500
+ gates: [land, ci]
1501
+ failureCodes: ["HFS_IMAGE_UNPINNED"]
1502
+ enforcers:
1503
+ - {kind: hfs, id: services-image-pins, at: scripts/hfs/rules/services.mjs}
1504
+ - id: R165
1505
+ code: "HFS_SERVICE_STACK_DECLARATION"
1506
+ law: "Every api or worker app `be/apps/<service>` of a product with more than one service is a component of the same name with `role: service` in `.starcistacks/application-stacks.yaml`."
1507
+ kinds: [check]
1508
+ gates: [land, ci]
1509
+ failureCodes: ["HFS_SERVICE_STACK_DECLARATION"]
1510
+ enforcers:
1511
+ - {kind: hfs, id: services-stack-declaration, at: scripts/hfs/rules/services.mjs}
1512
+ - id: R166
1513
+ code: "HFS_EVENT_CONTRACT"
1514
+ law: "The async contract between services is vendored under `be/contracts/<service>/events.json` (`starci/event-contract@1`, emitted by `starci app emit` from the literal `EVENTS` table of `be/apps/<service>/src/events.ts`, never written by hand) and equals that table; an event that undoes the step of another declares `compensates: \"<event>\"` and the named event must exist in a contract; a consumer declares what it reads in the literal `CONSUMES` table of `be/apps/<app>/src/consumes.ts` (service, event, version), every entry must exist in the provider's vendored snapshot at the same version, and a missing or stale snapshot, an unknown event or a version mismatch is a finding."
1515
+ kinds: [check, codemod]
1516
+ gates: [land, ci]
1517
+ failureCodes: ["HFS_EVENT_CONTRACT"]
1518
+ editions: [full]
1519
+ enforcers:
1520
+ - {kind: hfs, id: services-event-contract, at: scripts/hfs/rules/services.mjs}
1521
+ - id: R167
1522
+ code: "BE_ASYNC_SPEC_MISSING"
1523
+ law: "Every event an app consumes (an entry of its `consumes.ts`) is named by an e2e spec, a file `be/src/tests/e2e/<area>/*.e2e-spec.ts` that boots the apps through `useTestWorld` (it publishes the event, reads the effect back and redelivers it); when the contract of the event declares `compensates: \"<event>\"` it is a saga step, and a spec naming it also names the compensated event, so the spec drives the whole flow: the step, the failure, then the compensated state."
1524
+ kinds: [check]
1525
+ gates: [land, ci]
1526
+ failureCodes: ["BE_ASYNC_SPEC_MISSING"]
1527
+ editions: [full]
1528
+ enforcers:
1529
+ - {kind: hfs, id: services-async-specs, at: scripts/hfs/rules/services.mjs}
1530
+ - id: R168
1531
+ code: "BE_SERVICE_ISOLATION"
1532
+ law: "A service app (`be/apps/<service>/`) imports no file of a sibling service app: the app that owns the importing file and the app that owns the file a specifier resolves to (a relative path or an alias of the program's own `compilerOptions.paths`) are read from the HFS slot view, and two different apps is a finding; services share only the `be/src` libraries and meet through the wire (a client of the sibling's API, or the events of its vendored contract under `be/contracts/<service>/`)."
1533
+ kinds: [lint]
1534
+ gates: [pre-commit, pre-push, settle, land, ci]
1535
+ failureCodes: ["BE_SERVICE_ISOLATION"]
1536
+ enforcers:
1537
+ - {kind: eslint-be, id: service-isolation}
1538
+ - id: R169
1539
+ code: "BE_SAGA_STEP_COMPENSATION"
1540
+ law: "In a product that declares the `saga` pattern (`patterns: [\"saga\"]` in hfs.json sides.be) a saga is the folder `be/src/features/saga/<saga>/` (`<saga>.saga.ts` the orchestrator, `steps/<step>.step.ts`, `compensations/<step>.compensation.ts`): every step has a compensation of the same name and every compensation a step, the orchestrator imports every step and every compensation of its folder, and a folder of steps or compensations has an orchestrator; the folders themselves are slots enabled only by the declared pattern, so an undeclared saga is `HFS_SLOT_NOT_ENABLED`."
1541
+ kinds: [check]
1542
+ gates: [land, ci]
1543
+ failureCodes: ["BE_SAGA_STEP_COMPENSATION"]
1544
+ editions: [full]
1545
+ enforcers:
1546
+ - {kind: hfs, id: saga-step-compensation, at: scripts/hfs/rules/saga.mjs}
1547
+ - id: R170
1548
+ code: "BE_SAGA_STATE_VERSIONED"
1549
+ law: "Every saga orchestrator has `<saga>.saga-state.ts` beside it, whose exported state type declares `status` and `version: number`, and imports the fenced store of `platform/saga`: the persisted state of a run moves only through the store, every transition naming the version it read, so a zombie delivery cannot move a run twice."
1550
+ kinds: [check]
1551
+ gates: [land, ci]
1552
+ failureCodes: ["BE_SAGA_STATE_VERSIONED"]
1553
+ editions: [full]
1554
+ enforcers:
1555
+ - {kind: hfs, id: saga-state-versioned, at: scripts/hfs/rules/saga.mjs}
1556
+ - id: R171
1557
+ code: "BE_SAGA_EVENT_CONTRACT"
1558
+ law: "A saga step and a saga compensation each name their event (`readonly event = \"<name>\"`), both events are declared in a vendored contract (`be/contracts/<service>/events.json`), and the contract of the compensation's event declares `compensates: \"<event of the step of the same name>\"`."
1559
+ kinds: [check]
1560
+ gates: [land, ci]
1561
+ failureCodes: ["BE_SAGA_EVENT_CONTRACT"]
1562
+ editions: [full]
1563
+ enforcers:
1564
+ - {kind: hfs, id: saga-event-contract, at: scripts/hfs/rules/saga.mjs}
1565
+ - id: R172
1566
+ code: "BE_SAGA_CONSUMER_DEDUPE"
1567
+ law: "A consumer of a saga (`be/src/features/saga/<saga>/transport/message/<event>.consumer.ts`) passes the id of the delivery on (`message.eventId`) to the command it dispatches: the saga takes every event through the inbox (R80), so the consumer must hand it the dedupe key."
1568
+ kinds: [check]
1569
+ gates: [land, ci]
1570
+ failureCodes: ["BE_SAGA_CONSUMER_DEDUPE"]
1571
+ editions: [full]
1572
+ enforcers:
1573
+ - {kind: hfs, id: saga-consumer-dedupe, at: scripts/hfs/rules/saga.mjs}
1574
+ - id: R173
1575
+ code: "BE_SAGA_E2E_MISSING"
1576
+ law: "Every compensation path of a saga has an e2e spec (`be/src/tests/e2e/<area>/*.e2e-spec.ts` through `useTestWorld`) that names the event of the compensation and injects a failure through the world (an outage of an infra service or an app with `during`, `cut` or `latency`, a fake's `failNext`, `interruptDatabase`): the compensation is proven under a fault, not only on the happy flow."
1577
+ kinds: [check]
1578
+ gates: [land, ci]
1579
+ failureCodes: ["BE_SAGA_E2E_MISSING"]
1580
+ editions: [full]
1581
+ enforcers:
1582
+ - {kind: hfs, id: saga-e2e, at: scripts/hfs/rules/saga.mjs}
1583
+ - id: R174
1584
+ code: "BE_CONTEXT_OWNER"
1585
+ law: "A connection of hfs.json is a bounded context owned by one api or worker app (`owner`, `isolation` database or schema): an app composes only the connections and capabilities of the contexts it owns, and the migrate and cli apps compose every declared connection (migrations run only through them, once per connection)."
1586
+ kinds: [lint, design]
1587
+ gates: [settle, land, ci]
1588
+ failureCodes: ["BE_CONTEXT_OWNER"]
1589
+ enforcers:
1590
+ - {kind: eslint-be, id: context-owner}
1591
+ - id: R175
1592
+ code: "BE_CONTEXT_COUPLING"
1593
+ law: "Contexts are coupled only by events: no entity relation, no migration foreign key and no import (entities, services or SQL) of a domain or projection capability reaches a capability of another context."
1594
+ kinds: [lint, design]
1595
+ gates: [settle, land, ci]
1596
+ failureCodes: ["BE_CONTEXT_COUPLING"]
1597
+ enforcers:
1598
+ - {kind: eslint-be, id: context-coupling}
1599
+ - id: R176
1600
+ code: "BE_CONTEXT_TRANSACTION"
1601
+ law: "One transaction touches one context's connection: inside `transaction(async (manager) => ...)` of a connection's entity manager no entity manager of another connection is used and no capability of another context is called; work across contexts is a saga."
1602
+ kinds: [lint, design]
1603
+ gates: [settle, land, ci]
1604
+ failureCodes: ["BE_CONTEXT_TRANSACTION"]
1605
+ enforcers:
1606
+ - {kind: eslint-be, id: context-transaction}
1607
+ - id: R177
1608
+ code: "BE_CONTRACT_BREAKING"
1609
+ law: "An event contract evolves only additively: `be/contracts/<service>/events.json` against its pinned previous copy `events.pin.json` keeps every event, stream, version, payload field and type, adds only optional fields, and a breaking change is a new `<event>.v2` key."
1610
+ kinds: [check]
1611
+ gates: [settle, land, ci]
1612
+ failureCodes: ["BE_CONTRACT_BREAKING"]
1613
+ editions: [full]
1614
+ enforcers:
1615
+ - {kind: hfs, id: contract-compat, at: scripts/hfs/rules/contract-compat.mjs}
1616
+ - id: R178
1617
+ code: "BE_CONTEXT_PLATFORM_TABLES"
1618
+ law: "A platform capability the manifest lists as `perConnection` (the event-bus outbox, the job table) has its entities and migrations registered on every connection whose entity manager is passed to it."
1619
+ kinds: [lint, design]
1620
+ gates: [settle, land, ci]
1621
+ failureCodes: ["BE_CONTEXT_PLATFORM_TABLES"]
1622
+ enforcers:
1623
+ - {kind: eslint-be, id: context-platform-tables}
1624
+ - id: R179
1625
+ code: "BE_KIND_DECLARATION"
1626
+ law: "`hfs.json` `sides.be.kinds` lists exactly the trigger kinds (api, webhooks, realtime, saga, reactors, jobs, cli) the back end uses: a kind in use that is not declared, a declared kind with no feature, a declared kind whose patterns (`ruleParams.be.kindPatterns`, e.g. jobs needs fenced-job and queue) are missing from `sides.be.patterns`, and a declared kind whose platform capabilities (the tier-platform slots naming those patterns) are not tracked are each a finding. A kind is declared only when it is used; `starci app add <noun> <name>` creates the first member, the platform capabilities it needs and the registration."
1627
+ kinds: [check]
1628
+ gates: [settle, land, ci]
1629
+ failureCodes: ["BE_KIND_DECLARATION"]
1630
+ enforcers:
1631
+ - {kind: hfs, id: kind-declaration, at: scripts/hfs/rules/kinds.mjs}
1632
+ - id: R180
1633
+ code: "BE_KIND_EMPTY"
1634
+ law: "A kind folder `be/src/features/<kind>/` holds only instance folders `<kind>/<name>/`, and each instance holds TypeScript source: a file directly in the kind folder (a feature named like a kind, a `.gitkeep`) and an instance with no TypeScript file are findings; an empty kind folder is never kept."
1635
+ kinds: [check]
1636
+ gates: [settle, land, ci]
1637
+ failureCodes: ["BE_KIND_EMPTY"]
1638
+ enforcers:
1639
+ - {kind: hfs, id: kind-empty, at: scripts/hfs/rules/kinds.mjs}
1640
+ - id: R181
1641
+ code: "BE_SQL_RETURNING_SHAPE"
1642
+ law: "A `sql` constant that UPDATEs or DELETEs with RETURNING is wrapped as `WITH changed AS (UPDATE ... RETURNING ...) SELECT ... FROM changed`, because EntityManager.query returns a bare UPDATE or DELETE with RETURNING as `[rows, affectedCount]`, not as rows."
1643
+ kinds: [lint, design]
1644
+ gates: [settle, land, ci]
1645
+ failureCodes: ["BE_SQL_RETURNING_SHAPE"]
1646
+ enforcers:
1647
+ - {kind: eslint-be, id: sql-returning}
1648
+ - id: R182
1649
+ code: "BE_WEBHOOK_SHAPE"
1650
+ law: "A provider webhook door (`src/features/webhooks/<provider>/transport/http/<provider>.webhook.ts`) is one `@Controller` with one `@Post` handler declared `@Public({ reason: PublicReason.SignedWebhook })`; it injects only the `WebhookSignatureService` of `platform/http-security` and services of `modules/domain`, and its handler holds no branch, loop or `try`, calls only `verify` on the signature service and exactly one method of a domain service (the intake that records the delivery and publishes the event in its own transaction), and returns nothing."
1651
+ kinds: [lint]
1652
+ gates: [pre-commit, pre-push, settle, land, ci]
1653
+ failureCodes: ["BE_WEBHOOK_SHAPE"]
1654
+ enforcers:
1655
+ - {kind: eslint-be, id: webhook-shape}
1656
+ - id: R183
1657
+ code: "BE_WEBHOOK_UNVERIFIED"
1658
+ law: "The first statement of a webhook handler is the unconditional `verify` call of the `WebhookSignatureService` (signature and replay window): a delivery that fails the proof throws before any other code runs, so no path reaches the domain intake unproven."
1659
+ kinds: [lint]
1660
+ gates: [pre-commit, pre-push, settle, land, ci]
1661
+ failureCodes: ["BE_WEBHOOK_UNVERIFIED"]
1662
+ enforcers:
1663
+ - {kind: eslint-be, id: webhook-verify-first}
1664
+ - id: R184
1665
+ code: "BE_REALTIME_SHAPE"
1666
+ law: "A realtime file is the one door its role names: `<channel>.gateway.ts` holds one `@WebSocketGateway` class with `@SubscribeMessage` handlers and `<x>.subscription.ts` holds one `@Resolver` class with `@Subscription` operations, and neither carries a query, a mutation, a field resolver, a REST route or the other protocol."
1667
+ kinds: [lint]
1668
+ gates: [pre-commit, pre-push, settle, land, ci]
1669
+ failureCodes: ["BE_REALTIME_SHAPE"]
1670
+ enforcers:
1671
+ - {kind: eslint-be, id: realtime-shape}
1672
+ - id: R185
1673
+ code: "BE_REALTIME_WRITES"
1674
+ law: "A realtime door reads and pushes only: the one value injected into it is the `RealtimeHub` of `platform/realtime`; no `EntityManager`, bus, queue, event bus or domain service reaches it."
1675
+ kinds: [lint]
1676
+ gates: [pre-commit, pre-push, settle, land, ci]
1677
+ failureCodes: ["BE_REALTIME_WRITES"]
1678
+ enforcers:
1679
+ - {kind: eslint-be, id: realtime-read-only}
1680
+ - id: R186
1681
+ code: "BE_REALTIME_TOPIC_SCOPE"
1682
+ law: "The topic a realtime handler passes to `RealtimeHub.subscribe` is built from the handler's `@CurrentPrincipal()` parameter of `domain/identity`, so a client can only listen to channels its principal owns."
1683
+ kinds: [lint]
1684
+ gates: [pre-commit, pre-push, settle, land, ci]
1685
+ failureCodes: ["BE_REALTIME_TOPIC_SCOPE"]
1686
+ enforcers:
1687
+ - {kind: eslint-be, id: realtime-topic-scope}
1688
+ - id: R187
1689
+ code: "HFS_DOCKER_BUILD_CONTEXT"
1690
+ law: "Every app image is built from the app root: the header comment of an app's Dockerfile states `starci docker build <app>` for its own path, and no COPY or ADD source leaves the build context (a `..` segment or an absolute path)."
1691
+ kinds: [check]
1692
+ gates: [settle, land, ci]
1693
+ failureCodes: ["HFS_DOCKER_BUILD_CONTEXT"]
1694
+ enforcers:
1695
+ - {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
1696
+ - id: R188
1697
+ code: "HFS_DOCKER_STAGES"
1698
+ law: "An app Dockerfile is multi-stage: a stage named `build` and a last stage named `runtime`; the runtime stage ends as `USER node` and runs no build (`npm run build*`, tsc, next, turbo, nest); every install is `npm ci`, never `npm install`; a back-end runtime runs `npm ci --omit=dev --ignore-scripts` and a front-end runtime installs nothing."
1699
+ kinds: [check]
1700
+ gates: [settle, land, ci]
1701
+ failureCodes: ["HFS_DOCKER_STAGES"]
1702
+ enforcers:
1703
+ - {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
1704
+ - id: R189
1705
+ code: "HFS_DOCKER_ENTRY"
1706
+ law: "The runtime stage starts the app's own built entry in exec form (be `node be/dist/apps/<app>/src/main.js`, fe `node` on the standalone `server.js` of the app); an api or Next app sets `ENV PORT` and EXPOSEs the same port and carries a HEALTHCHECK, a worker carries a process HEALTHCHECK and exposes nothing, a cli app says `HEALTHCHECK NONE` and exposes nothing; a Next app's build stage runs `turbo run build --filter=@<project>/<app>` and its `next.config.ts` sets `output: \"standalone\"`."
1707
+ kinds: [check]
1708
+ gates: [settle, land, ci]
1709
+ failureCodes: ["HFS_DOCKER_ENTRY"]
1710
+ enforcers:
1711
+ - {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
1712
+ - id: R190
1713
+ code: "HFS_DOCKER_BASE_PIN"
1714
+ law: "Every FROM of an app Dockerfile is a prior stage, the one node base image of the canon (an exact node version on an exact alpine release, `NODE_IMAGE` in scripts/hfs/rules/docker.mjs), or an image pinned by `@sha256` digest; never a moving tag, never an unresolved build argument."
1715
+ kinds: [check]
1716
+ gates: [settle, land, ci]
1717
+ failureCodes: ["HFS_DOCKER_BASE_PIN"]
1718
+ enforcers:
1719
+ - {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
1720
+ - id: R191
1721
+ code: "HFS_DOCKER_SECRETS"
1722
+ law: "No secret enters an image: no COPY or ADD of an `.env*` file (except `.env.example`) or of a path under `.starcistacks`, `.secrets`, `.volume`, a key, certificate or kubeconfig; no ADD of a URL; no ARG or ENV whose name is a credential (PASSWORD, SECRET, TOKEN, KEY, CREDENTIAL, PRIVATE), `NEXT_PUBLIC_*` excepted because it is published by design."
1723
+ kinds: [check]
1724
+ gates: [settle, land, ci]
1725
+ failureCodes: ["HFS_DOCKER_SECRETS"]
1726
+ enforcers:
1727
+ - {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
1728
+ - id: R192
1729
+ code: "RT_FACT_FALSE"
1730
+ law: "Every claim of knowledge/hfs/facts.yaml holds against knowledge/hfs/slots.yaml (the one source of the product shape), and no prose of knowledge/**, docs/** or a README states the opposite of a fact (for example that an fe app lacks its own package.json, which fe.app.next requires); prose quotes a fact id or a slot id and never restates the claim."
1731
+ scope: runtime
1732
+ kinds: [check]
1733
+ gates: [land, runtime]
1734
+ failureCodes: ["RT_FACT_FALSE"]
1735
+ enforcers:
1736
+ - {kind: runtime, id: facts, at: scripts/hfs/runtime-rules/facts.mjs}
1737
+ - id: R193
1738
+ code: "RT_RULE_ID_UNKNOWN"
1739
+ law: "Every rule id (R<digits>) that tracked knowledge, docs, code or data names is a rule of this catalog, and the ids run from R01 to the last rule with no undeclared gap: an id with no rule is listed under `retired` with its reason (RT_RULE_ID_GAP), and every rule of the catalog is named by the `hfsRules:` of at least one knowledge/patterns topic or carries `scope: runtime` (RT_RULE_UNCITED). A retired id is a history name that only the contract changes and the changelogs may still spell; specs and generated copies are not read."
1740
+ scope: runtime
1741
+ kinds: [check]
1742
+ gates: [land, runtime]
1743
+ failureCodes: ["RT_RULE_ID_UNKNOWN", "RT_RULE_ID_GAP", "RT_RULE_UNCITED"]
1744
+ enforcers:
1745
+ - {kind: runtime, id: rule-ids, at: scripts/hfs/runtime-rules/rule-ids.mjs}
1746
+ - id: R194
1747
+ code: "RT_GENERATED_BLOCK_STALE"
1748
+ law: "The app map (section 4), the back-end source tree (5.1), the tier matrix (5.2), the front-end source tree (6.1) and the rule catalog (12) of knowledge/hfs/README.md are generated blocks, rendered by scripts/hfs/readme-blocks.mjs from knowledge/hfs/slots.yaml and rules.yaml between hfs:generated markers and equal to the render byte for byte; the README never restates them by hand. Likewise the `verification.automated` list of a pattern rule is the failure codes of the rules its `hfsRules` names, the `slot` of a `files:` entry is the slot that owns its path, and the `code` of a why-map entry is the code of the catalog rule that lists the lint rule: scripts/hfs/derived-fields.mjs writes them and they equal the derivation byte for byte."
1749
+ scope: runtime
1750
+ kinds: [check]
1751
+ gates: [land, runtime]
1752
+ failureCodes: ["RT_GENERATED_BLOCK_STALE"]
1753
+ enforcers:
1754
+ - {kind: runtime, id: generated-block, at: scripts/hfs/runtime-rules/generated-block.mjs}
1755
+ - id: R195
1756
+ code: "RT_PROSE_PATH_NO_SLOT"
1757
+ law: "Every product path the knowledge, the docs and the READMEs name (a be/ or fe/ path, whole or with placeholders, globs and braces) is owned by a slot of knowledge/hfs/slots.yaml or is a folder above one; a path no slot owns is prose that invented a place or kept one a slot no longer has. App names are free: a literal app name is one the examples or the starter declare, a placeholder stands for any; a ** glob and a topic file of the knowledge are not paths."
1758
+ scope: runtime
1759
+ kinds: [check]
1760
+ gates: [land, runtime]
1761
+ failureCodes: ["RT_PROSE_PATH_NO_SLOT"]
1762
+ enforcers:
1763
+ - {kind: runtime, id: prose-path, at: scripts/hfs/runtime-rules/prose-path.mjs}
1764
+ - id: R196
1765
+ code: "RT_PROSE_RESTATES_SLOTS"
1766
+ law: "Knowledge, docs and READMEs never restate knowledge/hfs/slots.yaml: no fenced block or line names three or more product paths a slot owns, and no sentence lists three or more of the root or side file names the slots declare; a slot is referenced by its id, and the only allowed copy is a generated block between hfs:generated markers. The slot and rule catalogs, the example READMEs and the templates are sources or snapshots, not prose."
1767
+ scope: runtime
1768
+ kinds: [check]
1769
+ gates: [land, runtime]
1770
+ failureCodes: ["RT_PROSE_RESTATES_SLOTS"]
1771
+ enforcers:
1772
+ - {kind: runtime, id: prose-restates, at: scripts/hfs/runtime-rules/prose-restates.mjs}
1773
+ - id: R197
1774
+ code: "RT_RETIRED_CLI_CALL"
1775
+ law: "No tracked file invokes a command the unified CLI removed: the retired kernel, hfs, test-stack and top-level runtime spellings listed in the `removed` fields of the catalog and in the dispatcher table; those fields, changelogs and contract-change history are exempt."
1776
+ scope: runtime
1777
+ kinds: [check]
1778
+ gates: [land, runtime]
1779
+ failureCodes: ["RT_RETIRED_CLI_CALL"]
1780
+ enforcers:
1781
+ - {kind: runtime, id: retired-cli, at: scripts/checks/check-retired-cli.mjs}
1782
+ - id: R198
1783
+ code: "RT_CLI_CATALOG_DRIFT"
1784
+ law: "Every generated output of the unified CLI catalog (packages/cli/src/catalog.generated.mjs, docs/cli.md, packages/cli/completions/*) equals what scripts/cli/gen-catalog.mjs writes from modules/cli/commands."
1785
+ scope: runtime
1786
+ kinds: [check]
1787
+ gates: [land, runtime]
1788
+ failureCodes: ["RT_CLI_CATALOG_DRIFT"]
1789
+ enforcers:
1790
+ - {kind: runtime, id: cli-catalog-drift, at: scripts/cli/gen-catalog.mjs}
1791
+ - id: R199
1792
+ code: "RT_CLI_VERB_PARITY"
1793
+ law: "The CLI catalog of modules/cli/commands and the implementation agree: every catalog verb has a handler and a doc, every kernel verb module and cli.mjs dispatch case has a catalog file, and every --flag a verb module names (usage, required, api-boolean-flags.txt) is a catalog flag."
1794
+ scope: runtime
1795
+ kinds: [check]
1796
+ gates: [land, runtime]
1797
+ failureCodes: ["RT_CLI_VERB_PARITY"]
1798
+ enforcers:
1799
+ - {kind: runtime, id: cli-parity, at: scripts/checks/check-cli-parity.mjs}
1800
+ - id: R200
1801
+ code: "RT_CLI_APP_ONLY_TEMPLATES"
1802
+ law: "Managed app templates and their generated runtime copies invoke product actions only through `starci app <verb>`; retired hfs and test-stack commands, and any other starci group, are forbidden there."
1803
+ scope: runtime
1804
+ kinds: [check]
1805
+ gates: [land, runtime]
1806
+ failureCodes: ["RT_CLI_APP_ONLY_TEMPLATES"]
1807
+ enforcers:
1808
+ - {kind: runtime, id: cli-app-only-templates, at: scripts/hfs/runtime-rules/app-cli-templates.mjs}
1809
+ - id: R201
1810
+ code: "CLI_ONLY_ENTRY"
1811
+ law: "Every action enters runtime code through `starci <group> <verb>`: tracked prompts, docs, skills, knowledge files, configs, CI files, hooks and scheduled-task registrations do not bypass the catalog through direct runtime scripts or package-script wrappers; the dispatcher and the specs' own spawn helpers are the declared exceptions."
1812
+ scope: runtime
1813
+ kinds: [check]
1814
+ gates: [land, runtime]
1815
+ failureCodes: ["CLI_ONLY_ENTRY"]
1816
+ enforcers:
1817
+ - {kind: runtime, id: cli-only-entry, at: scripts/checks/check-cli-only-entry.mjs}
1818
+ - id: R202
1819
+ code: "CI_UPLOAD_NOT_SILENT"
1820
+ law: "A coverage upload of a CI workflow never skips or fails silently: every step that uses codecov/codecov-action, in a root workflow, an example workflow or a template workflow of packages/hfs, authenticates by OIDC (`use_oidc: true`, with `permissions: id-token: write` on the job or the workflow), sets `fail_ci_if_error: true`, has no `continue-on-error`, and has no `if` that reads a secret or CODECOV_TOKEN, so a missing token can never turn the upload into a skipped step."
1821
+ scope: runtime
1822
+ kinds: [check]
1823
+ gates: [land, runtime]
1824
+ failureCodes: ["CI_UPLOAD_NOT_SILENT"]
1825
+ enforcers:
1826
+ - {kind: runtime, id: ci-upload, at: scripts/hfs/runtime-rules/ci-upload.mjs}
1827
+ - id: R203
1828
+ code: "BE_FEATURE_THIN"
1829
+ law: "A feature file (a role of ruleParams.be.thinRoles inside a slot of tier feature: handler, resolver, controller, consumer, processor, step, saga, saga-step, compensation, webhook, gateway, subscription, cli, mapper, the saga orchestrator service) maps its parameters and makes ONE delegating call into modules/ (a service, the command or query bus, the event bus, a queue producer): no branch, loop, ternary, try or throw and no computation. Business logic lives in modules/**, where every logic file owes 100 per file; orchestration (step order, compensation, claim, fencing, retry) lives in platform/*; the feature layer is proven by integration and e2e. The gate calls a kind requires are not delegation: a webhook's `verify` and a job step's `JobClaims` fence (`advance`, `runKey`); a logger call, a pure mapper function and a modules function in argument position are free too; a mapper may map a collection item by item (`map`) and write a date as text (`toISOString`), and a cli group command shows its help."
1830
+ kinds: [lint]
1831
+ gates: [pre-commit, pre-push, settle, land, ci]
1832
+ failureCodes: ["BE_FEATURE_THIN"]
1833
+ enforcers:
1834
+ - {kind: eslint-be, id: feature-thin}
1835
+ - id: R204
1836
+ code: "HFS_COVERAGE_SCOPE_DRIFT"
1837
+ law: "What a back end measures at 100 per file is derived once from the slot manifest (the `coverage: required|none` field of every be slot and ruleParams.be.logicRoles, scripts/hfs/coverage-scope.mjs), and the three files that state it are its render: be/jest.config.js (thresholds), the `sonar.coverage.exclusions` line of sonar-project.properties, and codecov.yml (paths and one component per service app plus platform, each at 100). A difference is drift; the scope is never edited by hand."
1838
+ kinds: [check, codemod]
1839
+ gates: [pre-push, land, ci]
1840
+ failureCodes: ["HFS_COVERAGE_SCOPE_DRIFT"]
1841
+ enforcers:
1842
+ - {kind: hfs, id: coverage-scope-drift, at: packages/hfs/sync/managed.mjs}
1843
+ - id: R205
1844
+ code: "RT_DOC_NO_OWNER"
1845
+ law: "Every tracked docs/*.md declares exactly one ownership header on its first line — \"Owner: <knowledge/ or modules/ path that exists>\" when the document explains a machine-owned source, \"Task: <one task>\" for a how-to — no two documents carry the same task, and two documents whose titles share two or more topic words while at least 30% of the shorter one's sentences appear in the other are one document merged, not two."
1846
+ scope: runtime
1847
+ kinds: [check]
1848
+ gates: [land, runtime]
1849
+ failureCodes: ["RT_DOC_NO_OWNER"]
1850
+ enforcers:
1851
+ - {kind: runtime, id: doc-owner, at: scripts/checks/check-doc-owner.mjs}
1852
+ - id: R206
1853
+ code: "RT_EXAMPLE_COUPLING"
1854
+ law: "Runtime source never couples to a product or its example: no tracked file under scripts/, engine/, packages/, ui/src or ui/api - and no managed template under packages/hfs/templates/ - spells a literal examples/<name>, one of the product names this runtime shipped against (the closed PRODUCT_NAMES list of scripts/lib/example-refs.mjs), an inc-<hash> a product name prefixes, or a host drive path. Specs, tests, the generated runtime copies, contract-change history, changelogs and .starciwork records are out of scope; a read the runtime genuinely makes at run time is declared once in scripts/lib/example-refs.mjs and every consumer names that constant."
1855
+ scope: runtime
1856
+ kinds: [check]
1857
+ gates: [land, runtime]
1858
+ failureCodes: ["RT_EXAMPLE_COUPLING", "RT_PRODUCT_NAME_IN_SOURCE", "RT_HOST_PATH_IN_TEMPLATE"]
1859
+ enforcers:
1860
+ - {kind: runtime, id: example-coupling, at: scripts/checks/check-example-coupling.mjs}
1861
+ - id: R207
1862
+ code: "RT_RETIRED_NAME_LIVE"
1863
+ law: "A name the retired registry declares dead never appears in a live tracked file — not in prose, a comment, a string literal or a path: every retired[].path and moved[].from of modules/kernel/retired-paths.yaml, and every retiredNames[] naming that is not a path (a deleted app, a layer naming, a verb prefix). History (the contract changes, a changelog, a benchmark finding, a .starciwork record), the registry itself and the files of the check may name what was deleted; a slot manifest's forbids value or forbidden-presence tombstone declares a refusal, not a use."
1864
+ scope: runtime
1865
+ kinds: [check]
1866
+ gates: [land, runtime]
1867
+ failureCodes: ["RT_RETIRED_NAME_LIVE"]
1868
+ enforcers:
1869
+ - {kind: runtime, id: retired-names, at: scripts/checks/check-retired-names.mjs}
1870
+ - id: R208
1871
+ code: "RT_PORT_RESTATED"
1872
+ law: "Every port literal of the runtime is spelled once, by its owner, and every other file references the owner: the harness UI ports live in modules/models/runtimes.yaml statusApp.port/devPort read through ui/ports.mjs, the local SonarQube host in scripts/gates/sonar-local.mjs DEFAULT_HOST. No in-scope file that is not the owner spells an owned literal in a port position (a scheme://host:NNNN or host:NNNN authority, a *port*/PORT assignment, a listen(NNNN) call), and an unowned literal in a port position of two or more runtime source files gets one owning exported constant the rest reference. Specs, tests, node_modules, the generated runtime copies, contract-change history, changelogs, .starciwork and the product stack declarations (.starcistacks, starcistacks-services - a product owns the ports of the services it runs) are out of scope."
1873
+ scope: runtime
1874
+ kinds: [check]
1875
+ gates: [land, runtime]
1876
+ failureCodes: ["RT_PORT_RESTATED"]
1877
+ enforcers:
1878
+ - {kind: runtime, id: port-once, at: scripts/checks/check-port-once.mjs}
1879
+ - id: R209
1880
+ code: "RT_CONFIG_DEFAULT_TWICE"
1881
+ law: "Every documented configuration default is stated once, by its owner, and read through it: a camelCase leaf or lowercase key of config.example.yaml, or a value an owner declares - scripts/machine/home.mjs DEFAULTS (supervisor workers, the land gate, frozenMinutes, pollIntervalMs), engine/config.mjs (connector, ask, UAT, spec and allocation defaults, and DEFAULT_OWNER_LANGUAGE which scripts/machine/home.mjs re-exports), engine/orca-config.mjs (worker depth), scripts/reconciler/state.mjs (the controller mode) - is never restated as a literal fallback (?? or ||, a parameter default, or a language ternary) in any other runtime source file; the reader calls the owner accessor (supervisorSettings(), configuredMode(), ownerLanguage(), ...). Specs, tests, the generated runtime copies, stories and the HFS templates are out of scope, and a bare identifier or a computed value is a read, not a restatement."
1882
+ scope: runtime
1883
+ kinds: [check]
1884
+ gates: [land, runtime]
1885
+ failureCodes: ["RT_CONFIG_DEFAULT_TWICE"]
1886
+ enforcers:
1887
+ - {kind: runtime, id: default-once, at: scripts/checks/check-default-once.mjs}
1888
+ - id: R210
1889
+ code: "RT_VERSION_RESTATED"
1890
+ law: "knowledge/hfs/canon-pins.yaml spells the version of every @starci package and every canon-pinned dependency once; a package.json is the declared install site and a lockfile its resolution. Anywhere else a semver literal equal to a pin - or a name@x.y.z specifier naming one - restates it, except on the declared binding keys a refresher owns and rewrites in place (canon.version of the code-pattern bindings held by check-canon-pins.mjs, provenance.version and identity.version of the grammar snapshots rewritten by scripts/work/ui/grammar-knowledge.mjs). Package manifests, lockfiles, changelogs, contract-change history, specs, tests, the generated runtime copies and .starciwork are out of scope; a restatement is fixed by naming the pin, never the literal."
1891
+ scope: runtime
1892
+ kinds: [check]
1893
+ gates: [land, runtime]
1894
+ failureCodes: ["RT_VERSION_RESTATED"]
1895
+ enforcers:
1896
+ - {kind: runtime, id: version-pin-once, at: scripts/checks/check-version-pin-once.mjs}
1897
+ - id: R211
1898
+ code: "RT_SLOT_ID_SHAPE"
1899
+ law: "A slot id follows the grammar its manifest declares, read from the manifest not from a list: dot-separated lowercase kebab segments, a first segment of naming.prefixes in knowledge/hfs/slots.yaml (profiles in knowledge/hfs/runtime-slots.yaml), <side>.app.<kind> naming a kind of appKinds.<side>, and every deeper id extending a declared id - an explicit parent field, or a family at least two slots share; a lone deep orphan invents a family. The role-suffix vocabulary holds no synonym pairs: a profile's suffixes never share a word with bannedSuffixes, no naming.refusedPairs pair is live in suffixes on both sides, and naming.sameConceptPairs is the one declared exception (step + saga-step), live on both sides or dropped; naming.glossary binds the five incoming-thing names (consumer, subscription, webhook, processor, handler) to their distinct kinds."
1900
+ scope: runtime
1901
+ kinds: [check]
1902
+ gates: [land, runtime]
1903
+ failureCodes: ["RT_SLOT_ID_SHAPE"]
1904
+ enforcers:
1905
+ - {kind: runtime, id: slot-id-shape, at: scripts/checks/check-slot-id-shape.mjs}
1906
+ - id: R212
1907
+ code: "HFS_EDITION_INVALID"
1908
+ law: "hfs.json names an edition the manifest knows (full, or lite; absent means full), and a lite app holds none of what lite does not have: no spec or test file, test script, test dependency or test-tool config, no UAT record, no supabase/functions, no worker app, no event, queue, job, projection or saga pattern, no saga, reactors, jobs or realtime kind."
1909
+ kinds: [check]
1910
+ gates: [pre-push, settle, land, ci]
1911
+ failureCodes: ["HFS_EDITION_INVALID", "HFS_EDITION_FORBIDDEN_PRESENT"]
1912
+ enforcers:
1913
+ - {kind: hfs, id: edition, at: scripts/hfs/rules/edition.mjs}
1914
+ - id: R213
1915
+ code: "DB_MIGRATION_SHAPE"
1916
+ law: "A migration is `supabase/migrations/<ts14>_<kebab>.sql`; the stamp is a valid UTC instant not in the future, new migrations sort strictly after every migration on the base branch, and a migration already on the base is immutable (a diff or a removal is a finding); a file that does not parse as PostgreSQL is a shape failure."
1917
+ kinds: [check]
1918
+ gates: [pre-push, settle, land, ci]
1919
+ failureCodes: ["DB_MIGRATION_SHAPE"]
1920
+ enforcers:
1921
+ - {kind: hfs, id: db-migration-shape, at: scripts/hfs/rules/database.mjs}
1922
+ - id: R214
1923
+ code: "DB_RLS_REQUIRED"
1924
+ law: "Row level security is code a parser can read: every `create table` (or `create table as`) in an exposed schema (`public` plus the config.toml `[api] schemas`) is followed, in the same migration, by `alter table <t> enable row level security` (and `force` for a table hfs.json `supabase.forceRls` declares); no `DO` block or function runs dynamic SQL that builds DDL or grants; a write or `for all` policy never uses `(true)`, `to anon`/`to public` is legal only on a `for select` policy named `*_public_read`, every policy is named `<table>_<role>_<action>`, and `service_role` is never granted."
1925
+ kinds: [check, codemod]
1926
+ gates: [pre-push, settle, land, ci]
1927
+ failureCodes: ["DB_RLS_REQUIRED", "DB_DYNAMIC_DDL", "DB_POLICY_SHAPE"]
1928
+ enforcers:
1929
+ - {kind: hfs, id: db-rls, at: scripts/hfs/rules/database.mjs}
1930
+ - id: R215
1931
+ code: "DB_DEFINER_SAFE"
1932
+ law: "Privileged database objects are declared safely: a `security definer` function pins `search_path` (never `from current`), lives outside the exposed schemas or is `revoke execute ... from public` in the same migration, declares a return type and interpolates parameters into dynamic SQL only through `format('%I/%L')`; a bucket inserted into `storage.buckets` declares `public = false` (true only for an id ending `_public`), `file_size_limit` and `allowed_mime_types`, and has a `storage.objects` policy naming its `bucket_id` in the same migration."
1933
+ kinds: [check]
1934
+ gates: [pre-push, settle, land, ci]
1935
+ failureCodes: ["DB_DEFINER_SAFE", "DB_STORAGE_POLICY"]
1936
+ enforcers:
1937
+ - {kind: hfs, id: db-privileged-objects, at: scripts/hfs/rules/database.mjs}
1938
+ - id: R216
1939
+ code: "DB_CONFIG_POLICY"
1940
+ law: "The database configuration and its types are checked facts: `supabase/config.toml` carries no literal value under a credential key (the value is `env(NAME)`), `auth.jwt_expiry` is at most 3600, and signup, site url and redirect urls equal the Supabase block of hfs.json where it declares them; `supabase/types/database.types.ts` is committed and equals the text `contract:emit` regenerates from the migrations."
1941
+ kinds: [check]
1942
+ gates: [land, ci]
1943
+ failureCodes: ["DB_CONFIG_POLICY", "DB_TYPES_DRIFT"]
1944
+ enforcers:
1945
+ - {kind: hfs, id: db-config-types, at: scripts/hfs/rules/database.mjs}
1946
+ - id: R217
1947
+ code: "FE_SUPABASE_CLIENT_OWNER"
1948
+ law: "The database has one door per side: `@supabase/ssr` and `@supabase/supabase-js` are imported and called only by the fe db owner (`fe.modules.db`, `fe.package.db`) and, on the back end, by `be.integrations.supabase`, and every client carries the committed `Database` type; the front end has no route handler beyond health/live and the auth callback slot, and holds no credential: `fe/` names no service-role or `*_SECRET`, `*_PRIVATE`, `*_TOKEN`, `*_PASSWORD` value (public configuration is read once by `modules/config`)."
1949
+ kinds: [check, design]
1950
+ gates: [pre-push, settle, land, ci]
1951
+ failureCodes: ["FE_SUPABASE_CLIENT_OWNER", "BE_SUPABASE_CLIENT_OWNER", "FE_ROUTE_HANDLER_FORBIDDEN", "FE_SERVICE_ROLE_FORBIDDEN"]
1952
+ enforcers:
1953
+ - {kind: machine, id: supabase-client-owner, at: scripts/hfs/architecture/supabase.mjs}
1954
+ - {kind: machine, id: supabase-service-role, at: scripts/hfs/architecture/supabase.mjs}
1955
+ - id: R218
1956
+ code: "FE_DB_RESULT_TYPED"
1957
+ law: "A Supabase result is typed by `Database` inference and never swallowed: no explicit generic on `single`/`maybeSingle`/`returns`, no cast or non-null assertion on a result, every list read carries `.limit()` or a keyset bound, every awaited call reads `error` or hands the whole result to `toOutcome`, and a failure never becomes empty data."
1958
+ kinds: [check, design]
1959
+ gates: [pre-push, settle, land, ci]
1960
+ failureCodes: ["FE_DB_RESULT_TYPED", "FE_DB_ERROR_HANDLED"]
1961
+ enforcers:
1962
+ - {kind: machine, id: supabase-result, at: scripts/hfs/architecture/supabase.mjs}
1963
+ - id: R219
1964
+ code: "FE_AUTH_SESSION_TRUST"
1965
+ law: "The principal is verified, never trusted: server code never calls `auth.getSession()` (`getClaims()` through `getPrincipal()`, `auth.getUser()` for money, account security and role changes); a `write-*.ts` Server Action awaits `getPrincipal()` first, refuses an anonymous principal (the sign-in action is the one exception) and parses its input by a schema before any use; a back-end principal comes only from a Supabase access token verified against the project JWKS (signature, issuer, audience, expiry, pinned algorithm), never decoded unverified and never from the request body."
1966
+ kinds: [check, design]
1967
+ gates: [pre-push, settle, land, ci]
1968
+ failureCodes: ["FE_AUTH_SESSION_TRUST", "FE_DB_WRITE_SHAPE", "BE_SUPABASE_JWT_VERIFIED"]
1969
+ enforcers:
1970
+ - {kind: machine, id: supabase-principal, at: scripts/hfs/architecture/supabase.mjs}
1971
+ - id: R220
1972
+ code: "HFS_LITE_SECRET_CUSTODY"
1973
+ editions: [lite]
1974
+ law: "A lite app holds no secret material in the tree: root `.env*`, `secrets.env`, `.secrets/` and an `.enc` file outside `.starcistacks/<env>/secrets` are forbidden, and `fe/` and `supabase/` hold no Supabase token, JWT or connection string with a password; the one custody is the sealed `.starcistacks/<env>/secrets/<slug>.enc`."
1975
+ kinds: [check]
1976
+ gates: [pre-commit, pre-push, settle, land, ci]
1977
+ failureCodes: ["HFS_LITE_SECRET_CUSTODY"]
1978
+ enforcers:
1979
+ - {kind: hfs, id: lite-secret-custody, at: scripts/hfs/rules/supabase-secrets.mjs}
1980
+ - id: R221
1981
+ code: "CI_TRIGGERS_RELEASE_ONLY"
1982
+ law: "The only workflow triggers are a push of release tags and a person dispatching it: every tracked workflow of the runtime, its examples and the hfs app templates (so every scaffolded app inherits it) declares an `on` that holds only `push` filtered to `tags: ['v*']` (no branches, paths or ignore filter beside it) and `workflow_dispatch`. A branch push, a pull_request, a schedule, a workflow_call or any other event refuses the workflow: CI runs once per release, on its tag, and Codecov and Sonar upload only from that run."
1983
+ scope: runtime
1984
+ kinds: [check]
1985
+ gates: [land, runtime]
1986
+ failureCodes: ["CI_TRIGGERS_RELEASE_ONLY"]
1987
+ enforcers:
1988
+ - {kind: runtime, id: git-triggers, at: scripts/hfs/runtime-rules/git-triggers.mjs}
1989
+ - id: R222
1990
+ code: "RELEASE_NOTES"
1991
+ law: "A release tag `v<version>` on HEAD has its CHANGELOG.md section `## [<version>]` with no TODO, PENDING or TBD left in it and no `in preparation` mark: the tag message and the GitHub Release are that section, so a tag is never cut over an unfinished or missing section."
1992
+ scope: runtime
1993
+ kinds: [check]
1994
+ gates: [land, runtime]
1995
+ failureCodes: ["RELEASE_NOTES"]
1996
+ enforcers:
1997
+ - {kind: runtime, id: release-notes, at: scripts/hfs/runtime-rules/release-notes.mjs}
1998
+ - id: R223
1999
+ code: "RIGHTS_ROLE_DENIED"
2000
+ law: "By the caller's role, the command guard refuses git push, git tag, raw git commit/add/rm/mv, raw git synchronization writes, npm publish, a whole-suite run outside the release cut and unit.verify/e2e.verify, npm ci without the host lock, a release cut, and raw side-effecting tools; modules/kernel/command-policy.yaml is the one rule table read by both the PreToolUse guard and the PATH shim."
2001
+ scope: runtime
2002
+ kinds: [check]
2003
+ gates: [land, runtime]
2004
+ failureCodes: ["RIGHTS_ROLE_DENIED", "RIGHTS_GIT_PUSH", "RIGHTS_GIT_TAG", "RIGHTS_GIT_COMMIT", "RIGHTS_GIT_SYNC", "RIGHTS_NPM_PUBLISH", "RIGHTS_NPM_CI_UNLOCKED", "RIGHTS_SUITE_RUN", "RIGHTS_RELEASE_CUT", "RIGHTS_RAW_TOOL"]
2005
+ enforcers:
2006
+ - {kind: runtime, id: role-rights, at: scripts/hfs/runtime-rules/rights-policy.mjs}
2007
+ - id: R224
2008
+ code: "RIGHTS_PROTECTED_ZONE"
2009
+ law: "The protected zone declared once in modules/kernel/protected-zone.yaml is edited only by the owner path; a supervisor self-upgrade files an owner proposal, and an op never writes the .claude runtime checkout."
2010
+ scope: runtime
2011
+ kinds: [check]
2012
+ gates: [land, runtime]
2013
+ failureCodes: ["RIGHTS_PROTECTED_ZONE", "RIGHTS_OP_RUNTIME_WRITE"]
2014
+ enforcers:
2015
+ - {kind: runtime, id: protected-zone, at: scripts/hfs/runtime-rules/rights-policy.mjs}
2016
+ - id: R225
2017
+ code: "RT_HOOK_SHAPE"
2018
+ law: "The app hook templates keep the gate model: pre-commit is L0 only (no typecheck, no test run); pre-push checks the release gate (the starci-release L4 record, refs/backup/, v[0-9] tags) and runs no npm test, jest, typecheck or lint."
2019
+ scope: runtime
2020
+ kinds: [check]
2021
+ gates: [land, runtime]
2022
+ failureCodes: ["RT_HOOK_SHAPE", "RIGHTS_PUSH_NOT_RELEASE"]
2023
+ enforcers:
2024
+ - {kind: runtime, id: hook-shape, at: scripts/hfs/runtime-rules/hook-shape.mjs}
2025
+ - id: R226
2026
+ code: "RT_SPEC_OVER_BUDGET"
2027
+ law: "Every recorded spec duration should stay within the shared per-file land budget in modules/kernel/spec-durations.yaml: for alpha.4 an over-budget spec is an advisory info finding ranked by its excess (it does not fail the check stage; the budget turns blocking in alpha.5), and every recorded spec path must still exist. An over-budget spec is repaired without removing tests or assertions: share a per-process fixture, remove per-test install or boot, inject the clock and never lengthen a timeout; stale rows are refreshed from the current land record."
2028
+ scope: runtime
2029
+ kinds: [check]
2030
+ gates: [land, runtime]
2031
+ failureCodes: ["RT_SPEC_OVER_BUDGET", "RT_SPEC_BUDGET_STALE"]
2032
+ enforcers:
2033
+ - {kind: runtime, id: spec-budget, at: scripts/checks/check-spec-budget.mjs}
2034
+ - id: R227
2035
+ code: "RT_SYNTAX_INVALID"
2036
+ law: "Every git-tracked .mjs, .cjs and .js file parses as JavaScript, including tests and package sources; node_modules, generated roots of ruleParams.runtime.generated and vendored build output are outside the authored-source scope. The runtime folder's Node-exact syntax pass remains in place, while this batched in-process pass extends coverage to the whole tracked repository and reports every parser diagnostic at its file and line."
2037
+ scope: runtime
2038
+ kinds: [check]
2039
+ gates: [land, runtime]
2040
+ failureCodes: ["RT_SYNTAX_INVALID"]
2041
+ enforcers:
2042
+ - {kind: runtime, id: syntax, at: scripts/hfs/runtime-rules/syntax.mjs}
2043
+ retired:
2044
+ - {id: R66, reason: "FE_E2E_SHAPE retired with eslint-canon-fe 7.0.0: a front end has no tests by standard"}
2045
+ - {id: R67, reason: "FE_SPEC_QUALITY retired with eslint-canon-fe 7.0.0: a front end has no tests by standard"}
2046
+ - {id: R123, reason: "RT_PENDING_STALE retired with the pending list itself: ruleParams.runtime.pending and its pending-ratchet enforcer are gone; nothing ratchets a tolerated-finding list"}
2047
+ - {id: R128, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2048
+ - {id: R129, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2049
+ - {id: R130, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2050
+ - {id: R131, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2051
+ - {id: R132, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2052
+ - {id: R133, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2053
+ - {id: R134, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2054
+ - {id: R135, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2055
+ - {id: R136, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2056
+ - {id: R137, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2057
+ - {id: R138, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2058
+ - {id: R139, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
2059
+ - {id: R140, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}