@starci/hfs 4.9.11 → 4.10.1

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 (222) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +7 -9
  3. package/db/backup-receipt.mjs +10 -47
  4. package/db/drift.mjs +1 -1
  5. package/db/dump.mjs +11 -10
  6. package/db/entity-list.mjs +1 -1
  7. package/db/migrate.mjs +83 -259
  8. package/db/new.mjs +46 -0
  9. package/db/post-cutover.mjs +13 -17
  10. package/db/postgres.mjs +80 -0
  11. package/emit/contracts.mjs +5 -9
  12. package/package.json +1 -1
  13. package/release/gate.mjs +3 -15
  14. package/runtime/knowledge/hfs/canon-pins.yaml +44 -24
  15. package/runtime/knowledge/hfs/design-truths.yaml +1 -1
  16. package/runtime/knowledge/hfs/fe-stack.yaml +4 -11
  17. package/runtime/knowledge/hfs/rules.yaml +179 -157
  18. package/runtime/knowledge/hfs/slots.yaml +185 -172
  19. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  20. package/runtime/knowledge/patterns/be/idempotency.yaml +3 -3
  21. package/runtime/knowledge/patterns/be/jobs.yaml +4 -4
  22. package/runtime/knowledge/patterns/be/projections.yaml +7 -5
  23. package/runtime/knowledge/patterns/be/queues.yaml +8 -8
  24. package/runtime/knowledge/patterns/be/reactors.yaml +2 -2
  25. package/runtime/knowledge/patterns/be/realtime.yaml +6 -6
  26. package/runtime/knowledge/patterns/be/saga.yaml +3 -3
  27. package/runtime/knowledge/patterns/be/webhooks.yaml +1 -1
  28. package/runtime/modules/kernel/command-policy.yaml +1 -1
  29. package/runtime/modules/kernel/failure-codes.yaml +65 -115
  30. package/runtime/modules/schemas/application-stacks.schema.yaml +5 -5
  31. package/runtime/scripts/hfs/architecture/canon-clones.mjs +1 -1
  32. package/runtime/scripts/hfs/architecture/entity-registry.mjs +53 -0
  33. package/runtime/scripts/hfs/architecture/{supabase-be.mjs → identity.mjs} +45 -15
  34. package/runtime/scripts/hfs/architecture/index.mjs +2 -3
  35. package/runtime/scripts/hfs/architecture/{supabase-ast.mjs → jwt-ast.mjs} +2 -103
  36. package/runtime/scripts/hfs/architecture/owners.mjs +11 -0
  37. package/runtime/scripts/hfs/architecture/register-once.mjs +1 -1
  38. package/runtime/scripts/hfs/architecture/schema-owner.mjs +29 -4
  39. package/runtime/scripts/hfs/architecture/server-action.mjs +3 -4
  40. package/runtime/scripts/hfs/architecture/side-boundary.mjs +1 -1
  41. package/runtime/scripts/hfs/architecture/{supabase-keys.mjs → sql-keys.mjs} +1 -1
  42. package/runtime/scripts/hfs/architecture/sql-owner.mjs +25 -27
  43. package/runtime/scripts/hfs/architecture/tiers.mjs +14 -3
  44. package/runtime/scripts/hfs/architecture/transport-owner.mjs +7 -3
  45. package/runtime/scripts/hfs/check.mjs +6 -6
  46. package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
  47. package/runtime/scripts/hfs/db-baseline-verb.mjs +48 -30
  48. package/runtime/scripts/hfs/db-plan-verb.mjs +17 -106
  49. package/runtime/scripts/hfs/db-restamp-verb.mjs +11 -10
  50. package/runtime/scripts/hfs/declaration-shape.mjs +15 -18
  51. package/runtime/scripts/hfs/manifest-shape.mjs +9 -5
  52. package/runtime/scripts/hfs/rule-params-shape.mjs +4 -1
  53. package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -1
  54. package/runtime/scripts/hfs/rules/cli.mjs +1 -1
  55. package/runtime/scripts/hfs/rules/database-access.mjs +8 -74
  56. package/runtime/scripts/hfs/rules/database-baseline.mjs +3 -3
  57. package/runtime/scripts/hfs/rules/database-config.mjs +6 -214
  58. package/runtime/scripts/hfs/rules/database-constants.mjs +8 -10
  59. package/runtime/scripts/hfs/rules/database-migrations.mjs +3 -3
  60. package/runtime/scripts/hfs/rules/database-queues.mjs +4 -5
  61. package/runtime/scripts/hfs/rules/database-sql.mjs +1 -1
  62. package/runtime/scripts/hfs/rules/database.mjs +36 -39
  63. package/runtime/scripts/hfs/rules/edition.mjs +1 -1
  64. package/runtime/scripts/hfs/rules/{supabase-secrets.mjs → lite-secrets.mjs} +6 -6
  65. package/runtime/scripts/hfs/rules/script-hygiene.mjs +1 -10
  66. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +5 -4
  67. package/runtime/scripts/hfs/rules/stacks-release.mjs +3 -2
  68. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +2 -2
  69. package/runtime/scripts/hfs/rules/stacks.mjs +1 -1
  70. package/runtime/scripts/hfs/slot-app-view.mjs +1 -1
  71. package/runtime/scripts/hfs/slots.mjs +8 -2
  72. package/runtime/scripts/hfs/sql/pg-parse.mjs +2 -2
  73. package/runtime/scripts/lib/secret-patterns.mjs +2 -2
  74. package/runtime/scripts/lib/swarm-stack.mjs +7 -4
  75. package/scaffold/add-app.mjs +2 -10
  76. package/scaffold/add-cli-lite.mjs +6 -6
  77. package/scaffold/add-compose.mjs +1 -1
  78. package/scaffold/add-inbox-lite.mjs +4 -8
  79. package/scaffold/add-plan.mjs +1 -1
  80. package/scaffold/add-queue-lite.mjs +16 -24
  81. package/scaffold/add-table.mjs +34 -26
  82. package/scaffold/add.mjs +2 -2
  83. package/scaffold/app.mjs +19 -86
  84. package/scaffold/lite-exports.mjs +1 -1
  85. package/src/main.mjs +19 -20
  86. package/stack/target-binding.mjs +0 -60
  87. package/stack/urgent-floor.mjs +2 -2
  88. package/sync/index.mjs +32 -8
  89. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -17
  90. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +6 -8
  91. package/templates/app/codegen-lite/scripts/codegen.mjs +2 -39
  92. package/templates/app/db-bootstrap/db/bootstrap.sql +139 -0
  93. package/templates/app/gitignore-lite +0 -3
  94. package/templates/app/package-scripts/db.json +9 -0
  95. package/templates/app/package-scripts/package.json +0 -1
  96. package/templates/app/package-scripts-lite/package.json +1 -1
  97. package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
  98. package/templates/app/skeleton/.starcistacks/production/README.md +1 -1
  99. package/templates/app/skeleton/README.md +1 -1
  100. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +84 -19
  101. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +5 -5
  102. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +1 -1
  103. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +1 -1
  104. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +69 -6
  105. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -7
  106. package/templates/app/skeleton-lite/.starcistacks/production/README.md +6 -6
  107. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +1 -1
  108. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +2 -2
  109. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +69 -6
  110. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +17 -9
  111. package/templates/app/skeleton-lite/README.md +11 -11
  112. package/templates/app/skeleton-lite/{supabase → db}/migrations/__timestamp___baseline.sql +21 -1
  113. package/templates/app/tool-config-lite/prettierignore +0 -1
  114. package/templates/be/inbox/database-client.spec.ts.tpl +9 -5
  115. package/templates/be/inbox/database-client.ts.tpl +13 -4
  116. package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +47 -0
  117. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +3 -1
  118. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +2 -2
  119. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +6 -1
  120. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +39 -7
  121. package/templates/be/inbox/platform/migration.sql.tpl +22 -0
  122. package/templates/be/inbox/service.spec.ts.tpl +2 -2
  123. package/templates/be/inbox/service.ts.tpl +1 -2
  124. package/templates/be/patterns/jobs/platform-lite/job-claim.service.spec.ts.tpl +156 -0
  125. package/templates/be/patterns/jobs/platform-lite/job-claim.service.ts.tpl +114 -0
  126. package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +51 -0
  127. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.spec.ts.tpl +62 -0
  128. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.ts.tpl +20 -0
  129. package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +35 -0
  130. package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +4 -1
  131. package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +16 -5
  132. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +8 -8
  133. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +3 -3
  134. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +2 -2
  135. package/templates/be/skeleton-lite/apps/api/Dockerfile +0 -1
  136. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +9 -5
  137. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +7 -4
  138. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +4 -4
  139. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +3 -3
  140. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +9 -13
  141. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +4 -2
  142. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +2 -2
  143. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +3 -2
  144. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +4 -4
  145. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +3 -3
  146. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +1 -1
  147. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +6 -6
  148. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +3 -3
  149. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +4 -0
  150. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +22 -0
  151. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +25 -0
  152. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +12 -0
  153. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +9 -0
  154. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +35 -0
  155. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +22 -0
  156. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +12 -0
  157. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +23 -0
  158. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +29 -0
  159. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +22 -0
  160. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +63 -0
  161. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +11 -0
  162. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +32 -0
  163. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +69 -0
  164. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +37 -0
  165. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +36 -0
  166. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +59 -0
  167. package/templates/be/skeleton-lite/src/modules/platform/database/database.functions.ts +24 -0
  168. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +2 -1
  169. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +4 -2
  170. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +19 -6
  171. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +7 -0
  172. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  173. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +1 -1
  174. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +2 -2
  175. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +2 -2
  176. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
  177. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +1 -1
  178. package/templates/be/table/entities.index.ts.tpl +4 -0
  179. package/templates/be/table/entity.ts.tpl +21 -0
  180. package/templates/be/table/index.ts.tpl +1 -0
  181. package/templates/be/table/module.ts.tpl +1 -1
  182. package/templates/be/table/service.api.ts.tpl +10 -11
  183. package/templates/be/table/service.ts.tpl +5 -6
  184. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +30 -0
  185. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +46 -14
  186. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +31 -0
  187. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +2 -5
  188. package/db/local-migrate.mjs +0 -194
  189. package/db/reset.mjs +0 -125
  190. package/emit/db-types.mjs +0 -271
  191. package/runtime/scripts/api/supabase/database-dump.mjs +0 -5
  192. package/runtime/scripts/api/supabase/database-push.mjs +0 -5
  193. package/runtime/scripts/api/supabase/database-query.mjs +0 -5
  194. package/runtime/scripts/api/supabase/lib.mjs +0 -55
  195. package/runtime/scripts/api/supabase/migration-repair.mjs +0 -5
  196. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +0 -50
  197. package/runtime/scripts/hfs/architecture/supabase.mjs +0 -226
  198. package/scaffold/supabase-ports.mjs +0 -26
  199. package/templates/app/package-scripts/supabase.json +0 -7
  200. package/templates/app/skeleton-lite/supabase/config.toml +0 -48
  201. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +0 -5
  202. package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +0 -33
  203. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +0 -33
  204. package/templates/be/inbox/sql.ts.tpl +0 -16
  205. package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +0 -33
  206. package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +0 -12
  207. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -16
  208. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +0 -6
  209. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +0 -27
  210. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +0 -24
  211. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +0 -8
  212. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +0 -17
  213. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +0 -32
  214. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +0 -24
  215. package/templates/be/table/rows.ts.tpl +0 -4
  216. package/templates/be/table/sql.api.ts.tpl +0 -7
  217. package/templates/be/table/sql.ts.tpl +0 -4
  218. package/templates/fe/auth/supabase-client.ts.tpl +0 -38
  219. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +0 -11
  220. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +0 -18
  221. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +0 -19
  222. /package/templates/app/skeleton-lite/{supabase → db}/seed.sql +0 -0
@@ -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.8.0
10
+ version: 3.10.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).
@@ -38,6 +38,9 @@ versioning:
38
38
  # or environment reads (BE_APP_OPTIONS_COMPOSITION). Secrets use EnvSource.secret(): <NAME>_FILE first, plain <NAME> only
39
39
  # when the file key is absent; .starcistacks owns custody. Capability <capability>.config.ts roles stay unchanged.
40
40
  # 3.3.0 is a minor (alpha.16): it ADDS the optional app.ci-stack-deploy slot, the managed workflow of the starci app stack verbs.
41
+ # 3.9.0 (ruling:lite-self-hosted-postgres-passport-minio, 2026-10-29) is a major for lite and changes no full slot but the S3 owner's
42
+ # folder: lite leaves Supabase. The app.supabase* and be.integrations.supabase slots and fe.route.callback are removed; app.db (db/, its
43
+ # migrations, archive, seed and managed bootstrap), be.integrations.passport and be.integrations.object-storage are added; lite's schemaAuthority is sql.
41
44
  # 3.4.0 is a minor (alpha.25): it ADDS the opt-in be.contract.mcp-tools slot, the committed MCP tool-catalog snapshot of an app.
42
45
  # 3.5.0 is a minor (alpha.27): it ADDS the opt-in app.browser-config slot, the managed browser/tsconfig.json that type-checks the browser
43
46
  # journey (enabled with app.browser by `browser: true`).
@@ -47,6 +50,8 @@ versioning:
47
50
  # transport client layer), and lets fe.hooks.swr hold the SWR subscription hooks (api/sse/subscriptions/useSubscribe<X>Swr.ts, one useSWRSubscription per stream operation).
48
51
  # 3.8.0 is a minor (urgent release, 2026-10-29): it ADDS the optional fe.dev-server-generated slot (apps/*/AGENTS.md, which `next dev` rewrites) and the
49
52
  # slot field rewrittenBy, so the release gate's clean-tree check passes over an untracked file a declared dev server writes, and over nothing else.
53
+ # 3.10.0 is a minor (2026-10-29): it ADDS the lite be.platform.database.entities slot, the entity registry (slot fields role and importedBy), so the
54
+ # tier matrix and the public-entry rules admit exactly its <c>Entities imports and its deep importers (persistence.md BE-PERSISTENCE-3).
50
55
 
51
56
  # Field meaning, per slot.
52
57
  # id stable name, never changes meaning inside a major.
@@ -69,6 +74,10 @@ versioning:
69
74
  # outcomeHome true on a file slot: the one file that declares the repository's Outcome union. A repository holds exactly one such
70
75
  # file (modules/api/outcome.ts, the api package's outcome.ts, or modules/db/outcome.ts of a lite app),
71
76
  # and no other file declares an Outcome union (one-outcome-union, FE_TRANSPORT_OWNER).
77
+ # role on a file slot: the one part it plays that a rule reads. entity-registry = the lite PRIMARY_ENTITIES list
78
+ # (be.platform.database.entities): the one platform file that imports capability <c>Entities, imported by deep path
79
+ # only from the files importedBy names (architecture/entity-registry.mjs).
80
+ # importedBy on a slot with a role: the side-relative path patterns of the only files that import its file by deep path.
72
81
  # requires files or directories that must exist inside each instance; <var> is replaced by the bound name,
73
82
  # a leading / means repository root; a trailing / means directory.
74
83
  # requiredInstances variable -> names that must exist as instances (config, logging ... of platform).
@@ -106,34 +115,34 @@ versioning:
106
115
  # requiredInstances, why), each shaped as the same field in the slot body; every other field is the edition's.
107
116
  # `why` under lite names the adapter behind the slot's ports (pgmq, Postgres locks, in-process socket.io).
108
117
  # `tests` is never overlaid: a lite slot owes exactly the unit specs its full slot owes.
109
- # provider the hfs.json connections[].provider that enables this opt-in slot (supabase); never an app kind.
118
+ # provider the hfs.json connections[].provider that enables this opt-in slot (postgres); never an app kind.
110
119
  # since the manifest version that introduced the slot.
111
120
  presenceValues: [required, optional, opt-in, forbidden]
112
121
  trackedValues: [tracked, ignored, external]
113
122
  testValues: [unit-beside, e2e, none]
114
123
 
115
124
  # The editions a repository declares in hfs.json `edition` (absent means full). ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite IS
116
- # the full architecture with Supabase backing services - the same monorepo, slots, CQRS shape and patterns (queue,
125
+ # the full architecture on its own self-hosted PostgreSQL (ruling:lite-self-hosted-postgres-passport-minio) - the same monorepo, slots, CQRS shape and patterns (queue,
117
126
  # event bus, saga, reactors, projection, realtime, webhooks, fenced job), declared in hfs.json the same way, and the same unit
118
127
  # specs (unitRoles, logicRoles, 100 per file, @starci/jest-preset doubles, builders). What differs is ADAPTERS and DELIVERY
119
- # (ruling:R212/case-10): schema authority in supabase/migrations, the queue port on BullMQ over the Redis hfs.json
128
+ # (ruling:R212/case-10): schema authority in db/migrations, the queue port on BullMQ over the Redis hfs.json
120
129
  # sides.be.backing declares (as in full, fed by the Postgres outbox), event-bus ports on pgmq-backed topics or in-process
121
130
  # delivery, cache and locks on Postgres or the declared Redis, schedules on pg_cron (+ pg_net) enqueueing through the queue
122
- # outbox, storage and auth on Supabase. Realtime uses SSE or the socket.io gateway
131
+ # outbox, storage on S3-compatible MinIO (integrations/object-storage) and auth through integrations/passport. Realtime uses SSE or the socket.io gateway
123
132
  # in the api process, which scales horizontally and stateless (memory hub for one replica or tests; the Redis hub and @socket.io/redis-adapter when Redis is declared, else the Postgres hub and @socket.io/postgres-adapter, for N replicas; websocket only, no sticky sessions). Integration and provider sandbox contract specs are optional;
124
133
  # declared patterns owe their integration proofs (R284). Lite has no e2e world. Production uses declared docker-swarm under
125
- # .starcistacks/production/infra/swarm/ (owner ruling 2026-10-14); lite runs no self-hosted database, and runs only the backing
126
- # services sides.be.backing declares. The front end is full's (R97).
134
+ # .starcistacks/production/infra/swarm/ (owner ruling 2026-10-14); lite runs its PostgreSQL as the db service of its
135
+ # stack and only the backing services sides.be.backing declares. The front end is full's (R97).
127
136
  # Lite is this same manifest filtered: a slot whose `editions` lacks lite does not exist for a lite app, `litePresence` replaces
128
137
  # presence and a `lite` map replaces the fields it names. The edition is chosen once at scaffold; there is no upgrade
129
138
  # or downgrade between lite and full. Lite contracts are allowed (owner decision 2026-10-14).
130
139
  editions: [full, lite]
131
140
 
132
141
  # The two sides of an app: the folder of each is its profile name (be/, fe/). `reads` lists the only paths of the OTHER side a side
133
- # may read (hfs.json sides.<side>.reads names a subset), or a tree an app-root slot owns (supabase/types/); nothing else crosses sides.
142
+ # may read (hfs.json sides.<side>.reads names a subset), or a tree an app-root slot owns; nothing else crosses sides.
134
143
  sides:
135
- be: {reads: [supabase/types/]} # the generated Database types of a Supabase connection (server code types its PostgREST calls)
136
- fe: {reads: [be/contracts/, supabase/types/]} # be/contracts/: the committed contract snapshots, the input of the front end's codegen; supabase/types/: the same generated types
144
+ be: {reads: []}
145
+ fe: {reads: [be/contracts/]} # be/contracts/: the committed contract snapshots, the input of the front end's codegen
137
146
 
138
147
  # The trigger kinds of the back end's features: what starts the work of a feature. A feature slot names its kind in `trigger`; no kind imports another
139
148
  # (BE_KIND_ISOLATION), every cross-kind call is an event on the bus, and only be/apps/* compose features.
@@ -196,9 +205,9 @@ crossApp: apps never import each other; shared code is a packages/<pkg> slot.
196
205
  ruleParams:
197
206
  db:
198
207
  # R259: btree_gist supplies UUID equality for the booking interval exclusion constraint.
199
- # R259: unaccent is a first-party Supabase extension used by Vietnamese-content search functions.
208
+ # R259: unaccent ships in the supabase/postgres image of the db service, used by Vietnamese-content search functions.
200
209
  extensions: [pgmq, pg_cron, pgcrypto, pg_trgm, vector, uuid-ossp, pg_net, supabase_vault, unaccent, btree_gist]
201
- # R258 DB_CRON_OWNED: THE queue outbox of a Supabase schema (the relay hands its unsent rows to BullMQ, ruling:R212/case-10). A pg_cron
210
+ # R258 DB_CRON_OWNED: THE queue outbox of a lite schema (the relay hands its unsent rows to BullMQ, ruling:R212/case-10). A pg_cron
202
211
  # job enqueues only by inserting one row into this table, writing only these columns, one of which is `queue`.
203
212
  queueOutbox: {table: public.queue, columns: [queue, payload, created_at, correlation_id]}
204
213
  common:
@@ -222,8 +231,18 @@ ruleParams:
222
231
  "node:https": [platform/http]
223
232
  http: [platform/http]
224
233
  https: [platform/http]
225
- "@aws-sdk/client-s3": [integrations/object-storage] # the ObjectStorage port: one S3 adapter for MinIO, S3, Spaces and Supabase Storage
234
+ "@aws-sdk/client-s3": [integrations/object-storage] # the ObjectStorage port: one S3 adapter for MinIO, S3 and Spaces (R331)
226
235
  "@aws-sdk/s3-request-presigner": [integrations/object-storage]
236
+ minio: [] # storage goes through the S3 client of integrations/object-storage
237
+ "@supabase": [] # no Supabase client in a back end
238
+ "@nestjs/passport": [integrations/passport] # the identity libraries (R328)
239
+ passport: [integrations/passport]
240
+ passport-google-oauth20: [integrations/passport]
241
+ passport-local: [integrations/passport]
242
+ "@nestjs/jwt": [integrations/passport]
243
+ jose: [integrations/passport]
244
+ bcryptjs: [integrations/passport]
245
+ bcrypt: []
227
246
  cache-manager: [integrations/cache, integrations/redis]
228
247
  "@nestjs/cache-manager": [integrations/cache, integrations/redis] # the CACHE_MANAGER token: caching goes through the cache capability
229
248
  redis: [integrations/cache, integrations/redis]
@@ -395,21 +414,41 @@ ruleParams:
395
414
  # end may declare in hfs.json sides.be.backing. `clients` are the libraries that consume the service: one of them as a dependency
396
415
  # is its consumer, and under lite none of them may be a dependency while the service is undeclared. `patterns` are the declared
397
416
  # patterns that need the service. `images` are the image repositories (the last path segment) a stack runs it from.
398
- # `editions` limits admission: owner:lite-vector-is-pgvector keeps vector search on Supabase pgvector in lite.
417
+ # `editions` limits admission: owner:lite-vector-is-pgvector keeps vector search on pgvector in lite.
399
418
  backing:
400
419
  redis: {clients: [bullmq, "@nestjs/bullmq", bull, "@nestjs/bull", ioredis, redis, "@keyv/redis", cache-manager-redis-yet, cache-manager-ioredis-yet, "@socket.io/redis-adapter"], patterns: [queue, fenced-job], images: [redis, redis-stack, redis-stack-server]}
401
420
  qdrant: {clients: ["@qdrant/js-client-rest", "@qdrant/qdrant-js"], patterns: [], images: [qdrant], editions: [full]}
402
421
  # Edition lite (ruleParams(manifest, 'be', 'lite') merges these keys over the base; a map merges one level deep): the schema
403
- # authority is the Supabase migration tree (supabase/migrations/*.sql, forward-only), so a lite back end holds no TypeORM
422
+ # authority is the SQL migration tree (db/migrations/*.sql, forward-only), so a lite back end holds no TypeORM
404
423
  # migration or entity under be/ (R34/R35 read schemaAuthority; be.persistence's lite overlay allows neither). The unit roles,
405
424
  # logic roles and spec doubles are full's (owner ruling 2026-10-12 (owner:lite-production-swarm), with the Work boundary amended by ruling:R212/case-4). infraOwners: full's owners,
406
425
  # bullmq in platform/queue and ioredis/redis in integrations/cache and integrations/redis (ruling:R212/case-10: the queue is
407
426
  # Redis in both editions; a backing library is legal only when hfs.json sides.be.backing declares its service, which
408
- # HFS_BACKING_UNDECLARED checks), except Kafka, which is owned nowhere (platform/event-bus runs Supabase Queues (pgmq) through
427
+ # HFS_BACKING_UNDECLARED checks), except Kafka, which is owned nowhere (platform/event-bus runs pgmq queues through
409
428
  # the shared EntityManager); realtime is the same socket.io gateway as full, run inside the api process, scaled horizontally and stateless (hub redis when
410
429
  # Redis is declared, else hub postgres with @socket.io/postgres-adapter; no sticky sessions).
430
+ # R334: the database functions code may call through platform/database's callDbFunction, advisoryXactLock and notify helpers,
431
+ # each {name, reason}; the canon lists the pg built-ins it uses itself, and an app adds its own in hfs.json sides.be.dbFunctions.
432
+ dbFunctions:
433
+ - {name: pg_advisory_xact_lock, reason: "advisoryXactLock: a transaction-scoped lock TypeORM has no API for"}
434
+ - {name: hashtextextended, reason: "advisoryXactLock: the 64-bit key of a text lock name"}
435
+ - {name: pg_notify, reason: "notify: LISTEN/NOTIFY of the Postgres realtime hub"}
436
+ - {name: to_regproc, reason: "callDbFunction: whether an optional database function exists"}
437
+ - {name: job_claim, reason: "platform/jobs (add job, lite): the fenced claim upsert of jobs.sql"}
438
+ - {name: job_advance, reason: "platform/jobs (add job, lite): the token-guarded step write of jobs.sql"}
439
+ - {name: job_complete, reason: "platform/jobs (add job, lite): the token-guarded completion of jobs.sql"}
440
+ - {name: job_fail, reason: "platform/jobs (add job, lite): the token-guarded failure of jobs.sql"}
441
+ - {name: queue_write, reason: "platform/queue (add queue, lite): the outbox insert of queue-outbox.sql"}
442
+ - {name: queue_waiting, reason: "platform/queue (add queue, lite): the relay read FOR UPDATE SKIP LOCKED of queue-outbox.sql"}
443
+ - {name: queue_mark_sent, reason: "platform/queue (add queue, lite): the relay mark of queue-outbox.sql"}
444
+ - {name: inbox_claim_event, reason: "platform/inbox (add webhook, lite): the conflict-safe delivery claim"}
445
+ - {name: inbox_claim_client, reason: "platform/inbox (add webhook, lite): the client claim upsert returning its committed outcome"}
446
+ - {name: inbox_save_client_outcome, reason: "platform/inbox (add webhook, lite): the saved client outcome"}
447
+ - {name: inbox_sweep_clients, reason: "platform/inbox (add webhook, lite): the bounded retention sweep FOR UPDATE SKIP LOCKED"}
448
+ - {name: event_bus_write, reason: "platform/event-bus (add webhook, lite): the typed envelope insert of the outbox"}
449
+
411
450
  lite:
412
- schemaAuthority: supabase
451
+ schemaAuthority: sql
413
452
  infraOwners:
414
453
  kafkajs: []
415
454
  "@qdrant/js-client-rest": []
@@ -439,7 +478,7 @@ slots:
439
478
  - id: app.package-manifest
440
479
  since: 3.1.0
441
480
  profiles: [app]
442
- editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite manages Supabase and wiring scripts; full also manages e2e and stack test scripts. Both may run optional contracts."
481
+ editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite manages its database and wiring scripts; full also manages e2e and stack test scripts. Both may run optional contracts."
443
482
  # The one package.json of the app, at the app root: be's dependencies and every tool, and every script; npm workspaces are
444
483
  # exactly fe/apps/* and fe/packages/* (HFS_MONO_WORKSPACES), each with its own package.json. be has no package.json of its own.
445
484
  path: package.json
@@ -508,7 +547,7 @@ slots:
508
547
  - id: app.format-config
509
548
  since: 3.1.0
510
549
  profiles: [app]
511
- editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite additionally ignores Supabase-generated row types when formatting; full has its TypeORM schema authority."
550
+ editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite additionally ignores the generated database row types when formatting; full has its TypeORM schema authority."
512
551
  # The one formatter configuration of the app (prettier walks up from every file of both sides to it): .prettierrc names
513
552
  # @starci/prettier-config, .prettierignore is the template.
514
553
  path: "{.prettierrc,.prettierignore}"
@@ -517,7 +556,7 @@ slots:
517
556
  tier: none
518
557
  tests: none
519
558
  managedBy: tool-config
520
- liteManagedBy: tool-config-lite # the same files plus the generated supabase/types, which the Supabase CLI writes unformatted
559
+ liteManagedBy: tool-config-lite # the same files as full
521
560
  rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL]
522
561
  - id: app.tool-config-optional
523
562
  since: 3.1.0
@@ -560,7 +599,7 @@ slots:
560
599
  - id: app.ci
561
600
  since: 3.1.0
562
601
  profiles: [app]
563
- editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite CI proves boot through R272 wiring and checks Supabase artifacts; full also provides its e2e lane."
602
+ editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite CI proves boot through R272 wiring and checks its database artifacts; full also provides its e2e lane."
564
603
  path: ".github/workflows/ci.yml"
565
604
  presence: required
566
605
  tracked: tracked
@@ -585,7 +624,7 @@ slots:
585
624
  - id: app.ci-images
586
625
  since: 3.1.0
587
626
  profiles: [app]
588
- editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite renders its Supabase-backed image workflow; full renders images for its separately deployed services."
627
+ editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite renders its image workflow; full renders images for its separately deployed services."
589
628
  # Builds every image of the app (one matrix entry per declared app, path-filtered), never pushes: rendered by starci app sync.
590
629
  path: ".github/workflows/images.yml"
591
630
  presence: required
@@ -600,7 +639,7 @@ slots:
600
639
  exemplifiedBy:
601
640
  - packages/hfs/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml
602
641
  - {path: packages/hfs/src/hfs-release-provenance.spec.mjs, edition: full}
603
- editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite manages its Supabase database deploy workflow; full owns an optional provider-specific workflow."
642
+ editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite manages its database deploy workflow; full owns an optional provider-specific workflow."
604
643
  # The database deploy workflow: workflow_dispatch only (like app.ci-e2e); starci app sync renders it under the managed ci group.
605
644
  path: ".github/workflows/db-deploy.yml"
606
645
  presence: optional
@@ -638,7 +677,7 @@ slots:
638
677
  since: 3.1.0
639
678
  profiles: [app]
640
679
  # Production delivery tooling for the app's own processes: compose files, Caddyfile, *.env.example and ops shell scripts.
641
- # Lite still keeps its backing services on Supabase and its encrypted custody in app.starcistacks; R06 scans every file here.
680
+ # Lite keeps its encrypted custody in app.starcistacks; R06 scans every file here.
642
681
  path: "deploy/"
643
682
  presence: optional
644
683
  tracked: tracked
@@ -722,7 +761,7 @@ slots:
722
761
  profiles: [app]
723
762
  exemplifiedBy:
724
763
  - {path: packages/hfs/templates/app/skeleton/scripts/codegen.mjs, edition: full}
725
- editionWhy: "Lite manages its Supabase codegen script; full owns operational scripts through the generic path (ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4))."
764
+ editionWhy: "Lite manages its database-types codegen script; full owns operational scripts through the generic path (ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4))."
726
765
  # The app's operational scripts only (`*.mjs`, `*.cjs`, `*.ps1`, `*.sh`), or nothing: an empty folder is `scripts/.gitkeep`, which
727
766
  # `starci app scaffold` writes. A spec or a test (BE_SPEC_PLACEMENT, FE_NO_TESTS), a check or lint source (`check-*`, `eslint-local-rules*`,
728
767
  # a local eslint plugin: HFS_REPO_LOCAL_CHECK) and anything that re-implements a canon check is not an operational script and has no place here.
@@ -807,7 +846,7 @@ slots:
807
846
  - id: app.starcistacks
808
847
  since: 3.1.0
809
848
  profiles: [app]
810
- editionWhy: "owner:lite-production-swarm: lite requires declared docker-swarm production with Supabase backing; owner:dev-docker-prod-swarm keeps dependencies in Docker Linux containers in dev and the app and dependencies on Swarm in prod except hosted Supabase."
849
+ editionWhy: "owner:lite-production-swarm: lite requires declared docker-swarm production with its PostgreSQL as a stack service; owner:dev-docker-prod-swarm keeps dependencies in Docker Linux containers in dev and the app and dependencies on Swarm in prod."
811
850
  exemplifiedBy:
812
851
  - {path: packages/hfs/templates/app/skeleton/.starcistacks/, edition: full}
813
852
  - {path: packages/hfs/templates/app/skeleton-lite/.starcistacks/, edition: lite}
@@ -822,7 +861,7 @@ slots:
822
861
  "<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.enc", "<env>/secrets/.gitkeep", "<env>/seeds/**"]
823
862
  forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
824
863
  # Owner ruling 2026-10-14: lite declares docker-swarm production, app runtime config, sealed custody and seed data.
825
- # Supabase supplies the database and broker; production has no Compose or host cron.
864
+ # The db service supplies the database and broker (pgmq, pg_cron); production has no Compose or host cron.
826
865
  lite:
827
866
  requires: [application-stacks.yaml]
828
867
  allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/swarm/**", "<env>/infra/metadata.json",
@@ -885,122 +924,78 @@ slots:
885
924
  goesTo: ".starcistacks/<env>/secrets/<slug>.enc (at the app root); decrypted values only in memory or under %LOCALAPPDATA%/StarCi/secrets/<project>/<env>/ via *_FILE"
886
925
  rules: [HFS_PLAINTEXT_SECRET]
887
926
 
888
- # ----- app root: the Supabase project of an app whose connection declares provider: supabase (every lite app) -------
889
- - id: app.supabase
890
- profiles: [app]
891
- exemplifiedBy:
892
- - packages/hfs/templates/app/skeleton-lite/supabase/
893
- - {path: packages/hfs/src/hfs-database-access.spec.mjs, edition: full}
894
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): Supabase is required in lite; full enables it only through a declared connection."
895
- # The one Supabase project directory, at the app root beside be/, fe/ and .starcistacks (never under a side).
896
- path: "supabase/"
897
- presence: opt-in # enabled by a connection with provider: supabase; required under edition lite
898
- provider: supabase
899
- litePresence: required
900
- tracked: tracked
901
- tier: none
902
- tests: none
903
- since: 2.1.0
904
- - id: app.supabase.config
927
+ # ----- app root: the database of a lite app (ruling:lite-self-hosted-postgres-passport-minio): a self-hosted PostgreSQL in the app's
928
+ # stack (the `db` service, image supabase/postgres for pg_cron, pg_net, pgmq, vector, unaccent and pgcrypto), migrated by
929
+ # `starci app db migrate --url-env <NAME>`. A full app keeps its TypeORM migrations under be/ and has no such folder.
930
+ - id: app.db
905
931
  profiles: [app]
932
+ editions: [lite]
906
933
  exemplifiedBy:
907
- - packages/hfs/templates/app/skeleton-lite/supabase/config.toml
908
- - {path: packages/hfs/src/db-config-policy.spec.mjs, edition: full}
909
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite requires Supabase configuration; full requires it only when Supabase is declared."
910
- # The project configuration: auth keys are env(NAME) references, never literals (L08 judges its policy keys).
911
- path: "supabase/config.toml"
912
- presence: opt-in
913
- provider: supabase
914
- litePresence: required
934
+ - packages/hfs/templates/app/skeleton-lite/db/
935
+ # The one database directory, at the app root beside be/, fe/ and .starcistacks (never under a side).
936
+ path: "db/"
937
+ presence: required
915
938
  tracked: tracked
916
939
  tier: none
917
940
  tests: none
918
- since: 2.1.0
919
- - id: app.supabase.migrations
941
+ since: 3.9.0
942
+ - id: app.db.migrations
920
943
  profiles: [app]
944
+ editions: [lite]
921
945
  exemplifiedBy:
922
- - packages/hfs/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql
923
- - {path: packages/hfs/src/db-migration-shape.spec.mjs, edition: full}
924
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite uses Supabase schema authority and requires a migration; full opts into that provider."
925
- # The schema authority of an app on Supabase: forward-only SQL migrations named <timestamp14>_<kebab-name>.sql,
926
- # one file per change, immutable once on origin/main (L02-L08 judge them).
927
- path: "supabase/migrations/<stamp>_<name>.sql"
928
- presence: opt-in
929
- provider: supabase
930
- litePresence: required
931
- lite: {minInstances: 1}
946
+ - packages/hfs/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql
947
+ - {path: packages/hfs/src/db-migration-shape.spec.mjs, edition: lite}
948
+ # The schema authority of a lite app: forward-only SQL migrations named <timestamp14>_<kebab-name>.sql, one file per change,
949
+ # immutable once on origin/main, applied in order by `starci app db migrate` and recorded in the history table.
950
+ path: "db/migrations/<stamp>_<name>.sql"
951
+ presence: required
952
+ minInstances: 1
932
953
  tracked: tracked
933
954
  tier: none
934
955
  tests: none
935
- since: 2.1.0
936
- - id: app.supabase.migrations.archive
956
+ since: 3.9.0
957
+ rules: [DB_MIGRATION_SHAPE, LITE_DB_MIGRATION]
958
+ - id: app.db.migrations.archive
937
959
  profiles: [app]
938
- # The migrations an existing app retired at its cutover (owner ruling 2026-10-13, migration baseline policy): the live schema is
939
- # dumped into ONE baseline migration written to the current standard, marked applied on production without a re-run, and the
940
- # older files move here. The Supabase CLI applies only supabase/migrations/<stamp>_<name>.sql, never this folder, and no
941
- # migration rule (L02-L08) judges it; new migrations append after the baseline.
942
- path: "supabase/migrations/_archive/"
943
- presence: opt-in
944
- provider: supabase
960
+ editions: [lite]
961
+ # The migrations an app retired at a baseline cutover: the live schema is dumped into ONE baseline migration, marked applied
962
+ # without a re-run, and the older files move here. `starci app db migrate` never applies this folder and no migration rule judges it.
963
+ path: "db/migrations/archive/"
964
+ presence: optional
945
965
  tracked: tracked
946
966
  tier: none
947
967
  tests: none
948
- since: 3.0.0
949
- - id: app.supabase.seed
968
+ since: 3.9.0
969
+ - id: app.db.seed
950
970
  profiles: [app]
951
- path: "supabase/seed.sql"
971
+ editions: [lite]
972
+ path: "db/seed.sql"
952
973
  presence: optional # synthetic dev data only; a lite app seeds nothing it cannot regenerate
953
974
  tracked: tracked
954
975
  tier: none
955
976
  tests: none
956
- since: 2.1.0
957
- - id: app.supabase.types
958
- profiles: [app]
959
- templateNa: "Database row types are generated from the app's Supabase schema by contract:emit; a hand-authored scaffold template would invent the schema contract."
960
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite requires generated Supabase row types; full enables them with the provider."
961
- # The generated Database types (database.types.ts) contract:emit writes from the migrations; both sides read it
962
- # (sides.<side>.reads supabase/types/); drift against regeneration is L09.
963
- path: "supabase/types/database.types.ts"
964
- presence: opt-in
965
- provider: supabase
966
- litePresence: required
967
- tracked: tracked
968
- tier: none
969
- tests: none
970
- since: 2.1.0
971
- exemplifiedBy:
972
- - {path: packages/hfs/src/db-types-returned-collector.spec.mjs, edition: full}
973
- - id: app.supabase.templates
977
+ since: 3.9.0
978
+ - id: app.db.bootstrap
974
979
  profiles: [app]
975
- path: "supabase/templates/*.html"
976
- presence: optional # the auth e-mail templates Supabase serves
980
+ editions: [lite]
981
+ # The managed pre-migration bootstrap (templates/app/db-bootstrap): the auth, storage and extensions schemas, auth.users with the
982
+ # GoTrue column set, auth.uid/role/jwt stubs, the NOLOGIN browser roles and the realtime publication, so migrations written for a
983
+ # hosted Supabase apply to the self-hosted db service. `starci app db migrate` applies it before the first migration of an empty database.
984
+ path: "db/bootstrap.sql"
985
+ presence: required
977
986
  tracked: tracked
987
+ managedBy: db-bootstrap
978
988
  tier: none
979
989
  tests: none
980
- since: 2.1.0
981
- - id: app.supabase.local-state
982
- profiles: [app]
983
- path: "supabase/{.temp,.branches}/"
984
- presence: optional
985
- tracked: ignored # the local CLI's working state: exists, never committed
986
- tier: none
987
- tests: none
988
- since: 2.1.0
989
- - id: app.supabase.functions
990
- profiles: [app]
991
- path: "supabase/functions/"
992
- presence: forbidden # a Deno runtime is a second language runtime outside the lint canon and the slot machine (lite v1)
993
- tracked: external
994
- tier: none
995
- tests: none
996
- goesTo: "a be feature: webhooks/<provider> for a third-party callback, api for an endpoint, a job of a queue (BullMQ on the declared Redis, its tick enqueued through the queue outbox) or a cli command on a schedule (pg_cron / pg_net)"
997
- since: 2.1.0
990
+ since: 3.9.0
991
+ rules: [HFS_MANAGED_FILE_DRIFT]
992
+
998
993
 
999
994
  # ----- side root (both sides): what a standalone repository root holds, less the app-root files ----------
1000
995
  - id: repo.side-root-forbidden
1001
996
  since: 3.1.0
1002
997
  profiles: [be, fe]
1003
- path: "{package.json,package-lock.json,hfs.json,README.md,.gitignore,.gitattributes,.husky/,.github/,.starciwork/,.starcistacks/,supabase/,.sops.yaml,sonar-project.properties,codecov.yml,.prettierrc,.prettierignore,scripts/}"
998
+ path: "{package.json,package-lock.json,hfs.json,README.md,.gitignore,.gitattributes,.husky/,.github/,.starciwork/,.starcistacks/,db/,.sops.yaml,sonar-project.properties,codecov.yml,.prettierrc,.prettierignore,scripts/}"
1004
999
  presence: forbidden
1005
1000
  tracked: external
1006
1001
  tier: none
@@ -1280,8 +1275,8 @@ slots:
1280
1275
  - {path: packages/hfs/src/mcp-tools-snapshot.spec.mjs, editions: [lite, full]}
1281
1276
 
1282
1277
  # ----- BE: apps (one slot per app kind; a new kind = a new slot, minor bump) ---------------------------
1283
- # The files of every app (owner ruling 2026-10-14, R245 and R246, since 3.1.0), each with a line budget that
1284
- # BE_APP_COMPOSITION_BUDGET holds absolutely (not a ratchet):
1278
+ # The files of every app (owner ruling 2026-10-14, R245 and R246, since 3.1.0), each with a line budget (lines excluding
1279
+ # import declarations, multi-line imports included) that BE_APP_COMPOSITION_BUDGET holds absolutely (not a ratchet):
1285
1280
  # main.ts the entry: builds EnvSource once, imports and calls read<App>Env from <app>.env.ts, builds options and boots.
1286
1281
  # <app>.env.ts exports interface <App>Env and read<App>Env(env: EnvSource): <App>Env (for example ApiEnv/readApiEnv);
1287
1282
  # fail-fast environment parsing only, via EnvSource and unchanged capability parse helpers; no localhost
@@ -1331,7 +1326,7 @@ slots:
1331
1326
  - id: be.app.cli
1332
1327
  since: 3.1.0
1333
1328
  profiles: [be]
1334
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): Supabase CLI owns lite migrations; a lite cli app is optional for declared operator commands."
1329
+ editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): `starci app db migrate` owns lite migrations; a lite cli app is optional for declared operator commands."
1335
1330
  # The ONE app of every one-off action (migrate, seed, sync, backup, any operator command): be/apps/cli, on nest-commander.
1336
1331
  # main.ts boots with CommandFactory.run (BE_CLI_BOOTSTRAP); app.module.ts composes the platform and the cli feature root
1337
1332
  # (src/features/cli/cli.module.ts); cli.env.ts exports CliEnv and readCliEnv(env: EnvSource): CliEnv, and cli.options.ts carries the env-derived values,
@@ -1341,7 +1336,7 @@ slots:
1341
1336
  path: "apps/<app>/src/"
1342
1337
  appKind: cli
1343
1338
  presence: opt-in
1344
- litePresence: optional # lite: schema migration is the Supabase CLI's job; a cli app exists only when the app declares one
1339
+ litePresence: optional # lite: schema migration is `starci app db migrate`'s job; a cli app exists only when the app declares one
1345
1340
  tracked: tracked
1346
1341
  tier: app
1347
1342
  owner: true
@@ -1692,7 +1687,7 @@ slots:
1692
1687
  - id: be.persistence
1693
1688
  since: 3.1.0
1694
1689
  profiles: [be]
1695
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): Supabase migrations own lite tables, with SQL and row types here; full keeps TypeORM entities and migrations here."
1690
+ editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): db/migrations own lite tables and both editions map them with TypeORM entities here (ruling:lite-persistence-typeorm-entities); full also keeps its TypeORM migrations here."
1696
1691
  path: "src/modules/{domain,platform,projections}/<capability>/persistence/"
1697
1692
  # R178: the platform capabilities whose tables exist on EVERY connection that uses them (the event-bus outbox, the inbox of every service, the job table); their arrays may be
1698
1693
  # registered on several declared connections. Any other capability registers its arrays on exactly one (BE_SCHEMA_OWNER).
@@ -1703,15 +1698,17 @@ slots:
1703
1698
  requires: [connection.ts] # the capability's <c>Entities / <c>Migrations arrays (the owner index re-exports the two arrays by name); no CONNECTION alias: the connection is the one the apps register the arrays on
1704
1699
  allows: ["entities/<table>.entity.ts", "migrations/<epochMs13>-<kebab-name>.ts", "<name>.sql.ts", "<name>.rows.ts", connection.ts]
1705
1700
  forbids: [index.ts, "<name>.repository.ts"]
1706
- # Lite keeps the shared-EntityManager persistence (raw SqlText + rows types over the generated Database row types) but the
1707
- # schema authority is supabase/migrations (ruleParams.be.lite.schemaAuthority): no TypeORM entity and no be migration.
1701
+ # Lite maps its tables with the same TypeORM entities as full (ruling:lite-persistence-typeorm-entities): each capability's
1702
+ # persistence/entities/<table>.entity.ts and an entities/index.ts exporting its <c>Entities array, which platform/database
1703
+ # lists by name; the schema authority stays db/migrations (ruleParams.be.lite.schemaAuthority), so no be migration and no
1704
+ # raw SQL file (R334 BE_PERSISTENCE_NO_RAW_SQL).
1708
1705
  lite:
1709
- requires: [] # no TypeORM arrays to re-export: the persistence of a lite capability is its sql and rows files
1710
- allows: ["<name>.sql.ts", "<name>.rows.ts", connection.ts]
1711
- forbids: [index.ts, "<name>.repository.ts", entities/, migrations/]
1706
+ requires: []
1707
+ allows: ["entities/<table>.entity.ts", entities/index.ts, connection.ts]
1708
+ forbids: [index.ts, "<name>.repository.ts", migrations/, "<name>.sql.ts", "<name>.rows.ts"]
1712
1709
  tests: none
1713
1710
  coverage: none
1714
- rules: [BE_SCHEMA_OWNER, BE_SCHEMA_AUTHORITY, BE_SQL_OUTSIDE_PERSISTENCE, BE_SQL_TABLE_OWNER, BE_SQL_RETURNING_SHAPE, BE_CONTEXT_COUPLING, BE_CONTEXT_PLATFORM_TABLES]
1711
+ rules: [BE_SCHEMA_OWNER, BE_SCHEMA_AUTHORITY, BE_SQL_OUTSIDE_PERSISTENCE, BE_PERSISTENCE_NO_RAW_SQL, BE_SQL_TABLE_OWNER, BE_SQL_RETURNING_SHAPE, BE_CONTEXT_COUPLING, BE_CONTEXT_PLATFORM_TABLES]
1715
1712
  - id: be.platform
1716
1713
  since: 3.1.0
1717
1714
  profiles: [be]
@@ -1734,6 +1731,21 @@ slots:
1734
1731
  coverage: required
1735
1732
  budget: {indexExports: 60}
1736
1733
  rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE]
1734
+ - id: be.platform.database.entities
1735
+ since: 3.10.0
1736
+ profiles: [be]
1737
+ editions: [lite]
1738
+ editionWhy: "lite registers the one PRIMARY_ENTITIES list on its single primary connection; full registers each capability's <c>Entities per connection from persistence/connection.ts."
1739
+ path: "src/modules/platform/database/entities.ts"
1740
+ presence: optional
1741
+ tracked: tracked
1742
+ tier: inherit
1743
+ role: entity-registry
1744
+ importedBy: ["apps/*/src/compose/*.ts", "apps/*/src/app.module.ts", "src/tests/world/**"]
1745
+ tests: none
1746
+ coverage: none
1747
+ why: "the entity registry (persistence.md BE-PERSISTENCE-3): it lists every capability's <c>Entities array by name, no glob, as PRIMARY_ENTITIES, the one list the primary connection maps, and exports nothing else. It is the one platform file that imports domain and platform capabilities, each import naming only that capability's <c>Entities from its index.ts (BE_TIER_DIRECTION, ARCH_OWNER_EXPORT_BYPASS); DatabaseModule and the platform/database index never import it, and only the files importedBy names (app composition and the test world) import it, by the deep path @modules/platform/database/entities (BE_PUBLIC_SURFACE), since database.module, this list and the capability indexes would otherwise form a cycle that fails at boot"
1748
+ rules: [BE_TIER_DIRECTION, BE_PUBLIC_SURFACE, BE_SCHEMA_OWNER]
1737
1749
  - id: be.platform.logging
1738
1750
  since: 3.5.0
1739
1751
  profiles: [be]
@@ -1762,7 +1774,7 @@ slots:
1762
1774
  owner: true
1763
1775
  lite:
1764
1776
  why: >-
1765
- the same EventPublisher and EventConsumer ports on Supabase Queues (pgmq): the outbox relay sends each committed row to the
1777
+ the same EventPublisher and EventConsumer ports on pgmq queues: the outbox relay sends each committed row to the
1766
1778
  pgmq queue of every consumer group of its topic through the shared EntityManager, a consumer reads with a visibility
1767
1779
  timeout, retries by letting it lapse and archives the message on dead letter (read_ct over the budget); a single api may
1768
1780
  deliver in process. No Kafka (knowledge/patterns/be/event-bus.yaml liteAdapter)
@@ -1776,7 +1788,7 @@ slots:
1776
1788
  - id: be.platform.queue
1777
1789
  since: 3.1.0
1778
1790
  profiles: [be]
1779
- editionWhy: "ruling:R212/case-10: both editions implement the queue ports with BullMQ on Redis; lite declares its Redis in hfs.json sides.be.backing and keeps the outbox table in a supabase migration."
1791
+ editionWhy: "ruling:R212/case-10: both editions implement the queue ports with BullMQ on Redis; lite declares its Redis in hfs.json sides.be.backing and keeps the outbox table in a db migration."
1780
1792
  path: "src/modules/platform/queue/"
1781
1793
  presence: opt-in
1782
1794
  pattern: queue # the ONE BullMQ owner: the outbox relay into queues (jobId = outbox id) and job schedulers (knowledge/patterns/be/queues.yaml)
@@ -1787,7 +1799,7 @@ slots:
1787
1799
  why: >-
1788
1800
  the same QueueOutbox and QueueWorkerRegistry ports and full's BullMQ adapter on the declared Redis (backing: [redis]): the
1789
1801
  relay adds each committed outbox row with the outbox id as the job id, workers retry with exponential backoff and keep a
1790
- job that exhausts its attempts as failed (the dead letter); the outbox table comes from a supabase migration, and a
1802
+ job that exhausts its attempts as failed (the dead letter); the outbox table comes from a db migration, and a
1791
1803
  schedule is a BullMQ job scheduler or a pg_cron tick that inserts one outbox row (knowledge/patterns/be/queues.yaml liteAdapter)
1792
1804
  requires: [index.ts, queue.module.ts, queue.port.ts, queue.contracts.ts, queue-outbox.service.ts, queue-outbox.service.spec.ts]
1793
1805
  tests: unit-beside
@@ -1881,7 +1893,7 @@ slots:
1881
1893
  - id: be.platform.jobs
1882
1894
  since: 3.1.0
1883
1895
  profiles: [be]
1884
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite persists fenced claims in Supabase migrations and dispatches through the BullMQ queue on its declared Redis (ruling:R212/case-10); full uses TypeORM schema authority and the same queue adapter."
1896
+ editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite persists fenced claims in db migrations and dispatches through the BullMQ queue on its declared Redis (ruling:R212/case-10); full uses TypeORM schema authority and the same queue adapter."
1885
1897
  path: "src/modules/platform/jobs/"
1886
1898
  presence: opt-in
1887
1899
  pattern: fenced-job # the ONE fencing primitive: the job entity, the claim that bumps the token, guarded writes (knowledge/patterns/be/jobs.yaml)
@@ -1896,7 +1908,7 @@ slots:
1896
1908
  - id: be.projections
1897
1909
  since: 3.1.0
1898
1910
  profiles: [be]
1899
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite projection tables live in Supabase migrations; full keeps the TypeORM projection entity beside its projection."
1911
+ editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite projection tables live in db migrations; full keeps the TypeORM projection entity beside its projection."
1900
1912
  path: "src/modules/projections/<name>/"
1901
1913
  presence: opt-in
1902
1914
  pattern: projection # a read model: only its projection writes its table; recompute is idempotent and replayable; readers use get*
@@ -1905,7 +1917,7 @@ slots:
1905
1917
  owner: true
1906
1918
  requires: [index.ts, "<name>.module.ts", "<name>.projection.ts", "<name>.projection-entity.ts"]
1907
1919
  allows: [index.ts, "<name>.module.ts", "<name>.module-definition.ts", "<name>.options.ts", "<name>.projection.ts", "<name>.projection-entity.ts", "<name>.contracts.ts", persistence/]
1908
- lite: # the read model's table is a supabase migration with its rows type (lite's schema authority): no TypeORM projection entity
1920
+ lite: # the read model's table is a db migration (lite's schema authority) mapped by its entity under persistence/entities/ (R334)
1909
1921
  requires: [index.ts, "<name>.module.ts", "<name>.projection.ts"]
1910
1922
  allows: [index.ts, "<name>.module.ts", "<name>.module-definition.ts", "<name>.options.ts", "<name>.projection.ts", "<name>.contracts.ts", persistence/]
1911
1923
  tests: unit-beside # a measured slot: <name>.projection.spec.ts beside its projection is legal (the file owes 100 per file; a spec of its own is optional)
@@ -1938,28 +1950,44 @@ slots:
1938
1950
  why: a self-hosted ML model client is an integration; weights and datasets are never in git, and a language model is called through the ai-router gateway by the integrations/ai LangChain client (BE-AI-1, BE-AI-3)
1939
1951
  rules: [BE_TIER_DIRECTION, BE_INFRA_OWNER, BE_INTEGRATION_SPEC_MISSING]
1940
1952
 
1941
- - id: be.integrations.supabase
1953
+ - id: be.integrations.passport
1954
+ profiles: [be]
1955
+ editions: [lite]
1956
+ exemplifiedBy:
1957
+ - packages/hfs/templates/be/skeleton-lite/src/modules/integrations/passport/
1958
+ # The one owner of the lite identity libraries (ruleParams.be.infraOwners: @nestjs/passport, passport, passport-google-oauth20,
1959
+ # passport-local, @nestjs/jwt, jose, bcryptjs): the Google OAuth 2.0 strategy (state and PKCE), the local email and password
1960
+ # strategy (bcrypt), the app's own asymmetric access tokens and their JWKS, and the rotating refresh cookie. domain/identity
1961
+ # verifies a token through this integration's verifier and builds the principal (contexts/code/be/api-auth.md).
1962
+ path: "src/modules/integrations/passport/"
1963
+ presence: required
1964
+ tracked: tracked
1965
+ tier: integrations
1966
+ owner: true
1967
+ 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
1968
+ allows: [index.ts, passport.config.ts, passport.options.ts, passport.decorators.ts, passport.tokens.ts, passport.password.ts, passport.cookie.ts, strategies/, errors/]
1969
+ tests: unit-beside
1970
+ 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]
1971
+ coverage: required
1972
+ since: 3.9.0
1973
+ - id: be.integrations.object-storage
1942
1974
  profiles: [be]
1943
1975
  exemplifiedBy:
1944
- - packages/hfs/templates/be/skeleton-lite/src/modules/integrations/supabase/
1945
- - {path: packages/hfs/src/be-supabase-jwt-verified.spec.mjs, edition: full}
1946
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite requires the Supabase Auth and Storage integration; full opts in through a connection."
1947
- # The only be import of @supabase/* (L10): the Auth admin and Storage admin client, plus the JWKS-based verification of
1948
- # the Supabase access token the guard chain reads (L16). The connection itself is ordinary Postgres (the shared
1949
- # EntityManager of the declared connection); what lives here are the service calls no database role covers.
1950
- path: "src/modules/integrations/supabase/"
1951
- presence: opt-in # enabled by a connection with provider: supabase; required under edition lite
1952
- provider: supabase
1976
+ - packages/hfs/templates/be/skeleton-lite/src/modules/integrations/object-storage/
1977
+ # The one owner of the S3 client (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) over S3-compatible storage (MinIO in
1978
+ # lite): the ObjectStorage port with presigned PUT and GET; no other module imports an S3 SDK or minio.
1979
+ path: "src/modules/integrations/object-storage/"
1980
+ presence: optional
1953
1981
  litePresence: required
1954
1982
  tracked: tracked
1955
1983
  tier: integrations
1956
1984
  owner: true
1957
- requires: [index.ts, supabase.config.ts, supabase.options.ts, supabase.decorators.ts, supabase.client.ts, supabase.jwks.ts] # Secrets, no defaults (R44); the options and the named injector (R83); the admin client; the token verification
1958
- allows: [index.ts, supabase.config.ts, supabase.options.ts, supabase.decorators.ts, supabase.client.ts, supabase.jwks.ts, errors/]
1985
+ requires: [index.ts, storage.config.ts, storage.options.ts, storage.decorators.ts, storage.client.ts]
1986
+ allows: [index.ts, storage.config.ts, storage.options.ts, storage.decorators.ts, storage.client.ts, errors/]
1959
1987
  tests: unit-beside
1960
- rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT]
1988
+ rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INFRA_OWNER, BE_STORAGE_OWNER]
1961
1989
  coverage: required
1962
- since: 2.1.0
1990
+ since: 3.9.0
1963
1991
 
1964
1992
  # ----- BE: tests (owner test layout 2026-09-30). Unit specs are <name>.service.spec.ts beside their service and nothing else; everything else
1965
1993
  # lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
@@ -1967,15 +1995,15 @@ slots:
1967
1995
  - id: be.tests.world
1968
1996
  since: 3.1.0
1969
1997
  profiles: [be]
1970
- editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite integration proofs use the local Supabase stack and network fakes; full also boots apps and self-hosted services."
1998
+ editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite integration proofs use the local db service and network fakes; full also boots apps and self-hosted services."
1971
1999
  path: "src/tests/world/"
1972
2000
  presence: optional
1973
2001
  tracked: tracked
1974
2002
  tier: e2e
1975
2003
  lite:
1976
2004
  why: >-
1977
- the world of the optional integration specs of a lite app: global-setup.ts connects to the local Supabase CLI stack
1978
- (`starci supabase start`, with the migrations applied by the local stack) and starts no container, no toxiproxy and no stack of its
2005
+ the world of the optional integration specs of a lite app: global-setup.ts connects to the local db service
2006
+ (`npm run db:start`, with the migrations applied by `npm run db:push`) and starts no container, no toxiproxy and no stack of its
1979
2007
  own; use-test-world.ts exports useTestWorld({ modules: [...] }) -> world.db.<connection> (the shared EntityManager) and
1980
2008
  world.fake.<provider> (a network-edge fake of an external SaaS). There is no world.apps (no e2e) and no world.infra
1981
2009
  (no self-hosted service to cut)
@@ -2044,7 +2072,7 @@ slots:
2044
2072
  - id: be.tests.integration
2045
2073
  since: 3.1.0
2046
2074
  profiles: [be]
2047
- editionWhy: "Lite integration specs use the local Supabase CLI stack without containers or toxiproxy (ruling:R212/case-1, amended by ruling:R212/case-2 (owner:lite-production-swarm) and ruling:R212/case-4, with contracts allowed by owner:lite-contract-specs); R284 requires declared-pattern proofs there. Full additionally proves infrastructure outages through its world (R112)."
2075
+ editionWhy: "Lite integration specs use the local db service without containers or toxiproxy (ruling:R212/case-1, amended by ruling:R212/case-2 (owner:lite-production-swarm) and ruling:R212/case-4, with contracts allowed by owner:lite-contract-specs); R284 requires declared-pattern proofs there. Full additionally proves infrastructure outages through its world (R112)."
2048
2076
  path: "src/tests/integration/<capability>/*.integration-spec.ts"
2049
2077
  presence: optional
2050
2078
  tracked: tracked
@@ -2053,8 +2081,8 @@ slots:
2053
2081
  coverage: none
2054
2082
  lite:
2055
2083
  why: >-
2056
- optional under lite: one capability module through useTestWorld({ modules }) against the local Supabase CLI stack
2057
- (`starci supabase start`), run by test:integration; no container, no toxiproxy and no outage through the world (R112 is full's)
2084
+ optional under lite: one capability module through useTestWorld({ modules }) against the local db service
2085
+ (`npm run db:start`), run by test:integration; no container, no toxiproxy and no outage through the world (R112 is full's)
2058
2086
  why: one capability module through useTestWorld({ modules }), no HTTP door of ours (SQL, transactions, concurrency, inbox claims on the real database; and every integration client, `<capability>` being its provider folder, against our real infra, a real peer app or the library fake at the network edge, with its success, its ErrorCode refusal mapping and an outage through the world, R112); run by test:integration
2059
2087
  rules: [BE_TEST_TOPOLOGY, BE_INTEGRATION_SPEC_MISSING]
2060
2088
  - id: be.tests.e2e
@@ -2067,7 +2095,7 @@ slots:
2067
2095
  tracked: tracked
2068
2096
  tier: e2e
2069
2097
  tests: e2e
2070
- goesTo: "the full edition: a lite app has no e2e world; its logic is proven by unit specs and its database by optional integration specs on `starci supabase start`"
2098
+ goesTo: "the full edition: a lite app has no e2e world; its logic is proven by unit specs and its database by optional integration specs on `npm run db:start`"
2071
2099
  coverage: none
2072
2100
  why: flow e2e through useTestWorld({ apps }); external services are the world's network fakes; run by test:e2e
2073
2101
  rules: [HFS_CI_E2E_AUTOMATIC, BE_TEST_TOPOLOGY, BE_E2E_FLOW]
@@ -2126,7 +2154,7 @@ slots:
2126
2154
  tracked: tracked
2127
2155
  tier: route
2128
2156
  # ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4) (lite = full): routes are thin and the front end serves no API - health/live is the one
2129
- # route handler of this slot, the Supabase auth callback is fe.route.callback, and no file holds a Server Action
2157
+ # route handler of this slot, the OAuth callback is a back-end door, and no file holds a Server Action
2130
2158
  # (FE_ROUTE_HANDLER_FORBIDDEN, FE_SERVER_ACTION_FORBIDDEN).
2131
2159
  allows: ["[locale]/**/{page,layout,template,loading,error,not-found,default}.tsx", "[locale]/providers.tsx",
2132
2160
  global-error.tsx, page.tsx, # the root page.tsx only redirects (FE_ROUTE_FILES_THIN)
@@ -2157,20 +2185,6 @@ slots:
2157
2185
  tests: none
2158
2186
  goesTo: "apps/<app>/src/proxy.ts: Next >= 16 reads proxy.ts and refuses middleware.ts"
2159
2187
  rules: [FE_NEXT_CONVENTIONS]
2160
- - id: fe.route.callback
2161
- profiles: [fe]
2162
- # The auth callback of a Supabase session (the PKCE code exchange), kept only when the auth flow truly needs a server exchange
2163
- # (prefer the client-side PKCE exchange in fe.modules.auth): the one non-health route handler of an app with a Supabase
2164
- # connection; an app without one has no route handler in fe/ (L15).
2165
- path: "apps/<app>/src/app/auth/callback/route.ts"
2166
- presence: opt-in
2167
- provider: supabase
2168
- tracked: tracked
2169
- tier: route
2170
- tests: none
2171
- since: 2.1.0
2172
- rules: [FE_ROUTE_HANDLER_FORBIDDEN, FE_CALLBACK_SHAPE, FE_SUPABASE_CLIENT_OWNER, FE_SERVICE_ROLE_FORBIDDEN]
2173
-
2174
2188
  - id: fe.feature
2175
2189
  since: 3.1.0
2176
2190
  profiles: [fe]
@@ -2502,18 +2516,17 @@ slots:
2502
2516
  - id: fe.modules.auth
2503
2517
  profiles: [fe]
2504
2518
  # The identity-provider client (fe-stack auth, FE-AUTH): the session, the access token the Apollo link attaches and the
2505
- # sign-in/sign-out calls. With a Supabase connection it is the ONLY fe importer of @supabase/supabase-js and uses its auth API
2506
- # only; Supabase on the front end never reads or writes data (owner ruling 2026-10-13).
2519
+ # 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
2520
+ # the back end's httpOnly cookie and the access token in memory (ruling:lite-self-hosted-postgres-passport-minio).
2507
2521
  path: "apps/<app>/src/modules/auth/"
2508
2522
  presence: optional
2509
2523
  tracked: tracked
2510
2524
  tier: modules
2511
2525
  owner: true
2512
2526
  requires: [index.ts]
2513
- entries: [server.ts] # the server-only PKCE exchange API used by the opted-in callback route
2514
2527
  tests: none
2515
2528
  since: 3.0.0
2516
- rules: [FE_SUPABASE_CLIENT_OWNER, FE_SERVICE_ROLE_FORBIDDEN]
2529
+ rules: [FE_STACK_FORBIDDEN]
2517
2530
  - id: fe.modules.toast
2518
2531
  profiles: [fe]
2519
2532
  # Mutation feedback (fe-stack toast, FE-TOAST): the HeroUI toast wrapper, showGraphQLToast over a GraphQLResponse and the