@starci/hfs 4.9.9 → 4.10.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 (241) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +20 -8
  3. package/db/backup-receipt.mjs +10 -47
  4. package/db/drift.mjs +289 -0
  5. package/db/dump.mjs +12 -11
  6. package/db/entity-list.mjs +9 -0
  7. package/db/migrate.mjs +84 -260
  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/lint/run.mjs +12 -2
  13. package/package.json +5 -3
  14. package/release/gate.mjs +82 -31
  15. package/release/index.mjs +19 -8
  16. package/release/push.mjs +4 -4
  17. package/release/records.mjs +54 -0
  18. package/release/urgent.mjs +129 -0
  19. package/runtime/knowledge/hfs/canon-pins.yaml +54 -24
  20. package/runtime/knowledge/hfs/design-truths.yaml +1 -1
  21. package/runtime/knowledge/hfs/fe-stack.yaml +4 -11
  22. package/runtime/knowledge/hfs/rules.yaml +250 -157
  23. package/runtime/knowledge/hfs/slots.yaml +199 -174
  24. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  25. package/runtime/knowledge/patterns/be/idempotency.yaml +3 -3
  26. package/runtime/knowledge/patterns/be/jobs.yaml +4 -4
  27. package/runtime/knowledge/patterns/be/projections.yaml +7 -5
  28. package/runtime/knowledge/patterns/be/queues.yaml +8 -8
  29. package/runtime/knowledge/patterns/be/reactors.yaml +2 -2
  30. package/runtime/knowledge/patterns/be/realtime.yaml +6 -6
  31. package/runtime/knowledge/patterns/be/saga.yaml +3 -3
  32. package/runtime/knowledge/patterns/be/webhooks.yaml +1 -1
  33. package/runtime/modules/kernel/command-policy.yaml +1 -1
  34. package/runtime/modules/kernel/failure-codes.yaml +86 -115
  35. package/runtime/modules/schemas/application-stacks.schema.yaml +5 -5
  36. package/runtime/scripts/hfs/app-wiring.mjs +23 -2
  37. package/runtime/scripts/hfs/architecture/canon-clones.mjs +1 -1
  38. package/runtime/scripts/hfs/architecture/{supabase-be.mjs → identity.mjs} +45 -15
  39. package/runtime/scripts/hfs/architecture/index.mjs +2 -3
  40. package/runtime/scripts/hfs/architecture/{supabase-ast.mjs → jwt-ast.mjs} +2 -103
  41. package/runtime/scripts/hfs/architecture/register-once.mjs +1 -1
  42. package/runtime/scripts/hfs/architecture/schema-owner.mjs +29 -4
  43. package/runtime/scripts/hfs/architecture/server-action.mjs +3 -4
  44. package/runtime/scripts/hfs/architecture/side-boundary.mjs +1 -1
  45. package/runtime/scripts/hfs/architecture/{supabase-keys.mjs → sql-keys.mjs} +1 -1
  46. package/runtime/scripts/hfs/architecture/sql-owner.mjs +25 -27
  47. package/runtime/scripts/hfs/architecture/transport-owner.mjs +7 -3
  48. package/runtime/scripts/hfs/check.mjs +8 -8
  49. package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
  50. package/runtime/scripts/hfs/db-baseline-verb.mjs +48 -30
  51. package/runtime/scripts/hfs/db-plan-verb.mjs +17 -106
  52. package/runtime/scripts/hfs/db-restamp-verb.mjs +11 -10
  53. package/runtime/scripts/hfs/declaration-shape.mjs +15 -18
  54. package/runtime/scripts/hfs/manifest-shape.mjs +6 -5
  55. package/runtime/scripts/hfs/rule-params-shape.mjs +4 -1
  56. package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -1
  57. package/runtime/scripts/hfs/rules/cli.mjs +1 -1
  58. package/runtime/scripts/hfs/rules/database-access.mjs +8 -74
  59. package/runtime/scripts/hfs/rules/database-baseline.mjs +3 -3
  60. package/runtime/scripts/hfs/rules/database-config.mjs +6 -214
  61. package/runtime/scripts/hfs/rules/database-constants.mjs +8 -10
  62. package/runtime/scripts/hfs/rules/database-migrations.mjs +3 -3
  63. package/runtime/scripts/hfs/rules/database-queues.mjs +4 -5
  64. package/runtime/scripts/hfs/rules/database-sql.mjs +1 -1
  65. package/runtime/scripts/hfs/rules/database.mjs +36 -39
  66. package/runtime/scripts/hfs/rules/edition.mjs +1 -1
  67. package/runtime/scripts/hfs/rules/{supabase-secrets.mjs → lite-secrets.mjs} +6 -6
  68. package/runtime/scripts/hfs/rules/script-hygiene.mjs +1 -10
  69. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +5 -4
  70. package/runtime/scripts/hfs/rules/stacks-release.mjs +3 -2
  71. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +2 -2
  72. package/runtime/scripts/hfs/rules/stacks.mjs +1 -1
  73. package/runtime/scripts/hfs/slot-app-view.mjs +1 -1
  74. package/runtime/scripts/hfs/slots.mjs +8 -2
  75. package/runtime/scripts/hfs/sql/pg-parse.mjs +2 -2
  76. package/runtime/scripts/lib/secret-patterns.mjs +2 -2
  77. package/runtime/scripts/lib/swarm-stack.mjs +7 -4
  78. package/scaffold/add-app.mjs +2 -10
  79. package/scaffold/add-cli-lite.mjs +6 -6
  80. package/scaffold/add-compose.mjs +1 -1
  81. package/scaffold/add-inbox-lite.mjs +4 -8
  82. package/scaffold/add-plan.mjs +1 -1
  83. package/scaffold/add-queue-lite.mjs +16 -24
  84. package/scaffold/add-table.mjs +34 -26
  85. package/scaffold/add.mjs +2 -2
  86. package/scaffold/app.mjs +19 -86
  87. package/scaffold/lite-exports.mjs +1 -1
  88. package/src/main.mjs +24 -22
  89. package/stack/cli.mjs +10 -2
  90. package/stack/deploy.mjs +21 -4
  91. package/stack/target-binding.mjs +0 -60
  92. package/stack/urgent-floor.mjs +147 -0
  93. package/sync/ci-commands.mjs +3 -1
  94. package/sync/index.mjs +32 -8
  95. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +4 -1
  96. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -17
  97. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +6 -8
  98. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +4 -1
  99. package/templates/app/codegen-lite/scripts/codegen.mjs +2 -39
  100. package/templates/app/db-bootstrap/db/bootstrap.sql +139 -0
  101. package/templates/app/gitignore-lite +0 -3
  102. package/templates/app/package-scripts/db.json +9 -0
  103. package/templates/app/package-scripts/package.json +0 -1
  104. package/templates/app/package-scripts-lite/package.json +1 -1
  105. package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
  106. package/templates/app/skeleton/.starcistacks/production/README.md +1 -1
  107. package/templates/app/skeleton/README.md +1 -1
  108. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +84 -19
  109. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +5 -5
  110. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +1 -1
  111. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +1 -1
  112. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +69 -6
  113. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -7
  114. package/templates/app/skeleton-lite/.starcistacks/production/README.md +6 -6
  115. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +1 -1
  116. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +2 -2
  117. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +69 -6
  118. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +17 -9
  119. package/templates/app/skeleton-lite/README.md +11 -11
  120. package/templates/app/skeleton-lite/{supabase → db}/migrations/__timestamp___baseline.sql +21 -1
  121. package/templates/app/tool-config-lite/prettierignore +0 -1
  122. package/templates/be/inbox/database-client.spec.ts.tpl +9 -5
  123. package/templates/be/inbox/database-client.ts.tpl +13 -4
  124. package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +47 -0
  125. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +3 -1
  126. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +2 -2
  127. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +6 -1
  128. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +39 -7
  129. package/templates/be/inbox/platform/migration.sql.tpl +22 -0
  130. package/templates/be/inbox/service.spec.ts.tpl +2 -2
  131. package/templates/be/inbox/service.ts.tpl +1 -2
  132. package/templates/be/patterns/jobs/platform-lite/job-claim.service.spec.ts.tpl +156 -0
  133. package/templates/be/patterns/jobs/platform-lite/job-claim.service.ts.tpl +114 -0
  134. package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +51 -0
  135. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.spec.ts.tpl +62 -0
  136. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.ts.tpl +20 -0
  137. package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +35 -0
  138. package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +4 -1
  139. package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +16 -5
  140. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +8 -8
  141. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +3 -3
  142. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +2 -2
  143. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -7
  144. package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +3 -0
  145. package/templates/be/skeleton/src/modules/platform/composition/index.ts +1 -1
  146. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +15 -1
  147. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.ts +10 -9
  148. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +1 -1
  149. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +34 -18
  150. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.ts +29 -8
  151. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +80 -4
  152. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.ts +39 -11
  153. package/templates/be/skeleton-lite/apps/api/Dockerfile +0 -1
  154. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +9 -5
  155. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +7 -4
  156. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +4 -4
  157. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +3 -3
  158. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +9 -13
  159. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +4 -2
  160. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +2 -2
  161. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +3 -2
  162. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +4 -4
  163. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +3 -3
  164. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +1 -1
  165. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -13
  166. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +3 -3
  167. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +4 -0
  168. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +22 -0
  169. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +25 -0
  170. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +12 -0
  171. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +9 -0
  172. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +35 -0
  173. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +22 -0
  174. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +12 -0
  175. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +23 -0
  176. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +29 -0
  177. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +22 -0
  178. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +63 -0
  179. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +11 -0
  180. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +32 -0
  181. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +69 -0
  182. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +37 -0
  183. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +36 -0
  184. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +59 -0
  185. package/templates/be/skeleton-lite/src/modules/platform/database/database.functions.ts +24 -0
  186. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +2 -1
  187. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +4 -2
  188. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +19 -6
  189. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +6 -0
  190. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  191. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +1 -1
  192. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +2 -2
  193. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +2 -2
  194. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
  195. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
  196. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +1 -1
  197. package/templates/be/table/entities.index.ts.tpl +4 -0
  198. package/templates/be/table/entity.ts.tpl +21 -0
  199. package/templates/be/table/index.ts.tpl +1 -0
  200. package/templates/be/table/module.ts.tpl +1 -1
  201. package/templates/be/table/service.api.ts.tpl +10 -11
  202. package/templates/be/table/service.ts.tpl +5 -6
  203. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +30 -0
  204. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +46 -14
  205. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +31 -0
  206. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +2 -5
  207. package/db/local-migrate.mjs +0 -194
  208. package/db/reset.mjs +0 -125
  209. package/emit/db-types.mjs +0 -271
  210. package/runtime/scripts/api/supabase/database-dump.mjs +0 -5
  211. package/runtime/scripts/api/supabase/database-push.mjs +0 -5
  212. package/runtime/scripts/api/supabase/database-query.mjs +0 -5
  213. package/runtime/scripts/api/supabase/lib.mjs +0 -55
  214. package/runtime/scripts/api/supabase/migration-repair.mjs +0 -5
  215. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +0 -50
  216. package/runtime/scripts/hfs/architecture/supabase.mjs +0 -226
  217. package/scaffold/supabase-ports.mjs +0 -26
  218. package/templates/app/package-scripts/supabase.json +0 -7
  219. package/templates/app/skeleton-lite/supabase/config.toml +0 -48
  220. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +0 -5
  221. package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +0 -33
  222. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +0 -33
  223. package/templates/be/inbox/sql.ts.tpl +0 -16
  224. package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +0 -33
  225. package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +0 -12
  226. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -16
  227. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +0 -6
  228. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +0 -27
  229. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +0 -24
  230. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +0 -8
  231. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +0 -17
  232. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +0 -32
  233. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +0 -24
  234. package/templates/be/table/rows.ts.tpl +0 -4
  235. package/templates/be/table/sql.api.ts.tpl +0 -7
  236. package/templates/be/table/sql.ts.tpl +0 -4
  237. package/templates/fe/auth/supabase-client.ts.tpl +0 -38
  238. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +0 -11
  239. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +0 -18
  240. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +0 -19
  241. /package/templates/app/skeleton-lite/{supabase → db}/seed.sql +0 -0
@@ -8,7 +8,8 @@
8
8
  # The environment input names a protected GitHub environment (required reviewers) that holds:
9
9
  # vars VPS_HOST, VPS_USER (the swarm manager); a variable named in deploy.passEnv, when the stack interpolates one
10
10
  # secrets SOPS_AGE_KEY (the age identity of .sops.yaml), VPS_SSH_KEY (CI-only deploy key), VPS_KNOWN_HOSTS (pinned host keys)
11
- # Nothing else is a secret: images are pushed with the workflow token. Dispatch inputs reach scripts only through env.
11
+ # Nothing else is a secret: images are pushed with the workflow token, which also reads the CI verdict of the tag's commit a production deploy
12
+ # waits for (requireCi deploy.prod). Dispatch inputs reach scripts only through env.
12
13
  name: deploy stack
13
14
 
14
15
  on:
@@ -48,6 +49,7 @@ jobs:
48
49
  permissions:
49
50
  contents: read
50
51
  packages: write
52
+ actions: read
51
53
  env:
52
54
  STACK_ENVIRONMENT: ${{ inputs.environment }}
53
55
  steps:{{stackGuard}}
@@ -88,6 +90,7 @@ jobs:
88
90
  - name: deploy the release
89
91
  env:
90
92
  XDG_DATA_HOME: ${{ runner.temp }}/starci-data
93
+ GH_TOKEN: ${{ github.token }}
91
94
  SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}{{stackImageEnv}}{{stackPassEnv}}
92
95
  run: |
93
96
  npx --no-install starci app stack deploy --env "$STACK_ENVIRONMENT" \{{stackImageArgs}}
@@ -17,7 +17,7 @@ concurrency:
17
17
  cancel-in-progress: false
18
18
 
19
19
  # The job shape of the StarCi CI standard (knowledge/hfs/ci-standard.yaml): fast, lint per side, the unit run in shards, one coverage merge, build,
20
- # sonar, and the database types of the lite edition. Timeouts and the shard count come from ci.jobs of .starcistacks/application-stacks.yaml (defaults in the standard).
20
+ # and sonar. Timeouts and the shard count come from ci.jobs of .starcistacks/application-stacks.yaml (defaults in the standard).
21
21
  jobs:
22
22
  fast:
23
23
  timeout-minutes: {{timeoutFast}}
@@ -110,19 +110,3 @@ jobs:
110
110
  NODE_OPTIONS: {{nodeOptions}}
111
111
 
112
112
  {{sonarJob}}
113
- db-types:
114
- timeout-minutes: {{timeoutDbTypes}}
115
- runs-on: ubuntu-latest
116
- steps:
117
- {{> app/ci-workflows/install-steps.yml}}
118
- - name: start Supabase
119
- run: npm run db:start
120
- - name: apply migrations
121
- run: npm run db:reset
122
- - name: regenerate database types
123
- run: npm run db:types
124
- - name: database types are committed
125
- run: git diff --exit-code -- supabase/types/database.types.ts
126
- - name: stop Supabase
127
- if: always()
128
- run: npm run db:stop
@@ -5,7 +5,7 @@ on:
5
5
  workflow_dispatch:
6
6
  inputs:
7
7
  environment:
8
- description: Protected environment with sealed Supabase custody under .starcistacks
8
+ description: Protected environment whose sealed custody under .starcistacks holds DATABASE_URL
9
9
  required: true
10
10
  type: environment
11
11
  backup_receipt:
@@ -45,7 +45,7 @@ jobs:
45
45
  uses: mdgreenwald/mozilla-sops-action@bb2d96a13ef438ad122e5827a57ebd75d6ff118d # v2.1.0
46
46
  with:
47
47
  version: v3.13.2
48
- - name: materialize sealed custody and push migrations
48
+ - name: materialize sealed custody and migrate the database
49
49
  env:
50
50
  XDG_DATA_HOME: ${{ runner.temp }}/starci-data
51
51
  shell: bash
@@ -57,9 +57,7 @@ jobs:
57
57
  secret_dir="$XDG_DATA_HOME/StarCi/secrets/{{project}}/$STACK_ENVIRONMENT"
58
58
  trap 'rm -rf -- "$secret_dir"' EXIT
59
59
  npx --no-install starci app secret materialize "$STACK_ENVIRONMENT"
60
- export SUPABASE_ACCESS_TOKEN="$(cat "$secret_dir/SUPABASE_ACCESS_TOKEN")"
61
- export SUPABASE_DB_PASSWORD="$(cat "$secret_dir/SUPABASE_DB_PASSWORD")"
62
- project_id="$(cat "$secret_dir/SUPABASE_PROJECT_ID")"
63
- npx --no-install supabase link --project-ref "$project_id"
64
- npm run db:push -- --dry-run
65
- npm run db:push
60
+ DATABASE_URL="$(cat "$secret_dir/DATABASE_URL")"
61
+ export DATABASE_URL
62
+ npx --no-install starci app db migrate --url-env DATABASE_URL
63
+ STARCI_DB_ARMED=1 npx --no-install starci app db migrate --url-env DATABASE_URL --execute
@@ -8,7 +8,8 @@
8
8
  # The environment input names a protected GitHub environment (required reviewers) that holds:
9
9
  # vars VPS_HOST, VPS_USER (the swarm manager); a variable named in deploy.passEnv, when the stack interpolates one
10
10
  # secrets SOPS_AGE_KEY (the age identity of .sops.yaml), VPS_SSH_KEY (CI-only deploy key), VPS_KNOWN_HOSTS (pinned host keys)
11
- # Nothing else is a secret: images are pushed with the workflow token. Dispatch inputs reach scripts only through env.
11
+ # Nothing else is a secret: images are pushed with the workflow token, which also reads the CI verdict of the tag's commit a production deploy
12
+ # waits for (requireCi deploy.prod). Dispatch inputs reach scripts only through env.
12
13
  name: deploy stack
13
14
 
14
15
  on:
@@ -48,6 +49,7 @@ jobs:
48
49
  permissions:
49
50
  contents: read
50
51
  packages: write
52
+ actions: read
51
53
  env:
52
54
  STACK_ENVIRONMENT: ${{ inputs.environment }}
53
55
  steps:{{stackGuard}}
@@ -88,6 +90,7 @@ jobs:
88
90
  - name: deploy the release
89
91
  env:
90
92
  XDG_DATA_HOME: ${{ runner.temp }}/starci-data
93
+ GH_TOKEN: ${{ github.token }}
91
94
  SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }}{{stackImageEnv}}{{stackPassEnv}}
92
95
  run: |
93
96
  npx --no-install starci app stack deploy --env "$STACK_ENVIRONMENT" \{{stackImageArgs}}
@@ -1,42 +1,5 @@
1
1
  // Generated by {{appBinary}} sync (app root). Do not edit: run "npx {{appBinary}} sync --write".
2
- // Database types hook, offline: the app compiles against the committed supabase/types/database.types.ts, which `npm run contract:emit`
3
- // regenerates from the local stack and stamps with the digest of the migrations it was generated from. This refuses a missing or
4
- // stale file, so lint, typecheck, dev and build never run against types older than the migrations; it starts nothing and needs no stack.
5
- // `--graphql` runs only the GraphQL wire types below (`npm run codegen:graphql`).
6
- import { createHash } from "node:crypto"
7
- import fs from "node:fs"
8
- import path from "node:path"
9
-
10
- const byCodeUnit = (left, right) => {
11
- if (left === right) return 0
12
- return left < right ? -1 : 1
13
- }
14
-
15
- const MIGRATIONS = path.join("supabase", "migrations")
16
- const TYPES = path.join("supabase", "types", "database.types.ts")
17
- const STAMP = /^\/\/ migrations-sha256: ([0-9a-f]{64})$/
18
-
19
- const fail = (message) => {
20
- console.error(`codegen: ${message}`)
21
- process.exit(1)
22
- }
23
-
24
- if (!process.argv.includes("--graphql")) {
25
- const hash = createHash("sha256")
26
- for (const name of (fs.existsSync(MIGRATIONS) ? fs.readdirSync(MIGRATIONS) : [])
27
- .filter((file) => file.endsWith(".sql"))
28
- .sort(byCodeUnit)) {
29
- hash.update(`${name}\0${fs.readFileSync(path.join(MIGRATIONS, name), "utf8").replace(/\r\n/g, "\n")}\0`)
30
- }
31
- const digest = hash.digest("hex")
32
-
33
- if (!fs.existsSync(TYPES))
34
- fail(`${TYPES} is missing; start the local stack and run \`npm run contract:emit\`, then commit it`)
35
- const stamped = STAMP.exec(fs.readFileSync(TYPES, "utf8").split(/\r?\n/, 1)[0])?.[1]
36
- if (stamped !== digest)
37
- fail(
38
- `${TYPES} is ${stamped === undefined ? "not stamped" : "older than the migrations"}; run \`npm run contract:emit\` and commit the regenerated types`,
39
- )
40
- }
2
+ // The GraphQL wire types of the front end (`npm run codegen`, also `npm run codegen:graphql`); a lite app generates no database types.
3
+ // {{> app/codegen-prelude.mjs.partial}}
41
4
 
42
5
  // {{> app/codegen-graphql.mjs.partial}}
@@ -0,0 +1,139 @@
1
+ -- Managed by starci app sync (slot app.db.bootstrap); do not edit: run "npx starci app sync --write".
2
+ -- The pre-migration bootstrap of a lite app's self-hosted PostgreSQL (ruling lite-self-hosted-postgres-passport-minio). Migrations written
3
+ -- for Supabase reference objects GoTrue and Supabase Storage created: the browser roles, the `auth` schema (auth.users, auth.identities,
4
+ -- auth.sessions, auth.uid/jwt/role) and the `storage` schema (storage.buckets, storage.objects). The supabase/postgres image ships only a partial
5
+ -- copy of them, and a lite app runs neither GoTrue nor storage-api: auth is the app's own (integrations/passport) and storage is the S3 endpoint
6
+ -- of integrations/object-storage. This file gives the migrations those objects, EMPTY, and nothing else. It is idempotent; `starci app db migrate`
7
+ -- applies it as the database owner before the first migration of an empty database.
8
+ --
9
+ -- auth.users keeps the GoTrue column set so restored rows and foreign keys from public tables stay valid; encrypted_password holds bcrypt hashes.
10
+ -- auth.uid(), auth.role() and auth.jwt() read the request.jwt.claims setting; the API connects as app_be and sets none, so they return NULL and
11
+ -- every policy that names them denies the browser roles, which have no door in lite.
12
+
13
+ create schema if not exists extensions;
14
+ create schema if not exists auth;
15
+ create schema if not exists storage;
16
+
17
+ do $roles$
18
+ begin
19
+ if not exists (select 1 from pg_roles where rolname = 'anon') then create role anon nologin noinherit; end if;
20
+ if not exists (select 1 from pg_roles where rolname = 'authenticated') then create role authenticated nologin noinherit; end if;
21
+ if not exists (select 1 from pg_roles where rolname = 'service_role') then create role service_role nologin noinherit bypassrls; end if;
22
+ end
23
+ $roles$;
24
+
25
+ grant usage on schema auth, storage to anon, authenticated, service_role;
26
+ grant usage on schema extensions to anon, authenticated, service_role;
27
+
28
+ -- auth.users: the full GoTrue column set (the image's old copy is extended in place).
29
+ create table if not exists auth.users (id uuid primary key);
30
+ alter table auth.users
31
+ add column if not exists instance_id uuid,
32
+ add column if not exists aud varchar(255),
33
+ add column if not exists role varchar(255),
34
+ add column if not exists email varchar(255),
35
+ add column if not exists encrypted_password varchar(255),
36
+ add column if not exists email_confirmed_at timestamptz,
37
+ add column if not exists invited_at timestamptz,
38
+ add column if not exists confirmation_token varchar(255),
39
+ add column if not exists confirmation_sent_at timestamptz,
40
+ add column if not exists recovery_token varchar(255),
41
+ add column if not exists recovery_sent_at timestamptz,
42
+ add column if not exists email_change_token_new varchar(255),
43
+ add column if not exists email_change varchar(255),
44
+ add column if not exists email_change_sent_at timestamptz,
45
+ add column if not exists last_sign_in_at timestamptz,
46
+ add column if not exists raw_app_meta_data jsonb,
47
+ add column if not exists raw_user_meta_data jsonb,
48
+ add column if not exists is_super_admin boolean,
49
+ add column if not exists created_at timestamptz,
50
+ add column if not exists updated_at timestamptz,
51
+ add column if not exists phone text,
52
+ add column if not exists phone_confirmed_at timestamptz,
53
+ add column if not exists confirmed_at timestamptz,
54
+ add column if not exists banned_until timestamptz,
55
+ add column if not exists deleted_at timestamptz,
56
+ add column if not exists is_anonymous boolean not null default false;
57
+ create unique index if not exists users_email_partial_key on auth.users (email) where is_anonymous = false;
58
+
59
+ create table if not exists auth.identities (
60
+ provider_id text not null,
61
+ user_id uuid not null references auth.users (id) on delete cascade,
62
+ identity_data jsonb not null,
63
+ provider text not null,
64
+ last_sign_in_at timestamptz,
65
+ created_at timestamptz,
66
+ updated_at timestamptz,
67
+ email text generated always as (lower(identity_data ->> 'email')) stored,
68
+ id uuid not null default gen_random_uuid() primary key,
69
+ unique (provider_id, provider)
70
+ );
71
+
72
+ create table if not exists auth.sessions (
73
+ id uuid primary key,
74
+ user_id uuid not null references auth.users (id) on delete cascade,
75
+ created_at timestamptz,
76
+ updated_at timestamptz,
77
+ factor_id uuid,
78
+ aal text,
79
+ not_after timestamptz,
80
+ refreshed_at timestamp,
81
+ user_agent text,
82
+ ip inet,
83
+ tag text
84
+ );
85
+ create index if not exists sessions_user_id_idx on auth.sessions (user_id);
86
+
87
+ create or replace function auth.uid() returns uuid language sql stable as
88
+ $$ select nullif(coalesce(current_setting('request.jwt.claim.sub', true), (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')), '')::uuid $$;
89
+
90
+ create or replace function auth.role() returns text language sql stable as
91
+ $$ select nullif(coalesce(current_setting('request.jwt.claim.role', true), (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role')), '')::text $$;
92
+
93
+ create or replace function auth.jwt() returns jsonb language sql stable as
94
+ $$ select coalesce(nullif(current_setting('request.jwt.claim', true), ''), nullif(current_setting('request.jwt.claims', true), ''))::jsonb $$;
95
+
96
+ grant execute on function auth.uid(), auth.role(), auth.jwt() to public;
97
+
98
+ -- storage: the two tables the migrations seed and police; the objects live in MinIO, these stay empty bookkeeping.
99
+ create table if not exists storage.buckets (
100
+ id text primary key,
101
+ name text not null,
102
+ owner uuid,
103
+ created_at timestamptz default now(),
104
+ updated_at timestamptz default now(),
105
+ public boolean default false,
106
+ avif_autodetection boolean default false,
107
+ file_size_limit bigint,
108
+ allowed_mime_types text[],
109
+ owner_id text
110
+ );
111
+ create unique index if not exists bname on storage.buckets (name);
112
+
113
+ create table if not exists storage.objects (
114
+ id uuid not null default gen_random_uuid() primary key,
115
+ bucket_id text references storage.buckets (id),
116
+ name text,
117
+ owner uuid,
118
+ created_at timestamptz default now(),
119
+ updated_at timestamptz default now(),
120
+ last_accessed_at timestamptz default now(),
121
+ metadata jsonb,
122
+ path_tokens text[] generated always as (string_to_array(name, '/')) stored,
123
+ version text,
124
+ owner_id text,
125
+ user_metadata jsonb
126
+ );
127
+ create unique index if not exists bucketid_objname on storage.objects (bucket_id, name);
128
+ alter table storage.objects enable row level security;
129
+ alter table storage.buckets enable row level security;
130
+
131
+ grant select, insert, update, delete on storage.objects, storage.buckets to authenticated, service_role;
132
+ grant select on storage.objects, storage.buckets to anon;
133
+
134
+ -- Replication publication the baseline adds tables to (the image ships it; a bare PostgreSQL does not).
135
+ do $pub$
136
+ begin
137
+ if not exists (select 1 from pg_publication where pubname = 'supabase_realtime') then create publication supabase_realtime; end if;
138
+ end
139
+ $pub$;
@@ -1,9 +1,6 @@
1
1
  {{> common/gitignore.base}}
2
2
  # Jest transforms reused by the CI test job
3
3
  .jest-cache/
4
- # Supabase CLI state is local Docker metadata, never application source.
5
- supabase/.temp/
6
- supabase/.branches/
7
4
  # be: the schema Nest writes when it boots GraphQL
8
5
  schema.gql
9
6
  # fe: Next build output and its generated type file
@@ -0,0 +1,9 @@
1
+ "db:create": "docker create --name {{project}}-dev-db -p 127.0.0.1:54322:5432 -e POSTGRES_HOST_AUTH_METHOD=trust -v {{project}}-dev-db-data:/var/lib/postgresql/data {{dbImage}}",
2
+ "db:start": "docker start {{project}}-dev-db",
3
+ "db:stop": "docker stop {{project}}-dev-db",
4
+ "db:reset": "docker rm -f {{project}}-dev-db && docker volume rm {{project}}-dev-db-data && npm run db:create && npm run db:start",
5
+ "db:new": "{{appBinary}} db new",
6
+ "db:push": "{{appBinary}} db migrate --url-env DATABASE_URL --execute",
7
+ "db:plan": "{{appBinary}} db plan --url-env DATABASE_URL",
8
+ "db:dump": "{{appBinary}} db dump --url-env DATABASE_URL --out ../{{project}}-db-backups",
9
+ "db:lint": "{{appBinary}} check --fast",
@@ -9,7 +9,6 @@
9
9
  "test:e2e": "npm run typecheck:tests && cd be && jest --selectProjects e2e",
10
10
  "test:contract": "npm run typecheck:tests && cd be && jest --selectProjects contract",
11
11
  "test:stack": "starci app stack",
12
- {{supabaseScripts}}
13
12
  "contract:emit": "starci app emit"
14
13
  }
15
14
  }
@@ -6,7 +6,7 @@
6
6
  "typecheck:tests": "tsc -p be/src/tests/tsconfig.json",
7
7
  "test": "cd be && jest --selectProjects unit --coverage",
8
8
  "test:integration": "npm run typecheck:tests && cd be && jest --selectProjects integration",
9
- {{> app/package-scripts/supabase.json}}
9
+ {{> app/package-scripts/db.json}}
10
10
  {{backingScripts}}
11
11
  "contract:emit": "{{appBinary}} emit"
12
12
  }
@@ -1,6 +1,6 @@
1
1
  # Development stack
2
2
 
3
- Every development dependency runs in Docker Linux containers. This starter's Docker Compose model contains the core API, the product and landing front ends, and a pinned local PostgreSQL database. Compose is an implementation detail; declared dependencies may also use managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts, and local Supabase, when declared, runs through the Supabase CLI. A declared native profile may run app processes locally while dependencies stay in Docker. Trust authentication is confined to this local, unpublished database.
3
+ Every development dependency runs in Docker Linux containers. This starter's Docker Compose model contains the core API, the product and landing front ends, and a pinned local PostgreSQL database. Compose is an implementation detail; declared dependencies may also use managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts. A declared native profile may run app processes locally while dependencies stay in Docker. Trust authentication is confined to this local, unpublished database.
4
4
  Starter release images and external secrets must be prepared before starting the container model. Local source development remains available through the app scripts.
5
5
 
6
6
  | Command | Operation |
@@ -1,6 +1,6 @@
1
1
  # Production stack
2
2
 
3
- The app and its dependencies run on Docker Swarm; Supabase, when declared, is a hosted service. The tracked model declares the application services, one digest-pinned Caddy ingress and one digest-pinned PostgreSQL service on an internal backing network, with its data on a named persistent volume and credentials supplied through mounted secrets. On the first boot of an empty data volume, PostgreSQL runs the mounted `.starcistacks/production/infra/swarm/postgres-init.sh` config to create the `app_be` role with access to this app's database and schema, without superuser, database-creation, role-creation, replication or RLS-bypass privileges. Existing data volumes skip initialization and retain their roles and credentials.
3
+ The app and its dependencies run on Docker Swarm. The tracked model declares the application services, one digest-pinned Caddy ingress and one digest-pinned PostgreSQL service on an internal backing network, with its data on a named persistent volume and credentials supplied through mounted secrets. On the first boot of an empty data volume, PostgreSQL runs the mounted `.starcistacks/production/infra/swarm/postgres-init.sh` config to create the `app_be` role with access to this app's database and schema, without superuser, database-creation, role-creation, replication or RLS-bypass privileges. Existing data volumes skip initialization and retain their roles and credentials.
4
4
  The model is a deployment declaration. It contains no owner credentials, real DNS receipt, completed release or restore proof. The public records under `.starcistacks/production/infra/swarm/` mark those owner-run operations as pending. Its all-zero application image digests are illustrative and must be replaced before deployment.
5
5
 
6
6
  The runtime template tracks no encrypted members. Scaffolding generates the declared database connection, application password and PostgreSQL administrator password members with placeholder values encrypted to this app's own recipient in `.sops.yaml`. Replace them with `starci app secret set production` using the matching host age identity in untracked local custody. The sealed connection uses role `app_be`, database `{{project}}` and internal endpoint `postgres:5432`; its URI-encoded password matches the separate `primary-db-password` member. The administrator password is distinct. The API receives only its least-privilege connection; only PostgreSQL receives the two password files for initialization. The non-empty application password contains no line breaks. The service has no TTY: initialization creates the role without a password, then feeds the two `psql` `\password` prompts through stdin using Bash's builtin `printf`, so psql encrypts it before sending SQL. No password travels in an initialization command argument or a plaintext SQL statement.
@@ -22,7 +22,7 @@ Declare `ci.github.repository` and `ci.github.defaultBranch` in `.starcistacks/a
22
22
 
23
23
  ## Development
24
24
 
25
- Every development dependency runs in Docker Linux containers. Local Supabase, when declared, runs through the Supabase CLI; every other declared dependency runs through its managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts or the declared container model. This starter uses Compose to run its pinned local PostgreSQL and app services; Compose is an implementation detail. A declared native profile may run the app processes locally while dependencies stay in Docker. In production, the app and its dependencies run on Docker Swarm, except Supabase, which is a hosted service.
25
+ Every development dependency runs in Docker Linux containers. Every declared dependency runs through its managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts or the declared container model. This starter uses Compose to run its pinned local PostgreSQL and app services; Compose is an implementation detail. A declared native profile may run the app processes locally while dependencies stay in Docker. In production, the app and its dependencies run on Docker Swarm, except Supabase, which is a hosted service.
26
26
 
27
27
  ```sh
28
28
  npm install
@@ -7,15 +7,15 @@ schema: starci/application-stacks@1
7
7
  sources:
8
8
  - repository: {{project}}
9
9
  backing:
10
- supabase:
11
- provider: supabase
12
- managed: true
10
+ db:
11
+ provider: postgres
12
+ component: db
13
13
  components:
14
14
  api:
15
15
  role: service
16
16
  image: "{{project}}/api:v0.0.0@sha256:0000000000000000000000000000000000000000000000000000000000000000"
17
17
  compose: infra/swarm/stack.yaml
18
- purpose: The application's GraphQL API; managed Supabase supplies its database and Auth integration.
18
+ purpose: The application's GraphQL API, its own Google and password sign-in and its storage integration.
19
19
  required: true
20
20
  web:
21
21
  role: service
@@ -23,6 +23,14 @@ components:
23
23
  compose: infra/swarm/stack.yaml
24
24
  purpose: The standalone Next.js application with public configuration supplied at build time.
25
25
  required: true
26
+ db:
27
+ role: service
28
+ stateful: true
29
+ volumes: [db-data]
30
+ image: "supabase/postgres:17.11.0.006@sha256:c141fd17ac6a76c4323e90b3aee8ff1a23d2e89e547c2b963cfadc309c079cc2"
31
+ compose: infra/swarm/stack.yaml
32
+ purpose: The app's own PostgreSQL (pg_cron, pg_net, pgmq, vector), migrated by starci app db migrate.
33
+ required: true
26
34
  ingress:
27
35
  role: ingress
28
36
  stateful: true
@@ -39,6 +47,7 @@ environments:
39
47
  components:
40
48
  api: {replicas: 1, port: 3001}
41
49
  web: {replicas: 1, port: 3000}
50
+ db: {replicas: 1, port: 5432}
42
51
  ingress: {replicas: 1, port: 80}
43
52
  runbook: README.md
44
53
  ingress:
@@ -62,8 +71,8 @@ environments:
62
71
  retention: Seven daily snapshots
63
72
  owner: Development operator
64
73
  restoreProof: .starcistacks/dev/infra/swarm/recovery/restore-proof.md
65
- - component: supabase
66
- target: Owner-provisioned encrypted off-host storage for development database exports
74
+ - component: db
75
+ target: Owner-provisioned encrypted off-host storage for starci app db dump archives
67
76
  retention: Seven daily exports
68
77
  owner: Development operator
69
78
  restoreProof: .starcistacks/dev/infra/swarm/recovery/restore-proof.md
@@ -75,10 +84,34 @@ environments:
75
84
  where: secrets/primary-db-url
76
85
  recipientPolicy: This app's own age recipient configured in .sops.yaml
77
86
  keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
78
- - name: supabase-service-role-key
79
- runtimeName: "{{project}}-dev-supabase-service-role-key-v1"
87
+ - name: db-password
88
+ runtimeName: "{{project}}-dev-db-password-v1"
89
+ version: 1
90
+ where: secrets/db-password
91
+ recipientPolicy: This app's own age recipient configured in .sops.yaml
92
+ keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
93
+ - name: google-client-secret
94
+ runtimeName: "{{project}}-dev-google-client-secret-v1"
95
+ version: 1
96
+ where: secrets/google-client-secret
97
+ recipientPolicy: This app's own age recipient configured in .sops.yaml
98
+ keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
99
+ - name: auth-jwt-private-key
100
+ runtimeName: "{{project}}-dev-auth-jwt-private-key-v1"
101
+ version: 1
102
+ where: secrets/auth-jwt-private-key
103
+ recipientPolicy: This app's own age recipient configured in .sops.yaml
104
+ keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
105
+ - name: storage-access-key-id
106
+ runtimeName: "{{project}}-dev-storage-access-key-id-v1"
107
+ version: 1
108
+ where: secrets/storage-access-key-id
109
+ recipientPolicy: This app's own age recipient configured in .sops.yaml
110
+ keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
111
+ - name: storage-secret-access-key
112
+ runtimeName: "{{project}}-dev-storage-secret-access-key-v1"
80
113
  version: 1
81
- where: secrets/supabase-service-role-key
114
+ where: secrets/storage-secret-access-key
82
115
  recipientPolicy: This app's own age recipient configured in .sops.yaml
83
116
  keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
84
117
  production:
@@ -88,6 +121,7 @@ environments:
88
121
  components:
89
122
  api: {replicas: 1, port: 3001}
90
123
  web: {replicas: 1, port: 3000}
124
+ db: {replicas: 1, port: 5432}
91
125
  ingress: {replicas: 1, port: 443, httpPort: 80}
92
126
  runbook: README.md
93
127
  ingress:
@@ -114,9 +148,9 @@ environments:
114
148
  retention: Thirty daily snapshots
115
149
  owner: Production operator
116
150
  restoreProof: .starcistacks/production/infra/swarm/recovery/restore-proof.md
117
- - component: supabase
118
- target: Provider PITR plus owner-provisioned encrypted off-host database exports
119
- retention: Seven days PITR and thirty daily exports
151
+ - component: db
152
+ target: Owner-provisioned encrypted off-host storage for starci app db dump archives
153
+ retention: Thirty daily encrypted exports
120
154
  owner: Production operator
121
155
  restoreProof: .starcistacks/production/infra/swarm/recovery/restore-proof.md
122
156
  # Scaffolding seals generated placeholders to this app's own recipient; set real values before use.
@@ -127,10 +161,34 @@ environments:
127
161
  where: secrets/primary-db-url
128
162
  recipientPolicy: This app's own age recipient configured in .sops.yaml
129
163
  keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
130
- - name: supabase-service-role-key
131
- runtimeName: "{{project}}-production-supabase-service-role-key-v1"
164
+ - name: db-password
165
+ runtimeName: "{{project}}-production-db-password-v1"
166
+ version: 1
167
+ where: secrets/db-password
168
+ recipientPolicy: This app's own age recipient configured in .sops.yaml
169
+ keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
170
+ - name: google-client-secret
171
+ runtimeName: "{{project}}-production-google-client-secret-v1"
132
172
  version: 1
133
- where: secrets/supabase-service-role-key
173
+ where: secrets/google-client-secret
174
+ recipientPolicy: This app's own age recipient configured in .sops.yaml
175
+ keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
176
+ - name: auth-jwt-private-key
177
+ runtimeName: "{{project}}-production-auth-jwt-private-key-v1"
178
+ version: 1
179
+ where: secrets/auth-jwt-private-key
180
+ recipientPolicy: This app's own age recipient configured in .sops.yaml
181
+ keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
182
+ - name: storage-access-key-id
183
+ runtimeName: "{{project}}-production-storage-access-key-id-v1"
184
+ version: 1
185
+ where: secrets/storage-access-key-id
186
+ recipientPolicy: This app's own age recipient configured in .sops.yaml
187
+ keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
188
+ - name: storage-secret-access-key
189
+ runtimeName: "{{project}}-production-storage-secret-access-key-v1"
190
+ version: 1
191
+ where: secrets/storage-secret-access-key
134
192
  recipientPolicy: This app's own age recipient configured in .sops.yaml
135
193
  keyCustody: SOPS_AGE_KEY in the host's untracked .claude/secret.env
136
194
  # What `starci app stack` deploys (verbs: prepare, doctor, deploy, verify, rollback); names and shapes only, never a value.
@@ -151,9 +209,9 @@ environments:
151
209
  buildArgs:
152
210
  values:
153
211
  NEXT_PUBLIC_SITE_URL: "https://{{project}}.example.invalid"
154
- NEXT_PUBLIC_SUPABASE_URL: "https://supabase.{{project}}.example.invalid"
155
- NEXT_PUBLIC_SUPABASE_ANON_KEY: "REPLACE_WITH_PUBLIC_SUPABASE_ANON_KEY"
212
+ NEXT_PUBLIC_API_URL: "https://api.{{project}}.example.invalid"
156
213
  volumes:
214
+ - {{project}}-production-db-data
157
215
  - {{project}}-production-ingress-data
158
216
  - {{project}}-production-ingress-config-data
159
217
  secrets:
@@ -162,7 +220,14 @@ environments:
162
220
  filePointers: true
163
221
  from:
164
222
  - member: primary-db-url
165
- - member: supabase-service-role-key
223
+ - member: google-client-secret
224
+ - member: auth-jwt-private-key
225
+ - member: storage-access-key-id
226
+ - member: storage-secret-access-key
227
+ - service: db
228
+ filePointers: true
229
+ from:
230
+ - member: db-password
166
231
  # Release tasks: an idempotent write the release owes the data (a catalogue sync, a seed upsert) is declared under tasks and listed in postDeploy.
167
232
  # A deploy runs postDeploy after the stack converged and before verify, which stays read-only. Uncomment and name the real command:
168
233
  # tasks:
@@ -184,7 +249,7 @@ environments:
184
249
  retry: true
185
250
  k8s:
186
251
  status: deferred
187
- reason: Lite production uses Docker Swarm with managed Supabase backing; Kubernetes belongs to a future full-edition deployment.
252
+ reason: Lite production uses Docker Swarm with its own PostgreSQL service; Kubernetes belongs to a future full-edition deployment.
188
253
  ci:
189
254
  secrets:
190
255
  - {name: SONAR_TOKEN, scope: repository, purpose: Sonar analysis token provisioned for this app}
@@ -1,14 +1,14 @@
1
1
  # Development stack
2
2
 
3
- Every development dependency runs in Docker Linux containers. Local Supabase starts through its CLI and supplies PostgreSQL, pgvector, Auth and Storage. Every other declared dependency, such as Redis, starts through its managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts or the declared container model; Compose is an implementation detail. The app services may run in the local Docker Swarm model or through a declared native development profile while dependencies stay in Docker.
4
- The application image references contain illustrative all-zero digests. Replace them with verified published release digests in the manifest, model and `.starcistacks/dev/infra/swarm/releases/images.lock.yaml` before starting the container model. Replace its starter `SUPABASE_URL` with the local Supabase endpoint reachable from the Swarm node, and seal that local database connection and integration credential; a container cannot use the host's loopback address without owner-configured networking. Local source development remains available through the app scripts.
3
+ Every development dependency runs in Docker Linux containers. The `db` service (image supabase/postgres: PostgreSQL with pg_cron, pg_net, pgmq and pgvector) starts with `npm run db:start` and takes the migrations with `npm run db:push`; storage is the S3-compatible MinIO the environment names. Every other declared dependency, such as Redis, starts through its managed `<dep>:create`, `<dep>:start` and `<dep>:stop` scripts or the declared container model; Compose is an implementation detail. The app services may run in the local Docker Swarm model or through a declared native development profile while dependencies stay in Docker.
4
+ The application image references contain illustrative all-zero digests. Replace them with verified published release digests in the manifest, model and `.starcistacks/dev/infra/swarm/releases/images.lock.yaml` before starting the container model. Seal the local database connection and the integration credentials; a container cannot use the host's loopback address without owner-configured networking. Local source development remains available through the app scripts.
5
5
 
6
- The runtime template tracks no encrypted members. Scaffolding generates the two declared members with placeholder values encrypted to this app's own recipient in `.sops.yaml`. Replace those values with `starci app secret set dev` using the matching host age identity in untracked local custody. Existing generated envelopes are decryptable with that app identity and can be updated in place. Each Docker secret contains the corresponding key's real value, and the API receives it through its declared mounted file.
6
+ The runtime template tracks no encrypted members. Scaffolding generates the six declared members with placeholder values encrypted to this app's own recipient in `.sops.yaml`. Replace those values with `starci app secret set dev` using the matching host age identity in untracked local custody. Existing generated envelopes are decryptable with that app identity and can be updated in place. Each Docker secret contains the corresponding key's real value, and the API receives it through its declared mounted file.
7
7
 
8
8
  | Command | Operation |
9
9
  | --- | --- |
10
- | prepare | Run `npm run db:start` at the app root; obtain the local Supabase connection and Auth configuration. For a declared Redis (its `redis:create`, `redis:start` and `redis:stop` scripts arrive in package.json with the first queue or job), run the create script once, then the start script when using the native app profile. An owner who chooses local Swarm initializes it, labels its node `{{project}}=dev` and provisions the ingress volumes; the model starts its declared Redis with the app. Replace the reserved development host consistently in the manifest, Caddyfile and public configuration; resolve it to the node and record this in `.starcistacks/dev/infra/swarm/owner-actions.md`. Create the external development secrets named in the model from real sealed members. |
11
- | doctor | From this directory, run `docker stack config -c infra/swarm/stack.yaml`. Confirm image, secret and local Supabase availability. |
10
+ | prepare | Run `npm run db:start` at the app root; then `npm run db:push` to apply the migrations; export `DATABASE_URL` for the database verbs. For a declared Redis (its `redis:create`, `redis:start` and `redis:stop` scripts arrive in package.json with the first queue or job), run the create script once, then the start script when using the native app profile. An owner who chooses local Swarm initializes it, labels its node `{{project}}=dev` and provisions the ingress volumes; the model starts its declared Redis with the app. Replace the reserved development host consistently in the manifest, Caddyfile and public configuration; resolve it to the node and record this in `.starcistacks/dev/infra/swarm/owner-actions.md`. Create the external development secrets named in the model from real sealed members. |
11
+ | doctor | From this directory, run `docker stack config -c infra/swarm/stack.yaml`. Confirm image, secret and local database availability. |
12
12
  | up | Run `docker stack deploy -c infra/swarm/stack.yaml {{project}}-dev` from this directory after preparation. |
13
13
  | status | Run `docker stack services {{project}}-dev`. |
14
14
  | logs | Run `docker service logs {{project}}-dev_api`; never print mounted credentials. |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "environment": "dev",
3
- "provider": "supabase",
3
+ "provider": "postgres",
4
4
  "project": "{{project}}",
5
5
  "runtime": "docker-swarm"
6
6
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  Status: pending owner execution. No backup or restore has been run by scaffolding.
4
4
 
5
- The development operator owns recovery of the managed Supabase database and both external ingress volumes, `{{project}}-dev-ingress-data` and `{{project}}-dev-ingress-config-data`. Retain seven daily encrypted off-host exports or snapshots for each target.
5
+ The development operator owns recovery of the db service's volume `{{project}}-dev-db-data` and both external ingress volumes, `{{project}}-dev-ingress-data` and `{{project}}-dev-ingress-config-data`. Retain seven daily encrypted off-host exports or snapshots for each target.
6
6
 
7
7
  Before relying on the backup policy, restore a database export and both volume snapshots into isolated infrastructure. Record the actual off-host destination, encryption custody, backup artifact digests, restoration time and operator. Verify database connectivity and the API readiness endpoint, then verify the restored ingress configuration and state. Attach the dated result here without credentials.