@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
@@ -79,13 +79,11 @@ const template = (relative) => read(path.join(TEMPLATES_DIR, ...relative.split('
79
79
  const DATABASE_ERROR = 'be/src/modules/platform/database/errors/database.error.ts';
80
80
  /** Skeleton files the lite starter leaves out and the queue capabilities use, each from its skeleton template. */
81
81
  const SKELETON_FILES = Object.freeze([
82
- ['be/src/modules/platform/database/database.client.ts', 'skeleton-lite/src/modules/platform/database/database.client.ts'],
83
- ['be/src/modules/platform/database/database.client.spec.ts', 'skeleton-lite/src/modules/platform/database/database.client.spec.ts'],
84
82
  ['be/src/modules/platform/database/database.sql.ts', 'skeleton-lite/src/modules/platform/database/database.sql.ts'],
85
83
  ['be/src/modules/platform/primitives/sequence.policy.ts', 'skeleton/src/modules/platform/primitives/sequence.policy.ts'],
86
84
  ['be/src/modules/platform/primitives/sequence.policy.spec.ts', 'skeleton/src/modules/platform/primitives/sequence.policy.spec.ts'],
87
85
  ]);
88
- /** The database-function call and the sql tag of the outbox and claim statements (with its identifier error) and the in-order relay the queue adapters walk their rows with. */
86
+ /** The sql tag of the outbox and claim statements (with its identifier error) and the in-order relay the queue adapters walk their rows with. */
89
87
  function skeletonFiles(repoRoot) {
90
88
  const created = SKELETON_FILES.filter(([relative]) => !fs.existsSync(path.join(repoRoot, ...relative.split('/'))))
91
89
  .map(([relative, source]) => ({ relative, text: render(template(`be/${source}`), {}) }));
@@ -300,6 +298,54 @@ function keysPatch(file) {
300
298
  lines.splice(last + 1, 0, ...KEY_ROWS);
301
299
  return { file, text: lines.join('\n') };
302
300
  }
301
+ const DEV_REDIS_PORT = 56379;
302
+ /** The redis service a dev compose file gains: loopback only, append-only, on the host port the environment declares. */
303
+ const devRedisService = ` redis:
304
+ image: ${REDIS_IMAGE}
305
+ command: ["redis-server", "--appendonly", "yes"]
306
+ ports:
307
+ - "127.0.0.1:\${STACK_PORT_REDIS}:6379"
308
+ volumes:
309
+ - redis-data:/data
310
+ healthcheck:
311
+ test: ["CMD", "redis-cli", "ping"]
312
+ interval: 5s
313
+ timeout: 3s
314
+ retries: 20
315
+ restart: unless-stopped
316
+ `;
317
+ /**
318
+ * A docker-compose dev environment (runtime docker-compose, a `local` block) runs the declared Redis from its compose file, on its own loopback
319
+ * port, and the queue processes reach it there: the compose service, the port, the service entry and the api and worker processes' queue settings.
320
+ */
321
+ function devQueuePatches(repoRoot, manifest, edit, consumers) {
322
+ const patches = [];
323
+ const dev = Object.entries(manifest?.environments ?? {}).filter(([, environment]) => environment?.runtime === 'docker-compose' && environment.local);
324
+ for (const [name, environment] of dev) {
325
+ const composeRelative = `.starcistacks/${name}/${environment.composeFiles?.[0] ?? 'infra/compose/compose.yaml'}`;
326
+ const composeFile = path.join(repoRoot, ...composeRelative.split('/'));
327
+ const compose = read(composeFile).replace(/\r\n/g, '\n');
328
+ if (/^ {2}redis:/m.test(compose))
329
+ continue;
330
+ const base = ['environments', name];
331
+ const value = edit.value.environments[name];
332
+ edit.append([...base, 'components'], `redis: {port: ${DEV_REDIS_PORT}}\n`);
333
+ value.components.redis = { port: DEV_REDIS_PORT };
334
+ edit.append([...base, 'local', 'ports'], `redis: ${DEV_REDIS_PORT}\n`);
335
+ value.local.ports.redis = DEV_REDIS_PORT;
336
+ edit.append([...base, 'local', 'services'], '- {name: redis, wait: healthy}\n');
337
+ value.local.services.push({ name: 'redis', wait: 'healthy' });
338
+ for (const run of value.local.processes ?? []) {
339
+ if (!consumers.includes(run.name))
340
+ continue;
341
+ edit.append([...base, 'local', 'processes', { item: 'name', is: run.name }, 'env'], 'QUEUE_ENABLED: "true"\nQUEUE_REDIS_HOST: 127.0.0.1\nQUEUE_REDIS_PORT: "${port.redis}"\n');
342
+ Object.assign(run.env, { QUEUE_ENABLED: 'true', QUEUE_REDIS_HOST: '127.0.0.1', QUEUE_REDIS_PORT: '${port.redis}' });
343
+ }
344
+ const head = compose.includes('\nvolumes:\n') ? compose.replace('\nvolumes:\n', `\n${devRedisService}volumes:\n`) : `${compose.replace(/\n*$/, '\n')}${devRedisService}volumes:\n`;
345
+ patches.push({ file: composeFile, text: `${head.replace(/\n*$/, '\n')} redis-data: {}\n` });
346
+ }
347
+ return patches;
348
+ }
303
349
  /** Every stack file the declared Redis adds to or changes, planned before anything is written. */
304
350
  function stackFiles(repoRoot, { project, consumers }) {
305
351
  const manifestRelative = '.starcistacks/application-stacks.yaml';
@@ -330,6 +376,7 @@ function stackFiles(repoRoot, { project, consumers }) {
330
376
  if (!fs.existsSync(path.join(repoRoot, ...recovery.split('/'))))
331
377
  created.push({ relative: recovery, text: recoveryRecord });
332
378
  }
379
+ patches.push(...devQueuePatches(repoRoot, manifest, edit, consumers));
333
380
  patches.unshift({ file: manifestFile, text: edit.text() });
334
381
  return { patches: patches.filter((patch) => patch !== null), created, environments: swarm.map(([name]) => name) };
335
382
  }
package/scaffold/add.mjs CHANGED
@@ -23,6 +23,7 @@ import { ScaffoldError, pascalOf } from './service.mjs';
23
23
  import { addTable, graphqlPlatformFiles } from './add-table.mjs';
24
24
  import { addModuleImport, compositionCode, readComposeGroup, writeComposeGroup } from './add-compose.mjs';
25
25
  import { addApp } from './add-app.mjs';
26
+ import { addModule } from './add-module.mjs';
26
27
  import { FE_UNIT_LAYERS, addFeUnit } from './add-fe-unit.mjs';
27
28
  import { addFePage } from './add-fe-page.mjs';
28
29
  import { addFeOverlay } from './add-fe-overlay.mjs';
@@ -57,6 +58,10 @@ export function addKind({ repoRoot, noun, name, options = {}, now = Date.now })
57
58
  const result = addApp({ root: repoRoot, name });
58
59
  return { ...result, registered: { patterns: [], kinds: [] } };
59
60
  }
61
+ if (noun === 'module') {
62
+ const result = addModule({ root: repoRoot, name });
63
+ return { created: [...result.created, ...result.updated], registered: { patterns: [], kinds: [] } };
64
+ }
60
65
  const feOwner = FE_OWNERS[noun];
61
66
  if (feOwner !== undefined || Object.hasOwn(FE_UNIT_LAYERS, noun)) {
62
67
  const result = feOwner === undefined ? addFeUnit({ repoRoot, noun, name, options }) : feOwner({ repoRoot, name, options });
@@ -160,8 +160,8 @@ export declare const PARTIAL_VARIABLES: any;
160
160
  * The be files a generator writes when it first needs them, never the starter, so a fresh app carries no file and no export that
161
161
  * nothing reads (R25). In both editions: the entity bases of platform/database (abstract.entity.ts, BE-PERSISTENCE-3), written by
162
162
  * the first table whose entity extends them, since no starter table has both timestamps. In lite also: the cli's runners, the in-order
163
- * relay, and the raw SQL helpers of platform/database (the sql tag, and the database-function call statement with its spec), which only
164
- * the table, queue, jobs and webhook capabilities call; a fresh app reads and writes through entities alone (R334).
163
+ * relay, and the sql tag of platform/database, which only the table, queue, jobs and webhook capabilities call; a fresh app reads and
164
+ * writes through entities and, for the account doors of its sign-in, the database-function call of database.client.ts (R334).
165
165
  */
166
166
  export declare const ENTITY_BASE_FILE = "src/modules/platform/database/abstract.entity.ts";
167
167
  /**
package/scaffold/app.mjs CHANGED
@@ -18,7 +18,7 @@ import { spawnSync } from 'node:child_process';
18
18
  import { loadSlotManifest, resolveAppDeclaration } from '../runtime/scripts/hfs/slots.mjs';
19
19
  import { parseYaml } from '../runtime/engine/yaml.mjs';
20
20
  import { HARNESS_PACKAGE, canonPins } from '../runtime/scripts/hfs/canon-pins.mjs';
21
- import { TEMPLATES_DIR, appSource, imageFiles, render, renderTargets, writeTargets } from '../sync/index.mjs';
21
+ import { TEMPLATES_DIR, appSource, imageFiles, render, renderTargets, settleSource, writeTargets } from '../sync/index.mjs';
22
22
  import { ScaffoldError } from './service.mjs';
23
23
  import { testWorldFiles } from './test-world.mjs';
24
24
  import { FE_APP_SCRIPTS, PACKAGE_MANAGER, WORKSPACES, feAppPackageName } from '../runtime/scripts/hfs/rules/monorepo.mjs';
@@ -139,7 +139,7 @@ const LITE_STARTER_SIDES = Object.freeze({
139
139
  const STARTER_DEPENDENCIES = Object.freeze({
140
140
  dependencies: {
141
141
  '@apollo/server': null, '@nestjs/apollo': null, '@nestjs/common': null, '@nestjs/core': null, '@nestjs/cqrs': null, '@nestjs/graphql': null,
142
- '@nestjs/platform-express': null, '@nestjs/typeorm': null, 'nest-commander': null, pg: null, graphql: null,
142
+ '@nestjs/platform-express': null, '@nestjs/typeorm': null, '@starci/nest-kit': null, 'nest-commander': null, pg: null, graphql: null,
143
143
  'class-transformer': null, 'class-validator': null, 'reflect-metadata': null, rxjs: null, tslib: null, typeorm: null, undici: null, zod: null,
144
144
  },
145
145
  devDependencies: {
@@ -158,15 +158,14 @@ const STARTER_DEPENDENCIES = Object.freeze({
158
158
  const LITE_STARTER_DEPENDENCIES = Object.freeze({
159
159
  dependencies: {
160
160
  '@apollo/server': null, '@nestjs/apollo': null, '@nestjs/common': null, '@nestjs/core': null, '@nestjs/cqrs': null, '@nestjs/graphql': null,
161
- '@nestjs/platform-express': null, '@nestjs/typeorm': null, '@nestjs/passport': null, passport: null, 'passport-google-oauth20': null, 'passport-local': null,
162
- '@aws-sdk/client-s3': null, '@aws-sdk/s3-request-presigner': null, bcryptjs: null,
163
- 'class-transformer': null, 'class-validator': null, graphql: null, jose: null, pg: null,
161
+ '@nestjs/platform-express': null, '@nestjs/typeorm': null, '@starci/auth': null, '@starci/mail': null, '@starci/nest-kit': null, '@starci/object-storage': null,
162
+ 'class-transformer': null, 'class-validator': null, graphql: null, pg: null,
164
163
  'reflect-metadata': null, rxjs: null, tslib: null, typeorm: null, undici: null, zod: null,
165
164
  },
166
165
  devDependencies: {
167
166
  turbo: null, '@nestjs/testing': null, '@starci/eslint-canon-be': null, '@starci/eslint-canon-fe': null,
168
167
  '@starci/jest-preset': null, '@starci/prettier-config': null, '@starci/stylelint-canon': null, '@starci/tsconfig': null, 'starci-harness': null, '@tailwindcss/postcss': null,
169
- '@types/express': null, '@types/jest': null, '@types/node': null, '@types/passport-google-oauth20': null, '@types/passport-local': null, '@types/react': null, '@types/react-dom': null,
168
+ '@types/express': null, '@types/jest': null, '@types/node': null, '@types/react': null, '@types/react-dom': null,
170
169
  eslint: null, 'eslint-plugin-react-hooks': null, husky: null, jest: null, 'postcss-value-parser': null, prettier: null,
171
170
  stylelint: null, tailwindcss: null, 'ts-jest': null, 'ts-node-dev': null, 'tsc-alias': null,
172
171
  'tsconfig-paths': null, typescript: null,
@@ -183,22 +182,22 @@ const FE_APP_DEPENDENCIES = Object.freeze({
183
182
  '@heroui/react': null, '@heroui/styles': null, '@starci/grammar': null, '@xyflow/react': null, 'react-aria-components': null, next: null, 'next-intl': null, react: null, 'react-dom': null,
184
183
  });
185
184
  /**
186
- * The lite fe workspace: the same base without the shared packages, plus server-only for its own next-intl request config. It
187
- * holds no identity SDK: modules/auth talks only to the app's own back-end auth doors.
185
+ * The lite fe workspace: the same base without the shared packages (its next-intl stack is @starci/next-i18n's, a core package of the
186
+ * fe stack). It holds no identity SDK: modules/auth talks only to the app's own back-end auth doors.
188
187
  */
189
188
  const LITE_FE_APP_DEPENDENCIES = Object.freeze({
190
- '@heroicons/react': null, '@heroui/react': null, '@heroui/styles': null, '@starci/grammar': null, '@starci/heroicons': null, '@xyflow/react': null, 'react-aria-components': null,
191
- next: null, 'next-intl': null, react: null, 'react-dom': null, 'server-only': null,
189
+ '@heroicons/react': null, '@heroui/react': null, '@heroui/styles': null, '@starci/auth': null, '@starci/grammar': null, '@starci/heroicons': null, '@xyflow/react': null, 'react-aria-components': null,
190
+ next: null, 'next-intl': null, react: null, 'react-dom': null,
192
191
  });
193
192
  /**
194
193
  * The shared fe workspace packages of the starter, fe/packages/<project>-<name> named @<project>/<name>: each one's exported
195
194
  * subpaths (built to dist) and the runtime dependencies its own source imports. ui holds the drawings both apps mount; i18n the
196
- * next-intl stack (the entry, a server module: createAppI18n; ./proxy, the locale negotiation the apps' proxy.ts re-exports;
197
- * ./routing, client-safe: the default locale a client boundary reads).
195
+ * product's description of its languages over @starci/next-i18n (the entry, a server module: createAppI18n; ./proxy, the locale
196
+ * negotiation the apps' proxy.ts re-exports; ./routing, client-safe: the description and the default locale a client boundary reads).
198
197
  */
199
198
  const FE_PACKAGES = Object.freeze({
200
199
  ui: { exports: ['.'], dependencies: { '@heroui/react': null, '@starci/grammar': null, '@starci/heroicons': null, '@xyflow/react': null, 'react-aria-components': null, 'next-intl': null, react: null, 'react-dom': null } },
201
- i18n: { exports: ['.', './proxy', './routing'], dependencies: { next: null, 'next-intl': null, 'server-only': null } },
200
+ i18n: { exports: ['.', './proxy', './routing'], dependencies: { '@starci/next-i18n': null, next: null, 'server-only': null } },
202
201
  });
203
202
  /** The scripts of every fe package workspace: tsc builds it to dist and type-checks it. */
204
203
  const PACKAGE_WORKSPACE_SCRIPTS = Object.freeze({ build: 'tsc -p tsconfig.build.json', typecheck: 'tsc --noEmit -p tsconfig.json' });
@@ -334,15 +333,13 @@ const LITE_APP_REUSED = new Set(['.editorconfig', '.gitattributes', '.nvmrc']);
334
333
  * The be files a generator writes when it first needs them, never the starter, so a fresh app carries no file and no export that
335
334
  * nothing reads (R25). In both editions: the entity bases of platform/database (abstract.entity.ts, BE-PERSISTENCE-3), written by
336
335
  * the first table whose entity extends them, since no starter table has both timestamps. In lite also: the cli's runners, the in-order
337
- * relay, and the raw SQL helpers of platform/database (the sql tag, and the database-function call statement with its spec), which only
338
- * the table, queue, jobs and webhook capabilities call; a fresh app reads and writes through entities alone (R334).
336
+ * relay, and the sql tag of platform/database, which only the table, queue, jobs and webhook capabilities call; a fresh app reads and
337
+ * writes through entities and, for the account doors of its sign-in, the database-function call of database.client.ts (R334).
339
338
  */
340
339
  export const ENTITY_BASE_FILE = 'src/modules/platform/database/abstract.entity.ts';
341
340
  const DEFERRED_GENERATOR_FILES = new Set([ENTITY_BASE_FILE]);
342
341
  const LITE_DEFERRED_GENERATOR_FILES = new Set([
343
342
  ...DEFERRED_GENERATOR_FILES,
344
- 'src/modules/platform/database/database.client.ts',
345
- 'src/modules/platform/database/database.client.spec.ts',
346
343
  'src/modules/platform/database/database.sql.ts',
347
344
  'src/modules/platform/database/migration-runner.service.ts',
348
345
  'src/modules/platform/database/seed-runner.service.ts',
@@ -656,7 +653,8 @@ export function scaffoldApp({ name, into, presets, harness, edition = 'full', re
656
653
  try {
657
654
  for (const file of files)
658
655
  writes.write(file.path, file.content);
659
- const targets = renderTargets(declaration, presets, { manifest, source: appSource(root) });
656
+ const source = appSource(root);
657
+ const targets = renderTargets(declaration, presets, { manifest, source: settleSource(source, renderTargets(declaration, presets, { manifest, source })) });
660
658
  for (const target of targets)
661
659
  writes.claim(target.path);
662
660
  writeTargets(root, targets);
@@ -59,20 +59,13 @@ const REGISTRATIONS = Object.freeze({
59
59
  names: ["sql"],
60
60
  beforeSource: "./errors/database.error",
61
61
  },
62
- {
63
- // The outbox write and the relay call the queue functions of their migration (R334).
64
- file: "be/src/modules/platform/database/index.ts",
65
- source: "./database.client",
66
- names: ["callDbFunction"],
67
- beforeSource: "./primary.decorators",
68
- },
69
62
  ],
70
63
  // The jobs capability (add job, the fenced-job pattern): its tick takes a transaction-scoped lock before it claims the minute (R335).
71
64
  jobs: [
72
65
  {
73
66
  file: "be/src/modules/platform/database/index.ts",
74
67
  source: "./database.client",
75
- names: ["advisoryXactLock", "callDbFunction"],
68
+ names: ["advisoryXactLock"],
76
69
  beforeSource: "./primary.decorators",
77
70
  },
78
71
  ],
@@ -83,13 +76,6 @@ const REGISTRATIONS = Object.freeze({
83
76
  names: ["BATCH_ROWS", "sql"],
84
77
  beforeSource: "./errors/database.error",
85
78
  },
86
- {
87
- // The inbox claim and the lite event bus call the functions of their migration (R334).
88
- file: "be/src/modules/platform/database/index.ts",
89
- source: "./database.client",
90
- names: ["callDbFunction"],
91
- beforeSource: "./primary.decorators",
92
- },
93
79
  {
94
80
  // PRIMARY_ENTITY_MANAGER: the token the inbox service's unit spec provides its EntityManager double under.
95
81
  file: "be/src/modules/platform/database/index.ts",
@@ -0,0 +1,19 @@
1
+ /** The folder name prefix of a module package. */
2
+ export declare const MODULE_PREFIX = "module-";
3
+ /** The package that lists every module package. */
4
+ export declare const REGISTRY_DIR = "packages/modules";
5
+ /** The product's agent SDK package folder every module builds on. */
6
+ export declare const SDK_DIR = "packages/agent-sdk";
7
+ /** A file of the registry package: its app-relative path and its content. */
8
+ export interface RegistryFile {
9
+ readonly path: string;
10
+ readonly content: string;
11
+ }
12
+ /** `loyalty-points` as the UPPER_SNAKE prefix of the constants a module exports. */
13
+ export declare const upperOf: (name: string) => string;
14
+ /** The module names under `packages/` of the app at `root` (a folder module-<name> holding a package.json), sorted. */
15
+ export declare function moduleNames(root: string): string[];
16
+ /** The files of the registry package for `names` (empty when no module exists: the package is then absent). */
17
+ export declare function registryFiles(project: string, names: readonly string[]): RegistryFile[];
18
+ /** The PascalCase label of a module, for the messages that name it. */
19
+ export declare const labelOf: (name: string) => string;
@@ -0,0 +1,84 @@
1
+ // module-registry.mts - the module packages of an app (packages/module-<name>) and the registry package (packages/modules) that lists them.
2
+ //
3
+ // The registry is managed: its manifests and its three source files are rendered from the module list, by `starci app add|remove module` and by
4
+ // `starci app sync` (the same function), so a hand edit is drift. A module package is written once by `add module` and is the product's from then on.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { byCodeUnit } from '../report/order.mjs';
8
+ import { pascalOf } from './service.mjs';
9
+ /** The folder name prefix of a module package. */
10
+ export const MODULE_PREFIX = 'module-';
11
+ /** The package that lists every module package. */
12
+ export const REGISTRY_DIR = 'packages/modules';
13
+ /** The product's agent SDK package folder every module builds on. */
14
+ export const SDK_DIR = 'packages/agent-sdk';
15
+ const HEADER = '// Generated by starci app sync (module registry). Do not edit: run "npx starci app sync --execute".';
16
+ const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
17
+ /** `loyalty-points` as the UPPER_SNAKE prefix of the constants a module exports. */
18
+ export const upperOf = (name) => name.replace(/-/g, '_').toUpperCase();
19
+ /** The module names under `packages/` of the app at `root` (a folder module-<name> holding a package.json), sorted. */
20
+ export function moduleNames(root) {
21
+ const folder = path.join(root, 'packages');
22
+ if (!fs.existsSync(folder))
23
+ return [];
24
+ return fs.readdirSync(folder, { withFileTypes: true })
25
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith(MODULE_PREFIX) && fs.existsSync(path.join(folder, entry.name, 'package.json')))
26
+ .map((entry) => entry.name.slice(MODULE_PREFIX.length))
27
+ .sort(byCodeUnit);
28
+ }
29
+ /** The exports map of the registry and of a module: the React-free halves and, for a module, its Grammar workbench. */
30
+ const registryExports = () => ({
31
+ '.': { types: './dist/index.d.ts', default: './dist/index.js' },
32
+ './template': { types: './dist/template/index.d.ts', default: './dist/template/index.js' },
33
+ './manifest': { types: './dist/manifest/index.d.ts', default: './dist/manifest/index.js' },
34
+ });
35
+ /** The files of the registry package for `names` (empty when no module exists: the package is then absent). */
36
+ export function registryFiles(project, names) {
37
+ if (names.length === 0)
38
+ return [];
39
+ const sorted = [...names].sort(byCodeUnit);
40
+ const sdk = `@${project}/agent-sdk`;
41
+ const module = (name) => `@${project}/${MODULE_PREFIX}${name}`;
42
+ const serverImports = sorted.map((name) => `import { ${upperOf(name)}_SERVER } from "${module(name)}/template"`).join('\n');
43
+ const workbenchImports = sorted.map((name) => `import { ${upperOf(name)}_WORKBENCH } from "${module(name)}/manifest"`).join('\n');
44
+ const dependencies = Object.fromEntries([[sdk, '*'], ...sorted.map((name) => [module(name), '*'])].sort(([a], [b]) => byCodeUnit(a, b)));
45
+ return [
46
+ {
47
+ path: `${REGISTRY_DIR}/package.json`,
48
+ content: json({
49
+ name: `@${project}/modules`,
50
+ version: '0.0.0',
51
+ private: true,
52
+ description: 'The one list of the product modules built on the agent SDK, all React-free: ./template (every module\'s server half, for the back end) and ./manifest (every workbench declaration and the spec registry they assemble).',
53
+ exports: registryExports(),
54
+ scripts: { build: 'tsc -p tsconfig.build.json', typecheck: 'tsc --noEmit --pretty false' },
55
+ dependencies,
56
+ }),
57
+ },
58
+ {
59
+ path: `${REGISTRY_DIR}/tsconfig.json`,
60
+ content: json({ extends: '@starci/tsconfig/next.json', compilerOptions: { incremental: false, types: [] }, include: ['src/**/*.ts'], exclude: ['node_modules', 'dist'] }),
61
+ },
62
+ {
63
+ path: `${REGISTRY_DIR}/tsconfig.build.json`,
64
+ content: json({
65
+ extends: './tsconfig.json',
66
+ compilerOptions: { rootDir: 'src', outDir: 'dist', noEmit: false, declaration: true, module: 'node16', moduleResolution: 'node16' },
67
+ }),
68
+ },
69
+ {
70
+ path: `${REGISTRY_DIR}/src/index.ts`,
71
+ content: `${HEADER}\nimport { MODULE_SERVERS } from "./template"\n\n/** The keys of every module this registry lists, for a host that checks its own wiring against it. */\nexport const REGISTERED_MODULE_KEYS: ReadonlyArray<string> = MODULE_SERVERS.map((module) => module.key)\n`,
72
+ },
73
+ {
74
+ path: `${REGISTRY_DIR}/src/template/index.ts`,
75
+ content: `${HEADER}\nimport type { ModuleServer } from "${sdk}/module"\n${serverImports}\n\n/** The server half of every module built on the SDK; the back end reads nothing else of a module. */\nexport const MODULE_SERVERS: ReadonlyArray<ModuleServer> = [${sorted.map((name) => `${upperOf(name)}_SERVER`).join(', ')}]\n`,
76
+ },
77
+ {
78
+ path: `${REGISTRY_DIR}/src/manifest/index.ts`,
79
+ content: `${HEADER}\nimport { specRegistryFrom } from "${sdk}/workbench/contract"\nimport type { WorkbenchDeclaration, WorkbenchSpecRegistry } from "${sdk}/workbench/contract"\n${workbenchImports}\n\n/** The workbench declaration of every module built on the SDK; the front end pairs each with how it draws. */\nexport const MODULE_WORKBENCHES: ReadonlyArray<WorkbenchDeclaration> = [${sorted.map((name) => `${upperOf(name)}_WORKBENCH`).join(', ')}]\n\n/** Everything a stored spec may name: the SDK's blocks and each module's exposed blocks, reads and actions. */\nexport const SPEC_REGISTRY: WorkbenchSpecRegistry = specRegistryFrom(MODULE_WORKBENCHES)\n`,
80
+ },
81
+ ];
82
+ }
83
+ /** The PascalCase label of a module, for the messages that name it. */
84
+ export const labelOf = (name) => pascalOf(name);
@@ -29,3 +29,11 @@ export declare function syncBundles(options: {
29
29
  readonly check: boolean;
30
30
  readonly packages?: readonly string[];
31
31
  }): SyncResult;
32
+ /** The harness runtime layers whose recipe hfs ships for `starci app stack up` (stack/runtime-layers.mts): the folder name under images/ of the harness repository. */
33
+ export declare const layerRecipes: readonly ["openclaw-runtime"];
34
+ /** Syncs (or judges) the shipped copy stack/images/<layer>/ of each recipe against the owner, `images/<layer>/` of the harness repository. */
35
+ export declare function syncLayerRecipes(options: {
36
+ readonly imagesDir: string;
37
+ readonly shippedDir: string;
38
+ readonly check: boolean;
39
+ }): SyncResult;
package/src/main.mjs CHANGED
@@ -101,6 +101,7 @@ import { emitContractsAsync } from '../emit/contracts.mjs';
101
101
  import { ScaffoldError, newService, newSpec } from '../scaffold/service.mjs';
102
102
  import { EditionRefusal } from '../scaffold/edition-gate.mjs';
103
103
  import { addKind } from '../scaffold/add.mjs';
104
+ import { removeModule } from '../scaffold/add-module.mjs';
104
105
  import { scaffoldApp } from '../scaffold/app.mjs';
105
106
  import { newImages } from '../scaffold/image.mjs';
106
107
  import { cachedLinters, eslintRepository, runLinter as nativeLinter } from '../lint/run.mjs';
@@ -140,7 +141,7 @@ const listFlag = (opts, flag) => { const given = opts[flag]; return Array.isArra
140
141
  /** An argument the parser refuses (an unknown flag, a missing value, a form a verb does not take): the one error `main` follows with the usage. */
141
142
  export class UsageError extends Error {
142
143
  }
143
- export const VERBS = Object.freeze(['scaffold', 'add', 'sync', 'check', 'upgrade', 'stack', 'world', 'release', 'slot', 'emit', 'secret', 'ci', 'db', 'coverage', 'codemod']);
144
+ export const VERBS = Object.freeze(['scaffold', 'add', 'remove', 'sync', 'check', 'upgrade', 'stack', 'world', 'release', 'slot', 'emit', 'secret', 'ci', 'db', 'coverage', 'codemod']);
144
145
  const DATABASE_VERBS = new Map([['baseline', dbBaselineMain], ['restamp', dbRestampMain], ['migrate', dbMigrateMain], ['post-cutover', dbPostCutoverMain], ['dump', dbDumpMain], ['new', dbNewMain], ['drift', dbDriftMain]]);
145
146
  /**
146
147
  * The verbs parsed here, each with the only flags it takes ({ boolean, value, list }); a flag of another verb is refused, never ignored.
@@ -158,12 +159,13 @@ const PARSED = {
158
159
  add: {
159
160
  run: addMain, ...ADD_FLAGS, list: ['--inject'],
160
161
  forms: [
161
- { operands: '<api|webhook|realtime|saga|job|reactor|queue|projection|cli|table|app|block|frame|composite|branch|leaf|page|overlay|layout> <name>', only: ADD_FLAGS },
162
+ { operands: '<api|webhook|realtime|saga|job|reactor|queue|projection|cli|table|app|module|block|frame|composite|branch|leaf|page|overlay|layout> <name>', only: ADD_FLAGS },
162
163
  { operands: 'service <dir> <name>', only: { list: ['--inject'] } },
163
164
  { operands: 'spec <file>.service.ts', flags: [] },
164
165
  { operands: 'image', flags: [] },
165
166
  ],
166
167
  },
168
+ remove: { run: removeMain, forms: [{ operands: 'module <name>', flags: [] }] },
167
169
  check: { run: checkMain, value: ['--coverage-from', '--shard', '--side', '--out'], list: GATE_FLAGS, boolean: ['--json', '--staged', '--no-cache', '--fix'], edition: true, forms: [{ only: { boolean: ['--json', '--no-cache', '--fix'], list: GATE_FLAGS, value: ['--coverage-from', '--shard', '--side', '--out'] } }, { only: { boolean: ['--staged'] }, edition: false }] },
168
170
  slot: { run: slotMain, boolean: ['--json'], forms: [{ operands: '<path>' }] },
169
171
  emit: { run: emitMain, forms: [{}] },
@@ -204,6 +206,11 @@ const VERB_USAGE = {
204
206
  world: ['starci app world <up|down|status> [--stack <dir>] [--services <list>] [--k3d] [--force] [--json] [--edition full|lite]'],
205
207
  stack: [
206
208
  ...STACK_ACTIONS.map((action) => line(`starci app stack ${action}`, STACK_OPERANDS[action], STACK_READ_ONLY.includes(action) ? flagUsage('--env', { value: true }) : '--env <env>', STACK_FLAGS[action].map((flag) => flagUsage(flag, { value: STACK_VALUE.has(flag), repeat: STACK_REPEAT.has(flag) })), '[--json]')),
209
+ // The dev actions of a docker-compose environment (stack/local-cli.mts); prepare, status and verify above answer them too.
210
+ 'starci app stack doctor --env <env> [--json]',
211
+ 'starci app stack up --env <env> [--execute] [--timeout <seconds>] [--json]',
212
+ 'starci app stack down --env <env> [--execute] [--json]',
213
+ 'starci app stack logs --env <env> --service <name> [--lines <n>] [--json]',
207
214
  ],
208
215
  release: Object.entries(RELEASE_FLAGS).map(([action, spec]) => line(`starci app release ${action}`, specUsage(spec))),
209
216
  secret: SECRET_VERBS.map((verb) => line(`starci app secret ${verb}`, secretUsage(verb))),
@@ -680,6 +687,20 @@ function addMain(opts, { cwd: repoRoot, stdout: out }) {
680
687
  out(`starci app add ${noun}: registered patterns ${registered.patterns.join(', ')}${kinds}\n`);
681
688
  return 0;
682
689
  }
690
+ /** `remove module <name>`: deletes the module package and its registrations and re-renders the registry. */
691
+ function removeMain(opts, { cwd: repoRoot, stdout: out }) {
692
+ const [noun, name, ...extra] = opts.positional;
693
+ if (noun !== 'module' || !name || extra.length)
694
+ throw new UsageError('starci app remove takes `module <name>`');
695
+ const { removed, updated, created } = removeModule({ root: repoRoot, name });
696
+ for (const file of removed)
697
+ out(`removed ${file}
698
+ `);
699
+ for (const file of [...updated, ...created])
700
+ out(`updated ${file}
701
+ `);
702
+ return 0;
703
+ }
683
704
  async function scaffoldMain(opts, { cwd, harness, edition = 'full', presets, lock, stdout: out }) {
684
705
  const [name, ...extra] = opts.positional;
685
706
  const registryVisibility = valueFlag(opts, 'registry-visibility');
package/stack/cli.mjs CHANGED
@@ -18,6 +18,7 @@ import { withDockerTransport } from './docker-transport.mjs';
18
18
  import { StackError, EXIT, refusal } from './errors.mjs';
19
19
  import { parseTargetBinding, bindStackTarget, nativeToolEnvironment } from './target-binding.mjs';
20
20
  import { oneCommand, prepareUrgentRelease } from './urgent-release.mjs';
21
+ import { isLocalEnvironment, isLocalOnly, isLocalShared, runLocal } from './local-cli.mjs';
21
22
  /** The actions that drive the docker CLI of the operator machine (pointed at the manager by docker-transport.mjs when the key is sealed). */
22
23
  const DOCKER_ACTIONS = new Set(['deploy', 'accept', 'converge', 'verify', 'rollback', 'prune', 'status']);
23
24
  export const ACTIONS = Object.freeze(['prepare', 'connect', 'disconnect', 'build-args', 'render', 'deploy', 'converge', 'verify', 'rollback', 'prune', 'backup', 'jobs', 'task', 'ingress', 'retire', 'build', 'accept', 'status', 'ci-key']);
@@ -185,8 +186,12 @@ export async function stackMain(argv, io = {}) {
185
186
  const ctx = stackContext(io);
186
187
  try {
187
188
  const action = argv[0];
189
+ if (isLocalOnly(action))
190
+ return await runLocal(ctx, action, argv.slice(1), io.localDeps);
188
191
  if (!isAction(action))
189
- throw refusal('STACK_USAGE', `starci app stack takes ${[...ACTIONS, 'up', 'down', 'status'].join(', ')}`);
192
+ throw refusal('STACK_USAGE', `starci app stack takes ${[...ACTIONS, 'doctor', 'up', 'down', 'logs'].join(', ')}`);
193
+ if (isLocalShared(action) && argv.includes('--env') && isLocalEnvironment(ctx.cwd, String(argv[argv.indexOf('--env') + 1])))
194
+ return await runLocal(ctx, action, argv.slice(1), io.localDeps);
190
195
  const { flags, positional } = parseFlags(action, argv.slice(1));
191
196
  if (flags.env === undefined && !READ_ONLY.includes(action))
192
197
  throw refusal('STACK_USAGE', `stack ${action} has an effect: name its environment with --env <env> (only ${READ_ONLY.join(', ')} default to production)`);
@@ -47,6 +47,16 @@ export interface PipeResult {
47
47
  sink: number;
48
48
  stderr: string;
49
49
  }
50
+ /** One heavy command the dev verbs run (`npm ci`, a compose build or up): admitted by the harness's heavy-command governor when it hands one in. */
51
+ export interface HeavyRequest {
52
+ kind: 'install' | 'build';
53
+ label: string;
54
+ cwd: string;
55
+ argv: string[];
56
+ env: NodeJS.ProcessEnv;
57
+ }
58
+ /** The governed runner: waits for a slot of the host-wide semaphore, runs the argv without a shell and returns its result. */
59
+ export type HeavyRun = (request: HeavyRequest) => Promise<RunResult>;
50
60
  /** The secret seam: decrypts the app's sops files. */
51
61
  export interface Sops {
52
62
  decrypt(file: string, format?: string): Record<string, string>;
@@ -82,6 +92,9 @@ export interface StackIo {
82
92
  role?: string | undefined;
83
93
  } | undefined;
84
94
  importer?: ((file: string) => Promise<Record<string, unknown>>) | undefined;
95
+ heavy?: HeavyRun | undefined;
96
+ /** The seams of the dev verbs a spec replaces (stack/local-run.mjs LocalDeps). */
97
+ localDeps?: import('./local-run.mjs').LocalDeps | undefined;
85
98
  }
86
99
  /** The context of one stack invocation (see stackContext). */
87
100
  export interface StackContext {
@@ -98,6 +111,8 @@ export interface StackContext {
98
111
  readonly fetch: typeof fetch;
99
112
  readonly pipe: (source: PipeEnd, sink: PipeEnd) => Promise<PipeResult>;
100
113
  readonly sops: Sops;
114
+ /** The governed runner of heavy commands (the harness CLI hands it in); absent, a dev verb runs them directly. */
115
+ readonly heavy?: HeavyRun | undefined;
101
116
  /** The command-owned SSH connection, set by the Docker transport (docker-transport.mjs). */
102
117
  readonly stackSsh?: SshConnection | undefined;
103
118
  }
package/stack/context.mjs CHANGED
@@ -74,6 +74,7 @@ export function stackContext(io = {}) {
74
74
  stderr: io.stderr ?? ((text) => process.stderr.write(text)),
75
75
  fetch: io.fetch ?? globalThis.fetch,
76
76
  pipe: io.pipe ?? realPipe,
77
+ heavy: io.heavy,
77
78
  /** The secret seam: the app's own sops/age tools, created on first use so verbs that open no custody never need them. */
78
79
  get sops() {
79
80
  const opened = sops ?? io.openSops?.() ?? defaultSops(env, cwd, io.platform ?? process.platform, undefined, io.hostIdentity ?? null);
@@ -0,0 +1,18 @@
1
+ # StarCi OpenClaw runtime layer: the harness-pinned source-built fork (images/openclaw, sources.json) plus the generic gateway wrapper.
2
+ # An app image does `FROM ghcr.io/starci183/openclaw-runtime@sha256:...` and only COPYs its own plugins into /opt/starci/plugins/<name>/;
3
+ # it carries no wrapper code (images/README.md "The OpenClaw runtime layer"). The base below is the recorded `published.reference` of the
4
+ # `openclaw` entry of sources.json; a new fork build raises it here in the same change.
5
+ FROM ghcr.io/starci183/openclaw:2026.10.10-beta.5-starci.1@sha256:0770c1da11ec4a46607ec16957ac3cff68d05db4a287f59f0e1d0e76de980ff8
6
+
7
+ USER root
8
+ # The wrapper is root-owned and not writable by the runtime user; the plugin directory is root-owned and world-readable, so a plugin an
9
+ # app copies in cannot be rewritten by the gateway it runs inside.
10
+ COPY --chmod=0555 entrypoint.sh /opt/starci/entrypoint.sh
11
+ COPY --chmod=0444 openclaw-init.mjs openclaw-relay.mjs /opt/starci/
12
+ RUN mkdir -p /opt/starci/plugins \
13
+ && chown -R root:root /opt/starci \
14
+ && chmod 0755 /opt/starci /opt/starci/plugins
15
+
16
+ USER node
17
+ ENV STARCI_PLUGINS_DIR=/opt/starci/plugins
18
+ ENTRYPOINT ["tini", "-s", "--", "sh", "/opt/starci/entrypoint.sh"]
@@ -0,0 +1,23 @@
1
+ #!/bin/sh
2
+ # Entrypoint of the StarCi OpenClaw runtime layer (/opt/starci/entrypoint.sh).
3
+ #
4
+ # 1. The OpenClaw image has no `<NAME>_FILE` support, so every secret named in STARCI_SECRETS (space separated; OPENCLAW_GATEWAY_TOKEN is always
5
+ # read) is exported from /run/secrets/<NAME> into THIS process tree only: no env file on the host, no value in the service specification.
6
+ # 2. openclaw-init.mjs merges the gateway settings into openclaw.json (idempotent; other keys, agents and plugins are kept) and enables the plugins
7
+ # found under STARCI_PLUGINS_DIR.
8
+ # 3. openclaw-relay.mjs listens on the overlay address and forwards to the loopback-only gateway: the gateway honours the scopes a client
9
+ # declares only for a loopback peer using the shared secret, so clients connect to the relay (OPENCLAW_RELAY_PORT, default 18790).
10
+ set -eu
11
+
12
+ for name in OPENCLAW_GATEWAY_TOKEN ${STARCI_SECRETS:-}; do
13
+ file="/run/secrets/$name"
14
+ if [ -r "$file" ]; then
15
+ value="$(cat "$file")"
16
+ export "$name=$value"
17
+ fi
18
+ done
19
+ : "${OPENCLAW_GATEWAY_TOKEN:?the secret OPENCLAW_GATEWAY_TOKEN is not granted to this service}"
20
+
21
+ OPENCLAW_GATEWAY_TOKEN_FILE=/run/secrets/OPENCLAW_GATEWAY_TOKEN node /opt/starci/openclaw-init.mjs
22
+ node /opt/starci/openclaw-relay.mjs &
23
+ exec node openclaw.mjs gateway