@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/sync/index.mjs CHANGED
@@ -34,6 +34,7 @@ import { DB_DOCKERFILE, DB_IMAGE_APP, RETIRED_DB_DOCKERFILE } from '../db/image.
34
34
  import { byCodeUnit } from '../report/order.mjs';
35
35
  import { isRuntimeExampleApp } from '../runtime/scripts/lib/runtime-example.mjs';
36
36
  import { ciWorkflowCommands } from './ci-commands.mjs';
37
+ import { MODULE_PREFIX, moduleNames, registryFiles } from '../scaffold/module-registry.mjs';
37
38
  import { dependabotUpdates, hasDeclaredServices, readDeclaredQuality, readDeclaredBadges, sonarHostLine } from './quality-services.mjs';
38
39
  import { qualityServices } from '../runtime/scripts/hfs/ci-declaration.mjs';
39
40
  import { grammarLintFor } from '../runtime/scripts/lib/grammar-lint.mjs';
@@ -236,6 +237,9 @@ export function sonarScope(presets) {
236
237
  const tests = preset.filter(glob => !glob.endsWith('/**'));
237
238
  return { tests, exclusions: [...new Set([...preset, '**/.next/**', '**/node_modules/**'])] };
238
239
  }
240
+ /** The managed ESLint config at the app root that lints the root workspace packages. */
241
+ export const PACKAGES_LINT_FILE = 'eslint.config.mjs';
242
+ const LINE = '\n';
239
243
  const SOURCE_FILE = /\.[cm]?ts$/;
240
244
  const SOURCE_SKIPPED = new Set(['node_modules', 'dist', 'coverage']);
241
245
  /**
@@ -244,6 +248,16 @@ const SOURCE_SKIPPED = new Set(['node_modules', 'dist', 'coverage']);
244
248
  * lite brand tree exists, so sync can preserve its absence. `hasImage` identifies authored optional Dockerfiles without reading
245
249
  * their content. An app with no be folder has no source files.
246
250
  */
251
+ /** The folders of `packages/` that are neither a module package nor the registry. */
252
+ function otherPackages(root) {
253
+ try {
254
+ return fs.readdirSync(path.join(root, 'packages'), { withFileTypes: true })
255
+ .filter(entry => entry.isDirectory() && !entry.name.startsWith(MODULE_PREFIX) && entry.name !== 'modules').map(entry => entry.name).sort(byCodeUnit);
256
+ }
257
+ catch {
258
+ return [];
259
+ }
260
+ }
247
261
  export function appSource(root, { parseYaml, notices } = {}) {
248
262
  const files = [];
249
263
  const walk = (dir) => {
@@ -267,6 +281,7 @@ export function appSource(root, { parseYaml, notices } = {}) {
267
281
  for (const dir of ['apps', 'src'])
268
282
  walk(dir);
269
283
  return { runtimeExample: isRuntimeExampleApp(root), files: files.sort(byCodeUnit), read: (file) => fs.readFileSync(path.join(root, 'be', file), 'utf8'),
284
+ modules: moduleNames(root), otherPackages: otherPackages(root),
270
285
  workPresent: fs.existsSync(path.join(root, '.starciwork')), hasImage: (file) => isFile(path.join(root, file), { followLinks: false }),
271
286
  stack: readDeclaredStack(root, { parseYaml }), quality: readDeclaredQuality(root, { parseYaml }),
272
287
  badges: readDeclaredBadges(root, { parseYaml, notices }),
@@ -321,6 +336,7 @@ export function appScripts(app, source) {
321
336
  }),
322
337
  ...fe.map(entry => line(`start:${entry.name}`, `npm run start -w ${workspace(entry)}`)),
323
338
  ...(app.edition !== 'lite' && app.optionalSlots.includes('app.browser') ? [line('test:browser', 'playwright test -c browser/playwright.config.ts')] : []),
339
+ ...(source?.modules?.length ? [line('build:modules', 'turbo run build --filter=./packages/*'), line('test:modules', 'turbo run test --filter=./packages/module-*')] : []),
324
340
  line('docker:build', images.map(image => `npm run docker:build:${image.entry.name}`).join(' && ')),
325
341
  ...images.map(image => line(`docker:build:${image.entry.name}`, imageCommand(image))),
326
342
  ].join('\n ');
@@ -377,6 +393,36 @@ export function imageFiles(app, source) {
377
393
  });
378
394
  }
379
395
  /** Every value a template of `scope` can name, derived from hfs.json and the presets. */
396
+ /** What a module package keeps out of the measured scope: its workbench (drawn with Grammar, a front end has no tests) and its specs. The logic of its template and manifest is measured. */
397
+ const moduleExclusions = (dirs) => dirs.flatMap(dir => [`${dir}/src/workbench/**`, `${dir}/src/**/index.ts`]);
398
+ /** The managed files of the registry package for the module packages the app has (empty when it has none). */
399
+ function registryTargets(app, source) {
400
+ return registryFiles(app.project, source?.modules ?? []).map((file) => ({ path: file.path, scope: APP_SCOPE, mode: 'file', content: file.content, hash: hashOf(file.content) }));
401
+ }
402
+ /**
403
+ * The root `eslint.config.mjs` of an app that has root workspace packages (packages/agent-sdk, packages/module-<name>, the registry): no side config
404
+ * reaches `packages/`, so this managed file lints their source by subpath. The back-end canon judges everything but the front-end subpaths
405
+ * (`src/workbench/` outside its contract, `src/public/`), the front-end canon and the selected Grammar family those, each over the package view of
406
+ * the app root. An app without a root package has no such file; the family factory for packages is the family's `<name>GrammarPackagesConfig`.
407
+ */
408
+ function packagesLintTargets(app, source) {
409
+ if (!(source?.modules?.length || source?.otherPackages?.length))
410
+ return [];
411
+ const grammar = grammarLintFor(app.sides.fe.grammar);
412
+ const factory = grammar.package ? grammar.factory.replace(/Config$/, 'PackagesConfig') : null;
413
+ const content = [
414
+ 'import { loadPackagesHfs as loadBePackages, starciBePackagesConfig } from "@starci/eslint-canon-be"',
415
+ 'import { loadPackagesHfs as loadFePackages, starciFePackagesConfig } from "@starci/eslint-canon-fe"',
416
+ ...(grammar.package ? [`import { ${factory} } from "${grammar.package}"`] : []),
417
+ '',
418
+ 'const be = loadBePackages(import.meta.url)',
419
+ 'const fe = loadFePackages(import.meta.url)',
420
+ '',
421
+ `export default [...(await starciBePackagesConfig({ hfs: be })), ...starciFePackagesConfig({ hfs: fe })${grammar.package ? `, ...${factory}({ hfs: fe })` : ''}]`,
422
+ '',
423
+ ].join(LINE);
424
+ return [{ path: PACKAGES_LINT_FILE, scope: APP_SCOPE, mode: 'file', content, hash: hashOf(content) }];
425
+ }
380
426
  export function variables(app, scope, presets, sonarKey, manifest = loadSlotManifest(), source = { files: [], read: () => '' }) {
381
427
  const stack = source.stack ?? { images: [], passEnv: [] };
382
428
  const fe = app.sides.fe;
@@ -384,6 +430,10 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
384
430
  const lite = app.edition === 'lite';
385
431
  const providers = (app.sides.be.connections ?? []).map(connection => connection.provider);
386
432
  const quality = source.quality ?? qualityServices({});
433
+ const modules = source.modules ?? [];
434
+ const moduleDirs = modules.map(name => `packages/${MODULE_PREFIX}${name}`);
435
+ // The module packages are built before anything imports them (typecheck, lint, the fe build and the be run all follow the registry).
436
+ const modulesFirst = modules.length ? 'npm run build:modules --silent && ' : '';
387
437
  const grammarLint = scope === 'fe' ? grammarLintFor(fe.grammar) : null;
388
438
  return {
389
439
  header: HEADER(scope),
@@ -401,11 +451,14 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
401
451
  // A lite app's declared Redis (sides.be.backing, ruling:R212/case-10) runs in development as one container of its pinned image.
402
452
  backingScripts: lite && (app.sides.be.backing ?? []).includes('redis') ? redisDevText(app.project) : '',
403
453
  // turbo builds every fe app workspace; ^build (turbo.json) builds the packages each app imports first, and caches both.
404
- buildFe: 'npm run codegen --silent && turbo run build --filter=./fe/apps/*',
454
+ buildFe: `${modulesFirst}npm run codegen --silent && turbo run build --filter=./fe/apps/*`,
405
455
  // The be side with its tsc; every fe workspace with its own typecheck script through turbo (the packages are built first: ^build).
406
- typecheck: 'npm run codegen --silent && tsc -p be/tsconfig.json && turbo run typecheck',
456
+ typecheck: `${modulesFirst}npm run codegen --silent && tsc -p be/tsconfig.json && turbo run typecheck`,
407
457
  // The typed lint reads the types the fe packages declare (their dist), so the packages are built first.
408
- lintPrefix: packages ? 'npm run codegen --silent && turbo run build --filter=./fe/packages/* && ' : 'npm run codegen --silent && ',
458
+ lintPrefix: `${modulesFirst}${packages ? 'npm run codegen --silent && turbo run build --filter=./fe/packages/* && ' : 'npm run codegen --silent && '}`,
459
+ // The unit run, the back-end build and every CI job that imports a module package find it built (build:modules precedes them); the logic of a package is held at 100 per file by its own jest run, a step of the fast job.
460
+ modulesFirst,
461
+ moduleTestStep: modules.length ? ' - name: module packages\n run: npm run test:modules\n' : '',
409
462
  nodeMajor: String(NODE_MAJOR),
410
463
  // The deploy server's age identity the restore workflow writes (stack/server-key.mjs owns the path).
411
464
  serverAgeIdentity: SERVER_AGE_IDENTITY,
@@ -415,19 +468,19 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
415
468
  sonarHostLine: sonarHostLine(quality.sonar),
416
469
  sonarExclusions: sonarScope(presets).exclusions.join(','),
417
470
  sonarTestInclusions: sonarScope(presets).tests.join(','),
418
- sonarSources: ['be/apps', 'be/src', 'fe/apps', ...(packages ? ['fe/packages'] : [])].join(','),
471
+ sonarSources: ['be/apps', 'be/src', 'fe/apps', ...(packages ? ['fe/packages'] : []), ...moduleDirs.map(dir => `${dir}/src`)].join(','),
419
472
  lcovReport: LCOV_REPORT,
420
473
  cpdExclusions: SONAR_CPD_EXCLUSIONS.join(','),
421
474
  ...ciWorkflowCommands(source, LCOV_REPORT, source.qualityDeclared === false ? undefined : source.quality, { app, packages }),
422
475
  // The coverage scope is the same in both editions: lite measures exactly what full measures (ruling:R212/case-1, amended by ruling:R212/case-4).
423
476
  jestCoverage: scope === 'be' ? jestCoverageSource(jestCoverage(manifest, providers)) : '',
424
- coverageExclusions: scope === APP_SCOPE ? sonarCoverageExclusions(manifest, providers).join(',') : '',
425
- codecovExclusions: scope === APP_SCOPE ? sonarCoverageExclusions(manifest, providers).map(glob => ` - ${JSON.stringify(glob)}`).join('\n') : '',
426
- codecovPaths: scope === APP_SCOPE ? codecovPaths(manifest, providers).map(glob => ` - ${JSON.stringify(glob)}`).join('\n') : '',
477
+ coverageExclusions: scope === APP_SCOPE ? [...sonarCoverageExclusions(manifest, providers), ...moduleExclusions(moduleDirs)].join(',') : '',
478
+ codecovExclusions: scope === APP_SCOPE ? [...sonarCoverageExclusions(manifest, providers), ...moduleExclusions(moduleDirs)].map(glob => ` - ${JSON.stringify(glob)}`).join('\n') : '',
479
+ codecovPaths: scope === APP_SCOPE ? [...codecovPaths(manifest, providers), ...moduleDirs.flatMap(dir => [`${dir}/src/manifest/**`, `${dir}/src/template/**`])].map(glob => ` - ${JSON.stringify(glob)}`).join('\n') : '',
427
480
  codecovComponents: scope === APP_SCOPE ? componentsYaml(coverageComponents(manifest, { ...source, apps: app.sides.be.apps, providers })) : '',
428
481
  codecovTarget: quality.codecov.target,
429
482
  dependabotUpdates: dependabotUpdates(quality.dependabot),
430
- tsconfigPaths: ['be/tsconfig.json', ...fe.apps.map(entry => `fe/apps/${entry.name}/tsconfig.json`), ...(packages ? ['fe/packages/*/tsconfig.json'] : [])].join(','),
483
+ tsconfigPaths: ['be/tsconfig.json', ...fe.apps.map(entry => `fe/apps/${entry.name}/tsconfig.json`), ...(packages ? ['fe/packages/*/tsconfig.json'] : []), ...moduleDirs.map(dir => `${dir}/tsconfig.json`)].join(','),
431
484
  styleGlob: STYLE_GLOB,
432
485
  grammarLintImport: grammarLint?.package ? `import { ${grammarLint.factory} } from "${grammarLint.package}"` : '',
433
486
  grammarLintExport: grammarLint?.package ? `export default [...starciFeConfig({ hfs }), ...${grammarLint.factory}({ hfs })]` : 'export default starciFeConfig({ hfs })',
@@ -446,12 +499,24 @@ function parseScripts(text) {
446
499
  return null;
447
500
  }
448
501
  }
502
+ /**
503
+ * The rendered targets of an app declaration. Without source facts the app counts the back-end source files its own targets
504
+ * render (the managed sign-in glue), as a check reading the disk after the targets are written does, so a render of a declaration
505
+ * alone is already in sync with itself.
506
+ */
507
+ export function renderTargets(hfs, presets, options = {}) {
508
+ const first = renderOnce(hfs, presets, options);
509
+ if (options.source !== undefined)
510
+ return first;
511
+ const settled = settleSource({ files: [], read: () => '' }, first); // reason: the settled facts name only the rendered source files; every other fact stays absent as without source
512
+ return settled.files.length === 0 ? first : renderOnce(hfs, presets, { ...options, source: settled });
513
+ }
449
514
  /**
450
515
  * The managed content of every target of the app: [{ path, mode, content, hash, scope }], paths app-relative (a side's targets under
451
516
  * its folder); a scripts target also carries `scripts`, the parsed block. `sonarKey` is the key the be side's stack declaration
452
517
  * names; without one the key is the project.
453
518
  */
454
- export function renderTargets(hfs, presets, { sonarKey, readTemplate = readBundled, manifest = loadSlotManifest(), source } = {}) {
519
+ function renderOnce(hfs, presets, { sonarKey, readTemplate = readBundled, manifest = loadSlotManifest(), source } = {}) {
455
520
  const app = validateHfs(hfs, manifest);
456
521
  const hasTemplate = (name) => { try {
457
522
  return readTemplate(name) !== undefined;
@@ -467,24 +532,24 @@ export function renderTargets(hfs, presets, { sonarKey, readTemplate = readBundl
467
532
  declaredTargets.push({ path: 'README.md', mode: 'block', markdown: true, template: 'app/readme-badges/README.md' });
468
533
  const targets = declaredTargets
469
534
  .filter(target => !(app.edition === 'lite' && source?.workPresent === false && target.path === '.starciwork/.gitignore'));
470
- return targets.map((target) => {
471
- const template = target.path === 'codecov.yml' && source?.quality?.codecov.enabled === false
472
- ? '{{header}}\n# Codecov is disabled in the manifest.\ncodecov:\n require_ci_to_pass: false\n'
473
- : readTemplate(target.template);
474
- const body = trailingNewline(render(template, vars, readTemplate));
475
- const where = onSide(scope, target.path);
476
- if (target.mode === 'scripts') {
477
- const scripts = parseScripts(body);
478
- if (!scripts)
479
- throw new SyncError('HFS_SYNC_TEMPLATE_VARIABLE', `the rendered ${target.template} has no scripts object`);
480
- const content = scriptsText(scripts);
481
- return { path: where, scope, mode: 'scripts', content, hash: hashOf(content), scripts };
482
- }
483
- const markers = target.markdown ? { begin: '<!-- >>> starci app sync (managed block; do not edit) >>> -->', end: '<!-- <<< starci app sync <<< -->' } : { begin: BLOCK_BEGIN, end: BLOCK_END };
484
- const spacing = target.markdown ? '\n' : '';
485
- const managed = target.mode === 'block' ? `${markers.begin}\n${spacing}${trailingNewline(body)}${spacing}${markers.end}\n` : body;
486
- return { path: where, scope, mode: target.mode, markdown: target.markdown, markers, content: managed, hash: hashOf(managed) };
487
- });
535
+ return [...(scope === APP_SCOPE ? [...registryTargets(app, source), ...packagesLintTargets(app, source)] : []), ...targets.map((target) => {
536
+ const template = target.path === 'codecov.yml' && source?.quality?.codecov.enabled === false
537
+ ? '{{header}}\n# Codecov is disabled in the manifest.\ncodecov:\n require_ci_to_pass: false\n'
538
+ : readTemplate(target.template);
539
+ const body = trailingNewline(render(template, vars, readTemplate));
540
+ const where = onSide(scope, target.path);
541
+ if (target.mode === 'scripts') {
542
+ const scripts = parseScripts(body);
543
+ if (!scripts)
544
+ throw new SyncError('HFS_SYNC_TEMPLATE_VARIABLE', `the rendered ${target.template} has no scripts object`);
545
+ const content = scriptsText(scripts);
546
+ return { path: where, scope, mode: 'scripts', content, hash: hashOf(content), scripts };
547
+ }
548
+ const markers = target.markdown ? { begin: '<!-- >>> starci app sync (managed block; do not edit) >>> -->', end: '<!-- <<< starci app sync <<< -->' } : { begin: BLOCK_BEGIN, end: BLOCK_END };
549
+ const spacing = target.markdown ? '\n' : '';
550
+ const managed = target.mode === 'block' ? `${markers.begin}\n${spacing}${trailingNewline(body)}${spacing}${markers.end}\n` : body;
551
+ return { path: where, scope, mode: target.mode, markdown: target.markdown, markers, content: managed, hash: hashOf(managed) };
552
+ })];
488
553
  });
489
554
  }
490
555
  // The part of a file sync manages: all of it, all of it but the verb's section, the marked block, or the scripts block as text.
@@ -591,17 +656,38 @@ export function loadHfs(root, manifest = loadSlotManifest()) {
591
656
  validateHfs(hfs, manifest);
592
657
  return hfs;
593
658
  }
659
+ /**
660
+ * The source facts as they stand once the rendered back-end source files are on disk: a managed `.ts` target of the be side counts as a
661
+ * file of the app (and reads as its render), so the CI shard count and the coverage scope that count or read the source name the same
662
+ * files before and after the targets are written, and one sync leaves the app in sync.
663
+ */
664
+ export function settleSource(source, targets) {
665
+ const added = new Map();
666
+ for (const target of targets) {
667
+ const rel = target.scope === 'be' && target.path.startsWith('be/') ? target.path.slice('be/'.length) : '';
668
+ if (SOURCE_FILE.test(rel) && (rel.startsWith('apps/') || rel.startsWith('src/')) && !source.files.includes(rel))
669
+ added.set(rel, target.content);
670
+ }
671
+ if (added.size === 0)
672
+ return source;
673
+ return { ...source, files: [...source.files, ...added.keys()].sort(byCodeUnit), read: (file) => added.get(file) ?? source.read(file) };
674
+ }
594
675
  /** The rendered targets of the app at `root`: its hfs.json, the Sonar key the be side's stack declaration names, the presets it installs. */
595
676
  export async function renderRepo(root, { presets, parseYaml, notices, manifest = loadSlotManifest() } = {}) {
596
677
  const hfs = loadHfs(root, manifest);
597
678
  const sonarKey = await readDeclaredSonarKey(root, { parseYaml, fail: (message) => { throw new SyncError('HFS_SYNC_SONAR_KEY', message); } });
598
- return { hfs, targets: renderTargets(hfs, presets ?? await loadPresets(root), { sonarKey, manifest, source: appSource(root, { parseYaml, notices }) }) };
679
+ const installed = presets ?? await loadPresets(root);
680
+ const source = appSource(root, { parseYaml, notices });
681
+ const settled = settleSource(source, renderTargets(hfs, installed, { sonarKey, manifest, source }));
682
+ return { hfs, targets: renderTargets(hfs, installed, { sonarKey, manifest, source: settled }) };
599
683
  }
600
684
  /** The folders a lite app's sync retires (ruling lite-self-hosted-postgres-passport-minio): the generated Supabase row types nothing reads any more. */
601
685
  const LITE_RETIRED_DIRS = Object.freeze(['supabase/types']);
602
686
  /** The files a lite app's sync retires, each with the reason the dry run prints. */
603
687
  const LITE_RETIRED_FILES = Object.freeze({
604
688
  [RETIRED_DB_DOCKERFILE]: `the db image moved to ${DB_DOCKERFILE} (ruling lite-db-image-in-stacks)`,
689
+ 'be/src/modules/domain/identity/persistence/entities/auth-user.entity.ts': 'the sign-in reaches accounts through the account doors (be/src/modules/domain/identity/persistence/account-doors.port.ts); the application role holds no grant on schema auth',
690
+ 'be/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts': 'the sign-in reaches accounts through the account doors (be/src/modules/domain/identity/persistence/account-doors.port.ts); the application role holds no grant on schema auth',
605
691
  '.github/workflows/db-deploy.yml': 'the stack deploy migrates the database (a verified backup, the expand migrations before the services, the contract migrations after the accept; ruling app-cicd-auto-deploy)',
606
692
  });
607
693
  /** Why a retired file goes: a retired file's own reason, else the folder's. */
@@ -0,0 +1,115 @@
1
+ -- The account doors of a lite app: the only way app_be reaches auth.users and auth.identities (it holds no grant on schema auth). Eight
2
+ -- SECURITY DEFINER functions with a pinned search_path, EXECUTE revoked from public and the browser roles and granted to app_be alone: an
3
+ -- account comes back only for an exact address or id, a hash only for an exact address, and no door writes role or app metadata; the hash
4
+ -- is written only by a new account and by be_auth_set_password.
5
+ -- The text is the canon owner of the doors (templates/app/auth-doors.sql.partial); the back end calls them through persistence/account-doors.port.ts.
6
+
7
+ -- Looks up an address in lower case; the bootstrap index serves email as written, not lower(email).
8
+ create index if not exists users_email_lower_idx on auth.users (lower(email)) where is_anonymous = false;
9
+
10
+ -- A person by sign-in address (lower-cased), with the stored bcrypt hash so the back end can verify it.
11
+ CREATE FUNCTION public.be_auth_user_by_email(p_email text)
12
+ RETURNS TABLE(id uuid, email text, encrypted_password text, email_confirmed_at timestamp with time zone, disabled boolean)
13
+ LANGUAGE sql STABLE SECURITY DEFINER
14
+ SET search_path TO ''
15
+ AS $$
16
+ select u.id, u.email::text, u.encrypted_password::text, u.email_confirmed_at,
17
+ (u.deleted_at is not null or (u.banned_until is not null and u.banned_until > now()))
18
+ from auth.users u
19
+ where lower(u.email) = p_email and u.is_anonymous = false
20
+ order by u.created_at asc nulls last
21
+ limit 1;
22
+ $$;
23
+
24
+ -- One person by id.
25
+ CREATE FUNCTION public.be_auth_user_by_id(p_user uuid)
26
+ RETURNS TABLE(id uuid, email text, last_sign_in_at timestamp with time zone, meta jsonb, disabled boolean)
27
+ LANGUAGE sql STABLE SECURITY DEFINER
28
+ SET search_path TO ''
29
+ AS $$
30
+ select u.id, u.email::text, u.last_sign_in_at, coalesce(u.raw_user_meta_data, '{}'::jsonb),
31
+ (u.deleted_at is not null or (u.banned_until is not null and u.banned_until > now()))
32
+ from auth.users u where u.id = p_user;
33
+ $$;
34
+
35
+ -- The person a Google account is linked to, if any.
36
+ CREATE FUNCTION public.be_auth_user_by_google(p_provider_id text) RETURNS uuid
37
+ LANGUAGE sql STABLE SECURITY DEFINER
38
+ SET search_path TO ''
39
+ AS $$
40
+ select i.user_id from auth.identities i where i.provider = 'google' and i.provider_id = p_provider_id limit 1;
41
+ $$;
42
+
43
+ -- Creates a person; a null hash is an invited or Google-only account that has no password yet.
44
+ CREATE FUNCTION public.be_auth_create_user(p_id uuid, p_email text, p_hash text, p_confirmed boolean, p_meta jsonb, p_now timestamp with time zone)
45
+ RETURNS uuid
46
+ LANGUAGE sql VOLATILE SECURITY DEFINER
47
+ SET search_path TO ''
48
+ AS $$
49
+ insert into auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at,
50
+ raw_app_meta_data, raw_user_meta_data, created_at, updated_at)
51
+ values ('00000000-0000-0000-0000-000000000000', p_id, 'authenticated', 'authenticated', p_email, p_hash,
52
+ case when p_confirmed then p_now end, case when p_hash is null and not p_confirmed then p_now end,
53
+ '{"provider":"email","providers":["email"]}'::jsonb, p_meta, p_now, p_now)
54
+ returning id;
55
+ $$;
56
+
57
+ -- Links a Google account to a person.
58
+ CREATE FUNCTION public.be_auth_link_google(p_user uuid, p_provider_id text, p_data jsonb, p_now timestamp with time zone) RETURNS void
59
+ LANGUAGE sql VOLATILE SECURITY DEFINER
60
+ SET search_path TO ''
61
+ AS $$
62
+ insert into auth.identities (provider_id, user_id, identity_data, provider, last_sign_in_at, created_at, updated_at)
63
+ values (p_provider_id, p_user, p_data, 'google', p_now, p_now, p_now)
64
+ on conflict (provider_id, provider) do update set last_sign_in_at = p_now, updated_at = p_now;
65
+ $$;
66
+
67
+ -- Records a sign-in; a verified Google address also confirms the mailbox; profile keys only fill what the person has not set.
68
+ CREATE FUNCTION public.be_auth_touch_user(p_user uuid, p_now timestamp with time zone, p_confirm boolean, p_meta jsonb) RETURNS void
69
+ LANGUAGE sql VOLATILE SECURITY DEFINER
70
+ SET search_path TO ''
71
+ AS $$
72
+ update auth.users
73
+ set last_sign_in_at = p_now,
74
+ updated_at = p_now,
75
+ email_confirmed_at = case when p_confirm then coalesce(email_confirmed_at, p_now) else email_confirmed_at end,
76
+ raw_user_meta_data = p_meta || coalesce(raw_user_meta_data, '{}'::jsonb)
77
+ where id = p_user;
78
+ $$;
79
+
80
+ -- Stamps the confirmation of a mailbox (a spent confirmation link); a mailbox that is already confirmed keeps its date and the sign-in record is untouched.
81
+ CREATE FUNCTION public.be_auth_confirm_user(p_user uuid, p_now timestamp with time zone) RETURNS void
82
+ LANGUAGE sql VOLATILE SECURITY DEFINER
83
+ SET search_path TO ''
84
+ AS $$
85
+ update auth.users set email_confirmed_at = coalesce(email_confirmed_at, p_now), updated_at = p_now where id = p_user;
86
+ $$;
87
+
88
+ -- Sets a new password hash; when p_confirm is true (a spent reset link proved the mailbox) the address is confirmed too.
89
+ CREATE FUNCTION public.be_auth_set_password(p_user uuid, p_hash text, p_confirm boolean, p_now timestamp with time zone) RETURNS void
90
+ LANGUAGE sql VOLATILE SECURITY DEFINER
91
+ SET search_path TO ''
92
+ AS $$
93
+ update auth.users
94
+ set encrypted_password = p_hash,
95
+ email_confirmed_at = case when p_confirm then coalesce(email_confirmed_at, p_now) else email_confirmed_at end,
96
+ updated_at = p_now
97
+ where id = p_user;
98
+ $$;
99
+
100
+ REVOKE EXECUTE ON FUNCTION public.be_auth_user_by_email(text) FROM PUBLIC, anon, authenticated;
101
+ GRANT EXECUTE ON FUNCTION public.be_auth_user_by_email(text) TO app_be;
102
+ REVOKE EXECUTE ON FUNCTION public.be_auth_user_by_id(uuid) FROM PUBLIC, anon, authenticated;
103
+ GRANT EXECUTE ON FUNCTION public.be_auth_user_by_id(uuid) TO app_be;
104
+ REVOKE EXECUTE ON FUNCTION public.be_auth_user_by_google(text) FROM PUBLIC, anon, authenticated;
105
+ GRANT EXECUTE ON FUNCTION public.be_auth_user_by_google(text) TO app_be;
106
+ REVOKE EXECUTE ON FUNCTION public.be_auth_create_user(uuid, text, text, boolean, jsonb, timestamp with time zone) FROM PUBLIC, anon, authenticated;
107
+ GRANT EXECUTE ON FUNCTION public.be_auth_create_user(uuid, text, text, boolean, jsonb, timestamp with time zone) TO app_be;
108
+ REVOKE EXECUTE ON FUNCTION public.be_auth_link_google(uuid, text, jsonb, timestamp with time zone) FROM PUBLIC, anon, authenticated;
109
+ GRANT EXECUTE ON FUNCTION public.be_auth_link_google(uuid, text, jsonb, timestamp with time zone) TO app_be;
110
+ REVOKE EXECUTE ON FUNCTION public.be_auth_touch_user(uuid, timestamp with time zone, boolean, jsonb) FROM PUBLIC, anon, authenticated;
111
+ GRANT EXECUTE ON FUNCTION public.be_auth_touch_user(uuid, timestamp with time zone, boolean, jsonb) TO app_be;
112
+ REVOKE EXECUTE ON FUNCTION public.be_auth_confirm_user(uuid, timestamp with time zone) FROM PUBLIC, anon, authenticated;
113
+ GRANT EXECUTE ON FUNCTION public.be_auth_confirm_user(uuid, timestamp with time zone) TO app_be;
114
+ REVOKE EXECUTE ON FUNCTION public.be_auth_set_password(uuid, text, boolean, timestamp with time zone) FROM PUBLIC, anon, authenticated;
115
+ GRANT EXECUTE ON FUNCTION public.be_auth_set_password(uuid, text, boolean, timestamp with time zone) TO app_be;
@@ -92,7 +92,7 @@ jobs:
92
92
  run: npm run typecheck
93
93
  env:
94
94
  NODE_OPTIONS: {{nodeOptions}}
95
-
95
+ {{moduleTestStep}}
96
96
  lint:
97
97
  needs: plan
98
98
  if: ${{ needs.plan.outputs.checks == 'true' }}
@@ -92,7 +92,7 @@ jobs:
92
92
  run: npm run typecheck
93
93
  env:
94
94
  NODE_OPTIONS: {{nodeOptions}}
95
-
95
+ {{moduleTestStep}}
96
96
  lint:
97
97
  needs: plan
98
98
  if: ${{ needs.plan.outputs.checks == 'true' }}
@@ -0,0 +1,8 @@
1
+ /** Unit run of the {{pascal}} module package: the logic of its template and manifest is measured at 100 per file. */
2
+ module.exports = {
3
+ testEnvironment: "node",
4
+ transform: { "^.+\.tsx?$": ["ts-jest", { tsconfig: "tsconfig.json", isolatedModules: true }] },
5
+ testMatch: ["<rootDir>/src/**/*.spec.ts"],
6
+ collectCoverageFrom: ["src/manifest/**/*.ts", "src/template/**/*.ts", "!src/**/*.spec.ts", "!src/**/index.ts"],
7
+ coverageThreshold: { "./src/manifest/**/*.ts": { lines: 100, branches: 100, functions: 100, statements: 100 }, "./src/template/**/*.ts": { lines: 100, branches: 100, functions: 100, statements: 100 } },
8
+ }
@@ -0,0 +1,2 @@
1
+ /** The key of the {{pascal}} module: its catalogue entry, its workbench and its message namespace. */
2
+ export const {{upper}}_MODULE_KEY: "{{name}}" = "{{name}}"
@@ -0,0 +1,11 @@
1
+ import { {{upper}}_VIEW_IDS, {{upper}}_WORKBENCH } from "./declaration"
2
+
3
+ describe("the {{pascal}} workbench declaration", () => {
4
+ it("declares one view per id, in strip order", () => {
5
+ expect({{upper}}_WORKBENCH.views.map((view) => view.id)).toEqual([...{{upper}}_VIEW_IDS])
6
+ })
7
+
8
+ it("is keyed like its module", () => {
9
+ expect({{upper}}_WORKBENCH).toMatchObject({ key: "{{name}}", namespace: "{{name}}" })
10
+ })
11
+ })
@@ -0,0 +1,13 @@
1
+ import type { WorkbenchDeclaration } from "@{{project}}/agent-sdk/workbench/contract"
2
+
3
+ /** The views of the {{pascal}} workbench in strip order (`?tab=`), each labelled from the module's namespace. */
4
+ export const {{upper}}_VIEW_IDS: readonly ["overview"] = ["overview"]
5
+
6
+ /** The {{pascal}} workbench: one overview every member of the workspace opens; grow its views, permissions, operations and exposed reads here. */
7
+ export const {{upper}}_WORKBENCH: WorkbenchDeclaration = {
8
+ key: "{{name}}",
9
+ namespace: "{{name}}",
10
+ views: [{ id: "overview", labelKey: "tab_overview" }],
11
+ permissions: [],
12
+ operations: [],
13
+ }
@@ -0,0 +1 @@
1
+ export { {{upper}}_VIEW_IDS, {{upper}}_WORKBENCH } from "./declaration"
@@ -0,0 +1,2 @@
1
+ /** The public face of the {{pascal}} module for a front end: what a page may import without the workbench. */
2
+ export { {{upper}}_MODULE_KEY } from "../index"
@@ -0,0 +1,13 @@
1
+ import { moduleListProblems } from "@{{project}}/agent-sdk/module"
2
+ import { {{upper}}_SERVER, {{upper}}_TEMPLATE } from "./{{name}}.template"
3
+
4
+ describe("{{upper}}_SERVER", () => {
5
+ it("is a module list the host may load", () => {
6
+ expect(moduleListProblems([{{upper}}_SERVER])).toEqual([])
7
+ })
8
+
9
+ it("serves its template under the key of the module", () => {
10
+ expect({{upper}}_SERVER.key).toBe({{upper}}_TEMPLATE.key)
11
+ expect({{upper}}_TEMPLATE).toMatchObject({ key: "{{name}}", workbench: "{{name}}" })
12
+ })
13
+ })
@@ -0,0 +1,41 @@
1
+ import type { ModuleServer, ModuleTemplate } from "@{{project}}/agent-sdk/module"
2
+
3
+ /** The {{pascal}} module's template: its copy, agent, setup gates, authority actions and accepted commands. */
4
+ export const {{upper}}_TEMPLATE: ModuleTemplate = {
5
+ key: "{{name}}",
6
+ order: 100,
7
+ name: { vi: "{{pascal}}", en: "{{pascal}}" },
8
+ shortName: { vi: "{{pascal}}", en: "{{pascal}}" },
9
+ description: { vi: "{{pascal}}", en: "{{pascal}}" },
10
+ points: { vi: [], en: [] },
11
+ purpose: "{{pascal}}",
12
+ icon: "module",
13
+ glyph: "M12 3l9 5v8l-9 5-9-5V8z",
14
+ mascot: "",
15
+ category: "operations",
16
+ status: "early",
17
+ audience: "customer",
18
+ includedWithWorkspace: false,
19
+ available: false,
20
+ knowledgeFolder: "{{name}}",
21
+ workbench: "{{name}}",
22
+ settingsExtras: null,
23
+ requires: { capabilities: [], connections: [] },
24
+ connectionProviders: [],
25
+ defaultOperatingMode: "assist",
26
+ multiInstance: false,
27
+ agent: {
28
+ label: { vi: "{{pascal}}", en: "{{pascal}}" },
29
+ handle: "{{name}}",
30
+ summary: { vi: "{{pascal}}", en: "{{pascal}}" },
31
+ capabilities: { vi: [], en: [] },
32
+ role: { vi: "{{pascal}}", en: "{{pascal}}" },
33
+ instructions: { vi: "{{pascal}}", en: "{{pascal}}" },
34
+ },
35
+ gates: [],
36
+ authorityActions: [],
37
+ accepts: [],
38
+ }
39
+
40
+ /** The server half of the {{pascal}} module: its template and the words it adds to a graph. */
41
+ export const {{upper}}_SERVER: ModuleServer = { key: "{{name}}", template: {{upper}}_TEMPLATE }
@@ -0,0 +1 @@
1
+ export { {{upper}}_SERVER, {{upper}}_TEMPLATE } from "./{{name}}.template"
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from "react"
2
+ import { EmptyNotice, Page } from "@starci/grammar/common"
3
+
4
+ /** What the {{pascal}} workbench draws: its title and the words of its empty state. */
5
+ export interface {{pascal}}WorkbenchViewProps {
6
+ readonly title: string
7
+ readonly emptyTitle: string
8
+ readonly emptyBody: string
9
+ }
10
+
11
+ /** Draws the {{pascal}} workbench with Grammar alone; it owns no class string, colour or visual of its own. */
12
+ export const {{pascal}}WorkbenchView = (props: {{pascal}}WorkbenchViewProps): ReactNode => (
13
+ <Page header={{ title: props.title }}>
14
+ <EmptyNotice message={props.emptyTitle} description={props.emptyBody} />
15
+ </Page>
16
+ )
@@ -0,0 +1,2 @@
1
+ export { {{pascal}}WorkbenchView } from "./components/{{pascal}}WorkbenchView"
2
+ export type { {{pascal}}WorkbenchViewProps } from "./components/{{pascal}}WorkbenchView"
@@ -1,4 +1,4 @@
1
- "build:be": "cd be && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
1
+ "build:be": "{{modulesFirst}}cd be && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
2
2
  "build:fe": "{{buildFe}}",
3
3
  "codegen": "node scripts/codegen.mjs",
4
4
  "codegen:graphql": "node scripts/codegen.mjs --graphql",
@@ -4,7 +4,7 @@
4
4
  {{appScripts}}
5
5
  {{> app/package-scripts/core.json}}
6
6
  "typecheck:tests": "tsc -p be/src/tests/tsconfig.json",
7
- "test": "cd be && jest --selectProjects unit --coverage",
7
+ "test": "{{modulesFirst}}cd be && jest --selectProjects unit --coverage",
8
8
  "test:integration": "npm run typecheck:tests && cd be && jest --selectProjects integration",
9
9
  "test:e2e": "npm run typecheck:tests && cd be && jest --selectProjects e2e",
10
10
  "test:contract": "npm run typecheck:tests && cd be && jest --selectProjects contract",
@@ -4,7 +4,7 @@
4
4
  {{appScripts}}
5
5
  {{> app/package-scripts/core.json}}
6
6
  "typecheck:tests": "tsc -p be/src/tests/tsconfig.json",
7
- "test": "cd be && jest --selectProjects unit --coverage",
7
+ "test": "{{modulesFirst}}cd be && jest --selectProjects unit --coverage",
8
8
  "test:integration": "npm run typecheck:tests && cd be && jest --selectProjects integration",
9
9
  {{> app/package-scripts/db.json}}
10
10
  {{backingScripts}}