@starci/hfs 4.9.11 → 4.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +7 -9
- package/db/backup-receipt.mjs +10 -47
- package/db/drift.mjs +1 -1
- package/db/dump.mjs +11 -10
- package/db/entity-list.mjs +1 -1
- package/db/migrate.mjs +83 -259
- package/db/new.mjs +46 -0
- package/db/post-cutover.mjs +13 -17
- package/db/postgres.mjs +80 -0
- package/emit/contracts.mjs +5 -9
- package/package.json +1 -1
- package/release/gate.mjs +3 -15
- package/runtime/knowledge/hfs/canon-pins.yaml +44 -24
- package/runtime/knowledge/hfs/design-truths.yaml +1 -1
- package/runtime/knowledge/hfs/fe-stack.yaml +4 -11
- package/runtime/knowledge/hfs/rules.yaml +179 -157
- package/runtime/knowledge/hfs/slots.yaml +185 -172
- package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
- package/runtime/knowledge/patterns/be/idempotency.yaml +3 -3
- package/runtime/knowledge/patterns/be/jobs.yaml +4 -4
- package/runtime/knowledge/patterns/be/projections.yaml +7 -5
- package/runtime/knowledge/patterns/be/queues.yaml +8 -8
- package/runtime/knowledge/patterns/be/reactors.yaml +2 -2
- package/runtime/knowledge/patterns/be/realtime.yaml +6 -6
- package/runtime/knowledge/patterns/be/saga.yaml +3 -3
- package/runtime/knowledge/patterns/be/webhooks.yaml +1 -1
- package/runtime/modules/kernel/command-policy.yaml +1 -1
- package/runtime/modules/kernel/failure-codes.yaml +65 -115
- package/runtime/modules/schemas/application-stacks.schema.yaml +5 -5
- package/runtime/scripts/hfs/architecture/canon-clones.mjs +1 -1
- package/runtime/scripts/hfs/architecture/entity-registry.mjs +53 -0
- package/runtime/scripts/hfs/architecture/{supabase-be.mjs → identity.mjs} +45 -15
- package/runtime/scripts/hfs/architecture/index.mjs +2 -3
- package/runtime/scripts/hfs/architecture/{supabase-ast.mjs → jwt-ast.mjs} +2 -103
- package/runtime/scripts/hfs/architecture/owners.mjs +11 -0
- package/runtime/scripts/hfs/architecture/register-once.mjs +1 -1
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +29 -4
- package/runtime/scripts/hfs/architecture/server-action.mjs +3 -4
- package/runtime/scripts/hfs/architecture/side-boundary.mjs +1 -1
- package/runtime/scripts/hfs/architecture/{supabase-keys.mjs → sql-keys.mjs} +1 -1
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +25 -27
- package/runtime/scripts/hfs/architecture/tiers.mjs +14 -3
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +7 -3
- package/runtime/scripts/hfs/check.mjs +6 -6
- package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
- package/runtime/scripts/hfs/db-baseline-verb.mjs +48 -30
- package/runtime/scripts/hfs/db-plan-verb.mjs +17 -106
- package/runtime/scripts/hfs/db-restamp-verb.mjs +11 -10
- package/runtime/scripts/hfs/declaration-shape.mjs +15 -18
- package/runtime/scripts/hfs/manifest-shape.mjs +9 -5
- package/runtime/scripts/hfs/rule-params-shape.mjs +4 -1
- package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -1
- package/runtime/scripts/hfs/rules/cli.mjs +1 -1
- package/runtime/scripts/hfs/rules/database-access.mjs +8 -74
- package/runtime/scripts/hfs/rules/database-baseline.mjs +3 -3
- package/runtime/scripts/hfs/rules/database-config.mjs +6 -214
- package/runtime/scripts/hfs/rules/database-constants.mjs +8 -10
- package/runtime/scripts/hfs/rules/database-migrations.mjs +3 -3
- package/runtime/scripts/hfs/rules/database-queues.mjs +4 -5
- package/runtime/scripts/hfs/rules/database-sql.mjs +1 -1
- package/runtime/scripts/hfs/rules/database.mjs +36 -39
- package/runtime/scripts/hfs/rules/edition.mjs +1 -1
- package/runtime/scripts/hfs/rules/{supabase-secrets.mjs → lite-secrets.mjs} +6 -6
- package/runtime/scripts/hfs/rules/script-hygiene.mjs +1 -10
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +5 -4
- package/runtime/scripts/hfs/rules/stacks-release.mjs +3 -2
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +2 -2
- package/runtime/scripts/hfs/rules/stacks.mjs +1 -1
- package/runtime/scripts/hfs/slot-app-view.mjs +1 -1
- package/runtime/scripts/hfs/slots.mjs +8 -2
- package/runtime/scripts/hfs/sql/pg-parse.mjs +2 -2
- package/runtime/scripts/lib/secret-patterns.mjs +2 -2
- package/runtime/scripts/lib/swarm-stack.mjs +7 -4
- package/scaffold/add-app.mjs +2 -10
- package/scaffold/add-cli-lite.mjs +6 -6
- package/scaffold/add-compose.mjs +1 -1
- package/scaffold/add-inbox-lite.mjs +4 -8
- package/scaffold/add-plan.mjs +1 -1
- package/scaffold/add-queue-lite.mjs +16 -24
- package/scaffold/add-table.mjs +34 -26
- package/scaffold/add.mjs +2 -2
- package/scaffold/app.mjs +19 -86
- package/scaffold/lite-exports.mjs +1 -1
- package/src/main.mjs +19 -20
- package/stack/target-binding.mjs +0 -60
- package/stack/urgent-floor.mjs +2 -2
- package/sync/index.mjs +32 -8
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -17
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +6 -8
- package/templates/app/codegen-lite/scripts/codegen.mjs +2 -39
- package/templates/app/db-bootstrap/db/bootstrap.sql +139 -0
- package/templates/app/gitignore-lite +0 -3
- package/templates/app/package-scripts/db.json +9 -0
- package/templates/app/package-scripts/package.json +0 -1
- package/templates/app/package-scripts-lite/package.json +1 -1
- package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/README.md +1 -1
- package/templates/app/skeleton/README.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +84 -19
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +5 -5
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +69 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -7
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +6 -6
- package/templates/app/skeleton-lite/.starcistacks/production/environment.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +2 -2
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +69 -6
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +17 -9
- package/templates/app/skeleton-lite/README.md +11 -11
- package/templates/app/skeleton-lite/{supabase → db}/migrations/__timestamp___baseline.sql +21 -1
- package/templates/app/tool-config-lite/prettierignore +0 -1
- package/templates/be/inbox/database-client.spec.ts.tpl +9 -5
- package/templates/be/inbox/database-client.ts.tpl +13 -4
- package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +47 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +3 -1
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +2 -2
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +6 -1
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +39 -7
- package/templates/be/inbox/platform/migration.sql.tpl +22 -0
- package/templates/be/inbox/service.spec.ts.tpl +2 -2
- package/templates/be/inbox/service.ts.tpl +1 -2
- package/templates/be/patterns/jobs/platform-lite/job-claim.service.spec.ts.tpl +156 -0
- package/templates/be/patterns/jobs/platform-lite/job-claim.service.ts.tpl +114 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +51 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.service.spec.ts.tpl +62 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.service.ts.tpl +20 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +35 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +4 -1
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +16 -5
- package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +8 -8
- package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +3 -3
- package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +2 -2
- package/templates/be/skeleton-lite/apps/api/Dockerfile +0 -1
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +9 -5
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +7 -4
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +4 -4
- package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +3 -3
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +9 -13
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +4 -2
- package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +2 -2
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +3 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +4 -4
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +6 -6
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +25 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +9 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +35 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +23 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +29 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +63 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +11 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +32 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +69 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +37 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +36 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +59 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.functions.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +4 -2
- package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +19 -6
- package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +7 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +2 -2
- package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +2 -2
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +1 -1
- package/templates/be/table/entities.index.ts.tpl +4 -0
- package/templates/be/table/entity.ts.tpl +21 -0
- package/templates/be/table/index.ts.tpl +1 -0
- package/templates/be/table/module.ts.tpl +1 -1
- package/templates/be/table/service.api.ts.tpl +10 -11
- package/templates/be/table/service.ts.tpl +5 -6
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +30 -0
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +46 -14
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +31 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +2 -5
- package/db/local-migrate.mjs +0 -194
- package/db/reset.mjs +0 -125
- package/emit/db-types.mjs +0 -271
- package/runtime/scripts/api/supabase/database-dump.mjs +0 -5
- package/runtime/scripts/api/supabase/database-push.mjs +0 -5
- package/runtime/scripts/api/supabase/database-query.mjs +0 -5
- package/runtime/scripts/api/supabase/lib.mjs +0 -55
- package/runtime/scripts/api/supabase/migration-repair.mjs +0 -5
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +0 -50
- package/runtime/scripts/hfs/architecture/supabase.mjs +0 -226
- package/scaffold/supabase-ports.mjs +0 -26
- package/templates/app/package-scripts/supabase.json +0 -7
- package/templates/app/skeleton-lite/supabase/config.toml +0 -48
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +0 -5
- package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +0 -33
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +0 -33
- package/templates/be/inbox/sql.ts.tpl +0 -16
- package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +0 -33
- package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +0 -12
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -16
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +0 -6
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +0 -27
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +0 -24
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +0 -8
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +0 -17
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +0 -32
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +0 -24
- package/templates/be/table/rows.ts.tpl +0 -4
- package/templates/be/table/sql.api.ts.tpl +0 -7
- package/templates/be/table/sql.ts.tpl +0 -4
- package/templates/fe/auth/supabase-client.ts.tpl +0 -38
- package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +0 -11
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +0 -18
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +0 -19
- /package/templates/app/skeleton-lite/{supabase → db}/seed.sql +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Production stack
|
|
2
2
|
|
|
3
|
-
The app and its dependencies run on Docker Swarm
|
|
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.
|
|
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
|
-
|
|
11
|
-
provider:
|
|
12
|
-
|
|
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
|
|
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:
|
|
66
|
-
target: Owner-provisioned encrypted off-host storage for
|
|
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:
|
|
79
|
-
runtimeName: "{{project}}-dev-
|
|
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/
|
|
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:
|
|
118
|
-
target:
|
|
119
|
-
retention:
|
|
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:
|
|
131
|
-
runtimeName: "{{project}}-production-
|
|
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/
|
|
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
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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;
|
|
11
|
-
| doctor | From this directory, run `docker stack config -c infra/swarm/stack.yaml`. Confirm image, secret and local
|
|
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. |
|
|
@@ -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
|
|
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
|
-
|
|
12
|
-
|
|
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:
|
|
17
|
-
target:
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
|
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
|
-
| `
|
|
12
|
-
| `
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
32
|
-
| Public build configuration | Build the front end with its declared public site and GraphQL URLs
|
|
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
|
|
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.
|
package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
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:
|
|
17
|
-
target:
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
-
| `
|
|
12
|
-
| `
|
|
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
|
|
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.
|