@starci/hfs 5.1.0 → 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 (199) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +10 -1
  3. package/db/postgres.mjs +9 -1
  4. package/package.json +1 -1
  5. package/runtime/knowledge/hfs/canon-pins.yaml +30 -9
  6. package/runtime/knowledge/hfs/fe-stack.yaml +7 -7
  7. package/runtime/knowledge/hfs/fork-images.yaml +7 -0
  8. package/runtime/knowledge/hfs/rules.yaml +55 -7
  9. package/runtime/knowledge/hfs/slots.yaml +75 -24
  10. package/runtime/modules/kernel/failure-codes.yaml +62 -0
  11. package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
  12. package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
  13. package/runtime/scripts/hfs/architecture/transport-owner.d.mts +1 -2
  14. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -6
  15. package/runtime/scripts/hfs/manifest-shape.d.mts +1 -0
  16. package/runtime/scripts/hfs/manifest-shape.mjs +3 -1
  17. package/runtime/scripts/hfs/rules/a15-findings.mjs +5 -0
  18. package/runtime/scripts/hfs/rules/stacks-local.d.mts +7 -0
  19. package/runtime/scripts/hfs/rules/stacks-local.mjs +71 -0
  20. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +6 -1
  21. package/runtime/scripts/hfs/rules/stacks-service-images.d.mts +12 -0
  22. package/runtime/scripts/hfs/rules/stacks-service-images.mjs +120 -0
  23. package/runtime/scripts/hfs/secret-dev-init.d.mts +11 -0
  24. package/runtime/scripts/hfs/secret-dev-init.mjs +79 -0
  25. package/runtime/scripts/hfs/secret.mjs +10 -7
  26. package/runtime/scripts/hfs/slots.mjs +1 -1
  27. package/runtime/scripts/hfs/stack-local-config.d.mts +101 -0
  28. package/runtime/scripts/hfs/stack-local-config.mjs +320 -0
  29. package/runtime/scripts/hfs/stacks-gate.mjs +1 -1
  30. package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
  31. package/runtime/scripts/lib/swarm-stack.mjs +2 -2
  32. package/scaffold/add-module.d.mts +16 -0
  33. package/scaffold/add-module.mjs +232 -0
  34. package/scaffold/add-queue-lite.mjs +49 -0
  35. package/scaffold/add.mjs +5 -0
  36. package/scaffold/app.mjs +7 -7
  37. package/scaffold/module-registry.d.mts +19 -0
  38. package/scaffold/module-registry.mjs +84 -0
  39. package/src/main.mjs +23 -2
  40. package/stack/cli.mjs +6 -1
  41. package/stack/context.d.mts +15 -0
  42. package/stack/context.mjs +1 -0
  43. package/stack/local-cli.d.mts +25 -0
  44. package/stack/local-cli.mjs +89 -0
  45. package/stack/local-config.d.mts +17 -0
  46. package/stack/local-config.mjs +37 -0
  47. package/stack/local-custody.d.mts +24 -0
  48. package/stack/local-custody.mjs +109 -0
  49. package/stack/local-doctor.d.mts +6 -0
  50. package/stack/local-doctor.mjs +49 -0
  51. package/stack/local-down.d.mts +8 -0
  52. package/stack/local-down.mjs +20 -0
  53. package/stack/local-host.d.mts +42 -0
  54. package/stack/local-host.mjs +163 -0
  55. package/stack/local-logs.d.mts +8 -0
  56. package/stack/local-logs.mjs +32 -0
  57. package/stack/local-ports.d.mts +7 -0
  58. package/stack/local-ports.mjs +39 -0
  59. package/stack/local-prepare.d.mts +22 -0
  60. package/stack/local-prepare.mjs +62 -0
  61. package/stack/local-run.d.mts +71 -0
  62. package/stack/local-run.mjs +200 -0
  63. package/stack/local-state.d.mts +35 -0
  64. package/stack/local-state.mjs +29 -0
  65. package/stack/local-status.d.mts +6 -0
  66. package/stack/local-status.mjs +36 -0
  67. package/stack/local-up.d.mts +11 -0
  68. package/stack/local-up.mjs +166 -0
  69. package/stack/local-verify.d.mts +6 -0
  70. package/stack/local-verify.mjs +21 -0
  71. package/sync/index.d.mts +13 -12
  72. package/sync/index.mjs +83 -28
  73. package/templates/app/module/jest.config.js.tpl +8 -0
  74. package/templates/app/module/src/index.ts.tpl +2 -0
  75. package/templates/app/module/src/manifest/declaration.spec.ts.tpl +11 -0
  76. package/templates/app/module/src/manifest/declaration.ts.tpl +13 -0
  77. package/templates/app/module/src/manifest/index.ts.tpl +1 -0
  78. package/templates/app/module/src/public/index.ts.tpl +2 -0
  79. package/templates/app/module/src/template/__name__.template.spec.ts.tpl +13 -0
  80. package/templates/app/module/src/template/__name__.template.ts.tpl +41 -0
  81. package/templates/app/module/src/template/index.ts.tpl +1 -0
  82. package/templates/app/module/src/workbench/components/__pascal__WorkbenchView.tsx.tpl +16 -0
  83. package/templates/app/module/src/workbench/index.ts.tpl +2 -0
  84. package/templates/app/package-scripts/package.json +1 -1
  85. package/templates/app/package-scripts-lite/package.json +1 -1
  86. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +76 -70
  87. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +23 -13
  88. package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +25 -0
  89. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -26
  90. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +1 -0
  91. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +2 -1
  92. package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +57 -14
  93. package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +11 -0
  94. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.handler.ts +23 -0
  95. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.query.ts +12 -0
  96. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.command.ts +11 -0
  97. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.handler.ts +22 -0
  98. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.command.ts +12 -0
  99. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.handler.ts +23 -0
  100. package/templates/be/auth-lite/src/features/api/auth/application/login.command.ts +12 -0
  101. package/templates/be/auth-lite/src/features/api/auth/application/login.handler.ts +23 -0
  102. package/templates/be/auth-lite/src/features/api/auth/application/logout.command.ts +11 -0
  103. package/templates/be/auth-lite/src/features/api/auth/application/logout.handler.ts +22 -0
  104. package/templates/be/auth-lite/src/features/api/auth/application/refresh.command.ts +12 -0
  105. package/templates/be/auth-lite/src/features/api/auth/application/refresh.handler.ts +23 -0
  106. package/templates/be/auth-lite/src/features/api/auth/application/session.command.ts +12 -0
  107. package/templates/be/auth-lite/src/features/api/auth/application/session.handler.ts +23 -0
  108. package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +13 -0
  109. package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +16 -0
  110. package/templates/be/auth-lite/src/features/api/auth/auth.options.ts +3 -0
  111. package/templates/be/auth-lite/src/features/api/auth/index.ts +2 -0
  112. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +13 -0
  113. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module.ts +9 -0
  114. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.options.ts +3 -0
  115. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +90 -0
  116. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/google-start.request.ts +17 -0
  117. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/login.request.ts +16 -0
  118. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +46 -0
  119. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +28 -0
  120. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +145 -0
  121. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +95 -0
  122. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +38 -0
  123. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +54 -0
  124. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts +18 -0
  125. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +34 -0
  126. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-session.entity.ts +30 -0
  127. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +17 -0
  128. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +86 -0
  129. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +45 -0
  130. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +97 -0
  131. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +81 -0
  132. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +78 -0
  133. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +87 -0
  134. package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +44 -0
  135. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +6 -5
  136. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +3 -2
  137. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +10 -5
  138. package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +34 -0
  139. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +22 -1
  140. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +47 -2
  141. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -0
  142. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +24 -0
  143. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +3 -3
  144. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +3 -1
  145. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +8 -4
  146. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +12 -0
  147. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts +29 -0
  148. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +6 -31
  149. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +18 -9
  150. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +35 -11
  151. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +44 -8
  152. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +40 -18
  153. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +18 -6
  154. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.spec.ts +73 -0
  155. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.ts +26 -0
  156. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +24 -53
  157. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.registration.ts +31 -0
  158. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +1 -1
  159. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
  160. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
  161. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +11 -4
  162. package/templates/be/test-world/e2e/serves-once-migrated.e2e-spec.ts.tpl +9 -9
  163. package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +4 -4
  164. package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
  165. package/templates/fe/common/site-shell.tsx.partial +3 -3
  166. package/templates/fe/skeleton/apps/__app__/src/components/providers/AppProviders/index.tsx +33 -10
  167. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/index.ts +1 -0
  168. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
  169. package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +15 -12
  170. package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
  171. package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +1 -1
  172. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -1
  173. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +4 -7
  174. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/index.tsx +2 -3
  175. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +4 -7
  176. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx +1 -2
  177. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +0 -16
  178. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +0 -11
  179. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +0 -7
  180. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +0 -6
  181. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +0 -8
  182. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +0 -6
  183. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -186
  184. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +0 -22
  185. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +0 -25
  186. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +0 -104
  187. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +0 -34
  188. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts +0 -28
  189. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +0 -11
  190. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +0 -88
  191. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +0 -69
  192. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +0 -41
  193. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -40
  194. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +0 -63
  195. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/classNames.ts +0 -4
  196. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +0 -30
  197. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +0 -50
  198. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +0 -31
  199. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/classNames.ts +0 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.2.0 - 2026-11-03
4
+
5
+ - Added: a scaffolded app of each edition type-checks and builds on every side (`scaffold-type-check.spec.mts`: one install per edition, `tsc` of the back end and its specs, the back-end build, the workspace packages, `tsc` and `next build` of each front-end app). The templates follow the Grammar contract: `Link` and `Page` for `SiteShell` and `NotFoundNotice`, a callable `toast`, the session reducers as a type, explicit next-intl navigation generics, optional access to the test world's apps and connections, no unused import in the migrations fixture. The lite dev api process no longer runs `--transpile-only`.
6
+ - Added: the custody kind `ed25519-pem` of `local.custody` (the dev sign-in key is EdDSA); the lite dev environment names `AUTH_WEB_ORIGIN`.
7
+ - Added: `starci app add module <name>` and `starci app remove module <name>`: a module package `packages/module-<name>` of the product's agent SDK (`./template`, `./manifest`, `./workbench`, `./public`), registered in the root `workspaces` and the back-end and front-end dependencies, listed in the managed registry package `packages/modules` that `starci app sync` renders and drift-checks. Slots `app.packages` and `app.module-registry` (manifest 3.17.0). The module packages enter the Sonar sources, the Codecov paths and a `build:modules` / `test:modules` script pair that the typecheck, the lint and the build run first.
8
+ - Added: rule R374 (`STACKS_CODE_BIND_MOUNT`, `STACKS_IMAGE_BASE_UNPINNED`, `STACKS_IMAGE_BASE_NOT_FORK`, `STACKS_IMAGE_ENV_DIVERGES`): a customised service is an image built from `.starcistacks/images/<name>/` on the harness's pinned base, no bind-mounted code, the same image in every environment (PROJECT-STACKS-11).
9
+ - Changed: FE-PACKAGES-2 (a shared package, a module's workbench included, draws only with Grammar) and BE-TEST-13 (a module package's logic is inside the 100 per file scope).
10
+ - Added: the lite starter composes `@starci/auth`: it depends on the package and on none of the identity libraries, `integrations/passport` is the glue the slot lists (`passport.config.ts`, `passport.options.ts`, `passport.decorators.ts`, `passport.kit.ts`, `errors/`), the web app keeps one `@starci/auth/client` instance in `modules/auth/client.ts`, and the baseline migration holds the package's session tables (`identity_sessions`, `identity_refresh_tokens`) byte for byte.
11
+ - Changed: `infraOwners` own `@nestjs/passport`, `passport*`, `@nestjs/jwt`, `jose` and `bcryptjs` by nobody and `@starci/auth` by `integrations/passport` alone (R328); the `own-jwks-principal` machine refuses a `jwtVerify` call anywhere in the app and requires the `@starci/auth/server` import in the passport integration (R329); `transport-owner` no longer admits an auth door in `modules/auth`; the `auth` stack concern names `@starci/auth` and forbids `jose`, `jwt-decode`, `next-auth`, `@auth/core`, `keycloak-js` and `oidc-client-ts`.
12
+ - Changed: the lite stack templates name `AUTH_WEB_ORIGIN` and the Ed25519 signing key; `AUTH_JWT_PRIVATE_KEY` is an EdDSA key and the public key is derived from it.
13
+ - Added: the lite starter signs in with no hand-written auth code. The managed slots `be.identity.sign-in`, `be.feature.auth` and `fe.modules.auth.client` render, from `templates/be/auth-lite` and `templates/fe/auth-lite`, the `/auth` doors (login, refresh, session, logout, Google start and callback, jwks), the entities of the session, attempt, user and identity tables, the session, account and rate stores, the authenticator, `IdentitySignInService`, their specs and the web app's `createAuthClient` client; an edit is `HFS_MANAGED_FILE_DRIFT`. The identity error family gains the sign-in codes, the passport family keeps its `PASSPORT_` codes.
14
+ - Added: the slot field `managedOnly` (a slot that only lists files sync renders; the slot owning the path judges them). Manifest 3.16.0.
15
+ - Changed: the sync and the scaffold count the back-end source files their own targets render, so the CI shard count and the coverage scope are the same before and after the targets are written (one sync leaves the app in sync).
16
+ - Added: the canon pin of `@starci/auth` 0.1.0.
17
+ - Added: `starci app stack prepare|doctor|up|status|logs|down|verify --env <env>` for a docker-compose (dev) environment, driven by the new `environments.<env>.local` block of application-stacks.yaml: ports, compose services and their health, the environment's own database role (the migrations run as it and the app role gets its own password), dev-only custody generators, least grants (`STACK_GRANT_<NAME>`), data directories (`STACK_DATA_DIR`, `${data}`), setup and seed commands (a seed runs after the migration or after readiness), native app processes, readiness and verification probes. `up --execute` is the one command (host table, custody, setup, services, database, migrate with a receipt, seed once, processes, readiness, origins); `prepare`, `up` and `down` are dry runs until `--execute`, `down` never removes a volume, `doctor` prints the owner-run install step per OS for each missing prerequisite and installs nothing. A swarm environment keeps the swarm verbs and refuses the dev-only ones.
18
+ - Added: `starci app secret init --env <dev env> [--execute]` creates the dev-only values `local.custody` declares (hex, base64, password, uuid, rsa-pem, ec-pem), once: a sealed value is kept, a half-present pair is refused, a vps or production environment is refused.
19
+ - Added: `db migrate` runs its client tools inside the database container named by `STARCI_DB_CONTAINER`, so a host without PostgreSQL client tools migrates a dev database.
20
+ - Added: rules R372 (`STACKS_LOCAL_CONFIG_INVALID`, `STACKS_DEV_UNDECLARED`: a coherent local block, and a lite app declares its dev environment) and R373 (`STACKS_RUNBOOK_DOCKER_COMMAND`: the dev runbook names `starci app stack up --env <env>` and carries no docker command).
21
+ - Changed: the lite skeleton's dev environment is `docker-compose` with a local block, a compose file and a runbook of the new verbs (it was a Swarm stack with illustrative digests); slot `app.starcistacks` admits `<env>/infra/compose/**` in lite; `add queue` in lite adds the Redis to the dev compose file and the queue settings to the dev processes.
22
+
3
23
  ## 5.1.0
4
24
 
5
25
  - Added: `starci app codemod explicit-surface` writes the inferred type of every export the classifier counts as open (R368): return types, variable, parameter and property types, from the app's own TypeScript program. It prefers a named type the program exports, writes `ReactElement` for components and Next's `Metadata` for `generateMetadata`, adds merged type-only imports after a directive prologue, compiles the result beside the original and writes a file only when it gains no diagnostic, and reports (with a suggested fix) destructured exports and anonymous, computed, too long, `any`, unnameable and predicate types. Prettier of the app formats what is written.
package/README.md CHANGED
@@ -24,10 +24,19 @@ starci app world <up|down|status> [--stack <dir>] [--services <list>] [--k3d] [-
24
24
  starci app slot <path> [--cwd <dir>] [--json]
25
25
  starci app emit [--cwd <dir>]
26
26
  starci app db drift [--db-url-env <NAME>] [--json] [--cwd <dir>]
27
- starci app secret init | <set|get|unset|rename|remove|materialize|list|verify|recipients|migrate|split|audit|server-key> --env <env> ... [--cwd <dir>]
27
+ starci app stack <prepare|up|down> --env <dev env> [--execute] | <doctor|status|verify> --env <dev env> | logs --env <dev env> --service <name> [--lines <n>]
28
+ starci app secret init [--env <dev env> [--execute]] | <set|get|unset|rename|remove|materialize|list|verify|recipients|migrate|split|audit|server-key> --env <env> ... [--cwd <dir>]
28
29
  starci app ci secret set <NAME> [--from-custody] [--env <env>] [--repo <owner/name>] [--execute]
29
30
  ```
30
31
 
32
+ **The dev environment in one command.** `starci app stack up --env dev --execute` brings a docker-compose environment up from its `local` block of
33
+ `.starcistacks/application-stacks.yaml` (PROJECT-STACKS-6 of `contexts/project/stacks.md`): the host table (`doctor` prints the owner-run install
34
+ step per OS for each missing prerequisite and installs nothing), the dev custody (`secret init --env dev --execute` creates the dev-only values once),
35
+ the setup commands, the compose services and their health, the environment's own database role, the migrations with a receipt (`db migrate` runs its
36
+ client tools in the database container named by `STARCI_DB_CONTAINER`, so the host needs no PostgreSQL client), the seed once, the native app
37
+ processes, readiness and the origins. `prepare`, `up` and `down` are dry runs until `--execute`; `down` never removes a volume. State (process ids,
38
+ logs, receipts) is under the state base (`STARCI_LOCAL_ROOT`), the opened custody under the private secrets directory outside the checkout.
39
+
31
40
  `starci app add` writes every front-end unit, in either edition, already in the shape of the harness's `contexts/code/fe`
32
41
  and `contexts/ui`, from `templates/fe/units`, with its copy in every catalog; nothing that exists is overwritten.
33
42
 
package/db/postgres.mjs CHANGED
@@ -35,7 +35,15 @@ export function dbTarget(ctx, name = DEFAULT_DB_URL_ENV) {
35
35
  const parsed = parseConnection(value);
36
36
  if (!parsed)
37
37
  throw refusal('DB_URL_INVALID', `${name} is not a postgres connection URL`);
38
- return { kind: 'url', how: `connection string from ${name}`, urlEnv: name, url: parsed.url, env: parsed.password ? { PGPASSWORD: parsed.password } : {}, secrets: [value, parsed.password].filter(Boolean) };
38
+ const target = { kind: 'url', how: `connection string from ${name}`, urlEnv: name, url: parsed.url, env: parsed.password ? { PGPASSWORD: parsed.password } : {}, secrets: [value, parsed.password].filter(Boolean) };
39
+ // A dev environment's database runs in its own container (starci app stack up): STARCI_DB_CONTAINER runs the client tools inside it, so the host needs no PostgreSQL client.
40
+ const container = ctx.env.STARCI_DB_CONTAINER;
41
+ if (!container)
42
+ return target;
43
+ const inside = inContainer(target, container);
44
+ const url = new URL(inside.url);
45
+ url.port = '5432';
46
+ return { ...target, ...inside, kind: 'url', urlEnv: name, url: url.toString() };
39
47
  }
40
48
  /** Where a `-f` file of an in-container target is copied before the client reads it. */
41
49
  const CONTAINER_DIR = '/tmp/starci-db';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starci/hfs",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "The StarCi app command implementation for scaffolding, linting, checking and synchronizing one product repository.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,36 +30,43 @@ pins:
30
30
  side: fe
31
31
  source: packages/event-stream/package.json
32
32
  why: 'the one server-sent-events client of every front end (fe-stack event-stream, owner:event-stream-package): the transport client layer (modules/api/sse/clients) creates one instance for the app with the bearer token source and the stream operations follow on it; it carries Last-Event-ID resume, the capped doubling backoff, the heartbeat timeout, the single 401 renewal and the useSWRSubscription adapter, so no app keeps a hand-written reader.'
33
+ '@starci/next-data':
34
+ version: 0.1.0
35
+ group: starci
36
+ install: registry
37
+ side: fe
38
+ source: packages/next-data/package.json
39
+ why: 'the SWR data helpers of every front end (fe-stack data, owner:next-data-package): pollingRestart, the viewer and tenant key helpers, viewerSwrConfig and the viewer cache; each operation hook stays in the app and the package holds no component.'
33
40
  '@starci/grammar':
34
- version: 0.21.13
41
+ version: 0.21.14
35
42
  group: starci
36
43
  install: registry
37
44
  side: fe
38
45
  source: packages/grammar/package.json
39
46
  why: 'the grammar front ends read: the brand layer sets `--font-sans` and `--font-mono` and the grammar reads them; it carries the Input tel kind, the copyable and read-only Input, the IconButton disclosure props and count badge, ChatBubble, FillViewport, Textarea mentions and Enter-to-send, the leading PrimaryRailLayout rail, the plate-less Image, ListRow, the soft Alert, Progress steps, the floating primary IconButton, FlowGraph, the STATE-8 loading views and the bordered selectable TagGroup chips. The runtime source version wins for a @starci package.'
40
47
  '@starci/eslint-canon-be':
41
- version: 3.12.0
48
+ version: 3.13.0
42
49
  group: starci
43
50
  install: registry
44
51
  side: be
45
52
  source: packages/eslint-canon-be/package.json
46
53
  why: 'the back-end ESLint canon: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; `starciBeConfig({ hfs: loadHfs(import.meta.url) })` is the typed factory and the BE-CONVENTION laws are its rules.'
47
54
  '@starci/eslint-canon-fe':
48
- version: 9.10.0
55
+ version: 9.11.0
49
56
  group: starci
50
57
  install: registry
51
58
  side: fe
52
59
  source: packages/eslint-canon-fe/package.json
53
60
  why: 'the front-end ESLint canon: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; the front end has no tests (FE_NO_TESTS R97 in hfs) and no-vietnamese-in-source (R91) applies.'
54
61
  '@starci/eslint-grammar-core':
55
- version: 0.21.13
62
+ version: 0.21.14
56
63
  group: starci
57
64
  install: registry
58
65
  side: fe
59
66
  source: packages/eslint-grammar-core/package.json
60
67
  why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
61
68
  '@starci/eslint-grammar-offset-pop':
62
- version: 0.21.13
69
+ version: 0.21.14
63
70
  group: starci
64
71
  install: registry
65
72
  side: fe
@@ -85,7 +92,7 @@ pins:
85
92
  side: both
86
93
  source: packages/prettier-config/package.json
87
94
  '@starci/jest-preset':
88
- version: 2.6.0
95
+ version: 2.6.1
89
96
  group: starci
90
97
  install: registry
91
98
  side: be
@@ -105,8 +112,22 @@ pins:
105
112
  side: be
106
113
  source: packages/ai-router/package.json
107
114
  why: 'the AI pool router of a back end: a NestJS module that routes OpenAI-compatible chat, stream and embeddings calls across keys, models and providers, with @starci/ai-router/testing for its specs; @starci/jest-preset compiles it as an ESM-only dependency.'
115
+ '@starci/auth':
116
+ version: 0.1.0
117
+ group: starci
118
+ install: registry
119
+ side: both
120
+ source: packages/auth/package.json
121
+ why: 'the lite sign-in: the NestJS module (EdDSA access tokens with a multi-key JWKS, passwords, Google OAuth with state and PKCE, rotating refresh sessions, the sign-in budget, the refresh cookie), the front-end client and its React bindings, the door contracts and test doubles; the one importer of the identity libraries, reached by a back end only through integrations/passport; @starci/jest-preset compiles it as an ESM-only dependency.'
122
+ '@starci/object-storage':
123
+ version: 0.1.0
124
+ group: starci
125
+ install: registry
126
+ side: be
127
+ source: packages/object-storage/package.json
128
+ why: 'object storage of a back end: a NestJS module and the ObjectStorage port over S3-compatible storage (presigned PUT and GET that sign the content type and size, head, get, put, list, delete, bucket setup, size and content-type limits, coded errors), with @starci/object-storage/testing for its specs; the only importer of @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner (R331).'
108
129
  '@starci/hfs':
109
- version: 5.1.0
130
+ version: 5.2.0
110
131
  group: starci
111
132
  install: registry
112
133
  side: both
@@ -543,12 +564,12 @@ pins:
543
564
  version: 3.1150.0
544
565
  group: framework
545
566
  side: be
546
- why: 'the S3 transport of integrations/object-storage, the one ObjectStorage adapter of both editions (MinIO, S3, Spaces); 3.1150.0 is the release of 2026-10-12, chosen over newer ones that had not aged two weeks.'
567
+ why: 'the S3 transport inside @starci/object-storage, the one ObjectStorage adapter of both editions (MinIO, S3, Spaces), which depends on it at exactly this version and which no app file imports; 3.1150.0 is the release of 2026-10-12, chosen over newer ones that had not aged two weeks.'
547
568
  '@aws-sdk/s3-request-presigner':
548
569
  version: 3.1150.0
549
570
  group: framework
550
571
  side: be
551
- why: 'signs the presigned PUT and GET of integrations/object-storage locally, the same release as client-s3.'
572
+ why: 'signs the presigned PUT and GET inside @starci/object-storage locally, the same release as client-s3; no app file imports it.'
552
573
  '@apollo/server':
553
574
  version: 5.5.1
554
575
  group: framework
@@ -37,17 +37,17 @@ concerns:
37
37
  - id: rest
38
38
  tier: core
39
39
  packages: [axios]
40
- home: [fe.modules.api, fe.modules.auth]
40
+ home: [fe.modules.api]
41
41
  pattern: FE-API-REST
42
42
  forbids: [ky, superagent, node-fetch, cross-fetch, isomorphic-fetch]
43
- why: modules/api/rest/<area>/*.ts for uploads and presigned URLs, and modules/auth/auth-door.ts for the app's own auth doors only; GraphQL carries every other call.
43
+ why: modules/api/rest/<area>/*.ts for uploads and presigned URLs, only; GraphQL carries every other call.
44
44
  - id: data
45
45
  tier: core
46
- packages: [swr]
46
+ packages: [swr, '@starci/next-data']
47
47
  home: [fe.hooks.swr]
48
48
  pattern: FE-DATA-SWR
49
49
  forbids: ['@tanstack/react-query', react-query]
50
- why: hooks/swr/api/{graphql,rest}/{queries,mutations}/use*Swr.ts wrap one operation each; components call hooks only.
50
+ why: hooks/swr/api/{graphql,rest}/{queries,mutations}/use*Swr.ts wrap one operation each; components call hooks only. The polling-restart middleware (pollingRestart), the key helpers, the viewer-keyed config and the viewer cache are @starci/next-data's (owner next-data-package); an app imports no Middleware or ScopedMutator type of swr and writes none of them.
51
51
  - id: toast
52
52
  tier: core
53
53
  packages: ['@heroui/react']
@@ -96,11 +96,11 @@ concerns:
96
96
  hand-written reader (read-event-stream.ts) and no second SSE library is installed.
97
97
  - id: auth
98
98
  tier: core
99
- packages: []
99
+ packages: ['@starci/auth']
100
100
  home: [fe.modules.auth]
101
101
  pattern: FE-AUTH
102
- forbids: ['@supabase/supabase-js', '@supabase/ssr', '@supabase/auth-js']
103
- why: modules/auth is the session issuer's client (lite - auth-door.ts over the app's own back-end auth doors with the in-memory token store; full - the app's IdP client); it yields the session and the access token for the Apollo link and never reads data.
102
+ forbids: ['@supabase/supabase-js', '@supabase/ssr', '@supabase/auth-js', jose, jwt-decode, next-auth, '@auth/core', keycloak-js, oidc-client-ts]
103
+ why: modules/auth is the session issuer's client (one @starci/auth client instance over the app's own back-end auth doors, the access token in its memory; full - the app's IdP client); it yields the session and the access token for the Apollo link and never reads data.
104
104
  - id: i18n
105
105
  tier: core
106
106
  packages: [next-intl]
@@ -0,0 +1,7 @@
1
+ # The images the harness owns as StarCi forks (the harness repository's images/sources.json is their owner; stacks-service-images.spec.mts keeps this list
2
+ # equal to it). A service of an app that has an image folder named like a key here is built FROM that repository (R374 STACKS_IMAGE_BASE_NOT_FORK):
3
+ # the fork itself, or the harness runtime layer built on it (openclaw-runtime, images/README.md).
4
+ schema: starci/fork-images@1
5
+ images:
6
+ minio: ghcr.io/starci183/minio
7
+ openclaw: ghcr.io/starci183/openclaw-runtime
@@ -4084,6 +4084,7 @@ rules:
4084
4084
  enforcers:
4085
4085
  - {kind: hfs, id: fe-stack, at: scripts/hfs/rules/fe-stack.mjs}
4086
4086
  - {kind: eslint-fe, id: fe-stack-import}
4087
+ - {kind: eslint-fe, id: swr-extension-from-package}
4087
4088
  proves:
4088
4089
  - {path: packages/eslint/fe/frontend-boundary-coverage.spec.mjs, editions: [lite, full], codes: [FE_STACK_FORBIDDEN]}
4089
4090
  - {path: packages/hfs/src/frontend-boundary-coverage.spec.mjs, editions: [lite, full], codes: [FE_STACK_FORBIDDEN]}
@@ -5696,7 +5697,7 @@ rules:
5696
5697
  ruling: "A model call made straight from code uses LangChain with retrieved context, through the ai-router gateway (owner ruling be-ai-langchain-gateway): the LangChain clients point at the gateway's base URL with its virtual key."
5697
5698
  - id: R328
5698
5699
  code: "BE_AUTH_PASSPORT_OWNER"
5699
- law: "The identity libraries (@nestjs/passport, passport, passport-google-oauth20, passport-local, @nestjs/jwt, jose and bcryptjs) are imported only by integrations/passport, the one owner of the Google OAuth 2.0 strategy (state and PKCE), the local email and password strategy (bcrypt) and the app's own access tokens; bcrypt and every Supabase client are owned nowhere."
5700
+ law: "The identity libraries (@nestjs/passport, passport, passport-google-oauth20, passport-local, @nestjs/jwt, jose and bcryptjs) are the dependencies of @starci/auth and are imported by no app file; @starci/auth/server is imported only by integrations/passport, its one glue. bcrypt and every Supabase client are owned nowhere."
5700
5701
  kinds: [lint]
5701
5702
  gates: [pre-commit, settle, land, ci]
5702
5703
  failureCodes: ["BE_AUTH_PASSPORT_OWNER"]
@@ -5718,7 +5719,7 @@ rules:
5718
5719
  ruling: "Lite leaves Supabase: its identity is the app own Passport integration (Google OAuth and email/password) on its self-hosted PostgreSQL; full keeps Keycloak."
5719
5720
  - id: R329
5720
5721
  code: "BE_AUTH_JWT_OWN_JWKS"
5721
- law: "A lite back-end principal comes only from the app's own access token verified by integrations/passport against its own key set (jose jwtVerify over createLocalJWKSet, issuer, audience and algorithms pinned), never from a remote JWKS, an unverified decode or a request body claim; domain/identity builds the principal from that verification."
5722
+ law: "A lite back-end principal comes only from the app's own access token, verified by @starci/auth (EdDSA pinned, issuer and audience checked, a local key set that may hold several keys), never from a remote JWKS, a token decoded without its signature check, a jwtVerify call in the app or a body claim."
5722
5723
  kinds: [check, lint]
5723
5724
  gates: [pre-commit, settle, land, ci]
5724
5725
  failureCodes: ["BE_AUTH_JWT_OWN_JWKS"]
@@ -5742,7 +5743,7 @@ rules:
5742
5743
  ruling: "The lite back end verifies its own asymmetric access tokens against its own JWKS; it trusts no remote identity provider."
5743
5744
  - id: R330
5744
5745
  code: "BE_AUTH_REFRESH_COOKIE"
5745
- law: "A refresh token travels only in an httpOnly, Secure, SameSite=Strict cookie scoped to the refresh door (set and cleared only through integrations/passport), is rotated on every use with reuse detection, and never appears as a field of a request or response body."
5746
+ law: "A refresh token travels only in the httpOnly, Secure cookie that @starci/auth sets, rotates (with a reuse grace window and reuse detection) and clears; no app file writes that cookie, and the token never appears as a field of a request or response body."
5746
5747
  kinds: [lint]
5747
5748
  gates: [pre-commit, settle, land, ci]
5748
5749
  failureCodes: ["BE_AUTH_REFRESH_COOKIE"]
@@ -5764,7 +5765,7 @@ rules:
5764
5765
  ruling: "The refresh token of the lite identity rotates and lives only in an httpOnly cookie; the browser holds no credential."
5765
5766
  - id: R331
5766
5767
  code: "BE_STORAGE_OWNER"
5767
- law: "The S3 client (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) is imported only by integrations/object-storage, the ObjectStorage port over S3-compatible storage (MinIO in lite) with presigned PUT and GET; no module imports minio."
5768
+ law: "The S3 client libraries (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) are the dependencies of @starci/object-storage, the ObjectStorage port over S3-compatible storage (MinIO in lite) with presigned PUT and GET, and are imported by no app file; @starci/object-storage is imported only by integrations/object-storage, its one glue; no module imports minio."
5768
5769
  kinds: [lint]
5769
5770
  gates: [pre-commit, settle, land, ci]
5770
5771
  failureCodes: ["BE_STORAGE_OWNER"]
@@ -5773,11 +5774,11 @@ rules:
5773
5774
  editions: [lite, full]
5774
5775
  docs: [contexts/code/be/infra.md]
5775
5776
  proves:
5776
- - packages/eslint-canon-be/src/auth-passport.spec.ts
5777
+ - packages/eslint-canon-be/src/object-storage-package.spec.ts
5777
5778
  exemplifiedBy:
5778
- - {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts, edition: lite}
5779
+ - {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts, edition: lite}
5779
5780
  witness:
5780
- - {path: packages/eslint-canon-be/src/auth-passport.spec.ts, editions: [lite, full], codes: [BE_STORAGE_OWNER]}
5781
+ - {path: packages/eslint-canon-be/src/object-storage-package.spec.ts, editions: [lite, full], codes: [BE_STORAGE_OWNER]}
5781
5782
  cases:
5782
5783
  - id: case-1
5783
5784
  date: "2026-10-29"
@@ -6576,3 +6577,50 @@ rules:
6576
6577
  source: owner
6577
6578
  quote: "no lint caught it"
6578
6579
  ruling: "A directive below an import broke Nivo's CI build on 2026-11-02; the directive is the first statement of its module and a lint reports it elsewhere."
6580
+ - id: R372
6581
+ code: "STACKS_LOCAL_CONFIG_INVALID"
6582
+ law: "In lite and full apps, every docker-compose environment of .starcistacks/application-stacks.yaml declares a coherent `local` block, judged by the same normalizeLocal the dev verbs of `starci app stack` (doctor, prepare, up, status, logs, verify, down) use: closed fields, one loopback port each, compose services that exist in the compose files, an environment-owned database role (never postgres, never the app role), dev-only custody generators and loopback probes; and a lite app declares at least one such dev environment, so `starci app stack up --env dev` has something to run."
6583
+ kinds: [check]
6584
+ gates: [settle, land, ci]
6585
+ failureCodes: ["STACKS_LOCAL_CONFIG_INVALID", "STACKS_DEV_UNDECLARED"]
6586
+ enforcers:
6587
+ - {kind: hfs, id: stacks-local, at: scripts/hfs/rules/stacks-local.mjs}
6588
+ editions: [lite, full]
6589
+ docs:
6590
+ - {path: contexts/project/stacks.md#project-stacks-6-every-runbook-declares-the-same-operations, editions: [lite, full]}
6591
+ proves:
6592
+ - {path: packages/hfs/src/stack-local.spec.mjs, editions: [lite, full]}
6593
+ exemplifiedBy:
6594
+ - {path: packages/hfs/templates/app/skeleton-lite/.starcistacks, edition: lite}
6595
+ witness:
6596
+ - {path: packages/hfs/src/stack-local.spec.mjs, editions: [lite, full]}
6597
+ - id: R373
6598
+ code: "STACKS_RUNBOOK_DOCKER_COMMAND"
6599
+ law: "The runbook of a docker-compose environment names `starci app stack up --env <env>` and carries no `docker build|run|compose|create|start|stop|pull|push` or `docker stack deploy` command: Docker is run by the harness verb, never by hand (hard rule 8)."
6600
+ kinds: [check]
6601
+ gates: [settle, land, ci]
6602
+ failureCodes: ["STACKS_RUNBOOK_DOCKER_COMMAND"]
6603
+ enforcers:
6604
+ - {kind: hfs, id: stacks-local, at: scripts/hfs/rules/stacks-local.mjs}
6605
+ editions: [lite, full]
6606
+ docs:
6607
+ - {path: contexts/project/stacks.md#project-stacks-10-local-runs, editions: [lite, full]}
6608
+ proves:
6609
+ - {path: packages/hfs/src/stack-local.spec.mjs, editions: [lite, full]}
6610
+ exemplifiedBy:
6611
+ - {path: packages/hfs/templates/app/skeleton-lite/.starcistacks, edition: lite}
6612
+ witness:
6613
+ - {path: packages/hfs/src/stack-local.spec.mjs, editions: [lite, full]}
6614
+ - id: R374
6615
+ code: "STACKS_CODE_BIND_MOUNT"
6616
+ law: "A service the app customises runs from an image built out of `.starcistacks/images/<name>/` (a Dockerfile whose every FROM is digest-pinned and whose final stage is the harness's image for that service, the fork or its runtime layer, when the harness owns one), the same image in every environment; no stack or compose file bind-mounts code, plugins or scripts into a service container (data volumes, secrets, least grants and read-only plain config data files stay allowed)."
6617
+ kinds: [check]
6618
+ gates: [settle, land, ci]
6619
+ failureCodes: ["STACKS_CODE_BIND_MOUNT", "STACKS_IMAGE_BASE_UNPINNED", "STACKS_IMAGE_BASE_NOT_FORK", "STACKS_IMAGE_ENV_DIVERGES"]
6620
+ enforcers:
6621
+ - {kind: hfs, id: stacks-service-images, at: scripts/hfs/rules/stacks-service-images.mjs}
6622
+ editions: [lite, full]
6623
+ docs:
6624
+ - {path: contexts/project/stacks.md#project-stacks-11-a-customised-service-is-an-image-the-app-builds-the-same-in-every-environment, editions: [lite, full]}
6625
+ proves:
6626
+ - {path: packages/hfs/src/stacks-service-images.spec.mjs, editions: [lite, full]}
@@ -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