@starci/hfs 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +110 -12
  3. package/bin/hfs.mjs +119 -15
  4. package/emit/compiler.mjs +35 -0
  5. package/emit/contracts.mjs +97 -0
  6. package/emit/operations-worker.mjs +24 -0
  7. package/emit/operations.mjs +126 -0
  8. package/emit/schema-worker.mjs +117 -0
  9. package/emit/static-graph.mjs +670 -0
  10. package/emit/type-schema.mjs +145 -0
  11. package/package.json +4 -1
  12. package/report/sonar.mjs +180 -0
  13. package/runtime/engine/admission.mjs +284 -0
  14. package/runtime/engine/digest.mjs +10 -0
  15. package/runtime/engine/ledger-db.mjs +1245 -0
  16. package/runtime/engine/machine-db.mjs +1484 -0
  17. package/runtime/engine/migrations/machine/0001-init.sql +887 -0
  18. package/runtime/engine/migrations/runtime/0001-init.sql +1072 -0
  19. package/runtime/engine/migrations/runtime/0003-usage-unavailable.sql +13 -0
  20. package/runtime/engine/migrations/runtime/0004-attempt-why.sql +43 -0
  21. package/runtime/engine/plain-object.mjs +5 -0
  22. package/runtime/knowledge/hfs/canon-pins.yaml +16 -58
  23. package/runtime/knowledge/hfs/slots.yaml +405 -137
  24. package/runtime/knowledge/patterns/fe/folder.yaml +309 -0
  25. package/runtime/knowledge/sonar-gate.yaml +85 -0
  26. package/runtime/modules/kernel/failure-codes.yaml +1480 -16
  27. package/runtime/scripts/checks/architecture/backend.mjs +350 -0
  28. package/runtime/scripts/checks/architecture/background-unowned.mjs +107 -0
  29. package/runtime/scripts/checks/architecture/client-reaches-server.mjs +94 -0
  30. package/runtime/scripts/checks/architecture/clones.mjs +200 -0
  31. package/runtime/scripts/checks/architecture/config-unread.mjs +35 -0
  32. package/runtime/scripts/checks/architecture/config.mjs +310 -0
  33. package/runtime/scripts/checks/architecture/connection-map.mjs +208 -0
  34. package/runtime/scripts/checks/architecture/constructor-deps.mjs +100 -0
  35. package/runtime/scripts/checks/architecture/contract-fixture-guard.mjs +128 -0
  36. package/runtime/scripts/checks/architecture/contracts.mjs +792 -0
  37. package/runtime/scripts/checks/architecture/cross-app-duplicate.mjs +73 -0
  38. package/runtime/scripts/checks/architecture/dead-exports.mjs +265 -0
  39. package/runtime/scripts/checks/architecture/default-deny.mjs +129 -0
  40. package/runtime/scripts/checks/architecture/doc-language.mjs +39 -0
  41. package/runtime/scripts/checks/architecture/entrypoint.mjs +57 -0
  42. package/runtime/scripts/checks/architecture/error-codes.mjs +45 -0
  43. package/runtime/scripts/checks/architecture/error-masked.mjs +60 -0
  44. package/runtime/scripts/checks/architecture/fe-slot-allows.mjs +38 -0
  45. package/runtime/scripts/checks/architecture/feature-shape.mjs +49 -0
  46. package/runtime/scripts/checks/architecture/framework-pinned.mjs +83 -0
  47. package/runtime/scripts/checks/architecture/frontend.mjs +995 -0
  48. package/runtime/scripts/checks/architecture/hfs-graph.mjs +61 -0
  49. package/runtime/scripts/checks/architecture/hfs.mjs +521 -0
  50. package/runtime/scripts/checks/architecture/hooks-are-hooks.mjs +88 -0
  51. package/runtime/scripts/checks/architecture/i18n-keys.mjs +146 -0
  52. package/runtime/scripts/checks/architecture/index.mjs +316 -0
  53. package/runtime/scripts/checks/architecture/injection-token-exported.mjs +62 -0
  54. package/runtime/scripts/checks/architecture/machine-ast.mjs +138 -0
  55. package/runtime/scripts/checks/architecture/module-per-transport.mjs +130 -0
  56. package/runtime/scripts/checks/architecture/next-data.mjs +775 -0
  57. package/runtime/scripts/checks/architecture/owners.mjs +89 -0
  58. package/runtime/scripts/checks/architecture/package-shape.mjs +63 -0
  59. package/runtime/scripts/checks/architecture/reachability.mjs +233 -0
  60. package/runtime/scripts/checks/architecture/register-once.mjs +141 -0
  61. package/runtime/scripts/checks/architecture/registration.mjs +319 -0
  62. package/runtime/scripts/checks/architecture/required-files.mjs +172 -0
  63. package/runtime/scripts/checks/architecture/route-files-thin.mjs +97 -0
  64. package/runtime/scripts/checks/architecture/schema-owner.mjs +261 -0
  65. package/runtime/scripts/checks/architecture/size-growth.mjs +73 -0
  66. package/runtime/scripts/checks/architecture/source-names.mjs +607 -0
  67. package/runtime/scripts/checks/architecture/sql-owner.mjs +142 -0
  68. package/runtime/scripts/checks/architecture/sql-tokens.mjs +327 -0
  69. package/runtime/scripts/checks/architecture/symbols.mjs +193 -0
  70. package/runtime/scripts/checks/architecture/test-world-files.mjs +163 -0
  71. package/runtime/scripts/checks/architecture/tiers.mjs +130 -0
  72. package/runtime/scripts/checks/architecture/transport-owner.mjs +112 -0
  73. package/runtime/scripts/checks/architecture/typescript.mjs +500 -0
  74. package/runtime/scripts/checks/architecture/unit-spec-providers.mjs +122 -0
  75. package/runtime/scripts/checks/architecture.mjs +41 -0
  76. package/runtime/scripts/checks/common.mjs +37 -0
  77. package/runtime/scripts/checks/typescript-programs.mjs +82 -0
  78. package/runtime/scripts/lib/artifact-hold.mjs +89 -0
  79. package/runtime/scripts/lib/artifact-store.mjs +103 -0
  80. package/runtime/scripts/lib/fs-kind.mjs +10 -0
  81. package/runtime/scripts/lib/git.mjs +53 -0
  82. package/runtime/scripts/lib/hfs-allows.mjs +57 -0
  83. package/runtime/scripts/lib/hfs-check.mjs +254 -28
  84. package/runtime/scripts/lib/hfs-rules/contract.mjs +126 -0
  85. package/runtime/scripts/lib/hfs-rules/deps.mjs +63 -0
  86. package/runtime/scripts/lib/hfs-rules/fe-no-tests.mjs +47 -0
  87. package/runtime/scripts/lib/hfs-rules/frontend.mjs +124 -0
  88. package/runtime/scripts/lib/hfs-rules/lint-suppression.mjs +34 -0
  89. package/runtime/scripts/lib/hfs-rules/pipeline.mjs +51 -0
  90. package/runtime/scripts/lib/hfs-rules/proof-commands.mjs +64 -0
  91. package/runtime/scripts/lib/hfs-rules/read.mjs +28 -0
  92. package/runtime/scripts/lib/hfs-rules/repo-local-checks.mjs +32 -0
  93. package/runtime/scripts/lib/hfs-rules/secrets.mjs +54 -0
  94. package/runtime/scripts/lib/hfs-rules/spec-placement.mjs +31 -0
  95. package/runtime/scripts/lib/hfs-rules/stacks.mjs +54 -0
  96. package/runtime/scripts/lib/hfs-rules/test-topology.mjs +31 -0
  97. package/runtime/scripts/lib/hfs-slots.mjs +88 -40
  98. package/runtime/scripts/lib/hfs-tree.mjs +80 -0
  99. package/runtime/scripts/lib/hfs-view.mjs +68 -0
  100. package/runtime/scripts/lib/json.mjs +22 -0
  101. package/runtime/scripts/lib/language.mjs +107 -0
  102. package/runtime/scripts/lib/path-key.mjs +2 -0
  103. package/runtime/scripts/lib/redact.mjs +148 -0
  104. package/runtime/scripts/lib/repo-identity.mjs +50 -0
  105. package/runtime/scripts/lib/safe-remove.mjs +179 -0
  106. package/runtime/scripts/lib/secret-patterns.mjs +44 -0
  107. package/runtime/scripts/lib/sleep-sync.mjs +17 -0
  108. package/runtime/scripts/lib/stack-declaration.mjs +52 -0
  109. package/runtime/scripts/lib/stack-services.mjs +217 -0
  110. package/runtime/scripts/lib/test-secrets.mjs +120 -0
  111. package/scaffold/service.mjs +333 -0
  112. package/sync/format.mjs +46 -0
  113. package/sync/hygiene.mjs +56 -24
  114. package/sync/index.mjs +126 -41
  115. package/sync/managed.mjs +170 -0
  116. package/sync/skeleton.mjs +32 -10
  117. package/sync/sonar-key.mjs +13 -0
  118. package/sync/ts-strict.mjs +48 -0
  119. package/templates/{common → be/ci-workflows/github/workflows}/ci.yml +13 -13
  120. package/templates/be/{e2e.yml → ci-workflows/github/workflows/e2e.yml} +1 -0
  121. package/templates/be/hooks/husky/pre-commit +13 -0
  122. package/templates/be/hooks/husky/pre-push +7 -0
  123. package/templates/be/package-scripts/package.json +21 -0
  124. package/templates/be/{sonar-project.properties → quality-config/sonar-project.properties} +2 -3
  125. package/templates/be/skeleton/apps/__app__/src/app.module.ts +14 -4
  126. package/templates/be/skeleton/apps/__app__/src/main.ts +9 -6
  127. package/templates/be/skeleton/scripts/.gitkeep +0 -0
  128. package/templates/be/skeleton/src/features/system-health/application/check-liveness.contracts.ts +2 -0
  129. package/templates/be/skeleton/src/features/system-health/application/check-liveness.handler.ts +22 -0
  130. package/templates/be/skeleton/src/features/system-health/application/check-liveness.query.ts +11 -0
  131. package/templates/be/skeleton/src/features/system-health/index.ts +1 -1
  132. package/templates/be/skeleton/src/features/system-health/system-health.module.ts +3 -3
  133. package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.ts +8 -9
  134. package/templates/be/skeleton/src/features/system-health/transport/http/system-health-http.module.ts +3 -6
  135. package/templates/be/skeleton/src/modules/domain/liveness/index.ts +3 -0
  136. package/templates/be/skeleton/src/modules/domain/liveness/liveness.contracts.ts +9 -0
  137. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +7 -0
  138. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module.ts +14 -0
  139. package/templates/be/skeleton/src/modules/domain/liveness/liveness.options.ts +2 -0
  140. package/templates/be/skeleton/src/modules/domain/liveness/liveness.service.spec.ts +40 -0
  141. package/templates/be/skeleton/src/modules/domain/liveness/liveness.service.ts +26 -0
  142. package/templates/be/skeleton/src/modules/platform/clock/clock.decorators.ts +9 -0
  143. package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +7 -0
  144. package/templates/be/skeleton/src/modules/platform/clock/clock.module.ts +19 -0
  145. package/templates/be/skeleton/src/modules/platform/clock/clock.options.ts +2 -0
  146. package/templates/be/skeleton/src/modules/platform/clock/clock.port.ts +5 -0
  147. package/templates/be/skeleton/src/modules/platform/clock/index.ts +4 -0
  148. package/templates/be/skeleton/src/modules/platform/clock/system-clock.service.spec.ts +20 -0
  149. package/templates/be/skeleton/src/modules/platform/clock/system-clock.service.ts +9 -0
  150. package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +8 -0
  151. package/templates/be/skeleton/src/modules/platform/composition/index.ts +2 -0
  152. package/templates/be/skeleton/src/modules/platform/config/config.decorators.ts +9 -0
  153. package/templates/be/skeleton/src/modules/platform/config/index.ts +2 -3
  154. package/templates/be/skeleton/src/modules/platform/config/server.config.ts +1 -1
  155. package/templates/be/skeleton/src/modules/platform/config/server.options.ts +0 -3
  156. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +5 -0
  157. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.decorators.ts +9 -0
  158. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.handler.ts +27 -0
  159. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.log-events.ts +5 -0
  160. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +7 -0
  161. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module.ts +18 -0
  162. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.options.ts +2 -0
  163. package/templates/be/skeleton/src/modules/platform/cqrs/index.ts +4 -0
  164. package/templates/be/skeleton/src/modules/platform/errors/error.filter.ts +8 -12
  165. package/templates/be/skeleton/src/modules/platform/errors/errors.log-events.ts +5 -0
  166. package/templates/be/skeleton/src/modules/platform/errors/index.ts +1 -1
  167. package/templates/be/skeleton/src/modules/platform/logging/index.ts +4 -4
  168. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +99 -0
  169. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +38 -0
  170. package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +9 -0
  171. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +7 -0
  172. package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +7 -0
  173. package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +23 -10
  174. package/templates/be/skeleton/src/modules/platform/logging/logging.options.ts +2 -0
  175. package/templates/be/skeleton/src/modules/platform/logging/logging.port.ts +15 -0
  176. package/templates/be/tool-config/eslint.config.mjs +3 -0
  177. package/templates/be/tool-config/jest.config.js +1 -0
  178. package/templates/be/tool-config/prettierignore +8 -0
  179. package/templates/be/tool-config/prettierrc +1 -0
  180. package/templates/be/tool-config/src/tests/tsconfig.json +5 -0
  181. package/templates/be/tool-config/tsconfig.build.json +5 -0
  182. package/templates/be/tool-config/tsconfig.json +11 -0
  183. package/templates/common/gitignore.base +1 -1
  184. package/templates/fe/ci-workflows/github/workflows/ci.yml +54 -0
  185. package/templates/fe/hooks/husky/pre-commit +16 -0
  186. package/templates/fe/hooks/husky/pre-push +6 -0
  187. package/templates/fe/package-scripts/package.json +17 -0
  188. package/templates/fe/parts/api-client.ts +44 -0
  189. package/templates/fe/parts/api-outcome.ts +7 -0
  190. package/templates/fe/quality-config/sonar-project.properties +8 -0
  191. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +1 -1
  192. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/not-found.tsx +1 -1
  193. package/templates/fe/skeleton/apps/__app__/src/app/global-error.tsx +1 -1
  194. package/templates/fe/skeleton/scripts/.gitkeep +0 -0
  195. package/templates/fe/skeleton-app/apps/__app__/src/modules/api/client.ts +1 -0
  196. package/templates/fe/skeleton-app/apps/__app__/src/modules/api/index.ts +3 -0
  197. package/templates/fe/skeleton-app/apps/__app__/src/modules/api/outcome.ts +1 -0
  198. package/templates/fe/skeleton-app/apps/__app__/src/modules/i18n/index.ts +4 -0
  199. package/templates/fe/skeleton-shared/apps/__app__/next.config.ts +12 -0
  200. package/templates/fe/skeleton-shared/apps/__app__/src/modules/api/index.ts +2 -0
  201. package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/index.ts +9 -0
  202. package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/request.ts +5 -0
  203. package/templates/fe/skeleton-shared/apps/__app__/src/proxy.ts +5 -0
  204. package/templates/fe/skeleton-shared/packages/__family__-api/package.json +12 -0
  205. package/templates/fe/skeleton-shared/packages/__family__-api/src/client.ts +1 -0
  206. package/templates/fe/skeleton-shared/packages/__family__-api/src/index.ts +3 -0
  207. package/templates/fe/skeleton-shared/packages/__family__-api/src/outcome.ts +1 -0
  208. package/templates/fe/skeleton-shared/packages/__family__-api/tsconfig.json +5 -0
  209. package/templates/fe/skeleton-shared/packages/__family__-i18n/package.json +18 -0
  210. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/app.ts +19 -0
  211. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/index.ts +2 -0
  212. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/proxy.ts +12 -0
  213. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/request.ts +15 -0
  214. package/templates/fe/skeleton-shared/packages/__family__-i18n/tsconfig.json +5 -0
  215. package/templates/fe/tool-config/eslint.config.mjs +3 -0
  216. package/templates/fe/tool-config/prettierignore +10 -0
  217. package/templates/fe/tool-config/prettierrc +1 -0
  218. package/templates/fe/tool-config/stylelint.config.mjs +3 -0
  219. package/templates/fe/tool-config/tsconfig.json +4 -0
  220. package/templates/be/pre-commit +0 -8
  221. package/templates/be/skeleton/apps/__app__/src/__app__.composition.spec.ts +0 -31
  222. package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.spec.ts +0 -13
  223. package/templates/be/skeleton/src/modules/platform/config/env-source.spec.ts +0 -36
  224. package/templates/be/skeleton/src/modules/platform/config/server.config.spec.ts +0 -19
  225. package/templates/be/skeleton/src/modules/platform/errors/domain-error.spec.ts +0 -15
  226. package/templates/be/skeleton/src/modules/platform/errors/error.filter.spec.ts +0 -38
  227. package/templates/be/skeleton/src/modules/platform/logging/json-logger.spec.ts +0 -33
  228. package/templates/be/skeleton/src/modules/platform/logging/json-logger.ts +0 -35
  229. package/templates/be/skeleton/src/modules/platform/logging/log-id.ts +0 -9
  230. package/templates/be/skeleton/src/modules/platform/logging/logger.port.ts +0 -19
  231. package/templates/common/codecov.yml +0 -13
  232. package/templates/common/pre-push +0 -5
  233. package/templates/fe/e2e.yml +0 -22
  234. package/templates/fe/pre-commit +0 -7
  235. package/templates/fe/skeleton/apps/__app__/src/app/health/live/route.spec.ts +0 -10
  236. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages.spec.ts +0 -27
  237. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.spec.ts +0 -10
  238. package/templates/fe/sonar-project.properties +0 -11
  239. /package/templates/be/skeleton/src/modules/platform/config/{env-source.ts → env-source.config.ts} +0 -0
  240. /package/templates/be/skeleton/src/modules/platform/errors/{domain-error.ts → domain.error.ts} +0 -0
  241. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/next.config.ts +0 -0
  242. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/config.ts +0 -0
  243. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/navigation.ts +0 -0
  244. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/request.ts +0 -0
  245. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/routing.ts +0 -0
  246. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/proxy.ts +0 -0
@@ -42,9 +42,13 @@ versioning:
42
42
  # requiredWhen connections: required only when hfs.json declares a database connection.
43
43
  # allows / forbids names inside the instance; the loader exposes them, slot-check enforces them.
44
44
  # tests unit-beside | e2e | none.
45
+ # composedBy the app kinds allowed to compose a transport slot (an app of another kind imports none of its modules).
45
46
  # budget size budgets (lines per file, files per slot, exports).
46
47
  # appKind the app kind of hfs.json apps[].kind whose directory this slot describes.
47
- # managedBy template id that hfs sync renders; a difference is HFS_MANAGED_FILE_DRIFT.
48
+ # managedBy the template directory hfs sync renders this slot's files from. The slot path then lists literal files
49
+ # only, and each is rendered from templates/<profile or common>/<managedBy>/<path, leading dots dropped>;
50
+ # the managed file list lives here and nowhere in code. A difference is HFS_MANAGED_FILE_DRIFT (a
51
+ # back end's tsconfig.json: HFS_TS_STRICT when a flag or an option was added).
48
52
  # rules rule ids (the catalog lands with knowledge/hfs/rules.yaml).
49
53
  # since / retiredIn / successor lifecycle.
50
54
  presenceValues: [required, optional, opt-in, forbidden]
@@ -80,7 +84,6 @@ tiers:
80
84
  foundation: {mayImport: [foundation, package], acyclic: true} # modules/{config,i18n,routes,types}
81
85
  modules: {mayImport: [foundation, modules, transport, package], acyclic: true}
82
86
  transport: {mayImport: [foundation, modules, package]} # modules/api: the one fetch
83
- e2e: {mayImport: [e2e, package]} # black box: no app source
84
87
  package: {mayImport: [package]}
85
88
  crossOwner: every import across owners targets the owner's public entry (index.ts or index.tsx); never export *.
86
89
  crossApp: apps never import each other; shared code is a packages/<pkg> slot.
@@ -88,18 +91,69 @@ crossApp: apps never import each other; shared code is a packages/<pkg> slot.
88
91
  # Parameters the lint factories and checks read per profile (through ruleParams(profile) in scripts/lib/hfs-slots.mjs).
89
92
  ruleParams:
90
93
  be:
91
- # BE_MODULE_SHAPE: the only modules that may be @Global(); directories under src/modules/platform/.
92
- globalModules: [src/modules/platform/config/, src/modules/platform/logging/, src/modules/platform/database/]
93
94
  # HFS_SIZE_GROWTH: a file above soft may not grow against its parent commit; a new file stays within soft.
94
95
  fileLines: {soft: 500, hardGrowth: true}
95
96
  # R21 HFS_DUPLICATE_CODE: the ONE threshold of duplicate code (the architecture machine reads it here; no other file states it):
96
97
  # a token-normalised block of at least `lines` source lines and `tokens` tokens that appears twice.
97
98
  duplicateBlock: {lines: 8, tokens: 60}
99
+ # R90 BE_INFRA_OWNER: raw infrastructure library (a module specifier or a global reference) -> the platform or
100
+ # integration capabilities allowed to import it; [] means nowhere in a back end. A specifier also covers its subpaths.
101
+ infraOwners:
102
+ fetch: [platform/http]
103
+ axios: [platform/http]
104
+ got: [platform/http]
105
+ undici: [platform/http]
106
+ "node:http": [platform/http]
107
+ "node:https": [platform/http]
108
+ http: [platform/http]
109
+ https: [platform/http]
110
+ cache-manager: [integrations/cache, integrations/redis]
111
+ "@nestjs/cache-manager": [integrations/cache, integrations/redis] # the CACHE_MANAGER token: caching goes through the cache capability (retired local rule must-use-cache-service)
112
+ redis: [integrations/cache, integrations/redis]
113
+ ioredis: [integrations/cache, integrations/redis]
114
+ bullmq: [platform/messaging]
115
+ "@nestjs/bullmq": [platform/messaging]
116
+ kafkajs: [platform/messaging]
117
+ "@nestjs/schedule": [platform/scheduling, platform/retry, platform/messaging, platform/http]
118
+ setInterval: [platform/scheduling, platform/retry, platform/messaging, platform/http]
119
+ setTimeout: [platform/scheduling, platform/retry, platform/messaging, platform/http]
120
+ "node:timers": [platform/scheduling, platform/retry, platform/messaging, platform/http]
121
+ "node:timers/promises": [platform/scheduling, platform/retry, platform/messaging, platform/http]
122
+ winston: [platform/logging]
123
+ dayjs: [platform/clock, platform/primitives]
124
+ moment: [platform/clock, platform/primitives]
125
+ date-fns-tz: [platform/clock, platform/primitives]
126
+ "@nestjs/config": []
127
+ dotenv: []
128
+ "@nestjs/event-emitter": []
129
+ # R48 BE_SPEC_QUALITY spec-infra-double-from-kit: in a `<name>.service.spec.ts` a provider `{ provide: TOKEN, useValue: X }` takes X from
130
+ # the kit named here (the package root of `kit`). The token name is normalised to UPPER_SNAKE (`EntityManager` -> ENTITY_MANAGER), the
131
+ # FIRST entry whose `token` regex matches decides, and `fallback` decides for every other token. `forms` say what X may be:
132
+ # call = `double(...)`, new = `new Double(...)`, curried = `double(...)(...)`, object = a plain object literal (REAL values),
133
+ # primitive = a string/number/boolean literal, array = an array literal (multi-providers of doubles). A const bound to such a value counts.
134
+ specDoubles:
135
+ kit: "@starci/jest-preset"
136
+ doubles:
137
+ - {token: "(?:^|_)OPTIONS$", double: builder, forms: [curried, object]}
138
+ - {token: "(?:^|_)ENTITY_MANAGER$", double: mockEntityManager, forms: [call]}
139
+ - {token: "(?:^|_)TRANSACTION(?:_RUNNER)?$", double: fakeTransaction, forms: [call]}
140
+ - {token: "(?:^|_)CLOCK$", double: FakeClock, forms: [new]}
141
+ - {token: "(?:^|_)OUTBOX(?:_|$)", double: recordingOutbox, forms: [call]}
142
+ - {token: "(?:^|_)CACHE(?:_|$)", double: fakeCache, forms: [call]}
143
+ - {token: "(?:^|_)(?:LOCK|LEASE|FENCE|HOLD)(?:_|$)", double: fakeLock, forms: [call]}
144
+ - {token: "(?:^|_)(?:IDS|ID_GENERATOR|ID_FACTORY)$", double: fakeIds, forms: [call]}
145
+ fallback: {double: mock, forms: [call, primitive, array]}
146
+ # R89 BE_SOURCE_FORM: the closed role-suffix vocabulary of a source file name (<kebab-name>.<suffix>.ts; BE-CONVENTION 1.15)
147
+ # and the suffixes that are never a role. index.ts, main.ts, app.module.ts and the migrations of be.persistence are the
148
+ # only names outside it.
149
+ suffixes: [module, module-definition, decorators, options, config, connection, service, command, query, handler, contracts, port, resolver, controller, gateway, consumer, job, cli, input, type, args, request, response, mapper, policy, entity, sql, rows, error, messages, log-events, cache-keys, guard, interceptor, filter, client, spec, integration-spec, e2e-spec, contract-spec, global-setup, builder]
150
+ # R47 BE_CONTRACT_UNGUARDED: the world's shape vocabulary. `helper` names the function a contract spec imports to erase the values of a JSON
151
+ # payload (`shapeOf`); a contract spec proves a fake's fixtures by asserting helper(real) equals helper(fixture).
152
+ contractShape: {helper: shapeOf}
153
+ bannedSuffixes: [use-case, repository, fixture, factory, store, worker, scheduler, cron, listener, dto, util, utils, helper, helpers, rules, types, constants, int-spec, harness-spec, test]
98
154
  fe:
99
155
  # Slot budgets (component.tsx 300, hooks 200, modules 400) are stricter than this cap where they apply.
100
156
  fileLines: {soft: 500, hardGrowth: true}
101
- # FE_TRANSPORT_OWNER: the one module per app that may call fetch.
102
- clientModule: "apps/<app>/src/modules/api/client.ts"
103
157
  duplicateBlock: {lines: 8, tokens: 60}
104
158
 
105
159
  slots:
@@ -121,14 +175,40 @@ slots:
121
175
  tier: none
122
176
  tests: none
123
177
  rules: [HFS_ARCH_CONFIG_UNREAD]
124
- - id: repo.package-manifest
125
- profiles: [be, fe]
126
- path: "{package.json,package-lock.json}"
178
+ - id: fe.package-manifest
179
+ profiles: [fe]
180
+ path: package.json
181
+ presence: required
182
+ tracked: tracked
183
+ tier: none
184
+ tests: none
185
+ managedBy: package-scripts # the `scripts` block only; the rest of the file is the repository's
186
+ rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW, HFS_CANON_PIN_DRIFT, HFS_MANAGED_FILE_DRIFT]
187
+ - id: fe.lockfile
188
+ profiles: [fe]
189
+ path: package-lock.json
190
+ presence: required
191
+ tracked: tracked
192
+ tier: none
193
+ tests: none
194
+ rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW]
195
+ - id: be.package-manifest
196
+ profiles: [be]
197
+ path: package.json
127
198
  presence: required
128
199
  tracked: tracked
129
200
  tier: none
130
201
  tests: none
131
- rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW, HFS_CANON_PIN_DRIFT]
202
+ managedBy: package-scripts # the `scripts` block only; the rest of the file is the repository's
203
+ rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW, HFS_CANON_PIN_DRIFT, HFS_MANAGED_FILE_DRIFT]
204
+ - id: be.lockfile
205
+ profiles: [be]
206
+ path: package-lock.json
207
+ presence: required
208
+ tracked: tracked
209
+ tier: none
210
+ tests: none
211
+ rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW]
132
212
  - id: repo.git-meta
133
213
  profiles: [be, fe]
134
214
  path: "{.gitignore,.gitattributes}"
@@ -136,47 +216,89 @@ slots:
136
216
  tracked: tracked
137
217
  tier: none
138
218
  tests: none
139
- managedBy: gitignore-base
140
- rules: [HFS_GITIGNORE_BLOCK_DRIFT]
219
+ rules: [HFS_GITIGNORE_BLOCK_DRIFT] # the marked block of .gitignore is rendered by hfs sync (templates/<profile>/gitignore)
141
220
  - id: repo.tool-config
142
221
  profiles: [be, fe]
143
- path: "{tsconfig.json,eslint.config.mjs,.editorconfig,.nvmrc,.prettierignore}"
144
- presence: required # each file is a thin extends of a canon package
222
+ path: "{.editorconfig,.nvmrc}"
223
+ presence: required # plain dotfiles, the repository's own
145
224
  tracked: tracked
146
225
  tier: none
147
226
  tests: none
148
- managedBy: tool-config
149
- rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT, HFS_TS_STRICT]
150
227
  - id: repo.tool-config-optional
151
228
  profiles: [be, fe]
152
- path: "{tsconfig.build.json,tsconfig.e2e.json,.npmrc,jest.config.e2e.js}"
229
+ path: .npmrc
153
230
  presence: optional
154
231
  tracked: tracked
155
232
  tier: none
156
233
  tests: none
157
- managedBy: tool-config
158
- rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT]
159
234
  - id: be.tool-config
160
235
  profiles: [be]
161
- path: "{nest-cli.json,jest.config.js}"
236
+ # Managed files (hfs sync renders them, hfs check compares them): the whole tool configuration of a back end. Each is
237
+ # a reference to a canon package, never a configuration: tsconfig.json extends @starci/tsconfig/be.json and adds the
238
+ # three path aliases and excludes the world, integration, e2e and contract trees (fixtures stay in the root program: unit
239
+ # specs import them); tsconfig.build.json puts an overlay preset after it and adds only the path-relative options a
240
+ # preset cannot hold; the tests' own src/tests/tsconfig.json (the nearest config of a world, integration, e2e or contract
241
+ # file, so typed lint and typecheck:tests both find it) extends it with the e2e preset; eslint.config.mjs is the
242
+ # one-line starciBeConfig call; jest.config.js calls the @starci/jest-preset factory; .prettierrc names
243
+ # @starci/prettier-config; .prettierignore is the template.
244
+ path: "{tsconfig.json,tsconfig.build.json,src/tests/tsconfig.json,eslint.config.mjs,jest.config.js,.prettierrc,.prettierignore}"
162
245
  presence: required
163
246
  tracked: tracked
164
247
  tier: none
165
248
  tests: none
166
249
  managedBy: tool-config
167
- rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT]
250
+ rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL, HFS_TS_STRICT]
251
+ - id: be.nest-cli
252
+ profiles: [be]
253
+ path: nest-cli.json
254
+ presence: required
255
+ tracked: tracked
256
+ tier: none
257
+ tests: none
258
+ - id: be.tool-config-local
259
+ profiles: [be]
260
+ path: "{.eslintrc,.eslintrc.*,.eslintignore,eslint.config.js,eslint.config.cjs,eslint.config.ts,eslint.config.mts,eslint.config.cts,.prettierrc.*,prettier.config.*,jest.config.ts,jest.config.mjs,jest.config.cjs,jest.config.json,jest.config.e2e.js}"
261
+ presence: forbidden
262
+ tracked: external
263
+ tier: none
264
+ tests: none
265
+ goesTo: "nowhere: a back end has exactly the managed tool configuration (be.tool-config); a change to a rule or a flag is proposed in the .claude runtime"
266
+ rules: [HFS_TOOL_CONFIG_LOCAL]
168
267
  - id: fe.tool-config
169
268
  profiles: [fe]
170
- path: "{vitest.config.ts,vitest.setup.ts,playwright.config.ts,turbo.json}"
171
- presence: optional
269
+ # Managed files (hfs sync renders them, hfs check compares them): the tool configuration of a front end. Each is a
270
+ # reference to a canon package, never a configuration: tsconfig.json extends @starci/tsconfig/next.json and only
271
+ # names the preset and nothing else (each app's own tsconfig.json, in fe.app.next, carries its aliases and include; there is
272
+ # no test tsconfig: a front end has no tests, FE_NO_TESTS); eslint.config.mjs is the one-line starciFeConfig call;
273
+ # stylelint.config.mjs is the one-line starciStylelintConfig call; .prettierrc names @starci/prettier-config;
274
+ # .prettierignore is the template. A front end has no test configuration at all (FE_NO_TESTS).
275
+ path: "{tsconfig.json,eslint.config.mjs,stylelint.config.mjs,.prettierrc,.prettierignore}"
276
+ presence: required
172
277
  tracked: tracked
173
278
  tier: none
174
279
  tests: none
175
280
  managedBy: tool-config
176
- rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT]
281
+ rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL, HFS_RULE_OFF_WITHOUT_REPLACEMENT, HFS_TS_STRICT]
282
+ - id: fe.tool-config-repo
283
+ profiles: [fe]
284
+ # The one tool file a preset cannot render because it carries the repository's own facts: turbo.json (its task graph).
285
+ path: turbo.json
286
+ presence: optional
287
+ tracked: tracked
288
+ tier: none
289
+ tests: none
290
+ - id: fe.tool-config-local
291
+ profiles: [fe]
292
+ path: "{.eslintrc,.eslintrc.*,.eslintignore,eslint.config.js,eslint.config.cjs,eslint.config.ts,eslint.config.mts,eslint.config.cts,.stylelintrc,.stylelintrc.*,.stylelintignore,stylelint.config.js,stylelint.config.cjs,stylelint.config.ts,stylelint.config.json,prettier.config.*,.prettierrc.*,lint-staged.config.*,.lintstagedrc*}"
293
+ presence: forbidden
294
+ tracked: external
295
+ tier: none
296
+ tests: none
297
+ goesTo: "nowhere: a front end has exactly the managed tool configuration (fe.tool-config); a change to a rule or a flag is proposed in the .claude runtime"
298
+ rules: [HFS_TOOL_CONFIG_LOCAL]
177
299
  - id: repo.quality-config
178
300
  profiles: [be, fe]
179
- path: "{sonar-project.properties,codecov.yml}"
301
+ path: sonar-project.properties
180
302
  presence: required
181
303
  tracked: tracked
182
304
  tier: none
@@ -190,7 +312,7 @@ slots:
190
312
  tracked: tracked
191
313
  tier: none
192
314
  tests: none
193
- managedBy: husky
315
+ managedBy: hooks
194
316
  rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
195
317
  - id: repo.ci
196
318
  profiles: [be, fe]
@@ -202,7 +324,7 @@ slots:
202
324
  managedBy: ci-workflows
203
325
  rules: [HFS_CI_E2E_AUTOMATIC, HFS_CI_MISSING_CANON, HFS_MANAGED_FILE_DRIFT]
204
326
  - id: repo.ci-e2e
205
- profiles: [be, fe]
327
+ profiles: [be]
206
328
  path: ".github/workflows/e2e.yml"
207
329
  presence: optional # on: workflow_dispatch only
208
330
  tracked: tracked
@@ -219,12 +341,15 @@ slots:
219
341
  tests: none
220
342
  - id: repo.scripts
221
343
  profiles: [be, fe]
222
- path: "scripts/<name>.mjs"
344
+ # The repository's operational scripts only (`*.mjs`, `*.cjs`, `*.ps1`, `*.sh`), or nothing: an empty folder is `scripts/.gitkeep`, which hfs sync --init writes
345
+ # when the folder is missing. A spec or a test (BE_SPEC_PLACEMENT, FE_NO_TESTS), a check or lint source (`check-*`, `eslint-local-rules*`, a local eslint
346
+ # plugin: HFS_REPO_LOCAL_CHECK) and anything that re-implements a canon check is not an operational script and has no place here.
347
+ path: "scripts/{.gitkeep,*.mjs,*.cjs,*.ps1,*.sh}"
223
348
  presence: optional
224
349
  tracked: tracked
225
350
  tier: none
226
351
  tests: none
227
- rules: [HFS_SCRIPT_ONE_OFF] # one-off codemods and fix-* scripts are agent output, not tooling
352
+ rules: [HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK] # one-off codemods and fix-* scripts are agent output, not tooling
228
353
  - id: repo.docs
229
354
  profiles: [be, fe]
230
355
  path: "docs/{adr,runbooks,guides}/**/*.md"
@@ -269,7 +394,7 @@ slots:
269
394
  # ----- never tracked: build output the tools need in place ---------------------------------------------
270
395
  - id: repo.build-output
271
396
  profiles: [be, fe]
272
- path: "{**/node_modules/,dist/,apps/*/dist/,packages/*/dist/,.next/,apps/*/.next/,coverage/,test-results/,playwright-report/,**/next-env.d.ts,**/*.tsbuildinfo}"
397
+ path: "{**/node_modules/,dist/,apps/*/dist/,packages/*/dist/,.next/,apps/*/.next/,coverage/,reports/,test-results/,**/next-env.d.ts,**/*.tsbuildinfo}"
273
398
  presence: optional
274
399
  tracked: ignored
275
400
  tier: none
@@ -335,7 +460,7 @@ slots:
335
460
  tracked: tracked
336
461
  tier: none
337
462
  tests: none
338
- why: emitted by `npm run contract:emit`; CI fails when the emitted file differs (a contract, not a build artefact)
463
+ why: emitted by `npm run contract:emit` (`hfs emit-contracts`, never written by hand); CI fails when the emitted file differs (a contract, not a build artefact)
339
464
  rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
340
465
  - id: be.contract.openapi
341
466
  profiles: [be]
@@ -344,6 +469,7 @@ slots:
344
469
  tracked: tracked
345
470
  tier: none
346
471
  tests: none
472
+ why: emitted by `npm run contract:emit` (`hfs emit-contracts`, never written by hand) from the app's typed operation table `apps/<app>/src/operations.ts` (canon BE-OPERATIONS-1); CI fails when the emitted file differs
347
473
  rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
348
474
  - id: fe.contract.copy
349
475
  profiles: [fe]
@@ -365,9 +491,9 @@ slots:
365
491
  tier: app
366
492
  owner: true
367
493
  minInstances: 1
368
- requires: [main.ts, app.module.ts, "<app>.composition.spec.ts"]
369
- allows: [main.ts, app.module.ts, "<app>.composition.spec.ts", "<app>.options.ts"]
370
- tests: unit-beside # the composition spec boots the REAL AppModule with stubbed options
494
+ requires: [main.ts, app.module.ts]
495
+ allows: [main.ts, app.module.ts, "<app>.options.ts", operations.ts]
496
+ tests: none # an app is proven by the e2e world (useTestWorld({ apps })), never by a unit spec
371
497
  budget: {app.module.ts: 250, main.ts: 80}
372
498
  rules: [BE_APP_COMPOSITION_ONLY, BE_APP_BUSINESS_ROLE, BE_DEFAULT_DENY, BE_ERROR_MASKED, BE_FEATURE_NOT_COMPOSED]
373
499
  - id: be.app.worker
@@ -378,8 +504,9 @@ slots:
378
504
  tracked: tracked
379
505
  tier: app
380
506
  owner: true
381
- requires: [main.ts, app.module.ts, "<app>.composition.spec.ts"]
382
- tests: unit-beside
507
+ requires: [main.ts, app.module.ts]
508
+ allows: [main.ts, app.module.ts, "<app>.options.ts"]
509
+ tests: none # an app is proven by the e2e world (useTestWorld({ apps })), never by a unit spec
383
510
  rules: [BE_APP_COMPOSITION_ONLY, BE_BACKGROUND_UNOWNED]
384
511
  - id: be.app.migrate
385
512
  profiles: [be]
@@ -390,8 +517,9 @@ slots:
390
517
  tracked: tracked
391
518
  tier: app
392
519
  owner: true
393
- requires: [main.ts, "<app>.composition.spec.ts"]
394
- tests: unit-beside
520
+ requires: [main.ts]
521
+ allows: [main.ts, "<app>.options.ts"]
522
+ tests: none # an app is proven by the e2e world (useTestWorld({ apps })), never by a unit spec
395
523
  rules: [BE_SCHEMA_AUTHORITY, BE_ENTRYPOINT_ONLY_IN_APPS]
396
524
  - id: be.app.cli
397
525
  profiles: [be]
@@ -401,8 +529,9 @@ slots:
401
529
  tracked: tracked
402
530
  tier: app
403
531
  owner: true
404
- requires: [main.ts, app.module.ts, "<app>.composition.spec.ts"]
405
- tests: unit-beside
532
+ requires: [main.ts, app.module.ts]
533
+ allows: [main.ts, app.module.ts, "<app>.options.ts"]
534
+ tests: none # an app is proven by the e2e world (useTestWorld({ apps })), never by a unit spec
406
535
 
407
536
  # ----- BE: features ------------------------------------------------------------------------------------
408
537
  - id: be.feature
@@ -415,7 +544,7 @@ slots:
415
544
  minInstances: 1
416
545
  requires: [index.ts, "<feature>.module.ts", application/]
417
546
  allows: [index.ts, "<feature>.module.ts", application/, transport/, messages/] # nothing else at feature root
418
- tests: unit-beside
547
+ tests: none
419
548
  budget: {files: 250, indexExports: 60} # a feature above 250 source files must be split by product capability
420
549
  rules: [BE_FEATURE_SHAPE, BE_FEATURE_IMPORTS_FEATURE, BE_PUBLIC_SURFACE, BE_FEATURE_NOT_COMPOSED]
421
550
  - id: be.feature.application
@@ -424,19 +553,19 @@ slots:
424
553
  presence: required
425
554
  tracked: tracked
426
555
  tier: feature
427
- allows: ["<action>.use-case.ts", "<action>.contracts.ts", "<action>.{command,query,handler}.ts", "*.spec.ts"]
556
+ allows: ["<action>.{command,query,handler,contracts}.ts"]
428
557
  forbids: [graphql/, http/, message/, schedule/, transport/] # no protocol names under application/
429
- tests: unit-beside
430
- rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_ENTITY_IN_CONTRACT]
558
+ tests: none
559
+ rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_ENTITY_IN_CONTRACT, BE_CQRS_SHAPE]
431
560
  - id: be.feature.application.support
432
561
  profiles: [be]
433
562
  path: "src/features/<feature>/application/support/"
434
563
  presence: optional # helpers and types local to ONE feature; same tier as application, never a shared home
435
564
  tracked: tracked
436
565
  tier: feature # feature tier: another feature cannot import it (features never import features)
437
- allows: ["<name>.ts", "<name>.types.ts", "*.spec.ts"]
566
+ allows: ["<name>.<role>.ts"] # <role> from ruleParams.be.suffixes; no <name>.types.ts
438
567
  forbids: [graphql/, http/, message/, schedule/, transport/]
439
- tests: unit-beside # a <name>.spec.ts beside every <name>.ts
568
+ tests: none
440
569
  rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_FEATURE_IMPORTS_FEATURE]
441
570
  since: 1.0.0
442
571
  - id: be.transport.http
@@ -446,9 +575,10 @@ slots:
446
575
  tracked: tracked
447
576
  tier: feature
448
577
  requires: ["<feature>-http.module.ts"]
449
- allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts", "*.spec.ts"]
450
- tests: unit-beside
451
- rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED]
578
+ allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts"]
579
+ tests: none
580
+ composedBy: [api]
581
+ rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED, BE_TRANSPORT_SHAPE]
452
582
  - id: be.transport.graphql
453
583
  profiles: [be]
454
584
  path: "src/features/<feature>/transport/graphql/"
@@ -456,9 +586,10 @@ slots:
456
586
  tracked: tracked
457
587
  tier: feature
458
588
  requires: ["<feature>-graphql.module.ts"]
459
- allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts", "*.spec.ts"]
460
- tests: unit-beside
461
- rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED]
589
+ allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts"]
590
+ tests: none
591
+ composedBy: [api]
592
+ rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED, BE_TRANSPORT_SHAPE]
462
593
  - id: be.transport.message
463
594
  profiles: [be]
464
595
  path: "src/features/<feature>/transport/message/"
@@ -466,8 +597,9 @@ slots:
466
597
  tracked: tracked
467
598
  tier: feature
468
599
  requires: ["<feature>-message.module.ts"]
469
- allows: ["<event>.consumer.ts", "<event>.message.ts", "*.spec.ts"]
470
- tests: unit-beside
600
+ allows: ["<event>.consumer.ts"] # BE-CONVENTION 1.6: transport/message holds only consumers; a .message.ts is outside the closed suffix list
601
+ tests: none
602
+ composedBy: [worker]
471
603
  rules: [BE_BACKGROUND_UNOWNED]
472
604
  - id: be.transport.schedule
473
605
  profiles: [be]
@@ -476,8 +608,9 @@ slots:
476
608
  tracked: tracked
477
609
  tier: feature
478
610
  requires: ["<feature>-schedule.module.ts"]
479
- allows: ["<job>.job.ts", "*.spec.ts"]
480
- tests: unit-beside
611
+ allows: ["<job>.job.ts"]
612
+ tests: none
613
+ composedBy: [worker]
481
614
  rules: [BE_BACKGROUND_UNOWNED]
482
615
  - id: be.transport.websocket
483
616
  profiles: [be]
@@ -486,18 +619,20 @@ slots:
486
619
  tracked: tracked
487
620
  tier: feature
488
621
  requires: ["<feature>-websocket.module.ts"]
489
- allows: ["<channel>.gateway.ts", "dto/*.ts", "*.spec.ts"]
490
- tests: unit-beside
622
+ allows: ["<channel>.gateway.ts", "dto/*.ts"]
623
+ tests: none
624
+ composedBy: [api]
491
625
  since: 1.0.0
492
626
  - id: be.feature.transport.cli
493
627
  profiles: [be]
494
628
  path: "src/features/<feature>/transport/cli/"
495
- presence: opt-in # a command line entry: calls application use cases only, composed by an app of kind cli
629
+ presence: opt-in # a command line entry: dispatches one command or query, composed by an app of kind cli
496
630
  tracked: tracked
497
631
  tier: feature
498
632
  requires: ["<feature>-cli.module.ts"]
499
- allows: ["<command>.command.ts", "dto/*.ts", "*.spec.ts"]
500
- tests: unit-beside
633
+ allows: ["<name>.cli.ts", "dto/*.ts"] # .cli.ts: .command.ts is a CQRS message (HFS delta 3)
634
+ tests: none
635
+ composedBy: [cli]
501
636
  rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_FEATURE_NOT_COMPOSED, BE_ENTRYPOINT_ONLY_IN_APPS]
502
637
  since: 1.0.0
503
638
 
@@ -510,9 +645,9 @@ slots:
510
645
  tier: domain
511
646
  owner: true
512
647
  requires: [index.ts]
513
- allows: ["<capability>.module.ts", "<capability>.config.ts", "<capability>.options.ts", errors/, messages/, persistence/, policies/, "*.service.ts", "*.contracts.ts", "*.spec.ts"]
648
+ allows: ["<capability>.module.ts", "<capability>.config.ts", "<capability>.options.ts", errors/, messages/, persistence/, policies/, "*.service.ts", "*.service.spec.ts", "*.contracts.ts"]
514
649
  forbids: [testing/, exceptions/, utils/, helpers/, shared/]
515
- tests: unit-beside
650
+ tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
516
651
  budget: {indexExports: 60}
517
652
  rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_PUBLIC_SURFACE, BE_ERROR_HOME, BE_CONFIG_OWNER]
518
653
  - id: be.errors
@@ -521,18 +656,17 @@ slots:
521
656
  presence: optional
522
657
  tracked: tracked
523
658
  tier: inherit
524
- allows: ["<capability>.error.ts", "<name>.error.ts", "*.spec.ts"] # one family per capability, extends platform/errors DomainError
525
- tests: unit-beside
659
+ allows: ["<capability>.error.ts", "<name>.error.ts"] # one family per capability, extends platform/errors DomainError
660
+ tests: none
526
661
  rules: [BE_ERROR_HOME]
527
662
  - id: be.domain.messages
528
663
  profiles: [be]
529
- path: "src/modules/{domain,integrations}/<capability>/messages/"
530
- presence: optional # a capability that raises no user-facing text carries none
664
+ path: "src/modules/{domain,platform,integrations}/<capability>/messages/"
665
+ presence: optional # a capability (any tier) that raises no user-facing text carries none
531
666
  tracked: tracked
532
667
  tier: inherit
533
- requires: [index.ts]
534
- allows: ["<capability>.messages.ts", "*.spec.ts"] # one keyed catalog per capability, vi and en, read through the platform/i18n MessageCatalog port
535
- tests: unit-beside
668
+ allows: ["<capability>.messages.ts"] # one keyed catalog per capability, vi and en, read through the platform/i18n MessageCatalog port
669
+ tests: none
536
670
  rules: [BE_USER_COPY_LITERAL]
537
671
  since: 1.0.0
538
672
  - id: be.feature.messages
@@ -541,9 +675,8 @@ slots:
541
675
  presence: optional
542
676
  tracked: tracked
543
677
  tier: feature
544
- requires: [index.ts]
545
- allows: ["<feature>.messages.ts", "*.spec.ts"] # one keyed catalog per feature, vi and en, read through the platform/i18n MessageCatalog port
546
- tests: unit-beside
678
+ allows: ["<feature>.messages.ts"] # one keyed catalog per feature, vi and en, read through the platform/i18n MessageCatalog port
679
+ tests: none
547
680
  rules: [BE_USER_COPY_LITERAL]
548
681
  since: 1.0.0
549
682
  - id: be.persistence
@@ -552,10 +685,11 @@ slots:
552
685
  presence: optional
553
686
  tracked: tracked
554
687
  tier: inherit
555
- requires: [connection.ts] # export const CONNECTION = "<name from hfs.json connections>"
556
- allows: ["entities/<table>.entity.ts", "migrations/<yyyyMMddHHmmss>-<kebab-name>.ts", "<name>.repository.ts", index.ts, "*.spec.ts"]
557
- tests: unit-beside
558
- rules: [BE_SCHEMA_OWNER, BE_SCHEMA_AUTHORITY, BE_SQL_OUTSIDE_REPOSITORY]
688
+ requires: [connection.ts] # the capability's <c>Entities / <c>Migrations arrays (the owner index re-exports the two arrays by name); no CONNECTION alias: the connection is the one the apps register the arrays on
689
+ allows: ["entities/<table>.entity.ts", "migrations/<epochMs13>-<kebab-name>.ts", "<name>.sql.ts", "<name>.rows.ts", connection.ts]
690
+ forbids: [index.ts, "<name>.repository.ts"]
691
+ tests: none
692
+ rules: [BE_SCHEMA_OWNER, BE_SCHEMA_AUTHORITY, BE_SQL_OUTSIDE_PERSISTENCE]
559
693
  - id: be.platform
560
694
  profiles: [be]
561
695
  path: "src/modules/platform/<capability>/"
@@ -573,7 +707,8 @@ slots:
573
707
  requiredInstances: {capability: [config, logging, errors, primitives, clock, i18n]}
574
708
  requires: [index.ts]
575
709
  forbids: [common/, utils/, shared/, testing/] # primitives live in platform/primitives
576
- tests: unit-beside
710
+ tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
711
+ budget: {indexExports: 60}
577
712
  rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE]
578
713
  - id: be.integrations
579
714
  profiles: [be]
@@ -583,7 +718,8 @@ slots:
583
718
  tier: integrations
584
719
  owner: true
585
720
  requires: [index.ts, "<provider>.config.ts"]
586
- tests: unit-beside
721
+ tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
722
+ budget: {indexExports: 60}
587
723
  rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT]
588
724
  - id: be.integrations.model
589
725
  profiles: [be]
@@ -591,42 +727,104 @@ slots:
591
727
  presence: opt-in
592
728
  tracked: tracked
593
729
  tier: integrations
594
- tests: unit-beside
730
+ tests: none
595
731
  why: an ML or LLM model client is an integration; weights and datasets are never in git
596
732
 
597
- # ----- BE: tests ---------------------------------------------------------------------------------------
598
- - id: be.tests.e2e
733
+ # ----- BE: tests (owner test layout 2026-09-30). Unit specs are <name>.service.spec.ts beside their service and nothing else; everything else
734
+ # lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
735
+ # not match its folder matches no slot: HFS_SLOT_UNDECLARED).
736
+ - id: be.tests.world
599
737
  profiles: [be]
600
- path: "src/tests/e2e/<area>/*.e2e-spec.ts"
738
+ path: "src/tests/world/"
601
739
  presence: optional
602
740
  tracked: tracked
603
741
  tier: e2e
604
- tests: e2e
605
- rules: [HFS_CI_E2E_AUTOMATIC, BE_TEST_TOPOLOGY, BE_E2E_FLOW]
606
- - id: be.tests.e2e-live
607
- profiles: [be]
608
- path: "src/tests/e2e/live/<area>/*.e2e-spec.ts"
609
- presence: opt-in
610
- tracked: tracked
611
- tier: e2e
612
- tests: e2e
613
- why: run only by test:e2e:live (E2E_LIVE=1); excluded from plain test:e2e by folder, never by file name
614
- - id: be.tests.e2e-setup
742
+ allows: [global-setup.ts, global-teardown.ts, use-test-world.ts, fakes/] # jest globalSetup/globalTeardown default-export (Jest API); plus root files with a role suffix (test-world-files, R47)
743
+ tests: none
744
+ why: >-
745
+ the ONLY test infrastructure location, shared by integration and e2e: global-setup.ts starts the shared
746
+ infrastructure (containers) and runs apps/migrate's exported bootstrap once; use-test-world.ts exports
747
+ useTestWorld({ apps: { <name>: { module, listen? } } } | { modules: [...] }) -> world.apps.<name>.api,
748
+ world.db.<connection> (the shared EntityManager), world.infra.<service> (latency/cut/restore on a REAL service of the repository's
749
+ own stack, `.starcistacks/<env>`, each behind toxiproxy), world.fake.<provider> (a network-edge fake of an external SaaS started by the world, with
750
+ failNext/replayWebhook/delay), world.waitFor; fakes/<provider>/ holds the fake servers and payload fixtures, kit/ the inlined test helpers, and the world root may hold role-suffixed helpers (<name>.client.ts, <name>.contracts.ts, ...). Nothing is
751
+ overridden in the DI container. It is the only test location that may import typeorm's DataSource or testcontainers,
752
+ call migrate/runMigrations/synchronize, or write process.env.
753
+ - id: be.tests.world.kit
615
754
  profiles: [be]
616
- path: "src/tests/e2e/setup/"
755
+ path: "src/tests/world/kit/"
617
756
  presence: optional
618
757
  tracked: tracked
619
758
  tier: e2e
759
+ allows: ["<name>.ts"] # plain kebab names, like platform/primitives (BE-CONVENTION 1.11 names primitives/time.ts): the world's inlined test helpers (poll, free-ports)
620
760
  tests: none
621
- why: boots the REAL AppModule and overrides providers; no per-lane hand-assembled modules
761
+ why: the helpers the test world needs (polling, free ports, transports), inlined so the world imports no other repository; a bare <name>.ts entry in allows is what admits a plain name in BE_SOURCE_FORM
622
762
  - id: be.tests.fixtures
623
763
  profiles: [be]
624
764
  path: "src/tests/fixtures/"
625
765
  presence: optional
626
766
  tracked: tracked
627
767
  tier: fixtures
628
- tests: unit-beside
629
- why: typed data builders and typed mock<T>() doubles; importable by unit specs and e2e; never imports a feature
768
+ allows: [i18n/] # i18n/ is the one folder that may carry localized text
769
+ tests: none
770
+ why: payload fixtures with a role suffix (the doubles come from @starci/jest-preset, not from here); the test data builders live in its builders/ slot; importable by unit specs, integration and e2e; never imports a feature
771
+ - id: be.tests.fixtures.builders
772
+ profiles: [be]
773
+ path: "src/tests/fixtures/builders/*.builder.ts"
774
+ presence: optional
775
+ tracked: tracked
776
+ tier: fixtures
777
+ tests: none
778
+ why: >-
779
+ the ONLY home of test data builders, one <area>.builder.ts per area, shared by every spec of that area: pure object builders
780
+ (commissionRow(overrides), accrueInput(overrides)) for unit specs and persisting builders (orderBuilder(world.db.primary).pending().build(overrides))
781
+ for integration and e2e; arrange only, constraints on, deterministic defaults. A .builder.ts anywhere else, and a *.repository.ts,
782
+ *.fixture.ts or *.factory.ts anywhere, is a BE_SOURCE_FORM finding
783
+ rules: [BE_SOURCE_FORM]
784
+ - id: be.tests.fixtures.i18n
785
+ profiles: [be]
786
+ path: "src/tests/fixtures/i18n/"
787
+ presence: optional
788
+ tracked: tracked
789
+ tier: fixtures
790
+ allows: ["<name>.<role>.ts"] # data files that deliberately carry localized text (a real Vietnamese string a parser or formatter must accept); no specs here
791
+ tests: none
792
+ why: the ONLY test-fixture location where Vietnamese or another language may appear in source (LANG_NOT_ENGLISH); placement is the marker, there is no comment pragma
793
+ - id: be.tests.integration
794
+ profiles: [be]
795
+ path: "src/tests/integration/<capability>/*.integration-spec.ts"
796
+ presence: optional
797
+ tracked: tracked
798
+ tier: e2e
799
+ tests: e2e
800
+ why: one capability module on the real database, no HTTP (SQL, transactions, concurrency, inbox claims) through useTestWorld({ modules }); run by test:integration
801
+ rules: [BE_TEST_TOPOLOGY]
802
+ - id: be.tests.e2e
803
+ profiles: [be]
804
+ path: "src/tests/e2e/<area>/*.e2e-spec.ts"
805
+ presence: optional
806
+ tracked: tracked
807
+ tier: e2e
808
+ tests: e2e
809
+ why: flow e2e through useTestWorld({ apps }); external services are the world's network fakes; run by test:e2e
810
+ rules: [HFS_CI_E2E_AUTOMATIC, BE_TEST_TOPOLOGY, BE_E2E_FLOW]
811
+ - id: be.tests.contract
812
+ profiles: [be]
813
+ path: "src/tests/contract/<provider>/*.contract-spec.ts"
814
+ presence: optional
815
+ tracked: tracked
816
+ tier: e2e
817
+ tests: e2e
818
+ why: provider sandbox contracts, skipped without sandbox config; run only by test:contract, never part of test or test:e2e
819
+ rules: [BE_TEST_TOPOLOGY]
820
+ - id: be.tests.e2e-world-retired
821
+ profiles: [be]
822
+ path: "src/tests/e2e/world/"
823
+ presence: forbidden
824
+ tracked: external
825
+ tier: none
826
+ tests: none
827
+ goesTo: "src/tests/world/ (the only test infrastructure location; e2e/<area>/ holds only *.e2e-spec.ts)"
630
828
 
631
829
  # ----- BE: work and stacks -----------------------------------------------------------------------------
632
830
  - id: be.starciwork
@@ -651,7 +849,7 @@ slots:
651
849
  tier: none
652
850
  tests: none
653
851
  requires: [application-stacks.yaml]
654
- allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,k8s,terraform}/**",
852
+ allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,k8s,terraform}/**", "<env>/infra/metadata.json",
655
853
  "<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.enc", "<env>/seeds/**"]
656
854
  forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
657
855
  rules: [HFS_STACKS_SHAPE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY]
@@ -662,7 +860,6 @@ slots:
662
860
  tracked: tracked
663
861
  tier: none
664
862
  tests: none
665
- managedBy: sops-policy
666
863
 
667
864
  # ----- FE: apps ----------------------------------------------------------------------------------------
668
865
  - id: fe.app.next
@@ -673,13 +870,13 @@ slots:
673
870
  tracked: tracked
674
871
  tier: none
675
872
  minInstances: 1
676
- requires: [package.json, next.config.ts, tsconfig.json, postcss.config.mjs, src/app/, src/modules/i18n/, src/modules/api/,
873
+ requires: [package.json, next.config.ts, tsconfig.json, postcss.config.mjs, src/app/, src/modules/i18n/,
677
874
  "src/app/global-error.tsx", "src/app/[locale]/layout.tsx", "src/app/[locale]/error.tsx", "src/app/[locale]/not-found.tsx", "src/app/[locale]/loading.tsx"]
678
875
  tests: none
679
876
  rules: [FE_APP_ISOLATION, FE_ERROR_BOUNDARY_MISSING, FE_NEXT_CONVENTIONS]
680
877
  - id: fe.app-optional
681
878
  profiles: [fe]
682
- path: "apps/<app>/{vitest.config.ts,public/}"
879
+ path: "apps/<app>/public/"
683
880
  presence: optional
684
881
  tracked: tracked
685
882
  tier: none
@@ -690,8 +887,13 @@ slots:
690
887
  presence: required
691
888
  tracked: tracked
692
889
  tier: route
693
- allows: ["[locale]/**/{page,layout,template,loading,error,not-found}.tsx", "[locale]/**/route.ts", global-error.tsx, "page.tsx (redirect only)",
694
- "health/{live,ready}/route.ts", globals.css, "icon.*", robots.ts, sitemap.ts]
890
+ allows: ["[locale]/**/{page,layout,template,loading,error,not-found,default}.tsx", "[locale]/**/route.ts", "[locale]/providers.tsx",
891
+ global-error.tsx, page.tsx, # the root page.tsx only redirects (FE_ROUTE_FILES_THIN)
892
+ not-found.tsx, layout.tsx, # next-intl: a request outside every locale renders the root not-found inside a root layout
893
+ "health/{live,ready}/route.ts", "api/**/route.ts", "_*/**", providers.tsx, globals.css,
894
+ "{icon,favicon,apple-icon,opengraph-image,twitter-image}.*", robots.ts, sitemap.ts, manifest.ts]
895
+ # providers.tsx (root or [locale]) is the client wrapper the server layout renders around its children to host context providers.
896
+ roles: {page: page.tsx, layout: layout.tsx, template: template.tsx, loading: loading.tsx, error: error.tsx, not-found: not-found.tsx, default: default.tsx, route: route.ts, global-error: global-error.tsx, providers: providers.tsx}
695
897
  tests: none
696
898
  rules: [FE_ROUTE_FILES_THIN, FE_CLIENT_BOUNDARY, FE_OWNER_REACHABLE]
697
899
  - id: fe.source-root-pinned
@@ -700,8 +902,18 @@ slots:
700
902
  presence: optional
701
903
  tracked: tracked
702
904
  tier: route
905
+ roles: {proxy: proxy.ts, instrumentation: instrumentation.ts, instrumentation-client: instrumentation-client.ts}
906
+ tests: none
907
+ rules: [FE_NEXT_CONVENTIONS] # middleware.ts is refused on Next >= 16 (fe.source-root-retired)
908
+ - id: fe.source-root-retired
909
+ profiles: [fe]
910
+ path: "apps/<app>/src/{middleware.ts,middleware.js}"
911
+ presence: forbidden
912
+ tracked: external
913
+ tier: none
703
914
  tests: none
704
- rules: [FE_NEXT_CONVENTIONS] # middleware.ts is refused on Next >= 16
915
+ goesTo: "apps/<app>/src/proxy.ts: Next >= 16 renamed middleware to proxy and refuses the old name"
916
+ rules: [FE_NEXT_CONVENTIONS]
705
917
  - id: fe.feature
706
918
  profiles: [fe]
707
919
  path: "apps/<app>/src/features/{pages,layouts,overlays}/<name>/"
@@ -709,10 +921,12 @@ slots:
709
921
  tracked: tracked
710
922
  tier: feature
711
923
  owner: true
924
+ kinds: [pages, layouts, overlays]
712
925
  requires: [index.tsx]
713
- allows: [index.tsx, component.tsx, classNames.ts, "*.spec.tsx", "*.spec.ts"]
714
- tests: unit-beside
715
- budget: {component.tsx: 300, index.tsx: 200}
926
+ allows: [index.tsx, component.tsx, classNames.ts]
927
+ roles: {entry: index.tsx, drawing: component.tsx, styles: classNames.ts} # the connected entry, the pure drawing half and the styling declarations of a component owner
928
+ tests: none
929
+ budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
716
930
  rules: [FE_SIZE_BUDGET, FE_OWNER_REACHABLE, FE_I18N_LITERAL]
717
931
  - id: fe.components
718
932
  profiles: [fe]
@@ -723,8 +937,9 @@ slots:
723
937
  owner: true
724
938
  layers: [blocks, composites, branches, leaves] # a layer imports only the layers after it
725
939
  requires: [index.tsx]
726
- allows: [index.tsx, component.tsx, classNames.ts, "*.spec.tsx", "*.spec.ts"]
727
- tests: unit-beside
940
+ allows: [index.tsx, component.tsx, classNames.ts]
941
+ roles: {entry: index.tsx, drawing: component.tsx, styles: classNames.ts} # the connected entry, the pure drawing half and the styling declarations of a component owner
942
+ tests: none
728
943
  budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
729
944
  rules: [FE_CONNECTED_BLOCK_RENDER_PAIR, FE_I18N_LITERAL, FE_NATIVE_FORM_CONTROL, FE_CLIENT_BOUNDARY]
730
945
  - id: fe.hooks
@@ -735,9 +950,10 @@ slots:
735
950
  tier: hooks
736
951
  owner: true # a domain is an owner: other domains import it through index.ts only
737
952
  requires: [index.ts]
738
- allows: [index.ts, "use<name>.ts", "<domain>.shared.ts", "*.spec.ts"] # React hooks only; <domain>.shared.ts = sanctioned non-hook helpers
739
- tests: unit-beside
740
- budget: {file: 200}
953
+ allows: [index.ts, "use<name>.ts", "<domain>.shared.ts"] # React hooks only; <domain>.shared.ts = sanctioned non-hook helpers
954
+ roles: {entry: index.ts, shared: "<domain>.shared.ts"}
955
+ tests: none
956
+ budget: {file: 200, dataHooks: 6, useState: 6}
741
957
  rules: [FE_HOOKS_ARE_HOOKS, FE_DATA_FRESHNESS]
742
958
  - id: fe.modules
743
959
  profiles: [fe]
@@ -746,28 +962,50 @@ slots:
746
962
  tracked: tracked
747
963
  tier: modules
748
964
  owner: true
749
- requiredInstances: {capability: [api, config, i18n, routes]}
965
+ requiredInstances: {capability: [config, i18n, routes]}
750
966
  requires: [index.ts]
751
- tests: unit-beside
967
+ tests: none
752
968
  budget: {file: 400}
753
969
  rules: [FE_ENV_OWNER, FE_TRANSPORT_OWNER]
754
970
  - id: fe.modules.api
755
971
  profiles: [fe]
756
972
  path: "apps/<app>/src/modules/api/"
757
- presence: required
973
+ presence: optional
758
974
  tracked: tracked
759
975
  tier: transport
760
- requires: [index.ts, client.ts, outcome.ts] # client.ts is the only fetch; outcome.ts the only result vocabulary
761
- allows: [client.ts, outcome.ts, "<domain>/read-*.ts", "<domain>/*.graphql", "<domain>/<domain>.mapper.ts", contract/, __generated__/, "*.spec.ts"]
762
- tests: unit-beside
976
+ # The app's reads of the one client. A repository has exactly ONE transport client and ONE Outcome union: the app's
977
+ # client.ts/outcome.ts (fe.transport.client/outcome) in a one-app repository, or the api package's
978
+ # (fe.package.api.client/outcome) when the repository shares it (FE_TRANSPORT_OWNER, machine).
979
+ requires: [index.ts]
980
+ allows: [index.ts, client.ts, outcome.ts, "<domain>/read-*.ts", "<domain>/*.graphql", "<domain>/<domain>.mapper.ts", contract/, __generated__/]
981
+ roles: {entry: index.ts, reader: "read-*.ts"} # a reader is a server module (FE_CLIENT_REACHES_SERVER)
982
+ tests: none
763
983
  rules: [FE_TRANSPORT_OWNER, FE_HTTP_STATUS_COLLAPSE, FE_WIRE_GENERATED]
984
+ - id: fe.transport.client
985
+ profiles: [fe]
986
+ path: "apps/<app>/src/modules/api/client.ts"
987
+ presence: optional
988
+ tracked: tracked
989
+ tier: transport
990
+ tests: none
991
+ why: the one fetch of a one-app repository (timeout, abort, 401/403 -> refused); a shared client is fe.package.api.client
992
+ rules: [FE_TRANSPORT_OWNER]
993
+ - id: fe.transport.outcome
994
+ profiles: [fe]
995
+ path: "apps/<app>/src/modules/api/outcome.ts"
996
+ presence: optional
997
+ tracked: tracked
998
+ tier: transport
999
+ tests: none
1000
+ why: the one Outcome union of a one-app repository; a shared union is fe.package.api.outcome
1001
+ rules: [FE_HTTP_STATUS_COLLAPSE]
764
1002
  - id: fe.modules.config
765
1003
  profiles: [fe]
766
1004
  path: "apps/<app>/src/modules/config/"
767
1005
  presence: required
768
1006
  tracked: tracked
769
1007
  tier: foundation
770
- tests: unit-beside
1008
+ tests: none
771
1009
  why: the only reader of process.env and NEXT_PUBLIC_*; fails fast, no localhost fallback
772
1010
  - id: fe.modules.i18n
773
1011
  profiles: [fe]
@@ -775,8 +1013,11 @@ slots:
775
1013
  presence: required
776
1014
  tracked: tracked
777
1015
  tier: foundation
778
- requires: [config.ts, routing.ts, navigation.ts, request.ts, messages/]
779
- tests: unit-beside
1016
+ # The app's i18n module: its catalogs and the one call of the repository's i18n factory. The next-intl stack (routing,
1017
+ # navigation, request config) is written once per repository: in the i18n package (fe.package.i18n, `createAppI18n`)
1018
+ # when the repository has one, else in the only app's module.
1019
+ requires: [index.ts]
1020
+ tests: none
780
1021
  rules: [FE_I18N_PLACEMENT, FE_I18N_CATALOG]
781
1022
  - id: fe.modules.brand
782
1023
  profiles: [fe]
@@ -793,7 +1034,7 @@ slots:
793
1034
  presence: required
794
1035
  tracked: tracked
795
1036
  tier: foundation
796
- tests: unit-beside
1037
+ tests: none
797
1038
  why: every href builder; FE_HREF_RESOLVES checks each against app/
798
1039
  - id: fe.modules.types
799
1040
  profiles: [fe]
@@ -801,7 +1042,7 @@ slots:
801
1042
  presence: optional
802
1043
  tracked: tracked
803
1044
  tier: foundation
804
- tests: unit-beside
1045
+ tests: none
805
1046
  why: shared types with no runtime behaviour; a component may import them
806
1047
  - id: fe.package.ui
807
1048
  profiles: [fe]
@@ -810,26 +1051,53 @@ slots:
810
1051
  tracked: tracked
811
1052
  tier: package
812
1053
  owner: true
1054
+ kinds: [composites, branches, leaves] # the layer folders under src/ (a package owns no blocks)
1055
+ roles: {entry: index.tsx, drawing: component.tsx, styles: classNames.ts}
813
1056
  requires: [package.json, src/index.ts, tsconfig.json]
814
- tests: unit-beside
1057
+ tests: none
1058
+ budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
815
1059
  why: built to dist with explicit named exports; only composites, branches and leaves; no unused export; no colour literal
816
1060
  rules: [FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT, FE_STYLE_TOKEN_ONLY]
817
- - id: fe.e2e
1061
+ - id: fe.package.api
818
1062
  profiles: [fe]
819
- path: "e2e/<area>/*.e2e-spec.ts"
820
- presence: optional
1063
+ path: "packages/<family>-api/"
1064
+ presence: opt-in
821
1065
  tracked: tracked
822
- tier: e2e
823
- tests: e2e
824
- requires: [/playwright.config.ts, /tsconfig.e2e.json]
825
- rules: [FE_E2E_SHAPE, HFS_CI_E2E_AUTOMATIC]
826
- - id: fe.e2e-support
1066
+ tier: package
1067
+ owner: true
1068
+ requires: [package.json, src/index.ts, src/client.ts, src/outcome.ts, tsconfig.json]
1069
+ tests: none
1070
+ why: the one transport client and the one Outcome union shared by every app of the repository; no app keeps its own
1071
+ rules: [FE_TRANSPORT_OWNER, FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
1072
+ - id: fe.package.api.client
827
1073
  profiles: [fe]
828
- path: "e2e/{support,fixtures}/"
829
- presence: optional
1074
+ path: "packages/<family>-api/src/client.ts"
1075
+ presence: optional # enabled with its package (fe.package.api is the opt-in)
830
1076
  tracked: tracked
831
- tier: e2e
1077
+ tier: package
1078
+ tests: none
1079
+ why: the repository's one fetch (timeout, abort, 401/403 -> refused)
1080
+ rules: [FE_TRANSPORT_OWNER]
1081
+ - id: fe.package.api.outcome
1082
+ profiles: [fe]
1083
+ path: "packages/<family>-api/src/outcome.ts"
1084
+ presence: optional # enabled with its package (fe.package.api is the opt-in)
1085
+ tracked: tracked
1086
+ tier: package
1087
+ tests: none
1088
+ why: the repository's one Outcome union
1089
+ rules: [FE_HTTP_STATUS_COLLAPSE]
1090
+ - id: fe.package.i18n
1091
+ profiles: [fe]
1092
+ path: "packages/<family>-i18n/"
1093
+ presence: opt-in
1094
+ tracked: tracked
1095
+ tier: package
1096
+ owner: true
1097
+ requires: [package.json, src/index.ts, tsconfig.json]
832
1098
  tests: none
1099
+ why: the next-intl stack (routing, navigation, request config) written once, exported as the createAppI18n factory every app calls
1100
+ rules: [FE_I18N_PLACEMENT, FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
833
1101
 
834
1102
  # Checks that read this manifest (all ship from .claude; none keeps its own path list)
835
1103
  consumers:
@@ -839,5 +1107,5 @@ consumers:
839
1107
  - packages/eslint/be starciBeConfig({hfs}) # file globs for rules come from slots
840
1108
  - packages/eslint/fe starciFeConfig({hfs})
841
1109
  - packages/stylelint-canon # colour and brand allowances from fe.modules.brand
842
- - scripts/hfs/sync.mjs # renders managedBy templates
1110
+ - packages/hfs/sync # renders managedBy templates (hfs sync) and judges them (hfs check)
843
1111
  - modules/kernel/failure-codes.yaml # every rule id listed above has a Vietnamese why