@starci/hfs 4.9.11 → 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 (219) hide show
  1. package/CHANGELOG.md +13 -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 +177 -155
  18. package/runtime/knowledge/hfs/slots.yaml +164 -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/{supabase-be.mjs → identity.mjs} +45 -15
  33. package/runtime/scripts/hfs/architecture/index.mjs +2 -3
  34. package/runtime/scripts/hfs/architecture/{supabase-ast.mjs → jwt-ast.mjs} +2 -103
  35. package/runtime/scripts/hfs/architecture/register-once.mjs +1 -1
  36. package/runtime/scripts/hfs/architecture/schema-owner.mjs +29 -4
  37. package/runtime/scripts/hfs/architecture/server-action.mjs +3 -4
  38. package/runtime/scripts/hfs/architecture/side-boundary.mjs +1 -1
  39. package/runtime/scripts/hfs/architecture/{supabase-keys.mjs → sql-keys.mjs} +1 -1
  40. package/runtime/scripts/hfs/architecture/sql-owner.mjs +25 -27
  41. package/runtime/scripts/hfs/architecture/transport-owner.mjs +7 -3
  42. package/runtime/scripts/hfs/check.mjs +6 -6
  43. package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
  44. package/runtime/scripts/hfs/db-baseline-verb.mjs +48 -30
  45. package/runtime/scripts/hfs/db-plan-verb.mjs +17 -106
  46. package/runtime/scripts/hfs/db-restamp-verb.mjs +11 -10
  47. package/runtime/scripts/hfs/declaration-shape.mjs +15 -18
  48. package/runtime/scripts/hfs/manifest-shape.mjs +4 -4
  49. package/runtime/scripts/hfs/rule-params-shape.mjs +4 -1
  50. package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -1
  51. package/runtime/scripts/hfs/rules/cli.mjs +1 -1
  52. package/runtime/scripts/hfs/rules/database-access.mjs +8 -74
  53. package/runtime/scripts/hfs/rules/database-baseline.mjs +3 -3
  54. package/runtime/scripts/hfs/rules/database-config.mjs +6 -214
  55. package/runtime/scripts/hfs/rules/database-constants.mjs +8 -10
  56. package/runtime/scripts/hfs/rules/database-migrations.mjs +3 -3
  57. package/runtime/scripts/hfs/rules/database-queues.mjs +4 -5
  58. package/runtime/scripts/hfs/rules/database-sql.mjs +1 -1
  59. package/runtime/scripts/hfs/rules/database.mjs +36 -39
  60. package/runtime/scripts/hfs/rules/edition.mjs +1 -1
  61. package/runtime/scripts/hfs/rules/{supabase-secrets.mjs → lite-secrets.mjs} +6 -6
  62. package/runtime/scripts/hfs/rules/script-hygiene.mjs +1 -10
  63. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +5 -4
  64. package/runtime/scripts/hfs/rules/stacks-release.mjs +3 -2
  65. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +2 -2
  66. package/runtime/scripts/hfs/rules/stacks.mjs +1 -1
  67. package/runtime/scripts/hfs/slot-app-view.mjs +1 -1
  68. package/runtime/scripts/hfs/slots.mjs +8 -2
  69. package/runtime/scripts/hfs/sql/pg-parse.mjs +2 -2
  70. package/runtime/scripts/lib/secret-patterns.mjs +2 -2
  71. package/runtime/scripts/lib/swarm-stack.mjs +7 -4
  72. package/scaffold/add-app.mjs +2 -10
  73. package/scaffold/add-cli-lite.mjs +6 -6
  74. package/scaffold/add-compose.mjs +1 -1
  75. package/scaffold/add-inbox-lite.mjs +4 -8
  76. package/scaffold/add-plan.mjs +1 -1
  77. package/scaffold/add-queue-lite.mjs +16 -24
  78. package/scaffold/add-table.mjs +34 -26
  79. package/scaffold/add.mjs +2 -2
  80. package/scaffold/app.mjs +19 -86
  81. package/scaffold/lite-exports.mjs +1 -1
  82. package/src/main.mjs +19 -20
  83. package/stack/target-binding.mjs +0 -60
  84. package/stack/urgent-floor.mjs +2 -2
  85. package/sync/index.mjs +32 -8
  86. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -17
  87. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +6 -8
  88. package/templates/app/codegen-lite/scripts/codegen.mjs +2 -39
  89. package/templates/app/db-bootstrap/db/bootstrap.sql +139 -0
  90. package/templates/app/gitignore-lite +0 -3
  91. package/templates/app/package-scripts/db.json +9 -0
  92. package/templates/app/package-scripts/package.json +0 -1
  93. package/templates/app/package-scripts-lite/package.json +1 -1
  94. package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
  95. package/templates/app/skeleton/.starcistacks/production/README.md +1 -1
  96. package/templates/app/skeleton/README.md +1 -1
  97. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +84 -19
  98. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +5 -5
  99. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +1 -1
  100. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +1 -1
  101. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +69 -6
  102. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -7
  103. package/templates/app/skeleton-lite/.starcistacks/production/README.md +6 -6
  104. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +1 -1
  105. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +2 -2
  106. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +69 -6
  107. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +17 -9
  108. package/templates/app/skeleton-lite/README.md +11 -11
  109. package/templates/app/skeleton-lite/{supabase → db}/migrations/__timestamp___baseline.sql +21 -1
  110. package/templates/app/tool-config-lite/prettierignore +0 -1
  111. package/templates/be/inbox/database-client.spec.ts.tpl +9 -5
  112. package/templates/be/inbox/database-client.ts.tpl +13 -4
  113. package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +47 -0
  114. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +3 -1
  115. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +2 -2
  116. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +6 -1
  117. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +39 -7
  118. package/templates/be/inbox/platform/migration.sql.tpl +22 -0
  119. package/templates/be/inbox/service.spec.ts.tpl +2 -2
  120. package/templates/be/inbox/service.ts.tpl +1 -2
  121. package/templates/be/patterns/jobs/platform-lite/job-claim.service.spec.ts.tpl +156 -0
  122. package/templates/be/patterns/jobs/platform-lite/job-claim.service.ts.tpl +114 -0
  123. package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +51 -0
  124. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.spec.ts.tpl +62 -0
  125. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.ts.tpl +20 -0
  126. package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +35 -0
  127. package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +4 -1
  128. package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +16 -5
  129. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +8 -8
  130. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +3 -3
  131. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +2 -2
  132. package/templates/be/skeleton-lite/apps/api/Dockerfile +0 -1
  133. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +9 -5
  134. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +7 -4
  135. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +4 -4
  136. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +3 -3
  137. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +9 -13
  138. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +4 -2
  139. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +2 -2
  140. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +3 -2
  141. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +4 -4
  142. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +3 -3
  143. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +1 -1
  144. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +6 -6
  145. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +3 -3
  146. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +4 -0
  147. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +22 -0
  148. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +25 -0
  149. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +12 -0
  150. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +9 -0
  151. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +35 -0
  152. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +22 -0
  153. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +12 -0
  154. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +23 -0
  155. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +29 -0
  156. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +22 -0
  157. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +63 -0
  158. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +11 -0
  159. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +32 -0
  160. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +69 -0
  161. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +37 -0
  162. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +36 -0
  163. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +59 -0
  164. package/templates/be/skeleton-lite/src/modules/platform/database/database.functions.ts +24 -0
  165. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +2 -1
  166. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +4 -2
  167. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +19 -6
  168. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +6 -0
  169. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  170. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +1 -1
  171. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +2 -2
  172. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +2 -2
  173. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
  174. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +1 -1
  175. package/templates/be/table/entities.index.ts.tpl +4 -0
  176. package/templates/be/table/entity.ts.tpl +21 -0
  177. package/templates/be/table/index.ts.tpl +1 -0
  178. package/templates/be/table/module.ts.tpl +1 -1
  179. package/templates/be/table/service.api.ts.tpl +10 -11
  180. package/templates/be/table/service.ts.tpl +5 -6
  181. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +30 -0
  182. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +46 -14
  183. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +31 -0
  184. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +2 -5
  185. package/db/local-migrate.mjs +0 -194
  186. package/db/reset.mjs +0 -125
  187. package/emit/db-types.mjs +0 -271
  188. package/runtime/scripts/api/supabase/database-dump.mjs +0 -5
  189. package/runtime/scripts/api/supabase/database-push.mjs +0 -5
  190. package/runtime/scripts/api/supabase/database-query.mjs +0 -5
  191. package/runtime/scripts/api/supabase/lib.mjs +0 -55
  192. package/runtime/scripts/api/supabase/migration-repair.mjs +0 -5
  193. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +0 -50
  194. package/runtime/scripts/hfs/architecture/supabase.mjs +0 -226
  195. package/scaffold/supabase-ports.mjs +0 -26
  196. package/templates/app/package-scripts/supabase.json +0 -7
  197. package/templates/app/skeleton-lite/supabase/config.toml +0 -48
  198. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +0 -5
  199. package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +0 -33
  200. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +0 -33
  201. package/templates/be/inbox/sql.ts.tpl +0 -16
  202. package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +0 -33
  203. package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +0 -12
  204. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -16
  205. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +0 -6
  206. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +0 -27
  207. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +0 -24
  208. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +0 -8
  209. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +0 -17
  210. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +0 -32
  211. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +0 -24
  212. package/templates/be/table/rows.ts.tpl +0 -4
  213. package/templates/be/table/sql.api.ts.tpl +0 -7
  214. package/templates/be/table/sql.ts.tpl +0 -4
  215. package/templates/fe/auth/supabase-client.ts.tpl +0 -38
  216. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +0 -11
  217. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +0 -18
  218. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +0 -19
  219. /package/templates/app/skeleton-lite/{supabase → db}/seed.sql +0 -0
@@ -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.
@@ -8,13 +8,29 @@ services:
8
8
  HTTP_SECURITY_ALLOWED_ORIGINS: http://dev.{{project}}.example.invalid
9
9
  PRIMARY_DB_URL_FILE: /run/secrets/primary-db-url
10
10
  PRIMARY_DB_SCHEMA: public
11
- SUPABASE_URL: https://{{project}}.supabase.co
12
- SUPABASE_SERVICE_ROLE_KEY_FILE: /run/secrets/supabase-service-role-key
11
+ GOOGLE_CLIENT_ID: REPLACE_WITH_GOOGLE_OAUTH_CLIENT_ID
12
+ GOOGLE_CLIENT_SECRET_FILE: /run/secrets/google-client-secret
13
+ GOOGLE_CALLBACK_URL: http://dev.{{project}}.example.invalid/auth/google/callback
14
+ AUTH_JWT_ISSUER: http://dev.{{project}}.example.invalid
15
+ AUTH_JWT_AUDIENCE: {{project}}
16
+ AUTH_JWT_KEY_ID: dev-1
17
+ AUTH_JWT_PRIVATE_KEY_FILE: /run/secrets/auth-jwt-private-key
18
+ STORAGE_ENDPOINT: https://storage.{{project}}.example.invalid
19
+ STORAGE_REGION: us-east-1
20
+ STORAGE_BUCKET: {{project}}-dev
21
+ STORAGE_ACCESS_KEY_ID_FILE: /run/secrets/storage-access-key-id
22
+ STORAGE_SECRET_ACCESS_KEY_FILE: /run/secrets/storage-secret-access-key
13
23
  secrets:
14
24
  - source: primary-db-url
15
25
  target: primary-db-url
16
- - source: supabase-service-role-key
17
- target: supabase-service-role-key
26
+ - source: google-client-secret
27
+ target: google-client-secret
28
+ - source: auth-jwt-private-key
29
+ target: auth-jwt-private-key
30
+ - source: storage-access-key-id
31
+ target: storage-access-key-id
32
+ - source: storage-secret-access-key
33
+ target: storage-secret-access-key
18
34
  networks: [app]
19
35
  healthcheck:
20
36
  test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3001/health/live').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
@@ -66,6 +82,38 @@ services:
66
82
  restart_policy:
67
83
  condition: on-failure
68
84
  delay: 5s
85
+ db:
86
+ image: "supabase/postgres:17.11.0.006@sha256:c141fd17ac6a76c4323e90b3aee8ff1a23d2e89e547c2b963cfadc309c079cc2"
87
+ environment:
88
+ POSTGRES_DB: postgres
89
+ POSTGRES_PASSWORD_FILE: /run/secrets/db-password
90
+ secrets:
91
+ - source: db-password
92
+ target: db-password
93
+ volumes:
94
+ - db-data:/var/lib/postgresql/data
95
+ networks: [app]
96
+ healthcheck:
97
+ test: ["CMD", "pg_isready", "-U", "postgres", "-h", "127.0.0.1"]
98
+ interval: 10s
99
+ timeout: 5s
100
+ start_period: 30s
101
+ retries: 5
102
+ deploy:
103
+ replicas: 1
104
+ placement:
105
+ constraints: ["node.labels.{{project}} == dev"]
106
+ update_config:
107
+ parallelism: 1
108
+ order: stop-first
109
+ failure_action: rollback
110
+ monitor: 30s
111
+ rollback_config:
112
+ parallelism: 1
113
+ order: stop-first
114
+ restart_policy:
115
+ condition: on-failure
116
+ delay: 5s
69
117
  ingress:
70
118
  image: caddy:2.10.2@sha256:c3d7ee5d2b11f9dc54f947f68a734c84e9c9666c92c88a7f30b9cba5da182adb
71
119
  configs:
@@ -104,13 +152,28 @@ secrets:
104
152
  primary-db-url:
105
153
  external: true
106
154
  name: "{{project}}-dev-primary-db-url-v1"
107
- supabase-service-role-key:
155
+ db-password:
156
+ external: true
157
+ name: "{{project}}-dev-db-password-v1"
158
+ google-client-secret:
159
+ external: true
160
+ name: "{{project}}-dev-google-client-secret-v1"
161
+ auth-jwt-private-key:
108
162
  external: true
109
- name: "{{project}}-dev-supabase-service-role-key-v1"
163
+ name: "{{project}}-dev-auth-jwt-private-key-v1"
164
+ storage-access-key-id:
165
+ external: true
166
+ name: "{{project}}-dev-storage-access-key-id-v1"
167
+ storage-secret-access-key:
168
+ external: true
169
+ name: "{{project}}-dev-storage-secret-access-key-v1"
110
170
  configs:
111
171
  ingress-config:
112
172
  file: ./Caddyfile
113
173
  volumes:
174
+ db-data:
175
+ external: true
176
+ name: "{{project}}-dev-db-data"
114
177
  ingress-data:
115
178
  external: true
116
179
  name: "{{project}}-dev-ingress-data"
@@ -1,18 +1,29 @@
1
1
  # Environment key roster
2
2
 
3
- This roster declares names only. Scaffolding generates the two declared encrypted members with placeholder values encrypted to this app's own age recipient. Replace their values through `starci app secret set dev` before creating external secrets. The runtime template tracks no ciphertext; its specs generate members with ephemeral identities in disposable copies. Store product credential values in sealed custody and materialize private KEY files outside the repository.
3
+ This roster declares names only. Scaffolding generates the six declared encrypted members with placeholder values encrypted to this app's own age recipient. Replace their values through `starci app secret set dev` before creating external secrets. The runtime template tracks no ciphertext; its specs generate members with ephemeral identities in disposable copies. Store product credential values in sealed custody and materialize private KEY files outside the repository.
4
4
 
5
5
  | Key | Consumer |
6
6
  | --- | --- |
7
7
  | `PORT` | API listener |
8
8
  | `HTTP_SECURITY_ALLOWED_ORIGINS` | API origin allowlist |
9
- | `PRIMARY_DB_URL` | API primary database connection; `.starcistacks/dev/secrets/primary-db-url.enc` maps to external `{{project}}-dev-primary-db-url-v1` and mounted `/run/secrets/primary-db-url` |
9
+ | `PRIMARY_DB_URL` | API least-privilege database connection; `.starcistacks/dev/secrets/primary-db-url.enc` maps to external `{{project}}-dev-primary-db-url-v1` and mounted `/run/secrets/primary-db-url` |
10
10
  | `PRIMARY_DB_SCHEMA` | API primary database schema |
11
- | `SUPABASE_URL` | API Auth and Storage integration |
12
- | `SUPABASE_SERVICE_ROLE_KEY` | API Auth and Storage integration; `.starcistacks/dev/secrets/supabase-service-role-key.enc` maps to external `{{project}}-dev-supabase-service-role-key-v1` and mounted `/run/secrets/supabase-service-role-key` |
11
+ | `POSTGRES_PASSWORD` | The db service's superuser password; `.starcistacks/dev/secrets/db-password.enc` maps to external `{{project}}-dev-db-password-v1` and mounted `/run/secrets/db-password` |
12
+ | `GOOGLE_CLIENT_ID` | API Google OAuth client |
13
+ | `GOOGLE_CLIENT_SECRET` | API Google OAuth client; `.starcistacks/dev/secrets/google-client-secret.enc` maps to external `{{project}}-dev-google-client-secret-v1` and mounted `/run/secrets/google-client-secret` |
14
+ | `GOOGLE_CALLBACK_URL` | API Google OAuth callback door |
15
+ | `AUTH_JWT_ISSUER` | API access-token issuer |
16
+ | `AUTH_JWT_AUDIENCE` | API access-token audience |
17
+ | `AUTH_JWT_KEY_ID` | API signing key id, published in the JWKS |
18
+ | `AUTH_JWT_PRIVATE_KEY` | API ES256 signing key; `.starcistacks/dev/secrets/auth-jwt-private-key.enc` maps to external `{{project}}-dev-auth-jwt-private-key-v1` and mounted `/run/secrets/auth-jwt-private-key` |
19
+ | `STORAGE_ENDPOINT` | API S3-compatible storage endpoint |
20
+ | `STORAGE_REGION` | API storage signing region |
21
+ | `STORAGE_BUCKET` | API storage bucket |
22
+ | `STORAGE_ACCESS_KEY_ID` | API storage credential; `.starcistacks/dev/secrets/storage-access-key-id.enc` maps to external `{{project}}-dev-storage-access-key-id-v1` and mounted `/run/secrets/storage-access-key-id` |
23
+ | `STORAGE_SECRET_ACCESS_KEY` | API storage credential; `.starcistacks/dev/secrets/storage-secret-access-key.enc` maps to external `{{project}}-dev-storage-secret-access-key-v1` and mounted `/run/secrets/storage-secret-access-key` |
24
+ | `DATABASE_URL` | `starci app db` verbs (migrate, dump, plan) and `contract:emit`, from the operator's environment |
13
25
  | `NEXT_PUBLIC_SITE_URL` | Front-end site URL |
26
+ | `NEXT_PUBLIC_API_URL` | Front-end auth doors of the API |
14
27
  | `NEXT_PUBLIC_GRAPHQL_URL` | Front-end GraphQL transport |
15
- | `NEXT_PUBLIC_SUPABASE_URL` | Browser Auth client |
16
- | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Browser Auth client public key |
17
28
 
18
- Public front-end configuration is supplied at build time and stays outside sealed custody. Production database deployment consumes `SUPABASE_ACCESS_TOKEN`, `SUPABASE_DB_PASSWORD` and `SUPABASE_PROJECT_ID` from its environment's sealed custody; the GitHub secret store carries only the age identity.
29
+ Public front-end configuration is supplied at build time and stays outside sealed custody. The database verbs read `DATABASE_URL` from the operator's environment; the GitHub secret store carries only the age identity.
@@ -1,11 +1,11 @@
1
1
  # Production stack
2
2
 
3
3
  The tracked model declares Docker Swarm application services and one digest-pinned Caddy ingress.
4
- Hosted Supabase supplies PostgreSQL, pgvector, Auth and Storage. The app and its other dependencies run on Docker Swarm. A Redis service runs only when `hfs.json` `sides.be.backing` declares it for a capability that needs it: one stateful replica with a digest-pinned image, a named volume with a backup entry and its password as a sealed member granted as a Swarm secret (Redis runs `--appendonly yes` with the volume at `/data`). The scaffold declares none; `starci app add queue|job` declares Redis and renders it here.
4
+ The `db` service (image supabase/postgres) supplies PostgreSQL with pg_cron, pg_net, pgmq and pgvector on a named volume; sign-in is the API's own (Passport) and storage is the S3-compatible MinIO the environment names. The app and its dependencies run on Docker Swarm. A Redis service runs only when `hfs.json` `sides.be.backing` declares it for a capability that needs it: one stateful replica with a digest-pinned image, a named volume with a backup entry and its password as a sealed member granted as a Swarm secret (Redis runs `--appendonly yes` with the volume at `/data`). The scaffold declares none; `starci app add queue|job` declares Redis and renders it here.
5
5
  The model is a deployment declaration. It contains no owner credentials, real DNS receipt, completed release or restore proof. Public records under `.starcistacks/production/infra/swarm/` explicitly mark those owner-run operations as pending.
6
6
  Its application image references contain illustrative all-zero digests; they identify no published image and must be replaced before deployment.
7
7
 
8
- 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 production` 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.
8
+ 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 production` 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.
9
9
 
10
10
  | Command | Operation |
11
11
  | --- | --- |
@@ -26,13 +26,13 @@ Every item is pending. Swarm initialization, provider URIs, DNS changes and key
26
26
  | Swarm and placement | Initialize the intended manager; label the intended stateful node `{{project}}=production`. Only that node owns the external ingress volumes. |
27
27
  | DNS | Replace the reserved `example.invalid` hosts consistently in the manifest, Caddyfile and public configuration; publish address records for the web and API hosts and retain a provider receipt in `.starcistacks/production/infra/swarm/owner-actions.md`. |
28
28
  | TLS | Caddy issues certificates after real DNS resolves. Provision `{{project}}-production-ingress-data` and `{{project}}-production-ingress-config-data` on the selected node; retain an issuance and renewal receipt in `.starcistacks/production/infra/swarm/owner-actions.md`. |
29
- | Database | Provision Supabase and the app_be connection; provider backups remain enabled. Record the provider URI and recovery owner without copying credentials here. |
29
+ | Database | Provision `{{project}}-production-db-data` on the stateful node; after the first deploy create the app_be login through the baseline migration (`starci app db migrate --url-env DATABASE_URL --execute`). Record the recovery owner without copying credentials here. |
30
30
  | Secret custody | Replace generated placeholder values with real values at the manifest's declared `.starcistacks/production/secrets/*.enc` paths; record their keys in `.starcistacks/production/runtime/env/KEYS.md`. The app's age identity stays only in the host's untracked `.claude/secret.env`. |
31
- | External secrets | Materialize the sealed members in owner custody and create `{{project}}-production-primary-db-url-v1` and `{{project}}-production-supabase-service-role-key-v1`. The API reads mounted files; rotation creates new versioned names before updating the model. |
32
- | Public build configuration | Build the front end with its declared public site and GraphQL URLs, the Supabase public URL and anonymous key. Public browser values stay outside sealed secret custody. |
31
+ | External secrets | Materialize the sealed members in owner custody and create the `{{project}}-production-<member>-v1` external secret of each declared member. The API reads mounted files; rotation creates new versioned names before updating the model. |
32
+ | Public build configuration | Build the front end with its declared public site, API and GraphQL URLs. Public browser values stay outside sealed secret custody. |
33
33
  | Immutable release | Replace the illustrative all-zero application digests in the manifest, model and `.starcistacks/production/infra/swarm/releases/images.lock.yaml` with digests produced by the verified release. Store each rendered release under `/srv/{{project}}/releases/<digest>`. Retain the last accepted record in `.starcistacks/production/infra/swarm/releases/previous.yaml`, then move the current pointer and update `.starcistacks/production/infra/swarm/releases/current.yaml` after verification. |
34
34
  | Rollback | Retain the previous model and images, redeploy that model if convergence or health fails, and record the resulting convergence receipt. Database changes follow an expand/contract sequence. |
35
- | Backup | Provision the manifest's seven days of database PITR and thirty daily encrypted off-host database exports and ingress-volume snapshots. Record the actual destination and encryption custody in `.starcistacks/production/infra/swarm/recovery/restore-proof.md`; the production operator owns both components. |
35
+ | Backup | Provision the manifest's thirty daily encrypted off-host database exports (`starci app db dump`) and ingress-volume snapshots. Record the actual destination and encryption custody in `.starcistacks/production/infra/swarm/recovery/restore-proof.md`; the production operator owns both components. |
36
36
  | Restore proof | Perform a restore into isolated infrastructure, verify the restored database and ingress state, and attach a dated receipt with artifact digests to `.starcistacks/production/infra/swarm/recovery/restore-proof.md`. Its initial record is pending; no restore has been run by scaffolding. |
37
37
 
38
38
  No host scheduler is installed. Scheduled application work belongs to the back-end scheduler or an explicitly declared replicated job.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "environment": "production",
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 production operator owns recovery of the managed Supabase database and both external ingress volumes, `{{project}}-production-ingress-data` and `{{project}}-production-ingress-config-data`. Provision seven days of provider PITR and thirty daily encrypted off-host database exports. Retain thirty daily encrypted off-host snapshots for both ingress volumes.
5
+ The production operator owns recovery of the db service's volume `{{project}}-production-db-data` and both external ingress volumes, `{{project}}-production-ingress-data` and `{{project}}-production-ingress-config-data`. Provision thirty daily encrypted off-host database exports through `starci app db dump`. Retain thirty daily encrypted off-host snapshots for both ingress volumes.
6
6
 
7
- Before relying on the backup policy, restore the provider database backup 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 TLS and ingress configuration. Attach the dated result here without credentials.
7
+ Before relying on the backup policy, restore a `starci app db dump` archive 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 TLS and ingress configuration. Attach the dated result here without credentials.
@@ -8,13 +8,29 @@ services:
8
8
  HTTP_SECURITY_ALLOWED_ORIGINS: https://{{project}}.example.invalid
9
9
  PRIMARY_DB_URL_FILE: /run/secrets/primary-db-url
10
10
  PRIMARY_DB_SCHEMA: public
11
- SUPABASE_URL: https://{{project}}.supabase.co
12
- SUPABASE_SERVICE_ROLE_KEY_FILE: /run/secrets/supabase-service-role-key
11
+ GOOGLE_CLIENT_ID: REPLACE_WITH_GOOGLE_OAUTH_CLIENT_ID
12
+ GOOGLE_CLIENT_SECRET_FILE: /run/secrets/google-client-secret
13
+ GOOGLE_CALLBACK_URL: https://api.{{project}}.example.invalid/auth/google/callback
14
+ AUTH_JWT_ISSUER: https://api.{{project}}.example.invalid
15
+ AUTH_JWT_AUDIENCE: {{project}}
16
+ AUTH_JWT_KEY_ID: production-1
17
+ AUTH_JWT_PRIVATE_KEY_FILE: /run/secrets/auth-jwt-private-key
18
+ STORAGE_ENDPOINT: https://storage.{{project}}.example.invalid
19
+ STORAGE_REGION: us-east-1
20
+ STORAGE_BUCKET: {{project}}-production
21
+ STORAGE_ACCESS_KEY_ID_FILE: /run/secrets/storage-access-key-id
22
+ STORAGE_SECRET_ACCESS_KEY_FILE: /run/secrets/storage-secret-access-key
13
23
  secrets:
14
24
  - source: primary-db-url
15
25
  target: primary-db-url
16
- - source: supabase-service-role-key
17
- target: supabase-service-role-key
26
+ - source: google-client-secret
27
+ target: google-client-secret
28
+ - source: auth-jwt-private-key
29
+ target: auth-jwt-private-key
30
+ - source: storage-access-key-id
31
+ target: storage-access-key-id
32
+ - source: storage-secret-access-key
33
+ target: storage-secret-access-key
18
34
  networks: [app]
19
35
  healthcheck:
20
36
  test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3001/health/live').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
@@ -66,6 +82,38 @@ services:
66
82
  restart_policy:
67
83
  condition: on-failure
68
84
  delay: 5s
85
+ db:
86
+ image: "supabase/postgres:17.11.0.006@sha256:c141fd17ac6a76c4323e90b3aee8ff1a23d2e89e547c2b963cfadc309c079cc2"
87
+ environment:
88
+ POSTGRES_DB: postgres
89
+ POSTGRES_PASSWORD_FILE: /run/secrets/db-password
90
+ secrets:
91
+ - source: db-password
92
+ target: db-password
93
+ volumes:
94
+ - db-data:/var/lib/postgresql/data
95
+ networks: [app]
96
+ healthcheck:
97
+ test: ["CMD", "pg_isready", "-U", "postgres", "-h", "127.0.0.1"]
98
+ interval: 10s
99
+ timeout: 5s
100
+ start_period: 30s
101
+ retries: 5
102
+ deploy:
103
+ replicas: 1
104
+ placement:
105
+ constraints: ["node.labels.{{project}} == production"]
106
+ update_config:
107
+ parallelism: 1
108
+ order: stop-first
109
+ failure_action: rollback
110
+ monitor: 30s
111
+ rollback_config:
112
+ parallelism: 1
113
+ order: stop-first
114
+ restart_policy:
115
+ condition: on-failure
116
+ delay: 5s
69
117
  ingress:
70
118
  image: caddy:2.10.2@sha256:c3d7ee5d2b11f9dc54f947f68a734c84e9c9666c92c88a7f30b9cba5da182adb
71
119
  configs:
@@ -105,13 +153,28 @@ secrets:
105
153
  primary-db-url:
106
154
  external: true
107
155
  name: "{{project}}-production-primary-db-url-v1"
108
- supabase-service-role-key:
156
+ db-password:
157
+ external: true
158
+ name: "{{project}}-production-db-password-v1"
159
+ google-client-secret:
160
+ external: true
161
+ name: "{{project}}-production-google-client-secret-v1"
162
+ auth-jwt-private-key:
109
163
  external: true
110
- name: "{{project}}-production-supabase-service-role-key-v1"
164
+ name: "{{project}}-production-auth-jwt-private-key-v1"
165
+ storage-access-key-id:
166
+ external: true
167
+ name: "{{project}}-production-storage-access-key-id-v1"
168
+ storage-secret-access-key:
169
+ external: true
170
+ name: "{{project}}-production-storage-secret-access-key-v1"
111
171
  configs:
112
172
  ingress-config:
113
173
  file: ./Caddyfile
114
174
  volumes:
175
+ db-data:
176
+ external: true
177
+ name: "{{project}}-production-db-data"
115
178
  ingress-data:
116
179
  external: true
117
180
  name: "{{project}}-production-ingress-data"
@@ -1,6 +1,6 @@
1
1
  # Production environment key roster
2
2
 
3
- This inventory declares names, consumers and mounted file paths. Scaffolding generates the two declared encrypted members with placeholder values encrypted to this app's own age recipient. Replace their values through `starci app secret set production` after owner provisioning and before creating external secrets. The runtime template tracks no ciphertext; its specs generate members with ephemeral identities in disposable copies. Scaffolding supplies no deployment credential or successful deployment.
3
+ This inventory declares names, consumers and mounted file paths. Scaffolding generates the six declared encrypted members with placeholder values encrypted to this app's own age recipient. Replace their values through `starci app secret set production` after owner provisioning and before creating external secrets. The runtime template tracks no ciphertext; its specs generate members with ephemeral identities in disposable copies. Scaffolding supplies no deployment credential or successful deployment.
4
4
 
5
5
  | Key | Consumer |
6
6
  | --- | --- |
@@ -8,14 +8,22 @@ This inventory declares names, consumers and mounted file paths. Scaffolding gen
8
8
  | `HTTP_SECURITY_ALLOWED_ORIGINS` | API origin allowlist |
9
9
  | `PRIMARY_DB_URL` | API least-privilege database connection; reads `PRIMARY_DB_URL_FILE` at `/run/secrets/primary-db-url` |
10
10
  | `PRIMARY_DB_SCHEMA` | API primary database schema |
11
- | `SUPABASE_URL` | API Auth and Storage integration |
12
- | `SUPABASE_SERVICE_ROLE_KEY` | API Auth and Storage integration; reads `SUPABASE_SERVICE_ROLE_KEY_FILE` at `/run/secrets/supabase-service-role-key` |
11
+ | `POSTGRES_PASSWORD` | The db service's superuser password; reads `POSTGRES_PASSWORD_FILE` at `/run/secrets/db-password` |
12
+ | `GOOGLE_CLIENT_ID` | API Google OAuth client |
13
+ | `GOOGLE_CLIENT_SECRET` | API Google OAuth client; reads `GOOGLE_CLIENT_SECRET_FILE` at `/run/secrets/google-client-secret` |
14
+ | `GOOGLE_CALLBACK_URL` | API Google OAuth callback door |
15
+ | `AUTH_JWT_ISSUER` | API access-token issuer |
16
+ | `AUTH_JWT_AUDIENCE` | API access-token audience |
17
+ | `AUTH_JWT_KEY_ID` | API signing key id, published in the JWKS |
18
+ | `AUTH_JWT_PRIVATE_KEY` | API ES256 signing key; reads `AUTH_JWT_PRIVATE_KEY_FILE` at `/run/secrets/auth-jwt-private-key` |
19
+ | `STORAGE_ENDPOINT` | API S3-compatible storage endpoint |
20
+ | `STORAGE_REGION` | API storage signing region |
21
+ | `STORAGE_BUCKET` | API storage bucket |
22
+ | `STORAGE_ACCESS_KEY_ID` | API storage credential; reads `STORAGE_ACCESS_KEY_ID_FILE` at `/run/secrets/storage-access-key-id` |
23
+ | `STORAGE_SECRET_ACCESS_KEY` | API storage credential; reads `STORAGE_SECRET_ACCESS_KEY_FILE` at `/run/secrets/storage-secret-access-key` |
24
+ | `DATABASE_URL` | `starci app db` verbs (migrate, dump, plan) and `contract:emit`, from the operator's environment |
13
25
  | `NEXT_PUBLIC_SITE_URL` | Front-end site URL |
26
+ | `NEXT_PUBLIC_API_URL` | Front-end auth doors of the API |
14
27
  | `NEXT_PUBLIC_GRAPHQL_URL` | Front-end GraphQL transport |
15
- | `NEXT_PUBLIC_SUPABASE_URL` | Browser Auth client |
16
- | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Browser Auth client public key |
17
- | `SUPABASE_ACCESS_TOKEN` | Production database deployment |
18
- | `SUPABASE_DB_PASSWORD` | Production database deployment |
19
- | `SUPABASE_PROJECT_ID` | Production database deployment |
20
28
 
21
- Public front-end configuration is supplied at build time and stays outside sealed custody. Set real values in `.starcistacks/production/secrets/primary-db-url.enc` and `.starcistacks/production/secrets/supabase-service-role-key.enc` at the declared paths. Versioned external application secrets are `{{project}}-production-primary-db-url-v1` and `{{project}}-production-supabase-service-role-key-v1`. Seal deployment-only credentials separately under `.starcistacks/production/secrets/*.enc`; they never enter an API container.
29
+ Public front-end configuration is supplied at build time and stays outside sealed custody. Set real values in the six `.starcistacks/production/secrets/*.enc` members at the declared paths; their versioned external secrets are `{{project}}-production-<member>-v1`. The database deploy workflow reads `DATABASE_URL` from the sealed `database-url` member of its environment; it never enters an API container.