@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
package/lint/run.mjs CHANGED
@@ -6,11 +6,13 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
- // The linters of `starci app check --gate lint`: ESLint once per side and stylelint over the fe stylesheets, through the app's own install.
9
+ // The linters of `starci app check --gate lint`: ESLint once per side (and once for the root workspace packages) and stylelint over the fe stylesheets, through the app's own install.
10
10
  //
11
11
  // 1. ESLint runs from the side folder with that side's eslint.config.mjs (be/ the be canon, fe/ the fe canon: the per-file rules and the
12
12
  // project-graph rules of the architecture machine, findings on the line of the offending TypeScript file); each side lints only its own
13
13
  // files, so the be rules never see fe/ and the fe rules never see be/;
14
+ // 1b. the root workspace packages (packages/<pkg>/src/**) are a third scope: ESLint runs from the app root with the managed root eslint.config.mjs (sync renders it), the files
15
+ // app-relative, whole on every run (no result records), the project graph left to the sides;
14
16
  // 2. stylelint judges the fe side's stylesheets (sync STYLE_GLOB) from fe/ with its stylelint.config.mjs.
15
17
  // `cachedLinters` is the one entry of `app check`: per side it judges only what the records of `.starcistacks/cache/lint/<side>/` cannot
16
18
  // vouch for (cache/lint-family.mjs, contexts/project/check-cache.md), and `eslintRepository` is the launcher it and the cacheless run share.
@@ -114,6 +116,41 @@ const eslintFindings = (results, appRoot) => results.flatMap((result) => (result
114
116
  })));
115
117
  /** The files of `changed` (app-relative) below `side`/, relative to the side folder. */
116
118
  const onSide = (changed, side) => changed.filter((file) => file.startsWith(`${side}/`)).map((file) => file.slice(side.length + 1));
119
+ /**
120
+ * The root workspace packages of an app (`packages/<pkg>/src/**`) are linted by the managed `eslint.config.mjs` of the app root, as a third scope beside
121
+ * the two sides: its files are app-relative and its working directory is the app root. It has a scope only while the config is there.
122
+ */
123
+ export const PACKAGES = 'packages';
124
+ export const PACKAGES_CONFIG = 'eslint.config.mjs';
125
+ const hasPackagesLint = (repoRoot) => fs.existsSync(path.join(repoRoot, PACKAGES_CONFIG)) && fs.existsSync(path.join(repoRoot, PACKAGES));
126
+ /** The root config of the app, its default export awaited: the flat config the packages are linted by. */
127
+ async function packagesConfig(repoRoot) {
128
+ const loaded = await import(__rewriteRelativeImportExtension(`${pathToFileURL(path.join(repoRoot, PACKAGES_CONFIG)).href}?scope=${Date.now()}`));
129
+ const config = await loaded.default;
130
+ if (!Array.isArray(config))
131
+ throw new Error(`${PACKAGES_CONFIG} did not export a flat config`);
132
+ return config;
133
+ }
134
+ /** The package sources the root config selects, app-relative: the walk stays below `packages/`, where the config's selectors point. */
135
+ async function packageSources(repoRoot) {
136
+ const scope = canonScope(await packagesConfig(repoRoot));
137
+ const files = [];
138
+ const visit = (folder) => {
139
+ for (const child of fs.readdirSync(path.join(repoRoot, folder), { withFileTypes: true })) {
140
+ const file = posix(path.join(folder, child.name));
141
+ if (scope.ignored(file) || scope.ignored(`${file}/__starci_scope_probe__`))
142
+ continue;
143
+ if (child.isSymbolicLink())
144
+ throw new Error(`cannot enumerate symlink ${file}`);
145
+ if (child.isDirectory())
146
+ visit(file);
147
+ else if (child.isFile() && scope.source(file))
148
+ files.push(file);
149
+ }
150
+ };
151
+ visit(PACKAGES);
152
+ return files.sort(byCodeUnit);
153
+ }
117
154
  const fileKey = (file) => process.platform === 'win32' ? path.resolve(file).toLowerCase() : path.resolve(file);
118
155
  const inside = (root, file) => { const rel = path.relative(root, file); return rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel); };
119
156
  /**
@@ -300,29 +337,36 @@ function graphCanonsOf(repoRoot, cwd, side) {
300
337
  /** Starts the ESLint child of every side that has work and returns one record per side: skipped, a scope error, or the running child. */
301
338
  async function launchEslint({ app, repoRoot, opts, changed, existing, run = runLinter, scopes = {}, handOutGraph = false }) {
302
339
  const launches = [];
303
- for (const side of app ? SIDES.filter((candidate) => opts.side === undefined || opts.side === candidate) : []) {
304
- const sources = onSide(existing, side).filter((file) => /\.(?:[cm]?[jt]sx?)$/.test(file));
340
+ const scopesToLint = app ? [...SIDES, ...(hasPackagesLint(repoRoot) ? [PACKAGES] : [])].filter((candidate) => opts.side === undefined || opts.side === candidate) : [];
341
+ for (const side of scopesToLint) {
342
+ const packages = side === PACKAGES;
343
+ const sources = (packages ? existing.filter((file) => file.startsWith(`${PACKAGES}/`)) : onSide(existing, side)).filter((file) => /\.(?:[cm]?[jt]sx?)$/.test(file));
305
344
  if (changed !== null && !sources.length) {
306
345
  launches.push({ side, skipped: 'no changed source file' });
307
346
  continue;
308
347
  }
309
- const cwd = path.join(repoRoot, side);
348
+ const cwd = packages ? repoRoot : path.join(repoRoot, side);
310
349
  let expected;
311
350
  // a caller that enumerated the side's scope already (the cache) hands it over: the canon config is loaded and the folders walked once per run
312
351
  const known = scopes[side];
313
352
  try {
314
- expected = changed ? sources : known !== undefined ? [...known] : await sourceFiles({ repoRoot, cwd, side });
353
+ expected = changed ? sources : known !== undefined ? [...known] : packages ? await packageSources(repoRoot) : await sourceFiles({ repoRoot, cwd, side });
315
354
  }
316
355
  catch (error) {
317
- launches.push({ side, scopeError: `eslint source scope is unavailable in ${side}/: ${String(error?.message ?? error)}` });
356
+ launches.push({ side, scopeError: `eslint source scope is unavailable in ${packages ? `${PACKAGES}/` : `${side}/`}: ${String(error?.message ?? error)}` });
357
+ continue;
358
+ }
359
+ // nothing the root config selects below packages/ is a scope with no file, not a run
360
+ if (packages && !expected.length) {
361
+ launches.push({ side, skipped: 'no package source' });
318
362
  continue;
319
363
  }
320
364
  const head = ['--format', 'json', ...(opts.fix ? ['--fix'] : [])];
321
- // the child that builds the canon's project graph for its rules hands the snapshot out when the caller keeps the graph findings (the cache)
322
- const graph = handOutGraph ? graphCanonsOf(repoRoot, cwd, side) : undefined;
365
+ // the child that builds the canon's project graph for its rules hands the snapshot out when the caller keeps the graph findings (the cache); the packages are no side folder and have no graph
366
+ const graph = handOutGraph && !packages ? graphCanonsOf(repoRoot, cwd, side) : undefined;
323
367
  // The files of a diff go to ONE child through a file (a diff of hundreds of long paths outgrows the Windows command line, and each extra
324
368
  // child would build the whole TypeScript program again); the whole side is a single directory argument.
325
- const launch = Promise.resolve().then(() => run(changed ? { cwd, pkg: 'eslint', bin: 'eslint', args: head, list: sources, bound: repoRoot, ...(graph === undefined ? {} : { graph }) } : { cwd, pkg: 'eslint', bin: 'eslint', args: [...head, '.'], files: expected.length, bound: repoRoot, ...(graph === undefined ? {} : { graph }) }));
369
+ const launch = Promise.resolve().then(() => run(changed ? { cwd, pkg: 'eslint', bin: 'eslint', args: head, list: sources, bound: repoRoot, ...(graph === undefined ? {} : { graph }) } : { cwd, pkg: 'eslint', bin: 'eslint', args: [...head, packages ? PACKAGES : '.'], files: expected.length, bound: repoRoot, ...(graph === undefined ? {} : { graph }) }));
326
370
  launches.push({ side, cwd, expected, run: launch.catch(spawnFailure) });
327
371
  }
328
372
  return launches;
@@ -358,7 +402,8 @@ export async function eslintRepository({ repoRoot, app = readRepoDeclaration(loa
358
402
  const checked = checkedEslintResults({ results: linted.results, expected: launch.expected, cwd: launch.cwd, changed: changed !== null });
359
403
  errors.push(...checked.errors);
360
404
  linted.results = checked.results;
361
- const appRelative = appRelativeMessages(side, path.join(repoRoot, side));
405
+ // the paths of a side's messages are side-relative and become app-relative; the packages' are app-relative already
406
+ const appRelative = side === PACKAGES ? ((message) => message) : appRelativeMessages(side, path.join(repoRoot, side));
362
407
  for (const result of linted.results)
363
408
  for (const message of result.messages ?? [])
364
409
  message.message = appRelative(message.message);
@@ -497,6 +542,14 @@ export async function cachedLinters({ repoRoot, app, side: only, run, session })
497
542
  style.ran = true;
498
543
  }
499
544
  }
545
+ // The root packages are small and have no result records: they are linted whole on every run, through the same launcher.
546
+ if ((only === undefined || only === PACKAGES) && hasPackagesLint(repoRoot)) {
547
+ const linted = await eslintRepository({ repoRoot, app, opts: { side: PACKAGES }, run });
548
+ eslint.errors.push(...linted.errors);
549
+ eslint.raw.push(...linted.raw);
550
+ eslint.findings.push(...linted.findings);
551
+ eslint.engine.sides[PACKAGES] = linted.engine.sides[PACKAGES] ?? { files: 0 };
552
+ }
500
553
  session.report(lintFamilyReport(sides));
501
554
  return { eslint, stylelint: style, sides };
502
555
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starci/hfs",
3
- "version": "5.1.0",
3
+ "version": "5.3.0",
4
4
  "description": "The StarCi app command implementation for scaffolding, linting, checking and synchronizing one product repository.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,43 +30,57 @@ pins:
30
30
  side: fe
31
31
  source: packages/event-stream/package.json
32
32
  why: 'the one server-sent-events client of every front end (fe-stack event-stream, owner:event-stream-package): the transport client layer (modules/api/sse/clients) creates one instance for the app with the bearer token source and the stream operations follow on it; it carries Last-Event-ID resume, the capped doubling backoff, the heartbeat timeout, the single 401 renewal and the useSWRSubscription adapter, so no app keeps a hand-written reader.'
33
+ '@starci/next-data':
34
+ version: 0.1.0
35
+ group: starci
36
+ install: registry
37
+ side: fe
38
+ source: packages/next-data/package.json
39
+ why: 'the SWR data helpers of every front end (fe-stack data, owner:next-data-package): pollingRestart, the viewer and tenant key helpers, viewerSwrConfig and the viewer cache; each operation hook stays in the app and the package holds no component.'
33
40
  '@starci/grammar':
34
- version: 0.21.13
41
+ version: 0.21.15
35
42
  group: starci
36
43
  install: registry
37
44
  side: fe
38
45
  source: packages/grammar/package.json
39
46
  why: 'the grammar front ends read: the brand layer sets `--font-sans` and `--font-mono` and the grammar reads them; it carries the Input tel kind, the copyable and read-only Input, the IconButton disclosure props and count badge, ChatBubble, FillViewport, Textarea mentions and Enter-to-send, the leading PrimaryRailLayout rail, the plate-less Image, ListRow, the soft Alert, Progress steps, the floating primary IconButton, FlowGraph, the STATE-8 loading views and the bordered selectable TagGroup chips. The runtime source version wins for a @starci package.'
47
+ '@starci/next-i18n':
48
+ version: 0.1.0
49
+ group: starci
50
+ install: registry
51
+ side: fe
52
+ source: packages/next-i18n/package.json
53
+ why: 'the next-intl stack of every front end (fe-stack i18n, owner:next-i18n-package): defineI18n over the locales of the app, the locale-negotiating proxy, the locale-aware navigation, the request config that loads the catalogs of the app and the locale read of the [locale] layout; the app keeps its message catalogs and writes none of the stack.'
40
54
  '@starci/eslint-canon-be':
41
- version: 3.12.0
55
+ version: 3.14.0
42
56
  group: starci
43
57
  install: registry
44
58
  side: be
45
59
  source: packages/eslint-canon-be/package.json
46
60
  why: 'the back-end ESLint canon: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; `starciBeConfig({ hfs: loadHfs(import.meta.url) })` is the typed factory and the BE-CONVENTION laws are its rules.'
47
61
  '@starci/eslint-canon-fe':
48
- version: 9.10.0
62
+ version: 9.12.0
49
63
  group: starci
50
64
  install: registry
51
65
  side: fe
52
66
  source: packages/eslint-canon-fe/package.json
53
67
  why: 'the front-end ESLint canon: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; the front end has no tests (FE_NO_TESTS R97 in hfs) and no-vietnamese-in-source (R91) applies.'
54
68
  '@starci/eslint-grammar-core':
55
- version: 0.21.13
69
+ version: 0.21.15
56
70
  group: starci
57
71
  install: registry
58
72
  side: fe
59
73
  source: packages/eslint-grammar-core/package.json
60
74
  why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
61
75
  '@starci/eslint-grammar-offset-pop':
62
- version: 0.21.13
76
+ version: 0.21.15
63
77
  group: starci
64
78
  install: registry
65
79
  side: fe
66
80
  source: packages/eslint-grammar-offset-pop/package.json
67
81
  why: 'the Offset Pop Grammar component API lint, versioned with @starci/grammar; its shared component contract comes from the StarCi family lint without copying rules.'
68
82
  '@starci/stylelint-canon':
69
- version: 2.5.1
83
+ version: 2.6.0
70
84
  group: starci
71
85
  install: registry
72
86
  side: fe
@@ -85,7 +99,7 @@ pins:
85
99
  side: both
86
100
  source: packages/prettier-config/package.json
87
101
  '@starci/jest-preset':
88
- version: 2.6.0
102
+ version: 2.6.2
89
103
  group: starci
90
104
  install: registry
91
105
  side: be
@@ -105,8 +119,43 @@ pins:
105
119
  side: be
106
120
  source: packages/ai-router/package.json
107
121
  why: 'the AI pool router of a back end: a NestJS module that routes OpenAI-compatible chat, stream and embeddings calls across keys, models and providers, with @starci/ai-router/testing for its specs; @starci/jest-preset compiles it as an ESM-only dependency.'
122
+ '@starci/nest-kit':
123
+ version: 0.1.0
124
+ group: starci
125
+ install: registry
126
+ side: be
127
+ source: packages/nest-kit/package.json
128
+ why: 'the module definition of every back-end module: defineConfigurableModule and defineCapabilityModule return the module base class, the options token and the options types with their types stated, so a <x>.module-definition.ts is one call that the lint on stated export types (R368) and the duplicate-code check (R21) both accept; @starci/jest-preset compiles it as an ESM-only dependency.'
129
+ '@starci/auth':
130
+ version: 0.2.0
131
+ group: starci
132
+ install: registry
133
+ side: both
134
+ source: packages/auth/package.json
135
+ why: 'the lite sign-in: the NestJS module (EdDSA access tokens with a multi-key JWKS, passwords, Google OAuth with state and PKCE, rotating refresh sessions, the sign-in budget, the refresh cookie), the front-end client and its React bindings, the door contracts and test doubles; the one importer of the identity libraries, reached by a back end only through integrations/passport; @starci/jest-preset compiles it as an ESM-only dependency.'
136
+ '@starci/auth-oidc':
137
+ version: 0.1.0
138
+ group: starci
139
+ install: registry
140
+ side: both
141
+ source: packages/auth-oidc/package.json
142
+ why: 'the full sign-in: the OIDC client of the identity provider (Keycloak) behind the PrincipalVerifier of @starci/auth, so guards do not change between editions: the NestJS module (issuer discovery, the JWKS with caching and rotation, algorithm-pinned token checks, role mapping, the authorization code flow with PKCE and nonce), the front-end client (PKCE redirect, one shared refresh, tokens in memory) and the fake issuer for tests; the one importer of jose in a full app, reached by a back end only through integrations/keycloak; @starci/jest-preset compiles it as an ESM-only dependency.'
143
+ '@starci/object-storage':
144
+ version: 0.1.0
145
+ group: starci
146
+ install: registry
147
+ side: be
148
+ source: packages/object-storage/package.json
149
+ why: 'object storage of a back end: a NestJS module and the ObjectStorage port over S3-compatible storage (presigned PUT and GET that sign the content type and size, head, get, put, list, delete, bucket setup, size and content-type limits, coded errors), with @starci/object-storage/testing for its specs; the only importer of @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner (R331).'
150
+ '@starci/mail':
151
+ version: 0.1.0
152
+ group: starci
153
+ install: registry
154
+ side: be
155
+ source: packages/mail/package.json
156
+ why: 'outgoing mail of a back end: a NestJS module and the Mailer port over SMTP (connect, greeting, socket and total timeouts, STARTTLS required by default, recipient, message and attachment checks, a shared SMTP target policy, coded errors, typed escaping render helpers returning { subject, text, html }), with @starci/mail/testing for its specs; the only importer of nodemailer (R90).'
108
157
  '@starci/hfs':
109
- version: 5.1.0
158
+ version: 5.3.0
110
159
  group: starci
111
160
  install: registry
112
161
  side: both
@@ -543,12 +592,17 @@ pins:
543
592
  version: 3.1150.0
544
593
  group: framework
545
594
  side: be
546
- why: 'the S3 transport of integrations/object-storage, the one ObjectStorage adapter of both editions (MinIO, S3, Spaces); 3.1150.0 is the release of 2026-10-12, chosen over newer ones that had not aged two weeks.'
595
+ why: 'the S3 transport inside @starci/object-storage, the one ObjectStorage adapter of both editions (MinIO, S3, Spaces), which depends on it at exactly this version and which no app file imports; 3.1150.0 is the release of 2026-10-12, chosen over newer ones that had not aged two weeks.'
547
596
  '@aws-sdk/s3-request-presigner':
548
597
  version: 3.1150.0
549
598
  group: framework
550
599
  side: be
551
- why: 'signs the presigned PUT and GET of integrations/object-storage locally, the same release as client-s3.'
600
+ why: 'signs the presigned PUT and GET inside @starci/object-storage locally, the same release as client-s3; no app file imports it.'
601
+ nodemailer:
602
+ version: 10.1.7
603
+ group: framework
604
+ side: be
605
+ why: 'the SMTP client inside @starci/mail, which depends on it at exactly this version and which no app file imports; 10.1.7 is the release of 2026-10-18, chosen over newer ones that had not aged two weeks.'
552
606
  '@apollo/server':
553
607
  version: 5.5.1
554
608
  group: framework
@@ -649,7 +703,7 @@ pins:
649
703
  version: 6.2.12
650
704
  group: framework
651
705
  side: be
652
- why: signs the app's own ES256 access tokens and verifies them against its own key set (createLocalJWKSet) in integrations/passport.
706
+ why: signs the app's own access tokens and verifies them against its own key set inside @starci/auth, and verifies the identity provider's tokens against its JWKS inside @starci/auth-oidc; no app file imports it.
653
707
  class-transformer:
654
708
  version: 0.5.1
655
709
  group: framework
@@ -37,17 +37,17 @@ concerns:
37
37
  - id: rest
38
38
  tier: core
39
39
  packages: [axios]
40
- home: [fe.modules.api, fe.modules.auth]
40
+ home: [fe.modules.api]
41
41
  pattern: FE-API-REST
42
42
  forbids: [ky, superagent, node-fetch, cross-fetch, isomorphic-fetch]
43
- why: modules/api/rest/<area>/*.ts for uploads and presigned URLs, and modules/auth/auth-door.ts for the app's own auth doors only; GraphQL carries every other call.
43
+ why: modules/api/rest/<area>/*.ts for uploads and presigned URLs, only; GraphQL carries every other call.
44
44
  - id: data
45
45
  tier: core
46
- packages: [swr]
46
+ packages: [swr, '@starci/next-data']
47
47
  home: [fe.hooks.swr]
48
48
  pattern: FE-DATA-SWR
49
49
  forbids: ['@tanstack/react-query', react-query]
50
- why: hooks/swr/api/{graphql,rest}/{queries,mutations}/use*Swr.ts wrap one operation each; components call hooks only.
50
+ why: hooks/swr/api/{graphql,rest}/{queries,mutations}/use*Swr.ts wrap one operation each; components call hooks only. The polling-restart middleware (pollingRestart), the key helpers, the viewer-keyed config and the viewer cache are @starci/next-data's (owner next-data-package); an app imports no Middleware or ScopedMutator type of swr and writes none of them.
51
51
  - id: toast
52
52
  tier: core
53
53
  packages: ['@heroui/react']
@@ -96,18 +96,18 @@ concerns:
96
96
  hand-written reader (read-event-stream.ts) and no second SSE library is installed.
97
97
  - id: auth
98
98
  tier: core
99
- packages: []
99
+ packages: ['@starci/auth', '@starci/auth-oidc']
100
100
  home: [fe.modules.auth]
101
101
  pattern: FE-AUTH
102
- forbids: ['@supabase/supabase-js', '@supabase/ssr', '@supabase/auth-js']
103
- why: modules/auth is the session issuer's client (lite - auth-door.ts over the app's own back-end auth doors with the in-memory token store; full - the app's IdP client); it yields the session and the access token for the Apollo link and never reads data.
102
+ forbids: ['@supabase/supabase-js', '@supabase/ssr', '@supabase/auth-js', jose, jwt-decode, next-auth, '@auth/core', keycloak-js, oidc-client-ts]
103
+ why: modules/auth is the session issuer's client (one @starci/auth client instance over the app's own back-end auth doors, the access token in its memory; full - the app's IdP client); it yields the session and the access token for the Apollo link and never reads data.
104
104
  - id: i18n
105
105
  tier: core
106
- packages: [next-intl]
106
+ packages: [next-intl, '@starci/next-i18n']
107
107
  home: [fe.modules.i18n]
108
108
  pattern: FE-I18N
109
109
  forbids: [i18next, react-i18next, next-i18next, react-intl, '@lingui/react']
110
- why: useTranslations for copy and the next-intl formatter for numbers, fixed dates and currency; FE-DATE owns live time display.
110
+ why: useTranslations for copy and the next-intl formatter for numbers, fixed dates and currency; FE-DATE owns live time display. The routing, the proxy, the navigation, the request config and the locale read of the layout are @starci/next-i18n's (owner next-i18n-package); an app calls no defineRouting, createNavigation, getRequestConfig or createMiddleware.
111
111
  - id: ui
112
112
  tier: core
113
113
  packages: ['@heroui/react', '@heroui/styles', tailwindcss, '@starci/grammar', react-aria-components]
@@ -0,0 +1,7 @@
1
+ # The images the harness owns as StarCi forks (the harness repository's images/sources.json is their owner; stacks-service-images.spec.mts keeps this list
2
+ # equal to it). A service of an app that has an image folder named like a key here is built FROM that repository (R374 STACKS_IMAGE_BASE_NOT_FORK):
3
+ # the fork itself, or the harness runtime layer built on it (openclaw-runtime, images/README.md).
4
+ schema: starci/fork-images@1
5
+ images:
6
+ minio: ghcr.io/starci183/minio
7
+ openclaw: ghcr.io/starci183/openclaw-runtime
@@ -133,8 +133,8 @@ rules:
133
133
  - {path: packages/hfs/src/hfs-managed-file-drift.spec.mjs, edition: full, codes: [HFS_MANAGED_FILE_DRIFT]}
134
134
  - id: R06
135
135
  code: "HFS_PLAINTEXT_SECRET"
136
- law: "Secrets exist only as `.starcistacks/<env>/secrets/<slug>.enc`."
137
- kinds: [check, design]
136
+ law: "Secrets exist only as `.starcistacks/<env>/secrets/<slug>.enc`; the committed check cache (`.starcistacks/cache/check/secrets/`) holds codes, paths, positions and digests only, and is judged by what it holds."
137
+ kinds: [check, design, codemod]
138
138
  gates: [pre-commit, land, ci]
139
139
  failureCodes: ["HFS_PLAINTEXT_SECRET"]
140
140
  enforcers:
@@ -143,7 +143,7 @@ rules:
143
143
  editions: [lite, full]
144
144
  docs: [docs/legacy-managed-rule-coverage.md#r06-plaintext-secrets]
145
145
  templateNa: "Encrypted secrets are supplied by the app secret command; plaintext secret files are forbidden and are never scaffolded."
146
- proves: [packages/hfs/src/hfs-plaintext-secret.spec.mjs]
146
+ proves: [packages/hfs/src/hfs-plaintext-secret.spec.mjs, packages/hfs/src/check-cache-secret-records.spec.mjs]
147
147
  exemplifiedBy:
148
148
  - {path: packages/hfs/src/hfs-plaintext-secret.spec.mjs, editions: [lite, full]}
149
149
  witness:
@@ -1216,7 +1216,7 @@ rules:
1216
1216
  code: "FE_I18N_PLACEMENT"
1217
1217
  editions: [lite, full]
1218
1218
  docs: [docs/architecture.md#r59]
1219
- law: "`next-intl` with `[locale]`; multiple apps share one `fe/packages/<family>-i18n` stack through `createAppI18n`. A single app keeps routing and request configuration in `modules/i18n` and `createNavigation` in the `modules/routes` public entry. The proxy imports routing or a routing-only shared proxy export without evaluating navigation runtime."
1219
+ law: "`next-intl` with `[locale]`; the stack (routing, proxy, navigation, request config, the layout's locale read) is built by `@starci/next-i18n` over the app's `defineI18n` description and by no product source. A single app keeps the description and its request config in `modules/i18n` and builds its navigation with `createI18nNavigation` in the `modules/routes` public entry; apps that share a product share one `fe/packages/<family>-i18n` workspace package that holds the description and composes the package's factories through `createAppI18n`. The proxy is `createProxy` of `@starci/next-i18n/proxy` and never evaluates navigation runtime."
1220
1220
  kinds: [check, codemod, lint]
1221
1221
  gates: [settle, land, ci]
1222
1222
  failureCodes: ["FE_I18N_PLACEMENT", "FE_I18N_NAVIGATION", "FE_I18N_FORMATTER"]
@@ -1269,6 +1269,7 @@ rules:
1269
1269
  - {kind: stylelint, id: raw-brand-value}
1270
1270
  - {kind: stylelint, id: no-apply-raw}
1271
1271
  - {kind: stylelint, id: no-important}
1272
+ - {kind: stylelint, id: no-self-reference}
1272
1273
  - {kind: stylelint, id: globals-shape}
1273
1274
  - {kind: stylelint, id: globals-import-order}
1274
1275
  - {kind: stylelint, id: no-token-redefinition}
@@ -4084,6 +4085,7 @@ rules:
4084
4085
  enforcers:
4085
4086
  - {kind: hfs, id: fe-stack, at: scripts/hfs/rules/fe-stack.mjs}
4086
4087
  - {kind: eslint-fe, id: fe-stack-import}
4088
+ - {kind: eslint-fe, id: swr-extension-from-package}
4087
4089
  proves:
4088
4090
  - {path: packages/eslint/fe/frontend-boundary-coverage.spec.mjs, editions: [lite, full], codes: [FE_STACK_FORBIDDEN]}
4089
4091
  - {path: packages/hfs/src/frontend-boundary-coverage.spec.mjs, editions: [lite, full], codes: [FE_STACK_FORBIDDEN]}
@@ -4262,7 +4264,7 @@ rules:
4262
4264
  editionWhy: "owner:lite-backing-services makes the db service lite's database; application-role and forced-RLS requirements apply to that backing."
4263
4265
  docs: [docs/microservices.md#r255]
4264
4266
  templateNa: "Application roles and table ownership are product-authored db migration content, rather than a scaffolded SQL surface."
4265
- law: "The declared back-end database role (app_be by default) has only the privileges it needs: role declarations refuse superuser and bypassrls, require noinherit and nobypassrls, and never grant all schema or table privileges. Tables owned by that role enable and force row level security by default; a forceRls exemption identifies the table and carries a nonempty reason. This applies to every edition that declares Supabase."
4267
+ law: "The declared back-end database role (app_be by default) has only the privileges it needs: role declarations refuse superuser and bypassrls, require noinherit and nobypassrls, never grant all schema or table privileges, and leave it with no grant after the last migration on schema auth, on a table or sequence in it, or by default privilege for it (accounts are reached through SECURITY DEFINER doors; a later migration that revokes a grant clears it). Tables owned by that role enable and force row level security by default; a forceRls exemption identifies the table and carries a nonempty reason. This applies to every edition that declares Supabase."
4266
4268
  kinds: [check]
4267
4269
  gates: [settle, land, ci]
4268
4270
  failureCodes: ["DB_APP_ROLE", "DB_FORCE_RLS_DEFAULT"]
@@ -4772,7 +4774,7 @@ rules:
4772
4774
 
4773
4775
  - id: R279
4774
4776
  code: "BE_THIRD_PARTY_OWNER"
4775
- law: "Third-party SDK ownership is derived from the app's package.json dependencies rather than a finite SDK table. A dependency outside the pure-library and framework allowlists is imported only by one integrations/<provider> capability; domain services call that integration's public API. Existing infrastructure owner restrictions remain in force."
4777
+ law: "Third-party SDK ownership is derived from the app's package.json dependencies rather than a finite SDK table. A dependency outside the pure-library and framework allowlists is imported only by one integrations/<provider> capability; domain services call that integration's public API. A workspace package of the app itself (a folder the root workspaces of package.json name) is first-party source and no SDK; what it imports is judged over its own files. Existing infrastructure owner restrictions remain in force."
4776
4778
  kinds: [lint]
4777
4779
  gates: [pre-commit, settle, land, ci]
4778
4780
  failureCodes: ["BE_THIRD_PARTY_OWNER"]
@@ -5082,7 +5084,7 @@ rules:
5082
5084
  - {path: packages/hfs/src/hfs-stacks-deploy.spec.mjs, editions: [lite, full]}
5083
5085
  - id: R295
5084
5086
  code: "BE_MODULE_DEFINITION_MISSING"
5085
- law: "Every NestJS module of the product has a module definition (owner ruling 2026-10-15, owner:module-definition-everywhere): capability modules, sub-modules, features, transport modules and cli groups alike. For a module `<x>` the folder holds `<x>.module-definition.ts` (`export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = new ConfigurableModuleBuilder<XOptions>().setClassMethodName(\"register\").build()`; the representative module of a capability keeps `.setExtras({ isGlobal: false }, ...)`), `<x>.options.ts` (`export type XOptions`, `Record<string, never>` with a one-line doc when it takes nothing yet) and `<x>.module.ts` (`@Module({...}) export class XModule extends ConfigurableModuleClass {}`, no `register` override except the capability representative's). A module appears in an `imports` array or in an app composition group only as `XModule.register({...})`, never as a bare class. The `AppModule` of an app root and the test world are composition roots and are not judged."
5087
+ law: "Every NestJS module of the product has a module definition (owner ruling 2026-10-15, owner:module-definition-everywhere): capability modules, sub-modules, features, transport modules and cli groups alike. For a module `<x>` the folder holds `<x>.module-definition.ts` (`export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = defineConfigurableModule<XOptions>()` of `@starci/nest-kit`, which states the exported types so the file is neither an inferred surface (R368) nor a clone of its neighbour (R21); the representative module of a capability calls `defineCapabilityModule<XOptions>()`, which adds the `isGlobal` extra defaulting to `false`; `starci app codemod module-definition` rewrites a definition written with `ConfigurableModuleBuilder`), `<x>.options.ts` (`export type XOptions`, `Record<string, never>` with a one-line doc when it takes nothing yet) and `<x>.module.ts` (`@Module({...}) export class XModule extends ConfigurableModuleClass {}`, no `register` override except the capability representative's). A module appears in an `imports` array or in an app composition group only as `XModule.register({...})`, never as a bare class. The `AppModule` of an app root and the test world are composition roots and are not judged."
5086
5088
  kinds: [lint, codemod]
5087
5089
  gates: [pre-commit, settle, land, ci]
5088
5090
  failureCodes: ["BE_MODULE_DEFINITION_MISSING"]
@@ -5696,20 +5698,23 @@ rules:
5696
5698
  ruling: "A model call made straight from code uses LangChain with retrieved context, through the ai-router gateway (owner ruling be-ai-langchain-gateway): the LangChain clients point at the gateway's base URL with its virtual key."
5697
5699
  - id: R328
5698
5700
  code: "BE_AUTH_PASSPORT_OWNER"
5699
- law: "The identity libraries (@nestjs/passport, passport, passport-google-oauth20, passport-local, @nestjs/jwt, jose and bcryptjs) are imported only by integrations/passport, the one owner of the Google OAuth 2.0 strategy (state and PKCE), the local email and password strategy (bcrypt) and the app's own access tokens; bcrypt and every Supabase client are owned nowhere."
5701
+ law: "The identity libraries (@nestjs/passport, passport, passport-google-oauth20, passport-local, @nestjs/jwt, jose and bcryptjs) are the dependencies of @starci/auth (lite) and of @starci/auth-oidc (full, which owns jose for its provider tokens) and are imported by no app file; @starci/auth/server is imported only by integrations/passport and @starci/auth-oidc/server only by integrations/keycloak, each package's one glue. bcrypt and every Supabase client are owned nowhere."
5700
5702
  kinds: [lint]
5701
5703
  gates: [pre-commit, settle, land, ci]
5702
5704
  failureCodes: ["BE_AUTH_PASSPORT_OWNER"]
5703
5705
  enforcers:
5704
5706
  - {kind: eslint-be, id: infra-import-owner}
5705
- editions: [lite]
5707
+ editions: [lite, full]
5706
5708
  docs: [contexts/code/be/api-auth.md]
5707
5709
  proves:
5708
5710
  - packages/eslint-canon-be/src/auth-passport.spec.ts
5711
+ - packages/eslint-canon-be/src/auth-oidc-package.spec.ts
5709
5712
  exemplifiedBy:
5710
5713
  - {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts, edition: lite}
5714
+ - {path: packages/auth-oidc/src/server/index.ts, edition: full}
5711
5715
  witness:
5712
5716
  - {path: packages/eslint-canon-be/src/auth-passport.spec.ts, edition: lite, codes: [BE_AUTH_PASSPORT_OWNER]}
5717
+ - {path: packages/eslint-canon-be/src/auth-oidc-package.spec.ts, edition: full, codes: [BE_AUTH_PASSPORT_OWNER]}
5713
5718
  cases:
5714
5719
  - id: case-1
5715
5720
  date: "2026-10-29"
@@ -5718,7 +5723,7 @@ rules:
5718
5723
  ruling: "Lite leaves Supabase: its identity is the app own Passport integration (Google OAuth and email/password) on its self-hosted PostgreSQL; full keeps Keycloak."
5719
5724
  - id: R329
5720
5725
  code: "BE_AUTH_JWT_OWN_JWKS"
5721
- law: "A lite back-end principal comes only from the app's own access token verified by integrations/passport against its own key set (jose jwtVerify over createLocalJWKSet, issuer, audience and algorithms pinned), never from a remote JWKS, an unverified decode or a request body claim; domain/identity builds the principal from that verification."
5726
+ law: "A lite back-end principal comes only from the app's own access token, verified by @starci/auth (EdDSA pinned, issuer and audience checked, a local key set that may hold several keys), never from a remote JWKS, a token decoded without its signature check, a jwtVerify call in the app or a body claim."
5722
5727
  kinds: [check, lint]
5723
5728
  gates: [pre-commit, settle, land, ci]
5724
5729
  failureCodes: ["BE_AUTH_JWT_OWN_JWKS"]
@@ -5742,7 +5747,7 @@ rules:
5742
5747
  ruling: "The lite back end verifies its own asymmetric access tokens against its own JWKS; it trusts no remote identity provider."
5743
5748
  - id: R330
5744
5749
  code: "BE_AUTH_REFRESH_COOKIE"
5745
- law: "A refresh token travels only in an httpOnly, Secure, SameSite=Strict cookie scoped to the refresh door (set and cleared only through integrations/passport), is rotated on every use with reuse detection, and never appears as a field of a request or response body."
5750
+ law: "A refresh token travels only in the httpOnly, Secure cookie that @starci/auth sets, rotates (with a reuse grace window and reuse detection) and clears; no app file writes that cookie, and the token never appears as a field of a request or response body."
5746
5751
  kinds: [lint]
5747
5752
  gates: [pre-commit, settle, land, ci]
5748
5753
  failureCodes: ["BE_AUTH_REFRESH_COOKIE"]
@@ -5764,7 +5769,7 @@ rules:
5764
5769
  ruling: "The refresh token of the lite identity rotates and lives only in an httpOnly cookie; the browser holds no credential."
5765
5770
  - id: R331
5766
5771
  code: "BE_STORAGE_OWNER"
5767
- law: "The S3 client (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) is imported only by integrations/object-storage, the ObjectStorage port over S3-compatible storage (MinIO in lite) with presigned PUT and GET; no module imports minio."
5772
+ law: "The S3 client libraries (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) are the dependencies of @starci/object-storage, the ObjectStorage port over S3-compatible storage (MinIO in lite) with presigned PUT and GET, and are imported by no app file; @starci/object-storage is imported only by integrations/object-storage, its one glue; no module imports minio."
5768
5773
  kinds: [lint]
5769
5774
  gates: [pre-commit, settle, land, ci]
5770
5775
  failureCodes: ["BE_STORAGE_OWNER"]
@@ -5773,11 +5778,11 @@ rules:
5773
5778
  editions: [lite, full]
5774
5779
  docs: [contexts/code/be/infra.md]
5775
5780
  proves:
5776
- - packages/eslint-canon-be/src/auth-passport.spec.ts
5781
+ - packages/eslint-canon-be/src/object-storage-package.spec.ts
5777
5782
  exemplifiedBy:
5778
- - {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts, edition: lite}
5783
+ - {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts, edition: lite}
5779
5784
  witness:
5780
- - {path: packages/eslint-canon-be/src/auth-passport.spec.ts, editions: [lite, full], codes: [BE_STORAGE_OWNER]}
5785
+ - {path: packages/eslint-canon-be/src/object-storage-package.spec.ts, editions: [lite, full], codes: [BE_STORAGE_OWNER]}
5781
5786
  cases:
5782
5787
  - id: case-1
5783
5788
  date: "2026-10-29"
@@ -6576,3 +6581,50 @@ rules:
6576
6581
  source: owner
6577
6582
  quote: "no lint caught it"
6578
6583
  ruling: "A directive below an import broke Nivo's CI build on 2026-11-02; the directive is the first statement of its module and a lint reports it elsewhere."
6584
+ - id: R372
6585
+ code: "STACKS_LOCAL_CONFIG_INVALID"
6586
+ law: "In lite and full apps, every docker-compose environment of .starcistacks/application-stacks.yaml declares a coherent `local` block, judged by the same normalizeLocal the dev verbs of `starci app stack` (doctor, prepare, up, status, logs, verify, down) use: closed fields, one loopback port each, compose services that exist in the compose files, an environment-owned database role (never postgres, never the app role), dev-only custody generators and loopback probes; and a lite app declares at least one such dev environment, so `starci app stack up --env dev` has something to run."
6587
+ kinds: [check]
6588
+ gates: [settle, land, ci]
6589
+ failureCodes: ["STACKS_LOCAL_CONFIG_INVALID", "STACKS_DEV_UNDECLARED"]
6590
+ enforcers:
6591
+ - {kind: hfs, id: stacks-local, at: scripts/hfs/rules/stacks-local.mjs}
6592
+ editions: [lite, full]
6593
+ docs:
6594
+ - {path: contexts/project/stacks.md#project-stacks-6-every-runbook-declares-the-same-operations, editions: [lite, full]}
6595
+ proves:
6596
+ - {path: packages/hfs/src/stack-local.spec.mjs, editions: [lite, full]}
6597
+ exemplifiedBy:
6598
+ - {path: packages/hfs/templates/app/skeleton-lite/.starcistacks, edition: lite}
6599
+ witness:
6600
+ - {path: packages/hfs/src/stack-local.spec.mjs, editions: [lite, full]}
6601
+ - id: R373
6602
+ code: "STACKS_RUNBOOK_DOCKER_COMMAND"
6603
+ law: "The runbook of a docker-compose environment names `starci app stack up --env <env>` and carries no `docker build|run|compose|create|start|stop|pull|push` or `docker stack deploy` command: Docker is run by the harness verb, never by hand (hard rule 8)."
6604
+ kinds: [check]
6605
+ gates: [settle, land, ci]
6606
+ failureCodes: ["STACKS_RUNBOOK_DOCKER_COMMAND"]
6607
+ enforcers:
6608
+ - {kind: hfs, id: stacks-local, at: scripts/hfs/rules/stacks-local.mjs}
6609
+ editions: [lite, full]
6610
+ docs:
6611
+ - {path: contexts/project/stacks.md#project-stacks-10-local-runs, editions: [lite, full]}
6612
+ proves:
6613
+ - {path: packages/hfs/src/stack-local.spec.mjs, editions: [lite, full]}
6614
+ exemplifiedBy:
6615
+ - {path: packages/hfs/templates/app/skeleton-lite/.starcistacks, edition: lite}
6616
+ witness:
6617
+ - {path: packages/hfs/src/stack-local.spec.mjs, editions: [lite, full]}
6618
+ - id: R374
6619
+ code: "STACKS_CODE_BIND_MOUNT"
6620
+ law: "A service the app customises runs from an image built out of `.starcistacks/images/<name>/` (a Dockerfile whose every FROM is digest-pinned and whose final stage is the harness's image for that service, the fork or its runtime layer, when the harness owns one), the same image in every environment; no stack or compose file bind-mounts code, plugins or scripts into a service container (data volumes, secrets, least grants and read-only plain config data files stay allowed)."
6621
+ kinds: [check]
6622
+ gates: [settle, land, ci]
6623
+ failureCodes: ["STACKS_CODE_BIND_MOUNT", "STACKS_IMAGE_BASE_UNPINNED", "STACKS_IMAGE_BASE_NOT_FORK", "STACKS_IMAGE_ENV_DIVERGES"]
6624
+ enforcers:
6625
+ - {kind: hfs, id: stacks-service-images, at: scripts/hfs/rules/stacks-service-images.mjs}
6626
+ editions: [lite, full]
6627
+ docs:
6628
+ - {path: contexts/project/stacks.md#project-stacks-11-a-customised-service-is-an-image-the-app-builds-the-same-in-every-environment, editions: [lite, full]}
6629
+ proves:
6630
+ - {path: packages/hfs/src/stacks-service-images.spec.mjs, editions: [lite, full]}