@starci/hfs 5.0.8 → 5.2.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 (339) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +10 -1
  3. package/cache/import-graph.mjs +6 -61
  4. package/cache/lint-family.d.mts +10 -1
  5. package/cache/lint-family.mjs +12 -5
  6. package/codemod/directive-first.d.mts +48 -0
  7. package/codemod/directive-first.mjs +136 -0
  8. package/codemod/explicit-surface-edit.d.mts +14 -0
  9. package/codemod/explicit-surface-edit.mjs +57 -0
  10. package/codemod/explicit-surface-names.d.mts +30 -0
  11. package/codemod/explicit-surface-names.mjs +135 -0
  12. package/codemod/explicit-surface-render.d.mts +29 -0
  13. package/codemod/explicit-surface-render.mjs +197 -0
  14. package/codemod/explicit-surface.d.mts +50 -0
  15. package/codemod/explicit-surface.mjs +374 -0
  16. package/codemod/main.d.mts +7 -5
  17. package/codemod/main.mjs +37 -16
  18. package/codemod/source-tree.d.mts +8 -0
  19. package/codemod/source-tree.mjs +23 -0
  20. package/db/postgres.mjs +9 -1
  21. package/lint/eslint-list.cjs +29 -0
  22. package/lint/run.d.mts +19 -5
  23. package/lint/run.mjs +47 -14
  24. package/package.json +1 -1
  25. package/runtime/knowledge/hfs/canon-pins.yaml +31 -10
  26. package/runtime/knowledge/hfs/fe-stack.yaml +7 -7
  27. package/runtime/knowledge/hfs/fork-images.yaml +7 -0
  28. package/runtime/knowledge/hfs/rules.yaml +107 -8
  29. package/runtime/knowledge/hfs/slots.yaml +75 -24
  30. package/runtime/modules/kernel/failure-codes.yaml +62 -0
  31. package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
  32. package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
  33. package/runtime/scripts/hfs/architecture/test-world-files.d.mts +4 -3
  34. package/runtime/scripts/hfs/architecture/test-world-files.mjs +26 -7
  35. package/runtime/scripts/hfs/architecture/transport-owner.d.mts +1 -2
  36. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -6
  37. package/runtime/scripts/hfs/explicit-surface-rule.d.mts +58 -0
  38. package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
  39. package/runtime/scripts/hfs/manifest-shape.d.mts +1 -0
  40. package/runtime/scripts/hfs/manifest-shape.mjs +3 -1
  41. package/runtime/scripts/hfs/rules/a15-findings.mjs +5 -0
  42. package/runtime/scripts/hfs/rules/stacks-local.d.mts +7 -0
  43. package/runtime/scripts/hfs/rules/stacks-local.mjs +71 -0
  44. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +6 -1
  45. package/runtime/scripts/hfs/rules/stacks-service-images.d.mts +12 -0
  46. package/runtime/scripts/hfs/rules/stacks-service-images.mjs +120 -0
  47. package/runtime/scripts/hfs/secret-dev-init.d.mts +11 -0
  48. package/runtime/scripts/hfs/secret-dev-init.mjs +79 -0
  49. package/runtime/scripts/hfs/secret.mjs +10 -7
  50. package/runtime/scripts/hfs/slots.mjs +1 -1
  51. package/runtime/scripts/hfs/stack-local-config.d.mts +101 -0
  52. package/runtime/scripts/hfs/stack-local-config.mjs +320 -0
  53. package/runtime/scripts/hfs/stacks-gate.mjs +1 -1
  54. package/runtime/scripts/lib/declaration-surface.d.mts +35 -0
  55. package/runtime/scripts/lib/declaration-surface.mjs +112 -0
  56. package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
  57. package/runtime/scripts/lib/swarm-stack.mjs +2 -2
  58. package/scaffold/add-module.d.mts +16 -0
  59. package/scaffold/add-module.mjs +232 -0
  60. package/scaffold/add-queue-lite.mjs +49 -0
  61. package/scaffold/add.mjs +5 -0
  62. package/scaffold/app.mjs +7 -7
  63. package/scaffold/module-registry.d.mts +19 -0
  64. package/scaffold/module-registry.mjs +84 -0
  65. package/src/main.mjs +23 -2
  66. package/stack/cli.mjs +6 -1
  67. package/stack/context.d.mts +15 -0
  68. package/stack/context.mjs +1 -0
  69. package/stack/local-cli.d.mts +25 -0
  70. package/stack/local-cli.mjs +89 -0
  71. package/stack/local-config.d.mts +17 -0
  72. package/stack/local-config.mjs +37 -0
  73. package/stack/local-custody.d.mts +24 -0
  74. package/stack/local-custody.mjs +109 -0
  75. package/stack/local-doctor.d.mts +6 -0
  76. package/stack/local-doctor.mjs +49 -0
  77. package/stack/local-down.d.mts +8 -0
  78. package/stack/local-down.mjs +20 -0
  79. package/stack/local-host.d.mts +42 -0
  80. package/stack/local-host.mjs +163 -0
  81. package/stack/local-logs.d.mts +8 -0
  82. package/stack/local-logs.mjs +32 -0
  83. package/stack/local-ports.d.mts +7 -0
  84. package/stack/local-ports.mjs +39 -0
  85. package/stack/local-prepare.d.mts +22 -0
  86. package/stack/local-prepare.mjs +62 -0
  87. package/stack/local-run.d.mts +71 -0
  88. package/stack/local-run.mjs +200 -0
  89. package/stack/local-state.d.mts +35 -0
  90. package/stack/local-state.mjs +29 -0
  91. package/stack/local-status.d.mts +6 -0
  92. package/stack/local-status.mjs +36 -0
  93. package/stack/local-up.d.mts +11 -0
  94. package/stack/local-up.mjs +166 -0
  95. package/stack/local-verify.d.mts +6 -0
  96. package/stack/local-verify.mjs +21 -0
  97. package/sync/index.d.mts +13 -12
  98. package/sync/index.mjs +83 -28
  99. package/templates/app/module/jest.config.js.tpl +8 -0
  100. package/templates/app/module/src/index.ts.tpl +2 -0
  101. package/templates/app/module/src/manifest/declaration.spec.ts.tpl +11 -0
  102. package/templates/app/module/src/manifest/declaration.ts.tpl +13 -0
  103. package/templates/app/module/src/manifest/index.ts.tpl +1 -0
  104. package/templates/app/module/src/public/index.ts.tpl +2 -0
  105. package/templates/app/module/src/template/__name__.template.spec.ts.tpl +13 -0
  106. package/templates/app/module/src/template/__name__.template.ts.tpl +41 -0
  107. package/templates/app/module/src/template/index.ts.tpl +1 -0
  108. package/templates/app/module/src/workbench/components/__pascal__WorkbenchView.tsx.tpl +16 -0
  109. package/templates/app/module/src/workbench/index.ts.tpl +2 -0
  110. package/templates/app/package-scripts/package.json +1 -1
  111. package/templates/app/package-scripts-lite/package.json +1 -1
  112. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +76 -70
  113. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +23 -13
  114. package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +25 -0
  115. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -26
  116. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +1 -0
  117. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +2 -1
  118. package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +57 -14
  119. package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +11 -0
  120. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.handler.ts +23 -0
  121. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.query.ts +12 -0
  122. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.command.ts +11 -0
  123. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.handler.ts +22 -0
  124. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.command.ts +12 -0
  125. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.handler.ts +23 -0
  126. package/templates/be/auth-lite/src/features/api/auth/application/login.command.ts +12 -0
  127. package/templates/be/auth-lite/src/features/api/auth/application/login.handler.ts +23 -0
  128. package/templates/be/auth-lite/src/features/api/auth/application/logout.command.ts +11 -0
  129. package/templates/be/auth-lite/src/features/api/auth/application/logout.handler.ts +22 -0
  130. package/templates/be/auth-lite/src/features/api/auth/application/refresh.command.ts +12 -0
  131. package/templates/be/auth-lite/src/features/api/auth/application/refresh.handler.ts +23 -0
  132. package/templates/be/auth-lite/src/features/api/auth/application/session.command.ts +12 -0
  133. package/templates/be/auth-lite/src/features/api/auth/application/session.handler.ts +23 -0
  134. package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +13 -0
  135. package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +16 -0
  136. package/templates/be/auth-lite/src/features/api/auth/auth.options.ts +3 -0
  137. package/templates/be/auth-lite/src/features/api/auth/index.ts +2 -0
  138. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +13 -0
  139. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module.ts +9 -0
  140. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.options.ts +3 -0
  141. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +90 -0
  142. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/google-start.request.ts +17 -0
  143. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/login.request.ts +16 -0
  144. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +46 -0
  145. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +28 -0
  146. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +145 -0
  147. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +95 -0
  148. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +38 -0
  149. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +54 -0
  150. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts +18 -0
  151. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +34 -0
  152. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-session.entity.ts +30 -0
  153. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +17 -0
  154. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +86 -0
  155. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +45 -0
  156. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +97 -0
  157. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +81 -0
  158. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +78 -0
  159. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +87 -0
  160. package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +44 -0
  161. package/templates/be/inbox/delivery-recorded.event.ts.tpl +2 -2
  162. package/templates/be/inbox/entities.index.ts.tpl +1 -1
  163. package/templates/be/inbox/module-definition.ts.tpl +8 -2
  164. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +10 -2
  165. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +8 -2
  166. package/templates/be/patterns/api/feature.module-definition.ts.tpl +9 -3
  167. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +9 -3
  168. package/templates/be/patterns/cli/group.module-definition.ts.tpl +9 -3
  169. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +8 -3
  170. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +3 -2
  171. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -3
  172. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +8 -3
  173. package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +3 -2
  174. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +7 -6
  175. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +9 -3
  176. package/templates/be/patterns/jobs/job.processor.ts.tpl +1 -1
  177. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +9 -3
  178. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +8 -3
  179. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +3 -2
  180. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +5 -4
  181. package/templates/be/patterns/projections/persistence.connection.ts.tpl +3 -2
  182. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +8 -3
  183. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +3 -2
  184. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -3
  185. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +8 -3
  186. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +9 -3
  187. package/templates/be/patterns/reactors/consumer.ts.tpl +1 -1
  188. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +9 -3
  189. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +9 -3
  190. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +9 -3
  191. package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +9 -3
  192. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +9 -3
  193. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +1 -1
  194. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +1 -1
  195. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +9 -3
  196. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +9 -3
  197. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +9 -3
  198. package/templates/be/platform/graphql/graphql.module-definition.ts +8 -3
  199. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +9 -3
  200. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +9 -3
  201. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +9 -3
  202. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +9 -3
  203. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +9 -3
  204. package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +8 -3
  205. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +8 -3
  206. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +8 -3
  207. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +3 -2
  208. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +2 -1
  209. package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +8 -3
  210. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +8 -3
  211. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +8 -3
  212. package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -0
  213. package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +10 -4
  214. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +8 -4
  215. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +10 -4
  216. package/templates/be/skeleton/src/modules/platform/http-security/persistence/connection.ts +3 -2
  217. package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts +3 -2
  218. package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +10 -4
  219. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +8 -3
  220. package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +8 -3
  221. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +8 -3
  222. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +6 -5
  223. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +3 -2
  224. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +10 -5
  225. package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +34 -0
  226. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -2
  227. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +47 -2
  228. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -0
  229. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +24 -0
  230. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +3 -3
  231. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +3 -1
  232. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +8 -4
  233. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +12 -0
  234. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts +29 -0
  235. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +6 -31
  236. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +18 -9
  237. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +35 -11
  238. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +44 -8
  239. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +40 -18
  240. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +18 -6
  241. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.spec.ts +73 -0
  242. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.ts +26 -0
  243. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +24 -53
  244. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.registration.ts +31 -0
  245. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +1 -1
  246. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +1 -1
  247. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
  248. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
  249. package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts +1 -1
  250. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
  251. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +7 -1
  252. package/templates/be/table/api/feature.module-definition.ts.tpl +9 -3
  253. package/templates/be/table/api/graphql.module-definition.ts.tpl +9 -3
  254. package/templates/be/table/entities.index.ts.tpl +1 -1
  255. package/templates/be/table/module-definition.ts.tpl +8 -3
  256. package/templates/be/test-world/e2e/serves-once-migrated.e2e-spec.ts.tpl +9 -9
  257. package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +4 -3
  258. package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
  259. package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
  260. package/templates/fe/common/app-layout.tsx.partial +1 -1
  261. package/templates/fe/common/site-shell.tsx.partial +3 -3
  262. package/templates/fe/skeleton/apps/__app__/src/components/providers/AppProviders/index.tsx +33 -10
  263. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/index.ts +1 -0
  264. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +2 -1
  265. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignIn.ts +11 -3
  266. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +8 -2
  267. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
  268. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +2 -1
  269. package/templates/fe/skeleton/apps/__app__/src/modules/toast/hooks.ts +1 -1
  270. package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +18 -12
  271. package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
  272. package/templates/fe/skeleton/apps/__app__/src/redux/hooks.ts +3 -2
  273. package/templates/fe/skeleton/apps/__app__/src/redux/index.ts +1 -0
  274. package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +12 -4
  275. package/templates/fe/skeleton/apps/__app__/src/redux/store.ts +8 -3
  276. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +4 -3
  277. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +1 -1
  278. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +2 -1
  279. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +2 -1
  280. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +2 -1
  281. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +2 -1
  282. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -1
  283. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +4 -3
  284. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +2 -2
  285. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +2 -1
  286. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +2 -1
  287. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +2 -1
  288. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +2 -1
  289. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -1
  290. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +20 -2
  291. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +1 -1
  292. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +5 -1
  293. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +4 -7
  294. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/index.tsx +2 -3
  295. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +4 -3
  296. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +4 -7
  297. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx +1 -2
  298. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +2 -2
  299. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +2 -1
  300. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +2 -1
  301. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +2 -1
  302. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +3 -1
  303. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +5 -1
  304. package/templates/fe/table/graphql/useMutateSwr.ts.tpl +2 -1
  305. package/templates/fe/table/graphql/useQuerySwr.ts.tpl +2 -1
  306. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  307. package/templates/fe/transport/graphql/clients/clients/no-auth.ts +1 -1
  308. package/templates/fe/transport/graphql/clients/links/attach-access-token.ts +1 -1
  309. package/templates/fe/transport/graphql/clients/links/error.ts +1 -1
  310. package/templates/fe/transport/graphql/clients/links/http.ts +1 -1
  311. package/templates/fe/transport/graphql/clients/links/locale.ts +1 -1
  312. package/templates/fe/transport/graphql/clients/links/refresh-token.ts +1 -1
  313. package/templates/fe/transport/graphql/clients/links/retry.ts +1 -1
  314. package/templates/fe/transport/graphql/clients/links/timeout.ts +1 -1
  315. package/templates/fe/units/page/index.tsx.tpl +3 -2
  316. package/templates/fe/units/slot/SlotView.tsx.tpl +1 -1
  317. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +0 -16
  318. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +0 -11
  319. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +0 -7
  320. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +0 -6
  321. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +0 -8
  322. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +0 -6
  323. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -186
  324. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +0 -22
  325. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +0 -25
  326. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +0 -104
  327. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +0 -34
  328. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts +0 -28
  329. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +0 -11
  330. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +0 -88
  331. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +0 -69
  332. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +0 -37
  333. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -36
  334. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +0 -63
  335. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/classNames.ts +0 -4
  336. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +0 -30
  337. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +0 -50
  338. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +0 -31
  339. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/classNames.ts +0 -4
@@ -7,7 +7,7 @@
7
7
  # catalog READ this file through scripts/hfs/slots.mjs; none of them hardcodes a path.
8
8
  # Shape: modules/schemas/hfs-slots.schema.yaml. The repository side is hfs.json: modules/schemas/hfs-repo.schema.yaml.
9
9
  schema: starci/hfs-slots@3
10
- version: 3.15.0
10
+ version: 3.16.0
11
11
 
12
12
  versioning:
13
13
  # MAJOR.MINOR.PATCH of this manifest. A repository pins only the MAJOR in hfs.json ("hfs": 3).
@@ -117,6 +117,8 @@ versioning:
117
117
  # liteManagedBy is managed only in lite; its effective lite.path still lists literal files.
118
118
  # the managed file list lives here and nowhere in code. A difference is HFS_MANAGED_FILE_DRIFT (a
119
119
  # back end's tsconfig.json: HFS_TS_STRICT when a flag or an option was added).
120
+ # managedOnly true: the slot only lists files sync renders (needs managedBy); the path is judged by the slot that owns it, so the rendered
121
+ # file keeps the rules and the tier of its folder (a sign-in service is a domain service)
120
122
  # rules rule codes from knowledge/hfs/rules.yaml.
121
123
  # editions the editions (below) the slot exists in; default all of them. A slot outside the declared edition owns
122
124
  # no path - under lite, what full keeps absent is removed, and what lite forbids is a finding.
@@ -250,17 +252,20 @@ ruleParams:
250
252
  "node:https": [platform/http]
251
253
  http: [platform/http]
252
254
  https: [platform/http]
253
- "@aws-sdk/client-s3": [integrations/object-storage] # the ObjectStorage port: one S3 adapter for MinIO, S3 and Spaces (R331)
254
- "@aws-sdk/s3-request-presigner": [integrations/object-storage]
255
- minio: [] # storage goes through the S3 client of integrations/object-storage
255
+ "@aws-sdk/client-s3": [] # the S3 client is a dependency of @starci/object-storage, imported by no app file (R331)
256
+ "@aws-sdk/s3-request-presigner": []
257
+ "@starci/object-storage": [integrations/object-storage] # the ObjectStorage port: one S3 adapter for MinIO, S3 and Spaces (R331)
258
+ minio: [] # storage goes through @starci/object-storage
256
259
  "@supabase": [] # no Supabase client in a back end
257
- "@nestjs/passport": [integrations/passport] # the identity libraries (R328)
258
- passport: [integrations/passport]
259
- passport-google-oauth20: [integrations/passport]
260
- passport-local: [integrations/passport]
261
- "@nestjs/jwt": [integrations/passport]
262
- jose: [integrations/passport]
263
- bcryptjs: [integrations/passport]
260
+ "@nestjs/passport": [] # the identity libraries are @starci/auth's dependencies, never an app's (R328)
261
+ passport: []
262
+ passport-google-oauth20: []
263
+ passport-local: []
264
+ "@nestjs/jwt": []
265
+ jose: []
266
+ bcryptjs: []
267
+ "@starci/auth": [integrations/passport] # the lite sign-in package: its server entry is reached only through integrations/passport (R328)
268
+ "@starci/auth/testing": [integrations/passport] # its test doubles, for the glue's specs
264
269
  bcrypt: []
265
270
  cache-manager: [integrations/cache, integrations/redis]
266
271
  "@nestjs/cache-manager": [integrations/cache, integrations/redis] # the CACHE_MANAGER token: caching goes through the cache capability
@@ -909,7 +914,7 @@ slots:
909
914
  # The db service supplies the database; schedules are platform/jobs ticks (R335); production has no Compose or host cron.
910
915
  lite:
911
916
  requires: [application-stacks.yaml]
912
- allows: [application-stacks.yaml, images/db/Dockerfile, "<env>/README.md", "<env>/environment.json", "<env>/infra/swarm/**", "<env>/infra/metadata.json",
917
+ allows: [application-stacks.yaml, images/db/Dockerfile, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,swarm}/**", "<env>/infra/metadata.json",
913
918
  "<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.{enc.yaml,enc}", "<env>/secrets/.gitkeep", "<env>/seeds/**"]
914
919
  forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
915
920
  rules: [HFS_STACKS_SHAPE, HFS_STACKS_IN_SIDE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY]
@@ -2028,36 +2033,68 @@ slots:
2028
2033
  editions: [lite]
2029
2034
  exemplifiedBy:
2030
2035
  - packages/hfs/templates/be/skeleton-lite/src/modules/integrations/passport/
2031
- # The one owner of the lite identity libraries (ruleParams.be.infraOwners: @nestjs/passport, passport, passport-google-oauth20,
2032
- # passport-local, @nestjs/jwt, jose, bcryptjs): the Google OAuth 2.0 strategy (state and PKCE), the local email and password
2033
- # strategy (bcrypt), the app's own asymmetric access tokens and their JWKS, and the rotating refresh cookie. domain/identity
2034
- # verifies a token through this integration's verifier and builds the principal (contexts/code/be/api-auth.md).
2036
+ # The glue of @starci/auth, the lite sign-in package (Google OAuth with state and PKCE, email and password, EdDSA access tokens with
2037
+ # their JWKS, rotating refresh sessions): the one importer of the package (ruleParams.be.infraOwners), which owns the identity
2038
+ # libraries (@nestjs/passport, passport, passport-google-oauth20, passport-local, @nestjs/jwt, jose, bcryptjs) that no app imports.
2039
+ # It parses the typed options, builds the verifier and names the injectors; domain/identity verifies a token through the kit and
2040
+ # builds the principal (contexts/code/be/api-auth.md).
2035
2041
  path: "src/modules/integrations/passport/"
2036
2042
  presence: required
2037
2043
  tracked: tracked
2038
2044
  tier: integrations
2039
2045
  owner: true
2040
- requires: [index.ts, passport.config.ts, passport.options.ts, passport.decorators.ts, passport.tokens.ts, passport.password.ts, passport.cookie.ts] # Secrets, no defaults (R44); options and named injectors (R83); tokens and JWKS; bcrypt; the refresh cookie
2041
- allows: [index.ts, passport.config.ts, passport.options.ts, passport.decorators.ts, passport.tokens.ts, passport.password.ts, passport.cookie.ts, strategies/, errors/]
2042
- logic: [passport.tokens.ts, passport.password.ts, passport.cookie.ts] # JWT sign and verify over the own JWKS, bcrypt, the refresh cookie: measured at 100 per file
2046
+ requires: [index.ts, passport.config.ts, passport.options.ts, passport.decorators.ts, passport.kit.ts, passport.registration.ts] # Secrets, no defaults (R44); options and named injectors (R83); the kit and the module over @starci/auth
2047
+ allows: [index.ts, passport.config.ts, passport.options.ts, passport.decorators.ts, passport.kit.ts, passport.registration.ts, errors/]
2048
+ logic: [passport.kit.ts] # the verifier mapping over the package: measured at 100 per file
2043
2049
  tests: unit-beside
2044
2050
  rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER, BE_AUTH_PASSPORT_OWNER, BE_AUTH_JWT_OWN_JWKS, BE_AUTH_REFRESH_COOKIE]
2045
2051
  coverage: required
2046
2052
  since: 3.9.0
2053
+ - id: be.identity.sign-in
2054
+ since: 3.16.0
2055
+ profiles: [be]
2056
+ editions: [lite]
2057
+ editionWhy: "lite signs people in with the package @starci/auth, so its sign-in glue is rendered, not written (owner ruling auth-package); full uses its identity provider."
2058
+ # The identity capability's sign-in glue over @starci/auth: the entities of the session, attempt and user tables, the stores the package asks for (sessions, accounts, rate counter), the authenticator and the service the doors call, with their specs. `starci app sync` renders them; a hand edit is drift.
2059
+ path: "{src/modules/domain/identity/account-directory.service.spec.ts,src/modules/domain/identity/account-directory.service.ts,src/modules/domain/identity/authenticator.service.spec.ts,src/modules/domain/identity/authenticator.service.ts,src/modules/domain/identity/persistence/entities/auth-identity.entity.ts,src/modules/domain/identity/persistence/entities/auth-user.entity.ts,src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts,src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts,src/modules/domain/identity/persistence/entities/identity-session.entity.ts,src/modules/domain/identity/persistence/entities/index.ts,src/modules/domain/identity/rate-counter.service.spec.ts,src/modules/domain/identity/rate-counter.service.ts,src/modules/domain/identity/session-store.service.spec.ts,src/modules/domain/identity/session-store.service.ts,src/modules/domain/identity/sign-in.service.spec.ts,src/modules/domain/identity/sign-in.service.ts,src/tests/fixtures/builders/identity.builder.ts}"
2060
+ presence: optional
2061
+ tracked: tracked
2062
+ tier: inherit
2063
+ tests: unit-beside
2064
+ coverage: none
2065
+ managedBy: auth-lite
2066
+ managedOnly: true # lists files sync renders; the slot owning the path judges them
2067
+ rules: [HFS_MANAGED_FILE_DRIFT]
2068
+ - id: be.feature.auth
2069
+ since: 3.16.0
2070
+ profiles: [be]
2071
+ editions: [lite]
2072
+ editionWhy: "lite signs people in with the package @starci/auth, so its sign-in glue is rendered, not written (owner ruling auth-package); full uses its identity provider."
2073
+ # The auth feature: the REST doors under /auth (login, refresh, session, logout, Google start and callback, jwks) over IdentitySignInService, one command or query and handler per door.
2074
+ path: "{src/features/api/auth/application/auth.contracts.ts,src/features/api/auth/application/get-jwks.handler.ts,src/features/api/auth/application/get-jwks.query.ts,src/features/api/auth/application/google-begin.command.ts,src/features/api/auth/application/google-begin.handler.ts,src/features/api/auth/application/google-complete.command.ts,src/features/api/auth/application/google-complete.handler.ts,src/features/api/auth/application/login.command.ts,src/features/api/auth/application/login.handler.ts,src/features/api/auth/application/logout.command.ts,src/features/api/auth/application/logout.handler.ts,src/features/api/auth/application/refresh.command.ts,src/features/api/auth/application/refresh.handler.ts,src/features/api/auth/application/session.command.ts,src/features/api/auth/application/session.handler.ts,src/features/api/auth/auth.module-definition.ts,src/features/api/auth/auth.module.ts,src/features/api/auth/auth.options.ts,src/features/api/auth/index.ts,src/features/api/auth/transport/http/auth-http.module-definition.ts,src/features/api/auth/transport/http/auth-http.module.ts,src/features/api/auth/transport/http/auth-http.options.ts,src/features/api/auth/transport/http/auth.controller.ts,src/features/api/auth/transport/http/dto/google-start.request.ts,src/features/api/auth/transport/http/dto/login.request.ts}"
2075
+ presence: optional
2076
+ tracked: tracked
2077
+ tier: inherit
2078
+ tests: none
2079
+ coverage: none
2080
+ managedBy: auth-lite
2081
+ managedOnly: true # lists files sync renders; the slot owning the path judges them
2082
+ rules: [HFS_MANAGED_FILE_DRIFT]
2047
2083
  - id: be.integrations.object-storage
2048
2084
  profiles: [be]
2049
2085
  exemplifiedBy:
2050
2086
  - packages/hfs/templates/be/skeleton-lite/src/modules/integrations/object-storage/
2051
- # The one owner of the S3 client (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) over S3-compatible storage (MinIO in
2052
- # lite): the ObjectStorage port with presigned PUT and GET; no other module imports an S3 SDK or minio.
2087
+ # The glue of @starci/object-storage, the owner of the S3 client (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) over
2088
+ # S3-compatible storage (MinIO in lite): this folder keeps the bucket, parses the settings and registers the module; no app
2089
+ # file imports an S3 SDK or minio, and no other module imports the package.
2053
2090
  path: "src/modules/integrations/object-storage/"
2054
2091
  presence: optional
2055
2092
  litePresence: required
2056
2093
  tracked: tracked
2057
2094
  tier: integrations
2058
2095
  owner: true
2059
- requires: [index.ts, object-storage.config.ts, object-storage.options.ts, object-storage.decorators.ts, object-storage.client.ts]
2060
- allows: [index.ts, object-storage.module.ts, object-storage.module-definition.ts, object-storage.config.ts, object-storage.options.ts, object-storage.decorators.ts, object-storage.client.ts, errors/]
2096
+ requires: [index.ts, object-storage.config.ts, object-storage.options.ts, object-storage.decorators.ts, object-storage.module-definition.ts, object-storage.module.ts]
2097
+ allows: [index.ts, object-storage.config.ts, object-storage.options.ts, object-storage.decorators.ts, object-storage.module-definition.ts, object-storage.module.ts, errors/]
2061
2098
  tests: unit-beside
2062
2099
  rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER, BE_STORAGE_OWNER]
2063
2100
  coverage: required
@@ -2591,7 +2628,7 @@ slots:
2591
2628
  profiles: [fe]
2592
2629
  # The identity-provider client (fe-stack auth, FE-AUTH): the session, the access token the Apollo link attaches and the
2593
2630
  # sign-in/sign-out calls. It talks only to the app's own back-end auth doors and holds no credential: the refresh token lives in
2594
- # the back end's httpOnly cookie and the access token in memory (ruling:lite-self-hosted-postgres-passport-minio).
2631
+ # the back end's httpOnly cookie and the access token in the memory of the one @starci/auth client (ruling:lite-self-hosted-postgres-passport-minio).
2595
2632
  path: "apps/<app>/src/modules/auth/"
2596
2633
  presence: optional
2597
2634
  tracked: tracked
@@ -2601,6 +2638,20 @@ slots:
2601
2638
  tests: none
2602
2639
  since: 3.0.0
2603
2640
  rules: [FE_STACK_FORBIDDEN]
2641
+ - id: fe.modules.auth.client
2642
+ since: 3.16.0
2643
+ profiles: [fe]
2644
+ editions: [lite]
2645
+ editionWhy: "lite signs people in with the package @starci/auth, so its auth client is rendered, not written (owner ruling auth-package); full wires its identity provider."
2646
+ # The AuthClient every edition shares, implemented by one @starci/auth/client instance over the app's own back-end auth doors.
2647
+ path: "apps/web/src/modules/auth/client.ts"
2648
+ presence: optional
2649
+ tracked: tracked
2650
+ tier: inherit
2651
+ tests: none
2652
+ managedBy: auth-lite
2653
+ managedOnly: true # lists files sync renders; the slot owning the path judges them
2654
+ rules: [HFS_MANAGED_FILE_DRIFT]
2604
2655
  - id: fe.modules.toast
2605
2656
  profiles: [fe]
2606
2657
  # Mutation feedback (fe-stack toast, FE-TOAST): the HeroUI toast wrapper, showGraphQLToast over a GraphQLResponse and the
@@ -5347,6 +5347,36 @@ SEALED_FILE_IN_WORK:
5347
5347
 
5348
5348
  # --- HFS rule catalog (knowledge/hfs/rules.yaml): one code per rule, from R01 up ---
5349
5349
 
5350
+ STACKS_LOCAL_CONFIG_INVALID:
5351
+ title: "The local block of a docker-compose environment is not coherent"
5352
+ title_vi: "Khối local của môi trường docker-compose không nhất quán"
5353
+ meaning_vi: "environments.<env>.local thiếu hoặc có trường lạ, cổng trùng, dịch vụ không có trong tệp compose, vai trò cơ sở dữ liệu dùng chung, hoặc thăm dò không nằm trên loopback."
5354
+ causes_vi:
5355
+ - "Vi phạm R372: khối local được kiểm bằng đúng normalizeLocal mà các động từ starci app stack up, down, doctor dùng."
5356
+ nextStep_vi: "Sửa đúng trường nêu trong thông báo (environments.<env>.local.<đường dẫn>), rồi chạy lại starci app check."
5357
+ owner: op-retry
5358
+ kind: check-finding
5359
+
5360
+ STACKS_DEV_UNDECLARED:
5361
+ title: "A lite app declares no runnable dev environment"
5362
+ title_vi: "Ứng dụng lite chưa khai báo môi trường dev chạy được"
5363
+ meaning_vi: "Không có environment nào có runtime docker-compose và khối local, nên starci app stack up --env dev không có gì để chạy."
5364
+ causes_vi:
5365
+ - "Vi phạm R372: môi trường dev vẫn là Swarm hoặc chưa được khai báo."
5366
+ nextStep_vi: "Khai báo environments.dev với runtime docker-compose, composeFiles và khối local; runbook của nó nêu starci app stack up --env dev."
5367
+ owner: op-retry
5368
+ kind: check-finding
5369
+
5370
+ STACKS_RUNBOOK_DOCKER_COMMAND:
5371
+ title: "A dev runbook carries a docker command or omits the one-command start"
5372
+ title_vi: "Runbook dev chứa lệnh docker hoặc không nêu lệnh khởi động duy nhất"
5373
+ meaning_vi: "README của môi trường docker-compose có dòng docker build, run, compose hay stack deploy, hoặc không nêu starci app stack up --env <env>."
5374
+ causes_vi:
5375
+ - "Vi phạm R373 và quy tắc cứng 8: Docker do harness chạy, tác tử không chạy tay."
5376
+ nextStep_vi: "Thay các lệnh docker bằng starci app stack up --env <env> --execute và các động từ down, status, logs của nó."
5377
+ owner: op-retry
5378
+ kind: check-finding
5379
+
5350
5380
  STACKS_DEPLOY_CONFIG_INVALID:
5351
5381
  title: "A deploy block of application-stacks.yaml is not coherent"
5352
5382
  title_vi: "Khối deploy trong application-stacks.yaml không nhất quán"
@@ -5674,3 +5704,35 @@ BE_JOBS_SCHEDULER_OWNER:
5674
5704
  nextStep_vi: "Khai tick vào JOB_TICKS của platform/jobs/job-ticks.contracts.ts (name, cron, timeZone UTC, queue, payload) và xoá lịch tự viết; giữ advisoryXactLock và job_tick_claim trong JobTickService."
5675
5705
  owner: op-retry
5676
5706
  kind: check-finding
5707
+
5708
+ STACKS_CODE_BIND_MOUNT:
5709
+ title: "A stack or compose file bind-mounts code into a service container"
5710
+ title_vi: "Tệp stack hoặc compose gắn (bind-mount) mã vào container của dịch vụ"
5711
+ meaning_vi: "Một dịch vụ gắn thư mục hoặc tệp mã, plugin, script hay đường dẫn ghi được của repository từ máy chủ vào container."
5712
+ causes_vi:
5713
+ - "Vi phạm R374: mã của dịch vụ phải nằm trong image dựng từ .starcistacks/images/<tên>/, không gắn lúc chạy."
5714
+ nextStep_vi: "Đưa mã vào Dockerfile của .starcistacks/images/<tên>/ (FROM image đã ghim digest, hoặc image fork của harness) và bỏ bind-mount; chỉ giữ volume dữ liệu, secret và tệp cấu hình thuần dữ liệu chỉ đọc."
5715
+
5716
+ STACKS_IMAGE_BASE_UNPINNED:
5717
+ title: "A service image folder has a FROM that is not digest-pinned"
5718
+ title_vi: "Thư mục image của dịch vụ có FROM chưa ghim digest"
5719
+ meaning_vi: "Dockerfile trong .starcistacks/images/<tên>/ dùng FROM theo tag, không theo name@sha256:..."
5720
+ causes_vi:
5721
+ - "Vi phạm R374: mọi image nền phải ghim digest."
5722
+ nextStep_vi: "Ghim FROM bằng name@sha256:<digest>."
5723
+
5724
+ STACKS_IMAGE_BASE_NOT_FORK:
5725
+ title: "A service image folder does not build on the harness fork image"
5726
+ title_vi: "Thư mục image của dịch vụ không dựng trên image fork của harness"
5727
+ meaning_vi: "Harness sở hữu image fork StarCi cho dịch vụ này (images/sources.json) nhưng giai đoạn cuối của Dockerfile không FROM image đó."
5728
+ causes_vi:
5729
+ - "Vi phạm R374: dịch vụ tùy biến phải dựng trên image fork do harness ghim."
5730
+ nextStep_vi: "Đặt FROM <repository fork>@sha256:<digest> ở giai đoạn cuối."
5731
+
5732
+ STACKS_IMAGE_ENV_DIVERGES:
5733
+ title: "A service with an image folder runs a third-party image in an environment"
5734
+ title_vi: "Dịch vụ có thư mục image chạy image bên thứ ba ở một môi trường"
5735
+ meaning_vi: "Dev và production phải cùng chạy một image dựng từ .starcistacks/images/<tên>/."
5736
+ causes_vi:
5737
+ - "Vi phạm R374: một môi trường dùng image upstream hoặc image nước ngoài thay vì image dựng từ thư mục."
5738
+ nextStep_vi: "Dùng build.context trỏ tới thư mục image (compose) hoặc tham chiếu image của dự án (stack)."
@@ -510,6 +510,12 @@ $defs:
510
510
  runbook: {type: string, minLength: 1}
511
511
  secrets: {type: array, items: {$ref: '#/$defs/secret'}}
512
512
  deploy: {$ref: '#/$defs/deploy'}
513
+ local:
514
+ description: >-
515
+ The dev declaration `starci app stack up|down|doctor|prepare|status|logs|verify` runs for a docker-compose environment (PROJECT-STACKS-6):
516
+ ports, compose services, the environment's own database role, dev-only custody to generate once, setup and seed commands, native app
517
+ processes, readiness and verification probes. Names, ports and argv lists only, never a value. The authority is scripts/hfs/stack-local-config.mjs; rule R340 judges it.
518
+ type: object
513
519
  ingress:
514
520
  description: "The one selected ingress component and the hosts it routes, or external with the stack name when the ingress is a shared stack outside this one (then no component; rule R275 judges which of the two is declared)."
515
521
  type: object
@@ -1,57 +1,17 @@
1
- // identity.mjs - the back-end identity machine. Under lite the one token verifier is be.integrations.passport: jose's jwtVerify
2
- // there checks the app's OWN key set (createLocalJWKSet over the keys it signs with, never createRemoteJWKSet), pins issuer,
3
- // audience and algorithms, and no file decodes a token unverified or reads authorization claims from a request body (R329).
4
- // In both editions every jwtVerify call holds the JWT policy (R260, jwtPolicyProblems).
1
+ // identity.mjs - the back-end identity machine. Under lite the one token verifier is @starci/auth, composed by be.integrations.passport:
2
+ // no app file calls jose's jwtVerify, decodes a token unverified or reads authorization claims from a request body (R329), and the
3
+ // passport integration imports @starci/auth/server. In both editions every jwtVerify call holds the JWT policy (R260, jwtPolicyProblems).
5
4
  import path from 'node:path';
6
5
  import { machineKit } from './machine-ast.mjs';
7
- import { fromConfig, importedFrom, jwtPolicyProblems, reportAt, unwrap } from './jwt-ast.mjs';
6
+ import { jwtPolicyProblems, reportAt, unwrap } from './jwt-ast.mjs';
8
7
  import { accessName } from '../../lib/ts-ast.mjs';
9
8
  const PASSPORT_SLOT = 'be.integrations.passport';
9
+ const PACKAGE_ENTRY = '@starci/auth/server';
10
10
  const BE_JWT_VERIFIED = 'BE_AUTH_JWT_OWN_JWKS';
11
11
  /** The rule ids the back-end identity machine reports. */
12
12
  export const IDENTITY_RULE_IDS = Object.freeze(['BE_AUTH_JWT_OWN_JWKS', 'BE_JWT_ALG_PINNED']);
13
13
  const JWT_MODULES = new Set(['jose', 'jsonwebtoken', 'jwt-decode']);
14
14
  const CLAIM_NAMES = new Set(['role', 'roles', 'membership', 'memberships']);
15
- const objectLiteral = (kit, checker, node) => {
16
- const current = unwrap(kit.ts, node);
17
- if (!current)
18
- return null;
19
- if (kit.ts.isObjectLiteralExpression(current))
20
- return current;
21
- if (!kit.ts.isIdentifier(current))
22
- return null;
23
- for (const declaration of kit.declarationsOf(checker, current)) {
24
- if (kit.ts.isVariableDeclaration(declaration) && declaration.initializer) {
25
- const value = unwrap(kit.ts, declaration.initializer);
26
- if (kit.ts.isObjectLiteralExpression(value))
27
- return value;
28
- }
29
- }
30
- return null;
31
- };
32
- const staticStrings = (kit, checker, node) => {
33
- const current = unwrap(kit.ts, node);
34
- if (kit.ts.isStringLiteralLike(current))
35
- return [current.text];
36
- if (kit.ts.isArrayLiteralExpression(current)) {
37
- const literals = current.elements.filter((element) => kit.ts.isStringLiteralLike(element));
38
- if (literals.length === current.elements.length)
39
- return literals.map((element) => element.text);
40
- }
41
- const value = kit.stringValue(checker, current);
42
- return value === null ? null : [value];
43
- };
44
- const jwksArgument = (kit, checker, node) => {
45
- const current = unwrap(kit.ts, node);
46
- if (!current)
47
- return false;
48
- if (kit.ts.isCallExpression(current))
49
- return importedFrom(kit, checker, current.expression, new Set(['jose']), new Set(['createLocalJWKSet']));
50
- if (!kit.ts.isIdentifier(current))
51
- return false;
52
- return kit.declarationsOf(checker, current).some((declaration) => kit.ts.isVariableDeclaration(declaration) && declaration.initializer
53
- && jwksArgument(kit, checker, declaration.initializer));
54
- };
55
15
  const importedCall = (kit, checker, call) => {
56
16
  const direct = kit.importBinding(checker, call.expression);
57
17
  if (direct?.module === 'jwt-decode' && direct.name === 'default')
@@ -84,41 +44,16 @@ const fromRequestBody = (kit, checker, node, seen = new Set()) => {
84
44
  return kit.declarationsOf(checker, current).some(declaration => kit.ts.isVariableDeclaration(declaration) && declaration.initializer
85
45
  && fromRequestBody(kit, checker, declaration.initializer, seen));
86
46
  };
87
- function verifyJwtCall(kit, checker, file, node, counters, violations) {
88
- counters.verifyCalls += 1;
89
- if (file.slot !== PASSPORT_SLOT) {
90
- reportAt(violations, kit, BE_JWT_VERIFIED, file, node, `jwtVerify is called outside ${PASSPORT_SLOT}; the integration is the one token verifier.`);
91
- return true;
92
- }
93
- const options = objectLiteral(kit, checker, node.arguments[2]);
94
- const property = (name) => options ? kit.propertyOf(options, name) : null;
95
- const values = (name) => {
96
- const entry = property(name);
97
- return entry ? staticStrings(kit, checker, kit.valueOfProperty(entry)) : null;
98
- };
99
- const issuer = property('issuer');
100
- const audience = values('audience');
101
- const algorithms = values('algorithms');
102
- // The audience is pinned by a literal or, as R260 accepts, by typed configuration (the skeleton's TokenOptions.audience).
103
- const audienceEntry = property('audience');
104
- const pinnedAudience = (audience !== null && audience.length > 0)
105
- || (audienceEntry != null && fromConfig(kit.ts, checker, kit.valueOfProperty(audienceEntry)));
106
- const good = jwksArgument(kit, checker, node.arguments[1]) && issuer != null && pinnedAudience
107
- && algorithms !== null && algorithms.length > 0;
108
- if (!good) {
109
- reportAt(violations, kit, BE_JWT_VERIFIED, file, node, 'jwtVerify must check the app\'s own key set (createLocalJWKSet(...) over the keys integrations/passport signs with) and pin issuer, audience and a non-empty algorithms list; jwtVerify then enforces signature and exp.');
110
- }
111
- else
112
- counters.verifiedCalls += 1;
113
- return false;
114
- }
115
47
  function inspectJwtCall(kit, checker, file, node, counters, violations) {
116
48
  const binding = importedCall(kit, checker, node);
117
49
  if (binding && JWT_MODULES.has(binding.module) && ['decode', 'decodeJwt', 'jwtDecode'].includes(binding.name)) {
118
50
  reportAt(violations, kit, BE_JWT_VERIFIED, file, node, `${binding.module}.${binding.name} decodes a token without proving its signature; verify it through integrations/passport.`, { module: binding.module, method: binding.name });
119
51
  }
120
- if (binding?.module === 'jose' && binding.name === 'jwtVerify')
121
- return verifyJwtCall(kit, checker, file, node, counters, violations);
52
+ if (binding?.module === 'jose' && binding.name === 'jwtVerify') {
53
+ counters.verifyCalls += 1;
54
+ reportAt(violations, kit, BE_JWT_VERIFIED, file, node, 'jwtVerify is called in the app; @starci/auth verifies the own access tokens, reached through ' + PASSPORT_SLOT + '.');
55
+ return true;
56
+ }
122
57
  return false;
123
58
  }
124
59
  function inspectRequestClaims(kit, file, node, checker, violations) {
@@ -146,18 +81,18 @@ function checkOwnJwks(input) {
146
81
  const violations = [];
147
82
  const integrations = [...input.graph.files.values()].filter((file) => file.slot === PASSPORT_SLOT);
148
83
  if (!integrations.length)
149
- return { violations, coverage: { status: 'not-applicable', integrationFiles: 0, verifiedCalls: 0 } };
150
- const counters = { verifyCalls: 0, verifiedCalls: 0 };
84
+ return { violations, coverage: { status: 'not-applicable', integrationFiles: 0, verifyCalls: 0 } };
85
+ const counters = { verifyCalls: 0 };
151
86
  for (const file of input.graph.files.values()) {
152
87
  const checker = kit.checkerOf(file.sourceFile);
153
88
  kit.walk(file.sourceFile, (node) => inspectBackendNode(kit, checker, file, node, counters, violations));
154
89
  }
155
- if (counters.verifyCalls === 0) {
156
- const anchor = integrations.find((file) => path.posix.basename(file.rel).endsWith('.tokens.ts')) ?? integrations[0];
157
- if (anchor)
158
- reportAt(violations, kit, BE_JWT_VERIFIED, anchor, anchor.sourceFile, `${PASSPORT_SLOT} has no jose.jwtVerify call backed by createLocalJWKSet over the app's own keys.`);
159
- }
160
- return { violations, coverage: { status: 'checked', integrationFiles: integrations.length, verifyCalls: counters.verifyCalls, verifiedCalls: counters.verifiedCalls } };
90
+ const composes = integrations.some((file) => file.sourceFile.statements.some((statement) => kit.ts.isImportDeclaration(statement)
91
+ && kit.ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === PACKAGE_ENTRY));
92
+ const anchor = integrations.find((file) => path.posix.basename(file.rel) === 'index.ts') ?? integrations[0];
93
+ if (!composes && anchor)
94
+ reportAt(violations, kit, BE_JWT_VERIFIED, anchor, anchor.sourceFile, PASSPORT_SLOT + ' does not import ' + PACKAGE_ENTRY + ', the one verifier of the own access tokens.');
95
+ return { violations, coverage: { status: 'checked', integrationFiles: integrations.length, verifyCalls: counters.verifyCalls, composesPackage: composes } };
161
96
  }
162
97
  /** R260 BE_JWT_ALG_PINNED: every jose jwtVerify call pins a literal secure algorithm list and a configured issuer, and binds the principal by a configured audience or the authenticated role. */
163
98
  export function checkJwtPolicy(input) {
@@ -18,9 +18,10 @@ interface WorldViolation {
18
18
  * its own slot, be.tests.world.kit) plus files at its root whose role suffix is in ruleParams.be.suffixes
19
19
  * (`stripe.client.ts`, `checkout.contracts.ts`, `test-world.config.ts`, ...). Every tracked file below the world root is
20
20
  * matched by the slot's own entries through `allowsFile`.
21
- * 2. The declaration. `test-world.config.ts` declares the world in ONE form, the named export R89 allows:
22
- * `export const { useTestWorld, useSandbox } = defineTestWorld({ ... })` (an exported `const` initialized by a
23
- * `defineTestWorld(<object literal>)` call). A config without it (a default export, or a declaration the machine cannot
21
+ * 2. The declaration. `test-world.config.ts` declares the world in a named form R89 allows: the destructured export
22
+ * `export const { useTestWorld, useSandbox } = defineTestWorld({ ... })`, or the typed form
23
+ * `const world: TestWorldApi = defineTestWorld({ ... })` whose `useTestWorld` and `useSandbox` are exported consts typed from
24
+ * `TestWorldApi` (a `const` initialized by a `defineTestWorld(<object literal>)` call). A config without it (a default export, or a declaration the machine cannot
24
25
  * read) is refused: the world it declares could not be judged.
25
26
  * 3. Fakes against the stack (owner refinement 2026-09-30). Every service the stack declares runs real in the world; a fake
26
27
  * (a `fakes/<provider>/` folder, or a `fakes` entry of the declaration such as `smtp: smtpFake()`) is a network-edge fake
@@ -15,9 +15,10 @@ import { byCodeUnit } from '../../lib/list.mjs';
15
15
  * its own slot, be.tests.world.kit) plus files at its root whose role suffix is in ruleParams.be.suffixes
16
16
  * (`stripe.client.ts`, `checkout.contracts.ts`, `test-world.config.ts`, ...). Every tracked file below the world root is
17
17
  * matched by the slot's own entries through `allowsFile`.
18
- * 2. The declaration. `test-world.config.ts` declares the world in ONE form, the named export R89 allows:
19
- * `export const { useTestWorld, useSandbox } = defineTestWorld({ ... })` (an exported `const` initialized by a
20
- * `defineTestWorld(<object literal>)` call). A config without it (a default export, or a declaration the machine cannot
18
+ * 2. The declaration. `test-world.config.ts` declares the world in a named form R89 allows: the destructured export
19
+ * `export const { useTestWorld, useSandbox } = defineTestWorld({ ... })`, or the typed form
20
+ * `const world: TestWorldApi = defineTestWorld({ ... })` whose `useTestWorld` and `useSandbox` are exported consts typed from
21
+ * `TestWorldApi` (a `const` initialized by a `defineTestWorld(<object literal>)` call). A config without it (a default export, or a declaration the machine cannot
21
22
  * read) is refused: the world it declares could not be judged.
22
23
  * 3. Fakes against the stack (owner refinement 2026-09-30). Every service the stack declares runs real in the world; a fake
23
24
  * (a `fakes/<provider>/` folder, or a `fakes` entry of the declaration such as `smtp: smtpFake()`) is a network-edge fake
@@ -38,6 +39,7 @@ const FAKES_DIRECTORY = 'fakes/';
38
39
  const CONFIG_FILE = 'test-world.config.ts';
39
40
  const DEFINE = 'defineTestWorld';
40
41
  const NAMED_FORM = `export const { useTestWorld, useSandbox } = ${DEFINE}({ ... })`;
42
+ const TYPED_FORM = `const world: TestWorldApi = ${DEFINE}({ ... })` + ' with export const useTestWorld / useSandbox typed from it';
41
43
  const defaultEnvironment = DEFAULT_ENVIRONMENT;
42
44
  const KEBAB = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
43
45
  const unwrap = (ts, node) => unwrapEach(ts, node, [ts.isParenthesizedExpression, ts.isAsExpression, ts.isSatisfiesExpression]);
@@ -52,21 +54,38 @@ function defineLiteral(ts, expression) {
52
54
  const literal = first ? unwrap(ts, first) : null;
53
55
  return literal && ts.isObjectLiteralExpression(literal) ? literal : null;
54
56
  }
55
- /** The declaration of the world: the object literal of an exported `const` initialized by `defineTestWorld({ ... })`, or null. */
57
+ /**
58
+ * The declaration of the world: the object literal of a top-level `const` initialized by `defineTestWorld({ ... })`, or null. The config
59
+ * exports `useTestWorld` and `useSandbox` either by destructuring that call (the original form) or, typed, from the world the call
60
+ * built: `const world: TestWorldApi = defineTestWorld({ ... })` with `export const useTestWorld: TestWorldApi["useTestWorld"] = world.useTestWorld`.
61
+ * Both are read; a world no exported name reaches is refused by the caller.
62
+ */
56
63
  function configLiteral(ts, sourceFile) {
57
64
  for (const statement of sourceFile.statements) {
58
- if (!ts.isVariableStatement(statement) || !isExported(ts, statement))
65
+ if (!ts.isVariableStatement(statement))
59
66
  continue;
60
67
  if (!hasAnyFlag(statement.declarationList.flags, ts.NodeFlags.Const))
61
68
  continue;
62
69
  for (const declaration of statement.declarationList.declarations) {
63
70
  const literal = declaration.initializer ? defineLiteral(ts, declaration.initializer) : null;
64
- if (literal)
71
+ if (literal && (isExported(ts, statement) || exportsWorld(ts, sourceFile)))
65
72
  return literal;
66
73
  }
67
74
  }
68
75
  return null;
69
76
  }
77
+ /** Whether the file exports both `useTestWorld` and `useSandbox` as consts (the typed form re-exports them from the built world). */
78
+ function exportsWorld(ts, sourceFile) {
79
+ const exported = new Set();
80
+ for (const statement of sourceFile.statements) {
81
+ if (!ts.isVariableStatement(statement) || !isExported(ts, statement))
82
+ continue;
83
+ for (const declaration of statement.declarationList.declarations)
84
+ if (ts.isIdentifier(declaration.name))
85
+ exported.add(declaration.name.text);
86
+ }
87
+ return exported.has('useTestWorld') && exported.has('useSandbox');
88
+ }
70
89
  /** `stack` as the environment it names: the last segment of `".starcistacks/<env>"`; null when absent or not a literal. */
71
90
  function environmentOf(ts, literal) {
72
91
  const node = literal ? propertyOf(ts, literal, 'stack') : null;
@@ -152,7 +171,7 @@ function worldDeclaration(graph, ts, worldRoot, violations) {
152
171
  if (configFile && !literal) {
153
172
  violations.push({
154
173
  ruleId: RULE, path: configPath, line: 1, column: 1, slot: WORLD_SLOT,
155
- message: `${configPath} declares no world the machine can read. Declare it in the one named form \`${NAMED_FORM}\` (an exported const of a ${DEFINE} call with an object literal, imported from @starci/test-world); a default export is refused by R89 and is not read.`,
174
+ message: `${configPath} declares no world the machine can read. Declare it in the named form \`${NAMED_FORM}\` or the typed form \`${TYPED_FORM}\` (a const of a ${DEFINE} call with an object literal, imported from @starci/test-world); a default export is refused by R89 and is not read.`,
156
175
  });
157
176
  }
158
177
  return { configPath, configFile, literal };
@@ -15,8 +15,7 @@ import type { ArchitectureViolation, CheckInput } from './hfs-graph.mjs';
15
15
  * - no file outside the client folder and the stream reader folder (`fe.transport.stream`, `modules/api/sse/clients/`) references the global `fetch`: a call, `globalThis.fetch`, `window.fetch`, or a
16
16
  * reference passed as a value (`useSWR(key, fetch)`, `const send = fetch`), all of which a spelling-based rule misses; with no
17
17
  * client at all every such reference is a finding that says the repository has no client;
18
- * - axios is imported only by the api owner (modules/api, the api package) for its rest calls and by the auth door
19
- * (modules/auth/auth-door.ts) for the app's own auth doors; every other HTTP library is a
18
+ * - axios is imported only by the api owner (modules/api, the api package) for its rest calls; every other HTTP library is a
20
19
  * second transport wherever it is imported, by static import, `import()` or `require()`;
21
20
  * - an `ApolloClient` is constructed only inside the client folder;
22
21
  * - every GraphQL operation (`graphql/queries/query-*.ts`, `graphql/mutations/mutation-*.ts`, `graphql/subscriptions/subscription-*.ts`) imports the client (or the api
@@ -17,8 +17,7 @@ import { propertyValue } from './function-spec-providers.mjs';
17
17
  * - no file outside the client folder and the stream reader folder (`fe.transport.stream`, `modules/api/sse/clients/`) references the global `fetch`: a call, `globalThis.fetch`, `window.fetch`, or a
18
18
  * reference passed as a value (`useSWR(key, fetch)`, `const send = fetch`), all of which a spelling-based rule misses; with no
19
19
  * client at all every such reference is a finding that says the repository has no client;
20
- * - axios is imported only by the api owner (modules/api, the api package) for its rest calls and by the auth door
21
- * (modules/auth/auth-door.ts) for the app's own auth doors; every other HTTP library is a
20
+ * - axios is imported only by the api owner (modules/api, the api package) for its rest calls; every other HTTP library is a
22
21
  * second transport wherever it is imported, by static import, `import()` or `require()`;
23
22
  * - an `ApolloClient` is constructed only inside the client folder;
24
23
  * - every GraphQL operation (`graphql/queries/query-*.ts`, `graphql/mutations/mutation-*.ts`, `graphql/subscriptions/subscription-*.ts`) imports the client (or the api
@@ -105,17 +104,15 @@ function isApolloClientConstruction(ts, kit, checker, node) {
105
104
  const binding = kit.importBinding(checker, node.expression);
106
105
  return Boolean(binding?.name === 'ApolloClient' && binding.module.startsWith('@apollo/client'));
107
106
  }
108
- /** The one auth door of the session issuer's client (`modules/auth/auth-door.ts`, slot fe.modules.auth): it calls the app's own auth doors with axios. */
109
- const isAuthDoor = (file) => file.slot === 'fe.modules.auth' && path.posix.basename(file.rel) === 'auth-door.ts';
110
107
  function inspectLibraryImport({ ts, file, node, transport, report }) {
111
108
  const specifier = moduleSpecifierOf(ts, node);
112
109
  if (!specifier || !ts.isStringLiteralLike(specifier))
113
110
  return;
114
111
  const library = specifier.text.split('/')[0];
115
- if (!HTTP_LIBRARIES.has(library) || (library === REST_LIBRARY && (API_OWNER_SLOTS.has(file.slot) || isAuthDoor(file))))
112
+ if (!HTTP_LIBRARIES.has(library) || (library === REST_LIBRARY && (API_OWNER_SLOTS.has(file.slot))))
116
113
  return;
117
114
  const where = library === REST_LIBRARY
118
- ? 'axios belongs to the api owner (modules/api/rest, the api package) and the auth door (modules/auth/auth-door.ts) only: call their functions'
115
+ ? 'axios belongs to the api owner (modules/api/rest, the api package) only: call their functions'
119
116
  : `the transport is Apollo GraphQL (${transport}) with axios for the rest calls of modules/api/rest`;
120
117
  report(file, node, `${specifier.text} is a second HTTP transport in ${file.rel}; ${where}.`, { library: specifier.text });
121
118
  }
@@ -0,0 +1,58 @@
1
+ import type { TypeScript } from '../lib/ts-ast.mjs';
2
+ /** The part of an ESLint rule context the rule reads (typescript-eslint's context satisfies it). */
3
+ export interface SurfaceContext {
4
+ readonly filename: string;
5
+ readonly sourceCode: {
6
+ readonly text: string;
7
+ getLocFromIndex(index: number): {
8
+ line: number;
9
+ column: number;
10
+ };
11
+ readonly parserServices?: {
12
+ readonly program?: {
13
+ getCompilerOptions(): {
14
+ module?: number;
15
+ };
16
+ } | null;
17
+ } | undefined;
18
+ };
19
+ report(descriptor: {
20
+ loc: {
21
+ start: {
22
+ line: number;
23
+ column: number;
24
+ };
25
+ end: {
26
+ line: number;
27
+ column: number;
28
+ };
29
+ };
30
+ messageId: string;
31
+ data: Record<string, string>;
32
+ }): void;
33
+ }
34
+ /** The rule module the factory returns: a plain ESLint rule with one message. */
35
+ export interface SurfaceRule {
36
+ meta: {
37
+ type: 'problem';
38
+ docs: {
39
+ description: string;
40
+ };
41
+ schema: [];
42
+ messages: {
43
+ inferred: string;
44
+ };
45
+ };
46
+ create(context: SurfaceContext): {
47
+ Program(): void;
48
+ };
49
+ }
50
+ /**
51
+ * The rule. `ts` is the canon's own TypeScript (its peer dependency); the side's `module` option comes from the typed program when the file is linted with one.
52
+ *
53
+ * @param input - `ts` is the compiler.
54
+ * @returns An ESLint rule reporting each export of a TypeScript source whose type is inferred.
55
+ */
56
+ export declare const explicitSurfaceRule: ({ ts }: {
57
+ ts: TypeScript;
58
+ }) => SurfaceRule;