@starci/hfs 5.1.0 → 5.3.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 (365) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +11 -2
  3. package/cache/hfs-family.d.mts +2 -3
  4. package/cache/hfs-family.mjs +4 -9
  5. package/cache/secret-records.d.mts +7 -0
  6. package/cache/secret-records.mjs +12 -0
  7. package/cache/session.mjs +4 -2
  8. package/codemod/account-doors.d.mts +36 -0
  9. package/codemod/account-doors.mjs +128 -0
  10. package/codemod/main.mjs +31 -1
  11. package/codemod/module-definition.d.mts +52 -0
  12. package/codemod/module-definition.mjs +242 -0
  13. package/codemod/quality-binding.d.mts +33 -0
  14. package/codemod/quality-binding.mjs +38 -0
  15. package/codemod/secrets-cache.d.mts +35 -0
  16. package/codemod/secrets-cache.mjs +70 -0
  17. package/codemod/sops-rules.d.mts +34 -0
  18. package/codemod/sops-rules.mjs +69 -0
  19. package/codemod/theme-tokens.mjs +48 -2
  20. package/db/postgres.mjs +9 -1
  21. package/lint/run.d.mts +6 -0
  22. package/lint/run.mjs +63 -10
  23. package/package.json +1 -1
  24. package/runtime/knowledge/hfs/canon-pins.yaml +65 -11
  25. package/runtime/knowledge/hfs/fe-stack.yaml +9 -9
  26. package/runtime/knowledge/hfs/fork-images.yaml +7 -0
  27. package/runtime/knowledge/hfs/rules.yaml +67 -15
  28. package/runtime/knowledge/hfs/slots.yaml +174 -30
  29. package/runtime/modules/kernel/failure-codes.yaml +67 -4
  30. package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
  31. package/runtime/scripts/hfs/architecture/config.d.mts +4 -0
  32. package/runtime/scripts/hfs/architecture/config.mjs +25 -1
  33. package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
  34. package/runtime/scripts/hfs/architecture/module-per-transport.d.mts +1 -1
  35. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +6 -1
  36. package/runtime/scripts/hfs/architecture/root-packages.d.mts +26 -0
  37. package/runtime/scripts/hfs/architecture/root-packages.mjs +78 -0
  38. package/runtime/scripts/hfs/architecture/transport-owner.d.mts +1 -2
  39. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -6
  40. package/runtime/scripts/hfs/architecture/type-context.d.mts +4 -1
  41. package/runtime/scripts/hfs/architecture/type-context.mjs +30 -0
  42. package/runtime/scripts/hfs/architecture/typescript.d.mts +7 -1
  43. package/runtime/scripts/hfs/architecture/typescript.mjs +21 -1
  44. package/runtime/scripts/hfs/explicit-surface-rule.mjs +1 -1
  45. package/runtime/scripts/hfs/manifest-shape.d.mts +1 -0
  46. package/runtime/scripts/hfs/manifest-shape.mjs +3 -1
  47. package/runtime/scripts/hfs/rules/a15-findings.mjs +5 -0
  48. package/runtime/scripts/hfs/rules/database-access.mjs +39 -1
  49. package/runtime/scripts/hfs/rules/monorepo.d.mts +4 -0
  50. package/runtime/scripts/hfs/rules/monorepo.mjs +10 -5
  51. package/runtime/scripts/hfs/rules/secrets.mjs +11 -0
  52. package/runtime/scripts/hfs/rules/stacks-local.d.mts +7 -0
  53. package/runtime/scripts/hfs/rules/stacks-local.mjs +71 -0
  54. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +6 -1
  55. package/runtime/scripts/hfs/rules/stacks-service-images.d.mts +12 -0
  56. package/runtime/scripts/hfs/rules/stacks-service-images.mjs +120 -0
  57. package/runtime/scripts/hfs/secret-dev-init.d.mts +11 -0
  58. package/runtime/scripts/hfs/secret-dev-init.mjs +79 -0
  59. package/runtime/scripts/hfs/secret-errors.d.mts +3 -0
  60. package/runtime/scripts/hfs/secret-errors.mjs +8 -0
  61. package/runtime/scripts/hfs/secret-init.d.mts +6 -0
  62. package/runtime/scripts/hfs/secret-init.mjs +23 -0
  63. package/runtime/scripts/hfs/secret-sops.mjs +9 -1
  64. package/runtime/scripts/hfs/secret-standard.d.mts +20 -0
  65. package/runtime/scripts/hfs/secret-standard.mjs +106 -0
  66. package/runtime/scripts/hfs/secret.mjs +15 -8
  67. package/runtime/scripts/hfs/slots.mjs +1 -1
  68. package/runtime/scripts/hfs/stack-local-config.d.mts +101 -0
  69. package/runtime/scripts/hfs/stack-local-config.mjs +320 -0
  70. package/runtime/scripts/hfs/stacks-gate.mjs +1 -1
  71. package/runtime/scripts/hfs/view.d.mts +23 -0
  72. package/runtime/scripts/hfs/view.mjs +68 -0
  73. package/runtime/scripts/lib/declaration-surface.d.mts +4 -0
  74. package/runtime/scripts/lib/declaration-surface.mjs +55 -1
  75. package/runtime/scripts/lib/secret-records.d.mts +13 -0
  76. package/runtime/scripts/lib/secret-records.mjs +83 -0
  77. package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
  78. package/runtime/scripts/lib/swarm-stack.mjs +2 -2
  79. package/scaffold/add-inbox-lite.mjs +2 -2
  80. package/scaffold/add-module.d.mts +16 -0
  81. package/scaffold/add-module.mjs +232 -0
  82. package/scaffold/add-queue-lite.mjs +50 -3
  83. package/scaffold/add.mjs +5 -0
  84. package/scaffold/app.d.mts +2 -2
  85. package/scaffold/app.mjs +16 -18
  86. package/scaffold/lite-exports.mjs +1 -15
  87. package/scaffold/module-registry.d.mts +19 -0
  88. package/scaffold/module-registry.mjs +84 -0
  89. package/scripts/sync-bundles.d.mts +8 -0
  90. package/src/main.mjs +23 -2
  91. package/stack/cli.mjs +6 -1
  92. package/stack/context.d.mts +15 -0
  93. package/stack/context.mjs +1 -0
  94. package/stack/images/openclaw-runtime/Dockerfile +18 -0
  95. package/stack/images/openclaw-runtime/entrypoint.sh +23 -0
  96. package/stack/images/openclaw-runtime/openclaw-init.mjs +101 -0
  97. package/stack/images/openclaw-runtime/openclaw-relay.mjs +25 -0
  98. package/stack/local-cli.d.mts +25 -0
  99. package/stack/local-cli.mjs +89 -0
  100. package/stack/local-config.d.mts +17 -0
  101. package/stack/local-config.mjs +37 -0
  102. package/stack/local-custody.d.mts +24 -0
  103. package/stack/local-custody.mjs +109 -0
  104. package/stack/local-doctor.d.mts +6 -0
  105. package/stack/local-doctor.mjs +49 -0
  106. package/stack/local-down.d.mts +8 -0
  107. package/stack/local-down.mjs +20 -0
  108. package/stack/local-host.d.mts +42 -0
  109. package/stack/local-host.mjs +163 -0
  110. package/stack/local-logs.d.mts +8 -0
  111. package/stack/local-logs.mjs +32 -0
  112. package/stack/local-ports.d.mts +7 -0
  113. package/stack/local-ports.mjs +39 -0
  114. package/stack/local-prepare.d.mts +22 -0
  115. package/stack/local-prepare.mjs +62 -0
  116. package/stack/local-run.d.mts +71 -0
  117. package/stack/local-run.mjs +200 -0
  118. package/stack/local-state.d.mts +35 -0
  119. package/stack/local-state.mjs +29 -0
  120. package/stack/local-status.d.mts +6 -0
  121. package/stack/local-status.mjs +36 -0
  122. package/stack/local-up.d.mts +11 -0
  123. package/stack/local-up.mjs +169 -0
  124. package/stack/local-verify.d.mts +6 -0
  125. package/stack/local-verify.mjs +21 -0
  126. package/stack/runtime-layers.d.mts +21 -0
  127. package/stack/runtime-layers.mjs +67 -0
  128. package/sync/hygiene.mjs +3 -1
  129. package/sync/index.d.mts +15 -12
  130. package/sync/index.mjs +114 -28
  131. package/templates/app/auth-doors.sql.partial +115 -0
  132. package/templates/app/ci-workflows/github/workflows/ci.yml +1 -1
  133. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -1
  134. package/templates/app/module/jest.config.js.tpl +8 -0
  135. package/templates/app/module/src/index.ts.tpl +2 -0
  136. package/templates/app/module/src/manifest/declaration.spec.ts.tpl +11 -0
  137. package/templates/app/module/src/manifest/declaration.ts.tpl +13 -0
  138. package/templates/app/module/src/manifest/index.ts.tpl +1 -0
  139. package/templates/app/module/src/public/index.ts.tpl +2 -0
  140. package/templates/app/module/src/template/__name__.template.spec.ts.tpl +13 -0
  141. package/templates/app/module/src/template/__name__.template.ts.tpl +41 -0
  142. package/templates/app/module/src/template/index.ts.tpl +1 -0
  143. package/templates/app/module/src/workbench/components/__pascal__WorkbenchView.tsx.tpl +16 -0
  144. package/templates/app/module/src/workbench/index.ts.tpl +2 -0
  145. package/templates/app/package-scripts/core.json +1 -1
  146. package/templates/app/package-scripts/package.json +1 -1
  147. package/templates/app/package-scripts-lite/package.json +1 -1
  148. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +85 -70
  149. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +24 -12
  150. package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +43 -0
  151. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +21 -26
  152. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +6 -0
  153. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +6 -1
  154. package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +76 -13
  155. package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +44 -0
  156. package/templates/be/auth-lite/src/features/api/auth/application/confirm.command.ts +11 -0
  157. package/templates/be/auth-lite/src/features/api/auth/application/confirm.handler.ts +22 -0
  158. package/templates/be/auth-lite/src/features/api/auth/application/forgot.command.ts +11 -0
  159. package/templates/be/auth-lite/src/features/api/auth/application/forgot.handler.ts +22 -0
  160. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.handler.ts +23 -0
  161. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.query.ts +12 -0
  162. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.command.ts +11 -0
  163. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.handler.ts +22 -0
  164. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.command.ts +12 -0
  165. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.handler.ts +23 -0
  166. package/templates/be/auth-lite/src/features/api/auth/application/login.command.ts +12 -0
  167. package/templates/be/auth-lite/src/features/api/auth/application/login.handler.ts +23 -0
  168. package/templates/be/auth-lite/src/features/api/auth/application/logout.command.ts +11 -0
  169. package/templates/be/auth-lite/src/features/api/auth/application/logout.handler.ts +22 -0
  170. package/templates/be/auth-lite/src/features/api/auth/application/refresh.command.ts +12 -0
  171. package/templates/be/auth-lite/src/features/api/auth/application/refresh.handler.ts +23 -0
  172. package/templates/be/auth-lite/src/features/api/auth/application/resend.command.ts +11 -0
  173. package/templates/be/auth-lite/src/features/api/auth/application/resend.handler.ts +22 -0
  174. package/templates/be/auth-lite/src/features/api/auth/application/reset.command.ts +11 -0
  175. package/templates/be/auth-lite/src/features/api/auth/application/reset.handler.ts +22 -0
  176. package/templates/be/auth-lite/src/features/api/auth/application/session.command.ts +12 -0
  177. package/templates/be/auth-lite/src/features/api/auth/application/session.handler.ts +23 -0
  178. package/templates/be/auth-lite/src/features/api/auth/application/signup.command.ts +11 -0
  179. package/templates/be/auth-lite/src/features/api/auth/application/signup.handler.ts +22 -0
  180. package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +6 -0
  181. package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +21 -0
  182. package/templates/be/auth-lite/src/features/api/auth/auth.options.ts +3 -0
  183. package/templates/be/auth-lite/src/features/api/auth/index.ts +2 -0
  184. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +6 -0
  185. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module.ts +9 -0
  186. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.options.ts +3 -0
  187. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +145 -0
  188. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/forgot.request.ts +16 -0
  189. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/google-start.request.ts +17 -0
  190. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/login.request.ts +16 -0
  191. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/resend.request.ts +16 -0
  192. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/reset.request.ts +18 -0
  193. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/signup.request.ts +22 -0
  194. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/token.request.ts +11 -0
  195. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +46 -0
  196. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +19 -0
  197. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +209 -0
  198. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +84 -0
  199. package/templates/be/auth-lite/src/modules/domain/identity/identity-email.log-events.ts +6 -0
  200. package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.spec.ts +233 -0
  201. package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.ts +176 -0
  202. package/templates/be/auth-lite/src/modules/domain/identity/identity-sign-in.log-events.ts +6 -0
  203. package/templates/be/auth-lite/src/modules/domain/identity/persistence/account-doors.port.ts +98 -0
  204. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts +18 -0
  205. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-email-token.entity.ts +34 -0
  206. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +34 -0
  207. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-session.entity.ts +30 -0
  208. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +10 -0
  209. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +86 -0
  210. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +45 -0
  211. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +97 -0
  212. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +81 -0
  213. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +95 -0
  214. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +93 -0
  215. package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +40 -0
  216. package/templates/be/inbox/module-definition.ts.tpl +2 -9
  217. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +2 -11
  218. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +2 -9
  219. package/templates/be/patterns/api/feature.module-definition.ts.tpl +3 -9
  220. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +3 -9
  221. package/templates/be/patterns/cli/group.module-definition.ts.tpl +2 -9
  222. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +3 -10
  223. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +2 -9
  224. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +2 -9
  225. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +3 -9
  226. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +3 -10
  227. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +2 -9
  228. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +3 -10
  229. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +2 -9
  230. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +3 -9
  231. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +2 -9
  232. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +3 -9
  233. package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +3 -9
  234. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +3 -9
  235. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +3 -9
  236. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +2 -9
  237. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +3 -9
  238. package/templates/be/platform/graphql/graphql.module-definition.ts +2 -9
  239. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +3 -9
  240. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +3 -9
  241. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +2 -9
  242. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +3 -9
  243. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +2 -9
  244. package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +2 -9
  245. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +2 -9
  246. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +2 -9
  247. package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +2 -9
  248. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +2 -0
  249. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +2 -9
  250. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +3 -13
  251. package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +4 -11
  252. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +4 -9
  253. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +5 -11
  254. package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +4 -11
  255. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +2 -9
  256. package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +2 -9
  257. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +2 -9
  258. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +4 -0
  259. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +3 -0
  260. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +6 -5
  261. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +3 -2
  262. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +12 -5
  263. package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +40 -0
  264. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +22 -1
  265. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +50 -2
  266. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +3 -0
  267. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity-mail.messages.ts +65 -0
  268. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +28 -0
  269. package/templates/be/skeleton-lite/src/modules/integrations/mail/index.ts +6 -0
  270. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.config.ts +33 -0
  271. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.decorators.ts +14 -0
  272. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module-definition.ts +12 -0
  273. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module.ts +27 -0
  274. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.options.ts +22 -0
  275. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +3 -3
  276. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +3 -1
  277. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +8 -4
  278. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +5 -0
  279. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts +29 -0
  280. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +6 -31
  281. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +18 -9
  282. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +35 -11
  283. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +44 -8
  284. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +40 -18
  285. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +18 -6
  286. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.spec.ts +73 -0
  287. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.ts +26 -0
  288. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +24 -53
  289. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.registration.ts +31 -0
  290. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +1 -1
  291. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
  292. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  293. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
  294. package/templates/be/skeleton-lite/src/modules/platform/http-security/rate-limit.guard.spec.ts +11 -0
  295. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +22 -4
  296. package/templates/be/table/api/feature.module-definition.ts.tpl +3 -9
  297. package/templates/be/table/api/graphql.module-definition.ts.tpl +3 -9
  298. package/templates/be/table/module-definition.ts.tpl +2 -9
  299. package/templates/be/test-world/e2e/serves-once-migrated.e2e-spec.ts.tpl +9 -9
  300. package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +4 -4
  301. package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
  302. package/templates/fe/auth-lite/apps/web/src/modules/auth/email-doors.ts +16 -0
  303. package/templates/fe/auth-lite/apps/web/src/modules/auth/index.ts +5 -0
  304. package/templates/fe/common/site-shell.tsx.partial +3 -3
  305. package/templates/fe/skeleton/apps/__app__/src/components/providers/AppProviders/index.tsx +33 -10
  306. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/index.ts +1 -0
  307. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
  308. package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +15 -12
  309. package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
  310. package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +1 -1
  311. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +30 -67
  312. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +6 -21
  313. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +10 -16
  314. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +4 -7
  315. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/index.tsx +2 -3
  316. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/confirm/page.tsx +9 -0
  317. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/forgot/page.tsx +9 -0
  318. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/resend/page.tsx +9 -0
  319. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/reset/page.tsx +9 -0
  320. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/signup/page.tsx +9 -0
  321. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +5 -9
  322. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/component.tsx +51 -0
  323. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/index.tsx +84 -0
  324. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +4 -7
  325. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx +1 -2
  326. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppConfirmPage/index.tsx +25 -0
  327. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppForgotPage/index.tsx +21 -0
  328. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +11 -3
  329. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +4 -1
  330. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResendPage/index.tsx +21 -0
  331. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResetPage/index.tsx +25 -0
  332. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppSignupPage/index.tsx +21 -0
  333. package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/emailAuth.shared.ts +12 -0
  334. package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/index.ts +2 -0
  335. package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/useEmailDoor.ts +32 -0
  336. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +2 -2
  337. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +50 -1
  338. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +50 -1
  339. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +7 -30
  340. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +13 -14
  341. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +5 -0
  342. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +4 -17
  343. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +0 -16
  344. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +0 -11
  345. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +0 -7
  346. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +0 -6
  347. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +0 -8
  348. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +0 -6
  349. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -186
  350. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +0 -22
  351. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +0 -25
  352. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +0 -104
  353. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +0 -34
  354. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts +0 -28
  355. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +0 -11
  356. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +0 -88
  357. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +0 -69
  358. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +0 -41
  359. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -40
  360. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +0 -63
  361. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/classNames.ts +0 -4
  362. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +0 -30
  363. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +0 -50
  364. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +0 -31
  365. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/classNames.ts +0 -4
@@ -7,7 +7,7 @@
7
7
  # catalog READ this file through scripts/hfs/slots.mjs; none of them hardcodes a path.
8
8
  # Shape: modules/schemas/hfs-slots.schema.yaml. The repository side is hfs.json: modules/schemas/hfs-repo.schema.yaml.
9
9
  schema: starci/hfs-slots@3
10
- version: 3.15.0
10
+ version: 3.17.0
11
11
 
12
12
  versioning:
13
13
  # MAJOR.MINOR.PATCH of this manifest. A repository pins only the MAJOR in hfs.json ("hfs": 3).
@@ -58,6 +58,9 @@ versioning:
58
58
  # 3.13.0 (rulings lite-pgmq-bus-bullmq-jobs and lite-db-image-in-stacks, 2026-10-29) is a change for lite only: ruleParams.db.extensions
59
59
  # admits pgmq, the event bus, ruleParams.db.queueOutbox is retired with the pg_cron half of R258, the lite event-bus functions replace
60
60
  # event_bus_write in ruleParams.be.dbFunctions, and app.db.image moves to .starcistacks/images/db/Dockerfile (db/ keeps schema and data).
61
+ # 3.17.0 (ruling auth-oidc-package, 2026-11-03) adds the optional full-edition be.integrations.keycloak, the glue of @starci/auth-oidc (the OIDC client of the
62
+ # identity provider behind the PrincipalVerifier of @starci/auth), and gives @starci/auth-oidc to it in ruleParams.be.infraOwners.
63
+ # 3.17.0 (ruling module-packages, 2026-11-03) adds the optional app.packages and app.module-registry: the workspace packages of the app root (the agent SDK, the module packages, the managed registry).
61
64
  # 3.15.0 (ruling app-check-cache, 2026-10-31) adds the optional app.starcistacks.cache, the committed check cache of the app (.starcistacks/cache/, both editions).
62
65
  # 3.14.0 (ruling app-secrets-one-standard, 2026-10-29) adds the optional app.ci-stack-key-restore, the managed workflow that writes the
63
66
  # deploy server's age identity back from its GitHub Environment backup.
@@ -117,6 +120,8 @@ versioning:
117
120
  # liteManagedBy is managed only in lite; its effective lite.path still lists literal files.
118
121
  # the managed file list lives here and nowhere in code. A difference is HFS_MANAGED_FILE_DRIFT (a
119
122
  # back end's tsconfig.json: HFS_TS_STRICT when a flag or an option was added).
123
+ # managedOnly true: the slot only lists files sync renders (needs managedBy); the path is judged by the slot that owns it, so the rendered
124
+ # file keeps the rules and the tier of its folder (a sign-in service is a domain service)
120
125
  # rules rule codes from knowledge/hfs/rules.yaml.
121
126
  # editions the editions (below) the slot exists in; default all of them. A slot outside the declared edition owns
122
127
  # no path - under lite, what full keeps absent is removed, and what lite forbids is a finding.
@@ -250,17 +255,29 @@ ruleParams:
250
255
  "node:https": [platform/http]
251
256
  http: [platform/http]
252
257
  https: [platform/http]
253
- "@aws-sdk/client-s3": [integrations/object-storage] # the ObjectStorage port: one S3 adapter for MinIO, S3 and Spaces (R331)
254
- "@aws-sdk/s3-request-presigner": [integrations/object-storage]
255
- minio: [] # storage goes through the S3 client of integrations/object-storage
258
+ "@aws-sdk/client-s3": [] # the S3 client is a dependency of @starci/object-storage, imported by no app file (R331)
259
+ "@aws-sdk/s3-request-presigner": []
260
+ "@starci/object-storage": [integrations/object-storage] # the ObjectStorage port: one S3 adapter for MinIO, S3 and Spaces (R331)
261
+ minio: [] # storage goes through @starci/object-storage
262
+ nodemailer: [] # the SMTP client is a dependency of @starci/mail alone; no app file imports it
263
+ "@starci/mail": [integrations/mail] # outgoing mail: the Mailer port, parsed and registered by its one glue
256
264
  "@supabase": [] # no Supabase client in a back end
257
- "@nestjs/passport": [integrations/passport] # the identity libraries (R328)
258
- passport: [integrations/passport]
259
- passport-google-oauth20: [integrations/passport]
260
- passport-local: [integrations/passport]
261
- "@nestjs/jwt": [integrations/passport]
262
- jose: [integrations/passport]
263
- bcryptjs: [integrations/passport]
265
+ "@nestjs/passport": [] # the identity libraries are @starci/auth's dependencies, never an app's (R328)
266
+ passport: []
267
+ passport-google-oauth20: []
268
+ passport-local: []
269
+ "@nestjs/jwt": []
270
+ jose: []
271
+ bcryptjs: []
272
+ jsonwebtoken: [] # no other JWT or OIDC client library in a back end: the tokens of an identity provider are verified by @starci/auth-oidc (R328)
273
+ jwks-rsa: []
274
+ jwt-decode: []
275
+ passport-jwt: []
276
+ openid-client: []
277
+ "@starci/auth": [integrations/passport] # the lite sign-in package: its server entry is reached only through integrations/passport (R328)
278
+ "@starci/auth/testing": [integrations/passport] # its test doubles, for the glue's specs
279
+ "@starci/auth-oidc": [integrations/keycloak] # the full sign-in package (jose is its dependency, imported by no app file): its server entry is reached only through integrations/keycloak (R328)
280
+ "@starci/auth-oidc/testing": [integrations/keycloak] # its fake issuer, for the glue's specs
264
281
  bcrypt: []
265
282
  cache-manager: [integrations/cache, integrations/redis]
266
283
  "@nestjs/cache-manager": [integrations/cache, integrations/redis] # the CACHE_MANAGER token: caching goes through the cache capability
@@ -474,6 +491,14 @@ ruleParams:
474
491
  - {name: public.event_bus_bury, reason: "platform/event-bus (add webhook, lite): the move of an exhausted or malformed message to its consumer's dead-letter queue"}
475
492
  - {name: public.event_bus_dead_letters, reason: "platform/event-bus (add webhook, lite): the operator read of a consumer's dead-letter queue"}
476
493
  - {name: public.event_bus_requeue, reason: "platform/event-bus (add webhook, lite): the operator requeue of one dead letter"}
494
+ - {name: public.be_auth_user_by_email, reason: "domain/identity (lite sign-in): the account of an exact sign-in address with its bcrypt hash, through the account-doors definer of the baseline"}
495
+ - {name: public.be_auth_user_by_id, reason: "domain/identity (lite sign-in): the standing of one account by id (a refresh), through the account-doors definer"}
496
+ - {name: public.be_auth_user_by_google, reason: "domain/identity (lite sign-in): the account a Google subject is linked to, through the account-doors definer"}
497
+ - {name: public.be_auth_create_user, reason: "domain/identity (lite sign-in): the account a first Google sign-in creates, through the account-doors definer"}
498
+ - {name: public.be_auth_link_google, reason: "domain/identity (lite sign-in): the link of a Google subject to an account, through the account-doors definer"}
499
+ - {name: public.be_auth_touch_user, reason: "domain/identity (lite sign-in): the sign-in record of an account and its profile keys, through the account-doors definer"}
500
+ - {name: public.be_auth_confirm_user, reason: "domain/identity (lite sign-in): the confirmation stamp of a mailbox whose link was spent, through the account-doors definer"}
501
+ - {name: public.be_auth_set_password, reason: "domain/identity (lite sign-in): the new password hash of a spent reset link (and the mailbox it proved), through the account-doors definer"}
477
502
  # R334 (lite): the only `.query(` outside platform/database's composer and the test world is the pass-through of a file listed here
478
503
  # (or in hfs.json sides.be.sqlPassThrough), whose SQL text is not the app's own. Paths are relative to the side folder.
479
504
  sqlPassThrough:
@@ -601,13 +626,24 @@ slots:
601
626
  - id: app.tool-config-local
602
627
  since: 3.1.0
603
628
  profiles: [app]
604
- path: "{tsconfig.json,.eslintrc,.eslintrc.*,.eslintignore,eslint.config.*,.stylelintrc,.stylelintrc.*,.stylelintignore,stylelint.config.*,.prettierrc.*,prettier.config.*,jest.config.*,lint-staged.config.*,.lintstagedrc*}"
629
+ path: "{tsconfig.json,.eslintrc,.eslintrc.*,.eslintignore,eslint.config.js,eslint.config.cjs,eslint.config.ts,eslint.config.mts,eslint.config.cts,.stylelintrc,.stylelintrc.*,.stylelintignore,stylelint.config.*,.prettierrc.*,prettier.config.*,jest.config.*,lint-staged.config.*,.lintstagedrc*}"
605
630
  presence: forbidden
606
631
  tracked: external
607
632
  tier: none
608
633
  tests: none
609
- goesTo: "nowhere at the app root: each side holds exactly its managed tool configuration (be.tool-config, fe.tool-config) and the root only the formatter (app.format-config)"
634
+ goesTo: "nowhere at the app root: each side holds exactly its managed tool configuration (be.tool-config, fe.tool-config) and the root only the formatter (app.format-config) and the eslint config of its root packages (app.packages-lint)"
610
635
  rules: [HFS_TOOL_CONFIG_LOCAL]
636
+ - id: app.packages-lint
637
+ since: 3.17.0
638
+ profiles: [app]
639
+ # The one eslint config at the app root: `starci app sync` renders it when the app has root workspace packages (app.packages), and it lints their
640
+ # source by subpath (the back-end canon, the front-end canon and the Grammar family each over their own subpaths). No other tool config lives here.
641
+ path: eslint.config.mjs
642
+ presence: optional
643
+ tracked: tracked
644
+ tier: none
645
+ tests: none
646
+ rules: [HFS_MANAGED_FILE_DRIFT]
611
647
  - id: app.quality-config
612
648
  since: 3.1.0
613
649
  profiles: [app]
@@ -852,6 +888,31 @@ slots:
852
888
  rules: [HFS_PLAINTEXT_SECRET, HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK]
853
889
  exemplifiedBy:
854
890
  - {path: packages/hfs/src/hfs-slot-ownership.spec.mjs, editions: [lite, full]}
891
+ - id: app.packages
892
+ since: 3.17.0
893
+ profiles: [app]
894
+ editionWhy: Lite and full share the slot; a product's agent SDK and its agent modules are workspace packages in both.
895
+ # A workspace package of the app ROOT (packages/agent-sdk, packages/module-<name>): first-party source that both sides import through its
896
+ # package entry points and never past them (ARCH_INTERNAL_IMPORT_OUTSIDE), linted by the root eslint config by subpath.
897
+ path: "packages/{agent-sdk,module-<name>}/"
898
+ presence: optional
899
+ tracked: tracked
900
+ tier: none
901
+ requires: [package.json, tsconfig.json, src/index.ts]
902
+ tests: unit-beside
903
+ rules: [HFS_UNUSED_EXPORT]
904
+ - id: app.module-registry
905
+ since: 3.17.0
906
+ profiles: [app]
907
+ editionWhy: Lite and full share the slot; the registry lists the module packages of the app.
908
+ path: packages/modules/
909
+ presence: optional
910
+ tracked: tracked
911
+ tier: none
912
+ requires: [package.json, src/index.ts]
913
+ allows: [package.json, tsconfig.json, tsconfig.build.json, src/index.ts, src/template/index.ts, src/manifest/index.ts]
914
+ tests: none
915
+ rules: [HFS_MANAGED_FILE_DRIFT]
855
916
  - id: app.sides
856
917
  since: 3.1.0
857
918
  profiles: [app]
@@ -909,7 +970,7 @@ slots:
909
970
  # The db service supplies the database; schedules are platform/jobs ticks (R335); production has no Compose or host cron.
910
971
  lite:
911
972
  requires: [application-stacks.yaml]
912
- allows: [application-stacks.yaml, images/db/Dockerfile, "<env>/README.md", "<env>/environment.json", "<env>/infra/swarm/**", "<env>/infra/metadata.json",
973
+ allows: [application-stacks.yaml, images/db/Dockerfile, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,swarm}/**", "<env>/infra/metadata.json",
913
974
  "<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.{enc.yaml,enc}", "<env>/secrets/.gitkeep", "<env>/seeds/**"]
914
975
  forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
915
976
  rules: [HFS_STACKS_SHAPE, HFS_STACKS_IN_SIDE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY]
@@ -2028,40 +2089,109 @@ slots:
2028
2089
  editions: [lite]
2029
2090
  exemplifiedBy:
2030
2091
  - packages/hfs/templates/be/skeleton-lite/src/modules/integrations/passport/
2031
- # The one owner of the lite identity libraries (ruleParams.be.infraOwners: @nestjs/passport, passport, passport-google-oauth20,
2032
- # passport-local, @nestjs/jwt, jose, bcryptjs): the Google OAuth 2.0 strategy (state and PKCE), the local email and password
2033
- # strategy (bcrypt), the app's own asymmetric access tokens and their JWKS, and the rotating refresh cookie. domain/identity
2034
- # verifies a token through this integration's verifier and builds the principal (contexts/code/be/api-auth.md).
2092
+ # The glue of @starci/auth, the lite sign-in package (Google OAuth with state and PKCE, email and password, EdDSA access tokens with
2093
+ # their JWKS, rotating refresh sessions): the one importer of the package (ruleParams.be.infraOwners), which owns the identity
2094
+ # libraries (@nestjs/passport, passport, passport-google-oauth20, passport-local, @nestjs/jwt, jose, bcryptjs) that no app imports.
2095
+ # It parses the typed options, builds the verifier and names the injectors; domain/identity verifies a token through the kit and
2096
+ # builds the principal (contexts/code/be/api-auth.md).
2035
2097
  path: "src/modules/integrations/passport/"
2036
2098
  presence: required
2037
2099
  tracked: tracked
2038
2100
  tier: integrations
2039
2101
  owner: true
2040
- requires: [index.ts, passport.config.ts, passport.options.ts, passport.decorators.ts, passport.tokens.ts, passport.password.ts, passport.cookie.ts] # Secrets, no defaults (R44); options and named injectors (R83); tokens and JWKS; bcrypt; the refresh cookie
2041
- allows: [index.ts, passport.config.ts, passport.options.ts, passport.decorators.ts, passport.tokens.ts, passport.password.ts, passport.cookie.ts, strategies/, errors/]
2042
- logic: [passport.tokens.ts, passport.password.ts, passport.cookie.ts] # JWT sign and verify over the own JWKS, bcrypt, the refresh cookie: measured at 100 per file
2102
+ requires: [index.ts, passport.config.ts, passport.options.ts, passport.decorators.ts, passport.kit.ts, passport.registration.ts] # Secrets, no defaults (R44); options and named injectors (R83); the kit and the module over @starci/auth
2103
+ allows: [index.ts, passport.config.ts, passport.options.ts, passport.decorators.ts, passport.kit.ts, passport.registration.ts, errors/]
2104
+ logic: [passport.kit.ts] # the verifier mapping over the package: measured at 100 per file
2043
2105
  tests: unit-beside
2044
2106
  rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER, BE_AUTH_PASSPORT_OWNER, BE_AUTH_JWT_OWN_JWKS, BE_AUTH_REFRESH_COOKIE]
2045
2107
  coverage: required
2046
2108
  since: 3.9.0
2109
+ - id: be.identity.sign-in
2110
+ since: 3.16.0
2111
+ profiles: [be]
2112
+ editions: [lite]
2113
+ editionWhy: "lite signs people in with the package @starci/auth, so its sign-in glue is rendered, not written (owner ruling auth-package); full uses its identity provider."
2114
+ # The identity capability's sign-in glue over @starci/auth: the entities of the session, attempt, refresh-token and email-token tables, the account doors over auth.users and auth.identities, the stores the package asks for (sessions, accounts, rate counter), the authenticator and the service the doors call, with their specs. `starci app sync` renders them; a hand edit is drift.
2115
+ path: "{src/modules/domain/identity/account-directory.service.spec.ts,src/modules/domain/identity/account-directory.service.ts,src/modules/domain/identity/authenticator.service.spec.ts,src/modules/domain/identity/authenticator.service.ts,src/modules/domain/identity/identity-email.log-events.ts,src/modules/domain/identity/identity-email.service.spec.ts,src/modules/domain/identity/identity-email.service.ts,src/modules/domain/identity/identity-sign-in.log-events.ts,src/modules/domain/identity/persistence/account-doors.port.ts,src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts,src/modules/domain/identity/persistence/entities/identity-email-token.entity.ts,src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts,src/modules/domain/identity/persistence/entities/identity-session.entity.ts,src/modules/domain/identity/persistence/entities/index.ts,src/modules/domain/identity/rate-counter.service.spec.ts,src/modules/domain/identity/rate-counter.service.ts,src/modules/domain/identity/session-store.service.spec.ts,src/modules/domain/identity/session-store.service.ts,src/modules/domain/identity/sign-in.service.spec.ts,src/modules/domain/identity/sign-in.service.ts,src/tests/fixtures/builders/identity.builder.ts}"
2116
+ presence: optional
2117
+ tracked: tracked
2118
+ tier: inherit
2119
+ tests: unit-beside
2120
+ coverage: none
2121
+ managedBy: auth-lite
2122
+ managedOnly: true # lists files sync renders; the slot owning the path judges them
2123
+ rules: [HFS_MANAGED_FILE_DRIFT]
2124
+ - id: be.feature.auth
2125
+ since: 3.16.0
2126
+ profiles: [be]
2127
+ editions: [lite]
2128
+ editionWhy: "lite signs people in with the package @starci/auth, so its sign-in glue is rendered, not written (owner ruling auth-package); full uses its identity provider."
2129
+ # The auth feature: the REST doors under /auth (login, refresh, session, logout, Google start and callback, jwks) over IdentitySignInService, one command or query and handler per door.
2130
+ path: "{src/features/api/auth/application/auth.contracts.ts,src/features/api/auth/application/confirm.command.ts,src/features/api/auth/application/confirm.handler.ts,src/features/api/auth/application/forgot.command.ts,src/features/api/auth/application/forgot.handler.ts,src/features/api/auth/application/get-jwks.handler.ts,src/features/api/auth/application/get-jwks.query.ts,src/features/api/auth/application/google-begin.command.ts,src/features/api/auth/application/google-begin.handler.ts,src/features/api/auth/application/google-complete.command.ts,src/features/api/auth/application/google-complete.handler.ts,src/features/api/auth/application/login.command.ts,src/features/api/auth/application/login.handler.ts,src/features/api/auth/application/logout.command.ts,src/features/api/auth/application/logout.handler.ts,src/features/api/auth/application/refresh.command.ts,src/features/api/auth/application/refresh.handler.ts,src/features/api/auth/application/resend.command.ts,src/features/api/auth/application/resend.handler.ts,src/features/api/auth/application/reset.command.ts,src/features/api/auth/application/reset.handler.ts,src/features/api/auth/application/session.command.ts,src/features/api/auth/application/session.handler.ts,src/features/api/auth/application/signup.command.ts,src/features/api/auth/application/signup.handler.ts,src/features/api/auth/auth.module-definition.ts,src/features/api/auth/auth.module.ts,src/features/api/auth/auth.options.ts,src/features/api/auth/index.ts,src/features/api/auth/transport/http/auth-http.module-definition.ts,src/features/api/auth/transport/http/auth-http.module.ts,src/features/api/auth/transport/http/auth-http.options.ts,src/features/api/auth/transport/http/auth.controller.ts,src/features/api/auth/transport/http/dto/forgot.request.ts,src/features/api/auth/transport/http/dto/google-start.request.ts,src/features/api/auth/transport/http/dto/login.request.ts,src/features/api/auth/transport/http/dto/resend.request.ts,src/features/api/auth/transport/http/dto/reset.request.ts,src/features/api/auth/transport/http/dto/signup.request.ts,src/features/api/auth/transport/http/dto/token.request.ts}"
2131
+ presence: optional
2132
+ tracked: tracked
2133
+ tier: inherit
2134
+ tests: none
2135
+ coverage: none
2136
+ managedBy: auth-lite
2137
+ managedOnly: true # lists files sync renders; the slot owning the path judges them
2138
+ rules: [HFS_MANAGED_FILE_DRIFT]
2139
+ - id: be.integrations.mail
2140
+ profiles: [be]
2141
+ editions: [lite]
2142
+ exemplifiedBy:
2143
+ - packages/hfs/templates/be/skeleton-lite/src/modules/integrations/mail/
2144
+ # The glue of @starci/mail, the owner of the SMTP client (nodemailer): this folder parses the settings (the server, the sender and the
2145
+ # link lifetimes of the confirmation and reset mails), registers the module and names the injectors; no app file imports nodemailer,
2146
+ # and no other module imports the package. The words and layout of each mail belong to the domain that sends it.
2147
+ path: "src/modules/integrations/mail/"
2148
+ presence: required
2149
+ tracked: tracked
2150
+ tier: integrations
2151
+ owner: true
2152
+ requires: [index.ts, mail.config.ts, mail.options.ts, mail.decorators.ts, mail.module-definition.ts, mail.module.ts]
2153
+ allows: [index.ts, mail.config.ts, mail.options.ts, mail.decorators.ts, mail.module-definition.ts, mail.module.ts, errors/]
2154
+ tests: unit-beside
2155
+ rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER]
2156
+ coverage: required
2157
+ since: 5.3.0
2047
2158
  - id: be.integrations.object-storage
2048
2159
  profiles: [be]
2049
2160
  exemplifiedBy:
2050
2161
  - packages/hfs/templates/be/skeleton-lite/src/modules/integrations/object-storage/
2051
- # The one owner of the S3 client (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) over S3-compatible storage (MinIO in
2052
- # lite): the ObjectStorage port with presigned PUT and GET; no other module imports an S3 SDK or minio.
2162
+ # The glue of @starci/object-storage, the owner of the S3 client (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) over
2163
+ # S3-compatible storage (MinIO in lite): this folder keeps the bucket, parses the settings and registers the module; no app
2164
+ # file imports an S3 SDK or minio, and no other module imports the package.
2053
2165
  path: "src/modules/integrations/object-storage/"
2054
2166
  presence: optional
2055
2167
  litePresence: required
2056
2168
  tracked: tracked
2057
2169
  tier: integrations
2058
2170
  owner: true
2059
- requires: [index.ts, object-storage.config.ts, object-storage.options.ts, object-storage.decorators.ts, object-storage.client.ts]
2060
- allows: [index.ts, object-storage.module.ts, object-storage.module-definition.ts, object-storage.config.ts, object-storage.options.ts, object-storage.decorators.ts, object-storage.client.ts, errors/]
2171
+ requires: [index.ts, object-storage.config.ts, object-storage.options.ts, object-storage.decorators.ts, object-storage.module-definition.ts, object-storage.module.ts]
2172
+ allows: [index.ts, object-storage.config.ts, object-storage.options.ts, object-storage.decorators.ts, object-storage.module-definition.ts, object-storage.module.ts, errors/]
2061
2173
  tests: unit-beside
2062
2174
  rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER, BE_STORAGE_OWNER]
2063
2175
  coverage: required
2064
2176
  since: 3.9.0
2177
+ - id: be.integrations.keycloak
2178
+ profiles: [be]
2179
+ editions: [full]
2180
+ # The glue of @starci/auth-oidc, the OIDC client of the full edition's identity provider (Keycloak): discovery, the provider's JWKS,
2181
+ # algorithm-pinned token checks and the authorization code flow with PKCE and nonce behind the PrincipalVerifier of @starci/auth.
2182
+ # This folder parses the typed options, hands the package the app's bounded HTTP client and clock, and names the injectors;
2183
+ # no app file imports jose, and no other module imports the package (ruleParams.be.infraOwners).
2184
+ path: "src/modules/integrations/keycloak/"
2185
+ presence: optional
2186
+ tracked: tracked
2187
+ tier: integrations
2188
+ owner: true
2189
+ requires: [index.ts, keycloak.config.ts, keycloak.options.ts, keycloak.decorators.ts, keycloak.registration.ts]
2190
+ allows: [index.ts, keycloak.config.ts, keycloak.options.ts, keycloak.decorators.ts, keycloak.registration.ts, errors/, messages/]
2191
+ tests: unit-beside
2192
+ rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER, BE_AUTH_PASSPORT_OWNER, BE_INTEGRATION_SPEC_MISSING]
2193
+ coverage: required
2194
+ since: 3.17.0
2065
2195
 
2066
2196
  # ----- BE: tests (owner test layout 2026-09-30). Unit specs are <name>.service.spec.ts beside their service and nothing else; everything else
2067
2197
  # lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
@@ -2535,9 +2665,9 @@ slots:
2535
2665
  presence: required
2536
2666
  tracked: tracked
2537
2667
  tier: foundation
2538
- # The app's routing, request config and catalogs, or its adapter to the shared i18n factory. A single app
2539
- # owns createNavigation in fe.modules.routes, keeping this public entry safe for its proxy. Shared stacks
2540
- # belong to fe.package.i18n and expose a routing-only proxy entry separately from navigation.
2668
+ # The app's description of its languages (defineI18n of @starci/next-i18n), its request config and catalogs, or its adapter to the
2669
+ # shared i18n workspace package. A single app builds its navigation (createI18nNavigation) in fe.modules.routes, keeping this
2670
+ # public entry safe for its proxy. A description shared by several apps belongs to fe.package.i18n.
2541
2671
  requires: [index.ts]
2542
2672
  tests: none
2543
2673
  rules: [FE_I18N_PLACEMENT, FE_I18N_CATALOG]
@@ -2591,7 +2721,7 @@ slots:
2591
2721
  profiles: [fe]
2592
2722
  # The identity-provider client (fe-stack auth, FE-AUTH): the session, the access token the Apollo link attaches and the
2593
2723
  # sign-in/sign-out calls. It talks only to the app's own back-end auth doors and holds no credential: the refresh token lives in
2594
- # the back end's httpOnly cookie and the access token in memory (ruling:lite-self-hosted-postgres-passport-minio).
2724
+ # the back end's httpOnly cookie and the access token in the memory of the one @starci/auth client (ruling:lite-self-hosted-postgres-passport-minio).
2595
2725
  path: "apps/<app>/src/modules/auth/"
2596
2726
  presence: optional
2597
2727
  tracked: tracked
@@ -2601,6 +2731,20 @@ slots:
2601
2731
  tests: none
2602
2732
  since: 3.0.0
2603
2733
  rules: [FE_STACK_FORBIDDEN]
2734
+ - id: fe.modules.auth.client
2735
+ since: 3.16.0
2736
+ profiles: [fe]
2737
+ editions: [lite]
2738
+ editionWhy: "lite signs people in with the package @starci/auth, so its auth client is rendered, not written (owner ruling auth-package); full wires its identity provider."
2739
+ # The AuthClient every edition shares, implemented by one @starci/auth/client instance over the app's own back-end auth doors. email-doors.ts is the lite-only call of the five email doors (sign up, resend, confirm, forgot, reset), kept off the shared AuthClient interface because the full edition identity provider owns its own recovery flows; index.ts is the module entry that exports both.
2740
+ path: "apps/web/src/modules/auth/{client.ts,email-doors.ts,index.ts}"
2741
+ presence: optional
2742
+ tracked: tracked
2743
+ tier: inherit
2744
+ tests: none
2745
+ managedBy: auth-lite
2746
+ managedOnly: true # lists files sync renders; the slot owning the path judges them
2747
+ rules: [HFS_MANAGED_FILE_DRIFT]
2604
2748
  - id: fe.modules.toast
2605
2749
  profiles: [fe]
2606
2750
  # Mutation feedback (fe-stack toast, FE-TOAST): the HeroUI toast wrapper, showGraphQLToast over a GraphQLResponse and the
@@ -2675,7 +2819,7 @@ slots:
2675
2819
  owner: true
2676
2820
  requires: [package.json, src/index.ts, tsconfig.json]
2677
2821
  tests: none
2678
- why: the next-intl stack (routing, navigation, request config) written once, exported as the createAppI18n factory every app calls
2822
+ why: the product's languages (defineI18n of @starci/next-i18n) written once and composed into the createAppI18n factory every app calls; the next-intl stack itself is the package's
2679
2823
  rules: [FE_I18N_PLACEMENT, FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
2680
2824
 
2681
2825
  # Checks that read this manifest (all ship from .claude; none keeps its own path list)
@@ -980,7 +980,7 @@ BE_MODULE_DEFINITION_MISSING:
980
980
  - "Có <x>.module.ts nhưng thiếu <x>.module-definition.ts hoặc <x>.options.ts bên cạnh"
981
981
  - "Lớp module không kế thừa ConfigurableModuleClass lấy từ ./<x>.module-definition"
982
982
  - "Module sản phẩm nằm trong mảng imports (hoặc nhóm compose của app) như một tên lớp trần thay vì XModule.register({...})"
983
- nextStep_vi: "Tạo <x>.module-definition.ts (new ConfigurableModuleBuilder<XOptions>().setClassMethodName(\"register\").build()) và <x>.options.ts (export type XOptions = Record<string, never> nếu chưa có tùy chọn), cho lớp kế thừa ConfigurableModuleClass, và đăng ký bằng XModule.register({}); op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra."
983
+ nextStep_vi: "Tạo <x>.module-definition.ts (export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = defineConfigurableModule<XOptions>() từ @starci/nest-kit; capability dùng defineCapabilityModule) và <x>.options.ts (export type XOptions = Record<string, never> nếu chưa có tùy chọn), cho lớp kế thừa ConfigurableModuleClass, và đăng ký bằng XModule.register({}); op vừa tạo ra bản này sẽ được chạy lại để sửa theo lời nhắn của bộ kiểm tra."
984
984
  owner: op-retry
985
985
  kind: check-finding
986
986
 
@@ -1013,7 +1013,7 @@ BE_MODULE_SHAPE:
1013
1013
  - "@Global() trên bất kỳ module nào (kể cả trong spec)"
1014
1014
  - "isGlobal: true ở ngoài app.module.ts của app"
1015
1015
  - "Module của một capability khác bị import vào imports thay vì dùng qua Inject<Thing>()"
1016
- - "new ConfigurableModuleBuilder() không có kiểu options"
1016
+ - "defineConfigurableModule() hay new ConfigurableModuleBuilder() không có kiểu options"
1017
1017
  - "register không static, hoặc có forRoot, forFeature, registerAsync"
1018
1018
  - "new XService() hoặc let ở cấp module tạo trạng thái toàn cục"
1019
1019
  - "Feature có hai module trong một thư mục transport (một module cho mỗi thao tác), module-definition không nằm cạnh module của nó, hoặc @Module ngoài `<f>.module.ts` và `<f>-<protocol>.module.ts`; app import module ứng dụng của feature hoặc module transport không thuộc loại app cho phép (api: graphql/http/websocket; worker: schedule/message; cli: cli)"
@@ -1797,11 +1797,12 @@ CLI_ONLY_ENTRY:
1797
1797
  DB_APP_ROLE:
1798
1798
  title: "The back-end database role has explicit least privileges"
1799
1799
  title_vi: "Role cơ sở dữ liệu của back end có quyền quá rộng"
1800
- meaning_vi: "Migration tại <path> tạo hoặc sửa role của ứng dụng với superuser, bypassrls hay thuộc tính thiếu an toàn, hoặc cấp all thay cho các quyền cần thiết; role mặc định là app_be và có thể khai báo bằng supabase.appRole."
1800
+ meaning_vi: "Migration tại <path> tạo hoặc sửa role của ứng dụng với superuser, bypassrls hay thuộc tính thiếu an toàn, hoặc cấp all thay cho các quyền cần thiết, hoặc cấp cho role quyền trên schema auth (schema, bảng hay sequence trong đó, hay default privileges của nó) mà không có migration sau đó thu hồi; role mặc định là app_be và có thể khai báo bằng supabase.appRole."
1801
1801
  causes_vi:
1802
1802
  - "Vi phạm R255: app role vượt qua RLS hoặc được cấp quyền rộng hơn công việc của back end."
1803
1803
  - "Role không khai báo rõ noinherit và nobypassrls, hoặc nhận grant all trên schema hay bảng."
1804
- nextStep_vi: "Khai báo app role với noinherit, nobypassrls và các thuộc tính không đặc quyền; bỏ superuser, bypassrls và grant all, rồi cấp từng quyền cần thiết."
1804
+ - "Role nhận grant trên schema auth: tài khoản chỉ được truy cập qua các hàm SECURITY DEFINER be_auth_* (account doors) của baseline."
1805
+ nextStep_vi: "Khai báo app role với noinherit, nobypassrls và các thuộc tính không đặc quyền; bỏ superuser, bypassrls và grant all, rồi cấp từng quyền cần thiết. Với grant trên schema auth: chạy `starci app codemod account-doors --execute`, `starci app sync --execute`, rồi `starci app codemod account-doors --contract --execute`."
1805
1806
  owner: op-retry
1806
1807
  kind: check-finding
1807
1808
 
@@ -5347,6 +5348,36 @@ SEALED_FILE_IN_WORK:
5347
5348
 
5348
5349
  # --- HFS rule catalog (knowledge/hfs/rules.yaml): one code per rule, from R01 up ---
5349
5350
 
5351
+ STACKS_LOCAL_CONFIG_INVALID:
5352
+ title: "The local block of a docker-compose environment is not coherent"
5353
+ title_vi: "Khối local của môi trường docker-compose không nhất quán"
5354
+ meaning_vi: "environments.<env>.local thiếu hoặc có trường lạ, cổng trùng, dịch vụ không có trong tệp compose, vai trò cơ sở dữ liệu dùng chung, hoặc thăm dò không nằm trên loopback."
5355
+ causes_vi:
5356
+ - "Vi phạm R372: khối local được kiểm bằng đúng normalizeLocal mà các động từ starci app stack up, down, doctor dùng."
5357
+ nextStep_vi: "Sửa đúng trường nêu trong thông báo (environments.<env>.local.<đường dẫn>), rồi chạy lại starci app check."
5358
+ owner: op-retry
5359
+ kind: check-finding
5360
+
5361
+ STACKS_DEV_UNDECLARED:
5362
+ title: "A lite app declares no runnable dev environment"
5363
+ title_vi: "Ứng dụng lite chưa khai báo môi trường dev chạy được"
5364
+ meaning_vi: "Không có environment nào có runtime docker-compose và khối local, nên starci app stack up --env dev không có gì để chạy."
5365
+ causes_vi:
5366
+ - "Vi phạm R372: môi trường dev vẫn là Swarm hoặc chưa được khai báo."
5367
+ nextStep_vi: "Khai báo environments.dev với runtime docker-compose, composeFiles và khối local; runbook của nó nêu starci app stack up --env dev."
5368
+ owner: op-retry
5369
+ kind: check-finding
5370
+
5371
+ STACKS_RUNBOOK_DOCKER_COMMAND:
5372
+ title: "A dev runbook carries a docker command or omits the one-command start"
5373
+ title_vi: "Runbook dev chứa lệnh docker hoặc không nêu lệnh khởi động duy nhất"
5374
+ meaning_vi: "README của môi trường docker-compose có dòng docker build, run, compose hay stack deploy, hoặc không nêu starci app stack up --env <env>."
5375
+ causes_vi:
5376
+ - "Vi phạm R373 và quy tắc cứng 8: Docker do harness chạy, tác tử không chạy tay."
5377
+ nextStep_vi: "Thay các lệnh docker bằng starci app stack up --env <env> --execute và các động từ down, status, logs của nó."
5378
+ owner: op-retry
5379
+ kind: check-finding
5380
+
5350
5381
  STACKS_DEPLOY_CONFIG_INVALID:
5351
5382
  title: "A deploy block of application-stacks.yaml is not coherent"
5352
5383
  title_vi: "Khối deploy trong application-stacks.yaml không nhất quán"
@@ -5674,3 +5705,35 @@ BE_JOBS_SCHEDULER_OWNER:
5674
5705
  nextStep_vi: "Khai tick vào JOB_TICKS của platform/jobs/job-ticks.contracts.ts (name, cron, timeZone UTC, queue, payload) và xoá lịch tự viết; giữ advisoryXactLock và job_tick_claim trong JobTickService."
5675
5706
  owner: op-retry
5676
5707
  kind: check-finding
5708
+
5709
+ STACKS_CODE_BIND_MOUNT:
5710
+ title: "A stack or compose file bind-mounts code into a service container"
5711
+ title_vi: "Tệp stack hoặc compose gắn (bind-mount) mã vào container của dịch vụ"
5712
+ meaning_vi: "Một dịch vụ gắn thư mục hoặc tệp mã, plugin, script hay đường dẫn ghi được của repository từ máy chủ vào container."
5713
+ causes_vi:
5714
+ - "Vi phạm R374: mã của dịch vụ phải nằm trong image dựng từ .starcistacks/images/<tên>/, không gắn lúc chạy."
5715
+ nextStep_vi: "Đưa mã vào Dockerfile của .starcistacks/images/<tên>/ (FROM image đã ghim digest, hoặc image fork của harness) và bỏ bind-mount; chỉ giữ volume dữ liệu, secret và tệp cấu hình thuần dữ liệu chỉ đọc."
5716
+
5717
+ STACKS_IMAGE_BASE_UNPINNED:
5718
+ title: "A service image folder has a FROM that is not digest-pinned"
5719
+ title_vi: "Thư mục image của dịch vụ có FROM chưa ghim digest"
5720
+ meaning_vi: "Dockerfile trong .starcistacks/images/<tên>/ dùng FROM theo tag, không theo name@sha256:..."
5721
+ causes_vi:
5722
+ - "Vi phạm R374: mọi image nền phải ghim digest."
5723
+ nextStep_vi: "Ghim FROM bằng name@sha256:<digest>."
5724
+
5725
+ STACKS_IMAGE_BASE_NOT_FORK:
5726
+ title: "A service image folder does not build on the harness fork image"
5727
+ title_vi: "Thư mục image của dịch vụ không dựng trên image fork của harness"
5728
+ meaning_vi: "Harness sở hữu image fork StarCi cho dịch vụ này (images/sources.json) nhưng giai đoạn cuối của Dockerfile không FROM image đó."
5729
+ causes_vi:
5730
+ - "Vi phạm R374: dịch vụ tùy biến phải dựng trên image fork do harness ghim."
5731
+ nextStep_vi: "Đặt FROM <repository fork>@sha256:<digest> ở giai đoạn cuối."
5732
+
5733
+ STACKS_IMAGE_ENV_DIVERGES:
5734
+ title: "A service with an image folder runs a third-party image in an environment"
5735
+ title_vi: "Dịch vụ có thư mục image chạy image bên thứ ba ở một môi trường"
5736
+ meaning_vi: "Dev và production phải cùng chạy một image dựng từ .starcistacks/images/<tên>/."
5737
+ causes_vi:
5738
+ - "Vi phạm R374: một môi trường dùng image upstream hoặc image nước ngoài thay vì image dựng từ thư mục."
5739
+ nextStep_vi: "Dùng build.context trỏ tới thư mục image (compose) hoặc tham chiếu image của dự án (stack)."
@@ -510,6 +510,12 @@ $defs:
510
510
  runbook: {type: string, minLength: 1}
511
511
  secrets: {type: array, items: {$ref: '#/$defs/secret'}}
512
512
  deploy: {$ref: '#/$defs/deploy'}
513
+ local:
514
+ description: >-
515
+ The dev declaration `starci app stack up|down|doctor|prepare|status|logs|verify` runs for a docker-compose environment (PROJECT-STACKS-6):
516
+ ports, compose services, the environment's own database role, dev-only custody to generate once, setup and seed commands, native app
517
+ processes, readiness and verification probes. Names, ports and argv lists only, never a value. The authority is scripts/hfs/stack-local-config.mjs; rule R340 judges it.
518
+ type: object
513
519
  ingress:
514
520
  description: "The one selected ingress component and the hosts it routes, or external with the stack name when the ingress is a shared stack outside this one (then no component; rule R275 judges which of the two is declared)."
515
521
  type: object
@@ -1,6 +1,7 @@
1
1
  import { slash } from '../../lib/path-key.mjs';
2
2
  import { isInside } from '../../lib/walk.mjs';
3
3
  import { openHfs } from '../slots.mjs';
4
+ import type { RootPackage } from './root-packages.mjs';
4
5
  /** The compiler API the checkers receive as their `ts` argument (the target repository's own installed TypeScript). */
5
6
  export type TypeScriptApi = typeof import('typescript');
6
7
  /** One owner derived from the slot manifest: an owner directory with its entry file. */
@@ -28,6 +29,8 @@ export interface ArchitectureConfig {
28
29
  kinds: string[];
29
30
  projects: string[];
30
31
  workspaces: string[];
32
+ /** The app's own root workspace packages (empty at the app root itself, whose workspaces already hold them). */
33
+ rootPackages?: RootPackage[];
31
34
  apps: {
32
35
  name: string;
33
36
  kind: string;
@@ -72,3 +75,4 @@ export declare function loadArchitectureConfig(repositoryRoot: string, { hfs }?:
72
75
  hfs?: ArchitectureConfig['hfs'] | undefined;
73
76
  }): ArchitectureConfig;
74
77
  export { canonical, exactKeys, isInside, slash };
78
+ export type { RootPackage };
@@ -3,7 +3,8 @@ import path from 'node:path';
3
3
  import { slash } from '../../lib/path-key.mjs';
4
4
  import { readJsonFile as readJson } from '../../lib/json.mjs';
5
5
  import { isInside } from '../../lib/walk.mjs';
6
- import { openHfs } from '../slots.mjs';
6
+ import { createSlotResolver, openHfs, readRepoDeclaration } from '../slots.mjs';
7
+ import { ROOT_PACKAGE_SLOTS, rootPackageRecord, rootWorkspaceDirectories } from './root-packages.mjs';
7
8
  import { byCodeUnit } from '../../lib/list.mjs';
8
9
  import { grammarContractFor } from '../../lib/grammar-lint.mjs';
9
10
  /** A file's identity: its resolved real path, or the resolved path when it does not exist. */
@@ -182,6 +183,28 @@ function workspaceDirectories(root, { packageRoot: appPackageRoot = root, side =
182
183
  }
183
184
  return [...directories].sort(byCodeUnit);
184
185
  }
186
+ /**
187
+ * The workspace packages of the app root that no side owns: the `workspaces` patterns of the app's one package.json that do not start
188
+ * with a side folder (`packages/*`), and the directories the slots `app.packages` and `app.module-registry` place under `packages/`
189
+ * even when the manifest forgot to list them. A side imports them as first-party packages (through their entry points), never as
190
+ * "outside the side". A side of the app is judged here; the app root itself already counts them among its workspaces.
191
+ */
192
+ function rootPackageRecords(root, packageRoot, side, opened) {
193
+ if (side === null)
194
+ return [];
195
+ const candidates = new Set(rootWorkspaceDirectories(packageRoot));
196
+ if (opened.manifest && existingDirectory(packageRoot, 'packages')) {
197
+ const resolver = createSlotResolver(opened.manifest, readRepoDeclaration(opened.manifest, packageRoot));
198
+ const container = path.join(packageRoot, 'packages');
199
+ for (const entry of fs.readdirSync(container, { withFileTypes: true }).filter(item => item.isDirectory())) {
200
+ const slot = resolver.classifyPath(`packages/${entry.name}`).slot;
201
+ if (slot !== undefined && ROOT_PACKAGE_SLOTS.has(slot))
202
+ candidates.add(path.join(container, entry.name));
203
+ }
204
+ }
205
+ return [...candidates].sort(byCodeUnit).filter(candidate => !isInside(root, candidate))
206
+ .flatMap(candidate => rootPackageRecord(packageRoot, candidate, canonical) ?? []);
207
+ }
185
208
  function discoveredProjects(root, workspaces) {
186
209
  const projects = [];
187
210
  if (fs.existsSync(path.join(root, 'tsconfig.json')))
@@ -373,6 +396,7 @@ export function loadArchitectureConfig(repositoryRoot, { hfs } = {}) {
373
396
  kinds,
374
397
  projects,
375
398
  workspaces,
399
+ rootPackages: rootPackageRecords(root, packageRoot, side, opened),
376
400
  apps: apps.map(app => ({ name: app.name, kind: app.kind })),
377
401
  hfs: opened,
378
402
  owners: derivedOwners(root, opened),