create-flowdular 0.2.6 → 0.3.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/agent-template/.agents/skills/agent-tool-design/SKILL.md +1 -1
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.agents/skills/cli-extension/SKILL.md +1 -1
- package/agent-template/.agents/skills/deploy-operate/SKILL.md +114 -0
- package/agent-template/.agents/skills/module-new/SKILL.md +29 -0
- package/agent-template/.agents/skills/module-update/SKILL.md +9 -1
- package/agent-template/.agents/skills/spec-interview/SKILL.md +114 -0
- package/agent-template/.agents/skills/ux-design/SKILL.md +34 -3
- package/agent-template/.ai/README.md +2 -1
- package/agent-template/.ai/agents/sandbox/business-manager.md +5 -1
- package/agent-template/.ai/blueprints/author-spec/README.md +1 -1
- package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +44 -0
- package/agent-template/.ai/blueprints/author-spec/steps.yaml +5 -5
- package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +99 -3
- package/agent-template/.ai/blueprints/edit-module/gates.yaml +4 -0
- package/agent-template/.ai/blueprints/edit-module/required-files.yaml +9 -0
- package/agent-template/.ai/blueprints/new-module/gates.yaml +4 -0
- package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +2 -2
- package/agent-template/.ai/blueprints/release/gates.yaml +4 -0
- package/agent-template/.ai/platform-capabilities.md +128 -0
- package/agent-template/.ai/policies/capabilities.yaml +130 -3
- package/agent-template/.ai/policies/path-ownership.yaml +5 -2
- package/agent-template/.ai/policies/task-budgets.yaml +5 -3
- package/agent-template/.ai/references/catalog/module.json +4 -4
- package/agent-template/.ai/references/catalog/package.json +2 -2
- package/agent-template/.ai/references/catalog/spec/module.yaml +5 -3
- package/agent-template/.ai/references/catalog/src/platform.ts +2 -0
- package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +89 -1
- package/agent-template/.ai/references/catalog/src/services/data-classes.ts +47 -0
- package/agent-template/.ai/references/catalog/src/services/database-repository.ts +98 -1
- package/agent-template/.ai/references/catalog/src/services/repository.ts +22 -1
- package/agent-template/.ai/references/catalog/tests/data-classes.test.ts +157 -0
- package/agent-template/.ai/references/catalog.provenance.json +12 -10
- package/agent-template/.ai/rules/flowdular.md +4 -0
- package/agent-template/.ai/skills/README.md +10 -0
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +1 -2
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.ai/skills/business-agent-design/SKILL.md +0 -1
- package/agent-template/.ai/skills/cli-extension/SKILL.md +1 -1
- package/agent-template/.ai/skills/deploy-operate/SKILL.md +119 -0
- package/agent-template/.ai/skills/module-new/SKILL.md +29 -3
- package/agent-template/.ai/skills/module-update/SKILL.md +9 -3
- package/agent-template/.ai/skills/perf-audit/SKILL.md +0 -1
- package/agent-template/.ai/skills/release-eject-pr/SKILL.md +0 -1
- package/agent-template/.ai/skills/spec-interview/SKILL.md +120 -0
- package/agent-template/.ai/skills/test-hardening/SKILL.md +1 -0
- package/agent-template/.ai/skills/ux-design/SKILL.md +34 -3
- package/agent-template/.ai/skills/variables/SKILL.md +0 -2
- package/agent-template/.ai/skills/workflow-development/SKILL.md +0 -1
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +1 -1
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.claude/skills/cli-extension/SKILL.md +1 -1
- package/agent-template/.claude/skills/deploy-operate/SKILL.md +114 -0
- package/agent-template/.claude/skills/module-new/SKILL.md +29 -0
- package/agent-template/.claude/skills/module-update/SKILL.md +9 -1
- package/agent-template/.claude/skills/spec-interview/SKILL.md +114 -0
- package/agent-template/.claude/skills/ux-design/SKILL.md +34 -3
- package/agent-template/AGENTS.md +4 -0
- package/agent-template/CLAUDE.md +4 -0
- package/agent-template/docs/adr/0003-module-settings.md +1 -1
- package/agent-template/docs/adr/0006-agentic-workflows.md +24 -21
- package/agent-template/docs/agent-contract.md +2 -2
- package/agent-template/docs/cli-extensions.md +82 -0
- package/agent-template/docs/cli.md +195 -0
- package/agent-template/docs/configuration.md +593 -36
- package/agent-template/docs/design-system.md +185 -31
- package/agent-template/docs/getting-started.md +118 -0
- package/agent-template/docs/module-distribution.md +96 -0
- package/agent-template/docs/module-web-surfaces.md +221 -0
- package/agent-template/docs/modules.md +216 -0
- package/agent-template/docs/operations.md +545 -0
- package/agent-template/docs/sandbox.md +212 -0
- package/agent-template/platform/scripts/build.mjs +11 -0
- package/dist/bin.js +29 -0
- package/package.json +1 -1
- package/template/default/.dockerignore +14 -0
- package/template/default/.env.example +96 -0
- package/template/default/README.md +37 -1
- package/template/default/flowdular.json +15 -4
- package/template/default/infra/README.md +116 -0
- package/template/default/infra/docker/Dockerfile +37 -0
- package/template/default/infra/docker/compose.yaml +158 -0
- package/template/default/infra/docker/postgres/10-roles.sh +31 -0
- package/template/default/infra/docker/postgres/tls-init.sh +28 -0
- package/template/default/infra/kubernetes/database-secret.example.yaml +15 -0
- package/template/default/infra/kubernetes/deployment.yaml +211 -0
- package/template/default/infra/kubernetes/kustomization.yaml +9 -0
- package/template/default/infra/kubernetes/secrets.example.yaml +52 -0
- package/template/default/infra/kubernetes/service.yaml +13 -0
- package/template/default/modules/example/module.json +2 -1
- package/template/default/modules/example/package.json +1 -1
- package/template/default/modules/example/spec/module.yaml +1 -1
- package/template/default/modules/example/src/services/database-repository.ts +2 -12
- package/template/default/package.json +3 -2
- package/template/default/platform/octane.config.ts +99 -9
- package/template/default/platform/package.json +1 -1
- package/template/default/platform/src/generated/modules.client.ts +26 -2
- package/template/default/platform/src/generated/modules.server.ts +241 -10
- package/template/default/platform/src/server/health.ts +47 -0
- package/template/default/platform/src/server/metrics.ts +100 -0
- package/template/default/platform/src/server/storage.ts +172 -0
- package/template/default/platform/src/server/tracing.ts +85 -0
- package/template/default/specs/application.yaml +15 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres-tls:
|
|
3
|
+
image: postgres:17
|
|
4
|
+
entrypoint: ['/bin/bash', '/tls-init.sh']
|
|
5
|
+
volumes:
|
|
6
|
+
- ./postgres/tls-init.sh:/tls-init.sh:ro
|
|
7
|
+
- flowdular-postgres-tls:/tls
|
|
8
|
+
restart: 'no'
|
|
9
|
+
|
|
10
|
+
postgres:
|
|
11
|
+
image: postgres:17
|
|
12
|
+
depends_on:
|
|
13
|
+
postgres-tls:
|
|
14
|
+
condition: service_completed_successfully
|
|
15
|
+
environment:
|
|
16
|
+
POSTGRES_DB: flowdular
|
|
17
|
+
POSTGRES_PASSWORD: ${FD_POSTGRES_SUPERUSER_PASSWORD:?Set FD_POSTGRES_SUPERUSER_PASSWORD in infra/docker/.env (openssl rand -base64 32)}
|
|
18
|
+
FD_DATABASE_MIGRATOR_PASSWORD: ${FD_DATABASE_MIGRATOR_PASSWORD:?Set FD_DATABASE_MIGRATOR_PASSWORD in infra/docker/.env (openssl rand -base64 32)}
|
|
19
|
+
FD_DATABASE_RUNTIME_PASSWORD: ${FD_DATABASE_RUNTIME_PASSWORD:?Set FD_DATABASE_RUNTIME_PASSWORD in infra/docker/.env (openssl rand -base64 32)}
|
|
20
|
+
FD_DATABASE_BACKGROUND_PASSWORD: ${FD_DATABASE_BACKGROUND_PASSWORD:?Set FD_DATABASE_BACKGROUND_PASSWORD in infra/docker/.env (openssl rand -base64 32)}
|
|
21
|
+
command:
|
|
22
|
+
- postgres
|
|
23
|
+
- -c
|
|
24
|
+
- ssl=on
|
|
25
|
+
- -c
|
|
26
|
+
- ssl_cert_file=/tls/server.crt
|
|
27
|
+
- -c
|
|
28
|
+
- ssl_key_file=/tls/server.key
|
|
29
|
+
volumes:
|
|
30
|
+
- flowdular-postgres:/var/lib/postgresql/data
|
|
31
|
+
- flowdular-postgres-tls:/tls:ro
|
|
32
|
+
- ./postgres/10-roles.sh:/docker-entrypoint-initdb.d/10-roles.sh:ro
|
|
33
|
+
healthcheck:
|
|
34
|
+
test: ['CMD-SHELL', 'pg_isready -U postgres -d flowdular']
|
|
35
|
+
interval: 10s
|
|
36
|
+
timeout: 3s
|
|
37
|
+
retries: 10
|
|
38
|
+
start_period: 15s
|
|
39
|
+
security_opt:
|
|
40
|
+
- no-new-privileges:true
|
|
41
|
+
restart: unless-stopped
|
|
42
|
+
|
|
43
|
+
# Rollout gate. Each module applies its own migrations on first use through a
|
|
44
|
+
# migration lease, so this one-shot run does not apply anything: it reports
|
|
45
|
+
# what is pending and fails the rollout when an applied migration no longer
|
|
46
|
+
# matches the checksum of the code being deployed. It needs the workspace and
|
|
47
|
+
# the CLI, so it runs the builder stage rather than the runtime image.
|
|
48
|
+
migration-check:
|
|
49
|
+
build:
|
|
50
|
+
context: ../..
|
|
51
|
+
dockerfile: infra/docker/Dockerfile
|
|
52
|
+
target: builder
|
|
53
|
+
image: flowdular-app-builder:local
|
|
54
|
+
depends_on:
|
|
55
|
+
postgres:
|
|
56
|
+
condition: service_healthy
|
|
57
|
+
command: ['pnpm', 'flowdular', 'migration', 'status', '--json']
|
|
58
|
+
environment:
|
|
59
|
+
FD_DATABASE_ADAPTER: postgresql
|
|
60
|
+
FD_DATABASE_URL: postgresql://coreloom_runtime:${FD_DATABASE_RUNTIME_PASSWORD}@postgres:5432/flowdular
|
|
61
|
+
FD_DATABASE_MIGRATOR_URL: postgresql://coreloom_migrator:${FD_DATABASE_MIGRATOR_PASSWORD}@postgres:5432/flowdular
|
|
62
|
+
FD_DATABASE_TLS: verify-full
|
|
63
|
+
FD_DATABASE_TLS_CA_FILE: /tls/server.crt
|
|
64
|
+
volumes:
|
|
65
|
+
- flowdular-postgres-tls:/tls:ro
|
|
66
|
+
security_opt:
|
|
67
|
+
- no-new-privileges:true
|
|
68
|
+
restart: 'no'
|
|
69
|
+
|
|
70
|
+
app:
|
|
71
|
+
build:
|
|
72
|
+
context: ../..
|
|
73
|
+
dockerfile: infra/docker/Dockerfile
|
|
74
|
+
image: flowdular-app:local
|
|
75
|
+
depends_on:
|
|
76
|
+
postgres:
|
|
77
|
+
condition: service_healthy
|
|
78
|
+
migration-check:
|
|
79
|
+
condition: service_completed_successfully
|
|
80
|
+
environment:
|
|
81
|
+
NODE_ENV: production
|
|
82
|
+
PORT: 3000
|
|
83
|
+
# Every module reads and writes through the platform provider; the
|
|
84
|
+
# deployment has one database and no per-module files.
|
|
85
|
+
FD_DATABASE_ADAPTER: postgresql
|
|
86
|
+
FD_DATABASE_URL: postgresql://coreloom_runtime:${FD_DATABASE_RUNTIME_PASSWORD}@postgres:5432/flowdular
|
|
87
|
+
FD_DATABASE_MIGRATOR_URL: postgresql://coreloom_migrator:${FD_DATABASE_MIGRATOR_PASSWORD}@postgres:5432/flowdular
|
|
88
|
+
# Cross-tenant reads (the scheduler poll) run as this role. It carries no
|
|
89
|
+
# blanket grant: a migration hands it named columns under its own policy.
|
|
90
|
+
FD_DATABASE_BACKGROUND_URL: postgresql://coreloom_background:${FD_DATABASE_BACKGROUND_PASSWORD}@postgres:5432/flowdular
|
|
91
|
+
FD_DATABASE_TLS: verify-full
|
|
92
|
+
FD_DATABASE_TLS_CA_FILE: /tls/server.crt
|
|
93
|
+
FD_AUTH_ALLOW_SIGN_UP: 'false'
|
|
94
|
+
# TLS terminates in front of this container. Only a plain-HTTP run on a
|
|
95
|
+
# workstation may set FD_AUTH_SECURE_COOKIE=false in .env.
|
|
96
|
+
FD_AUTH_SECURE_COOKIE: ${FD_AUTH_SECURE_COOKIE:-true}
|
|
97
|
+
# Every key comes from infra/docker/.env (copy the root .env.example).
|
|
98
|
+
# Compose refuses to start without them; the owning module would refuse at
|
|
99
|
+
# boot anyway.
|
|
100
|
+
FD_AGENT_CREDENTIAL_KEY: ${FD_AGENT_CREDENTIAL_KEY:?Set FD_AGENT_CREDENTIAL_KEY in infra/docker/.env (openssl rand -base64 32)}
|
|
101
|
+
FD_AGENT_RUN_GRANT_KEY: ${FD_AGENT_RUN_GRANT_KEY:?Set FD_AGENT_RUN_GRANT_KEY in infra/docker/.env (openssl rand -base64 32)}
|
|
102
|
+
FD_AUTOMATIONS_CREDENTIAL_KEY: ${FD_AUTOMATIONS_CREDENTIAL_KEY:?Set FD_AUTOMATIONS_CREDENTIAL_KEY in infra/docker/.env (openssl rand -base64 32)}
|
|
103
|
+
FD_NOTIFICATIONS_SECRET_KEY: ${FD_NOTIFICATIONS_SECRET_KEY:?Set FD_NOTIFICATIONS_SECRET_KEY in infra/docker/.env (openssl rand -base64 32)}
|
|
104
|
+
FD_WORKFLOWS_PAYLOAD_KEY: ${FD_WORKFLOWS_PAYLOAD_KEY:?Set FD_WORKFLOWS_PAYLOAD_KEY in infra/docker/.env (openssl rand -base64 32)}
|
|
105
|
+
FD_WORKFLOWS_CURSOR_KEY: ${FD_WORKFLOWS_CURSOR_KEY:?Set FD_WORKFLOWS_CURSOR_KEY in infra/docker/.env (openssl rand -base64 32)}
|
|
106
|
+
FD_STORAGE_ENCRYPTION_KEY: ${FD_STORAGE_ENCRYPTION_KEY:?Set FD_STORAGE_ENCRYPTION_KEY in infra/docker/.env (openssl rand -base64 32)}
|
|
107
|
+
FD_CONNECTORS_SECRET_KEY: ${FD_CONNECTORS_SECRET_KEY:?Set FD_CONNECTORS_SECRET_KEY in infra/docker/.env (openssl rand -base64 32)}
|
|
108
|
+
FD_AUDIT_ANCHOR_KEY: ${FD_AUDIT_ANCHOR_KEY:?Set FD_AUDIT_ANCHOR_KEY in infra/docker/.env (openssl rand -base64 32)}
|
|
109
|
+
# Object storage. Production refuses the local adapter, so the app writes
|
|
110
|
+
# to an S3-compatible bucket and encrypts every object under
|
|
111
|
+
# FD_STORAGE_ENCRYPTION_KEY before it leaves the process. The named volume
|
|
112
|
+
# below is the writable path the local adapter needs when this stack is
|
|
113
|
+
# run outside production for a demonstration.
|
|
114
|
+
FD_STORAGE_ADAPTER: ${FD_STORAGE_ADAPTER:-s3}
|
|
115
|
+
FD_STORAGE_LOCAL_DIRECTORY: /var/lib/flowdular/storage
|
|
116
|
+
FD_STORAGE_S3_BUCKET: ${FD_STORAGE_S3_BUCKET:-}
|
|
117
|
+
FD_STORAGE_S3_REGION: ${FD_STORAGE_S3_REGION:-}
|
|
118
|
+
FD_STORAGE_S3_ENDPOINT: ${FD_STORAGE_S3_ENDPOINT:-}
|
|
119
|
+
FD_STORAGE_S3_ACCESS_KEY_ID: ${FD_STORAGE_S3_ACCESS_KEY_ID:-}
|
|
120
|
+
FD_STORAGE_S3_SECRET_ACCESS_KEY: ${FD_STORAGE_S3_SECRET_ACCESS_KEY:-}
|
|
121
|
+
FD_STORAGE_S3_FORCE_PATH_STYLE: ${FD_STORAGE_S3_FORCE_PATH_STYLE:-false}
|
|
122
|
+
# Optional: without it the app runs and only MFA enrollment is unavailable.
|
|
123
|
+
FD_AUTH_MFA_KEY: ${FD_AUTH_MFA_KEY:-}
|
|
124
|
+
# Outgoing mail. With none, invitations and password resets are refused.
|
|
125
|
+
FD_AUTH_MAIL_TRANSPORT: ${FD_AUTH_MAIL_TRANSPORT:-none}
|
|
126
|
+
FD_TRUST_PROXY: ${FD_TRUST_PROXY:-false}
|
|
127
|
+
FD_METRICS: ${FD_METRICS:-false}
|
|
128
|
+
FD_METRICS_TOKEN: ${FD_METRICS_TOKEN:-}
|
|
129
|
+
FD_AUTH_SMTP_URL: ${FD_AUTH_SMTP_URL:-}
|
|
130
|
+
FD_AUTH_MAIL_FROM: ${FD_AUTH_MAIL_FROM:-}
|
|
131
|
+
ports:
|
|
132
|
+
- '${FD_PORT:-3000}:3000'
|
|
133
|
+
healthcheck:
|
|
134
|
+
test:
|
|
135
|
+
- CMD
|
|
136
|
+
- node
|
|
137
|
+
- -e
|
|
138
|
+
- "fetch('http://127.0.0.1:3000/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"
|
|
139
|
+
interval: 10s
|
|
140
|
+
timeout: 3s
|
|
141
|
+
retries: 5
|
|
142
|
+
start_period: 10s
|
|
143
|
+
init: true
|
|
144
|
+
read_only: true
|
|
145
|
+
tmpfs:
|
|
146
|
+
- /tmp:size=64m,mode=1777
|
|
147
|
+
volumes:
|
|
148
|
+
- flowdular-postgres-tls:/tls:ro
|
|
149
|
+
- flowdular-storage:/var/lib/flowdular/storage
|
|
150
|
+
security_opt:
|
|
151
|
+
- no-new-privileges:true
|
|
152
|
+
restart: unless-stopped
|
|
153
|
+
|
|
154
|
+
volumes:
|
|
155
|
+
flowdular-postgres:
|
|
156
|
+
flowdular-postgres-tls:
|
|
157
|
+
# Only the local adapter writes here; a production run uses the S3 bucket.
|
|
158
|
+
flowdular-storage:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Runs once, during first cluster initialization. The migrator owns the schema;
|
|
3
|
+
# the runtime and background roles hold neither SUPERUSER nor BYPASSRLS, so the
|
|
4
|
+
# row-level security every tenant table forces actually binds the application.
|
|
5
|
+
# The background role exists for the cross-tenant scheduler poll only. It gets
|
|
6
|
+
# no blanket table grant here: each migration grants it the exact columns its
|
|
7
|
+
# poll reads, under a policy of that table's own.
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<SQL
|
|
11
|
+
CREATE ROLE coreloom_migrator LOGIN PASSWORD '${FD_DATABASE_MIGRATOR_PASSWORD}'
|
|
12
|
+
NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE;
|
|
13
|
+
CREATE ROLE coreloom_runtime LOGIN PASSWORD '${FD_DATABASE_RUNTIME_PASSWORD}'
|
|
14
|
+
NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE;
|
|
15
|
+
CREATE ROLE coreloom_background LOGIN PASSWORD '${FD_DATABASE_BACKGROUND_PASSWORD}'
|
|
16
|
+
NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE;
|
|
17
|
+
|
|
18
|
+
REVOKE CONNECT ON DATABASE "$POSTGRES_DB" FROM PUBLIC;
|
|
19
|
+
GRANT CONNECT ON DATABASE "$POSTGRES_DB" TO coreloom_migrator, coreloom_runtime, coreloom_background;
|
|
20
|
+
|
|
21
|
+
ALTER SCHEMA public OWNER TO coreloom_migrator;
|
|
22
|
+
REVOKE ALL ON SCHEMA public FROM PUBLIC;
|
|
23
|
+
GRANT USAGE ON SCHEMA public TO coreloom_runtime, coreloom_background;
|
|
24
|
+
|
|
25
|
+
-- Every table the migrator creates later is reachable by the runtime role
|
|
26
|
+
-- without a second grant step after each migration.
|
|
27
|
+
ALTER DEFAULT PRIVILEGES FOR ROLE coreloom_migrator IN SCHEMA public
|
|
28
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO coreloom_runtime;
|
|
29
|
+
ALTER DEFAULT PRIVILEGES FOR ROLE coreloom_migrator IN SCHEMA public
|
|
30
|
+
GRANT USAGE, SELECT ON SEQUENCES TO coreloom_runtime;
|
|
31
|
+
SQL
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Generates the server certificate Postgres presents and the app verifies.
|
|
3
|
+
# Idempotent: an existing pair is kept so a restart does not invalidate a
|
|
4
|
+
# certificate the app already trusts.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
certificate=/tls/server.crt
|
|
8
|
+
key=/tls/server.key
|
|
9
|
+
|
|
10
|
+
if [ -s "$certificate" ] && [ -s "$key" ]; then
|
|
11
|
+
echo "PostgreSQL TLS material is already present."
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# The common name and SAN must be the compose service name, because the app
|
|
16
|
+
# connects to host "postgres" under FD_DATABASE_TLS=verify-full.
|
|
17
|
+
openssl req -new -x509 -nodes -days 3650 \
|
|
18
|
+
-newkey rsa:2048 \
|
|
19
|
+
-subj '/CN=postgres' \
|
|
20
|
+
-addext 'subjectAltName=DNS:postgres' \
|
|
21
|
+
-keyout "$key" \
|
|
22
|
+
-out "$certificate"
|
|
23
|
+
|
|
24
|
+
# Postgres refuses to start when the private key is group or world readable.
|
|
25
|
+
chown 999:999 "$key" "$certificate"
|
|
26
|
+
chmod 600 "$key"
|
|
27
|
+
chmod 644 "$certificate"
|
|
28
|
+
echo "Generated a self-signed PostgreSQL server certificate for CN=postgres."
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# The deployment reads one database through three roles. The migrator owns the
|
|
2
|
+
# schema, the runtime role holds neither SUPERUSER nor BYPASSRLS so forced row
|
|
3
|
+
# level security binds it, and the background role serves the cross-tenant
|
|
4
|
+
# scheduler poll with no blanket table grant.
|
|
5
|
+
#
|
|
6
|
+
# Create the real Secret out of band; this file documents the shape.
|
|
7
|
+
apiVersion: v1
|
|
8
|
+
kind: Secret
|
|
9
|
+
metadata:
|
|
10
|
+
name: flowdular-database
|
|
11
|
+
type: Opaque
|
|
12
|
+
stringData:
|
|
13
|
+
migratorUrl: postgresql://coreloom_migrator:CHANGE_ME@postgres:5432/flowdular
|
|
14
|
+
runtimeUrl: postgresql://coreloom_runtime:CHANGE_ME@postgres:5432/flowdular
|
|
15
|
+
backgroundUrl: postgresql://coreloom_background:CHANGE_ME@postgres:5432/flowdular
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
apiVersion: apps/v1
|
|
2
|
+
kind: Deployment
|
|
3
|
+
metadata:
|
|
4
|
+
name: flowdular-app
|
|
5
|
+
labels:
|
|
6
|
+
app.kubernetes.io/name: flowdular-app
|
|
7
|
+
spec:
|
|
8
|
+
replicas: 1
|
|
9
|
+
selector:
|
|
10
|
+
matchLabels:
|
|
11
|
+
app.kubernetes.io/name: flowdular-app
|
|
12
|
+
template:
|
|
13
|
+
metadata:
|
|
14
|
+
labels:
|
|
15
|
+
app.kubernetes.io/name: flowdular-app
|
|
16
|
+
annotations:
|
|
17
|
+
prometheus.io/scrape: 'true'
|
|
18
|
+
prometheus.io/path: /api/metrics
|
|
19
|
+
prometheus.io/port: '3000'
|
|
20
|
+
spec:
|
|
21
|
+
securityContext:
|
|
22
|
+
runAsNonRoot: true
|
|
23
|
+
fsGroup: 1001
|
|
24
|
+
fsGroupChangePolicy: OnRootMismatch
|
|
25
|
+
seccompProfile:
|
|
26
|
+
type: RuntimeDefault
|
|
27
|
+
containers:
|
|
28
|
+
- name: app
|
|
29
|
+
# Replace with the image you publish; kustomization.yaml pins the tag.
|
|
30
|
+
image: ghcr.io/OWNER/REPOSITORY:0.1.0
|
|
31
|
+
imagePullPolicy: IfNotPresent
|
|
32
|
+
ports:
|
|
33
|
+
- name: http
|
|
34
|
+
containerPort: 3000
|
|
35
|
+
env:
|
|
36
|
+
- name: NODE_ENV
|
|
37
|
+
value: production
|
|
38
|
+
- name: PORT
|
|
39
|
+
value: '3000'
|
|
40
|
+
- name: FD_DATABASE_ADAPTER
|
|
41
|
+
value: postgresql
|
|
42
|
+
- name: FD_DATABASE_TLS
|
|
43
|
+
value: verify-full
|
|
44
|
+
# One database for the whole deployment. The runtime role holds
|
|
45
|
+
# neither SUPERUSER nor BYPASSRLS, the migrator owns the schema, and
|
|
46
|
+
# the background role serves the cross-tenant scheduler poll.
|
|
47
|
+
- name: FD_DATABASE_URL
|
|
48
|
+
valueFrom:
|
|
49
|
+
secretKeyRef:
|
|
50
|
+
name: flowdular-database
|
|
51
|
+
key: runtimeUrl
|
|
52
|
+
- name: FD_DATABASE_MIGRATOR_URL
|
|
53
|
+
valueFrom:
|
|
54
|
+
secretKeyRef:
|
|
55
|
+
name: flowdular-database
|
|
56
|
+
key: migratorUrl
|
|
57
|
+
- name: FD_DATABASE_BACKGROUND_URL
|
|
58
|
+
valueFrom:
|
|
59
|
+
secretKeyRef:
|
|
60
|
+
name: flowdular-database
|
|
61
|
+
key: backgroundUrl
|
|
62
|
+
- name: FD_AUTH_ALLOW_SIGN_UP
|
|
63
|
+
value: 'false'
|
|
64
|
+
- name: FD_AUTH_SECURE_COOKIE
|
|
65
|
+
value: 'true'
|
|
66
|
+
# With none, invitations and password resets are refused. The smtp
|
|
67
|
+
# transport also needs FD_AUTH_SMTP_URL, which carries the relay
|
|
68
|
+
# password and belongs in a Secret, and FD_AUTH_MAIL_FROM.
|
|
69
|
+
# The Service and any ingress sit in front of this port. Set true
|
|
70
|
+
# only when that ingress overwrites x-forwarded-for.
|
|
71
|
+
- name: FD_TRUST_PROXY
|
|
72
|
+
value: 'false'
|
|
73
|
+
- name: FD_METRICS
|
|
74
|
+
value: 'true'
|
|
75
|
+
# Without a token /api/metrics answers any client that can reach the
|
|
76
|
+
# application port. Optional so a cluster that keeps the port on a
|
|
77
|
+
# scrape-only network can decide to leave it unset.
|
|
78
|
+
- name: FD_METRICS_TOKEN
|
|
79
|
+
valueFrom:
|
|
80
|
+
secretKeyRef:
|
|
81
|
+
name: flowdular-secrets
|
|
82
|
+
key: metricsToken
|
|
83
|
+
optional: true
|
|
84
|
+
# Outgoing mail. none refuses workspace invitations and delivers no
|
|
85
|
+
# password reset message. Switch to smtp once the Secret carries
|
|
86
|
+
# smtpUrl and set the sender the relay accepts.
|
|
87
|
+
- name: FD_AUTH_MAIL_TRANSPORT
|
|
88
|
+
value: 'none'
|
|
89
|
+
# agents.core, automations.core and workflows.core refuse to boot in
|
|
90
|
+
# production without their key. See secrets.example.yaml.
|
|
91
|
+
- name: FD_AGENT_CREDENTIAL_KEY
|
|
92
|
+
valueFrom:
|
|
93
|
+
secretKeyRef:
|
|
94
|
+
name: flowdular-secrets
|
|
95
|
+
key: agentCredentialKey
|
|
96
|
+
- name: FD_AGENT_RUN_GRANT_KEY
|
|
97
|
+
valueFrom:
|
|
98
|
+
secretKeyRef:
|
|
99
|
+
name: flowdular-secrets
|
|
100
|
+
key: agentRunGrantKey
|
|
101
|
+
- name: FD_AUTOMATIONS_CREDENTIAL_KEY
|
|
102
|
+
valueFrom:
|
|
103
|
+
secretKeyRef:
|
|
104
|
+
name: flowdular-secrets
|
|
105
|
+
key: automationsCredentialKey
|
|
106
|
+
- name: FD_NOTIFICATIONS_SECRET_KEY
|
|
107
|
+
valueFrom:
|
|
108
|
+
secretKeyRef:
|
|
109
|
+
name: flowdular-secrets
|
|
110
|
+
key: notificationsSecretKey
|
|
111
|
+
- name: FD_WORKFLOWS_PAYLOAD_KEY
|
|
112
|
+
valueFrom:
|
|
113
|
+
secretKeyRef:
|
|
114
|
+
name: flowdular-secrets
|
|
115
|
+
key: workflowsPayloadKey
|
|
116
|
+
- name: FD_WORKFLOWS_CURSOR_KEY
|
|
117
|
+
valueFrom:
|
|
118
|
+
secretKeyRef:
|
|
119
|
+
name: flowdular-secrets
|
|
120
|
+
key: workflowsCursorKey
|
|
121
|
+
# Object storage. Production refuses the local adapter, so every object
|
|
122
|
+
# goes to an S3-compatible bucket, encrypted under
|
|
123
|
+
# FD_STORAGE_ENCRYPTION_KEY before it leaves the pod.
|
|
124
|
+
- name: FD_STORAGE_ADAPTER
|
|
125
|
+
value: s3
|
|
126
|
+
- name: FD_STORAGE_ENCRYPTION_KEY
|
|
127
|
+
valueFrom:
|
|
128
|
+
secretKeyRef:
|
|
129
|
+
name: flowdular-secrets
|
|
130
|
+
key: storageEncryptionKey
|
|
131
|
+
- name: FD_CONNECTORS_SECRET_KEY
|
|
132
|
+
valueFrom:
|
|
133
|
+
secretKeyRef:
|
|
134
|
+
name: flowdular-secrets
|
|
135
|
+
key: connectorsSecretKey
|
|
136
|
+
- name: FD_AUDIT_ANCHOR_KEY
|
|
137
|
+
valueFrom:
|
|
138
|
+
secretKeyRef:
|
|
139
|
+
name: flowdular-secrets
|
|
140
|
+
key: auditAnchorKey
|
|
141
|
+
- name: FD_STORAGE_S3_BUCKET
|
|
142
|
+
valueFrom:
|
|
143
|
+
secretKeyRef:
|
|
144
|
+
name: flowdular-secrets
|
|
145
|
+
key: storageBucket
|
|
146
|
+
- name: FD_STORAGE_S3_REGION
|
|
147
|
+
valueFrom:
|
|
148
|
+
secretKeyRef:
|
|
149
|
+
name: flowdular-secrets
|
|
150
|
+
key: storageRegion
|
|
151
|
+
# Empty for AWS. MinIO and R2 set their own endpoint, and most
|
|
152
|
+
# self-hosted gateways also need FD_STORAGE_S3_FORCE_PATH_STYLE.
|
|
153
|
+
- name: FD_STORAGE_S3_ENDPOINT
|
|
154
|
+
valueFrom:
|
|
155
|
+
secretKeyRef:
|
|
156
|
+
name: flowdular-secrets
|
|
157
|
+
key: storageEndpoint
|
|
158
|
+
optional: true
|
|
159
|
+
- name: FD_STORAGE_S3_FORCE_PATH_STYLE
|
|
160
|
+
value: 'false'
|
|
161
|
+
- name: FD_STORAGE_S3_ACCESS_KEY_ID
|
|
162
|
+
valueFrom:
|
|
163
|
+
secretKeyRef:
|
|
164
|
+
name: flowdular-secrets
|
|
165
|
+
key: storageAccessKeyId
|
|
166
|
+
- name: FD_STORAGE_S3_SECRET_ACCESS_KEY
|
|
167
|
+
valueFrom:
|
|
168
|
+
secretKeyRef:
|
|
169
|
+
name: flowdular-secrets
|
|
170
|
+
key: storageSecretAccessKey
|
|
171
|
+
# Optional: without it only MFA enrollment is unavailable.
|
|
172
|
+
- name: FD_AUTH_MFA_KEY
|
|
173
|
+
valueFrom:
|
|
174
|
+
secretKeyRef:
|
|
175
|
+
name: flowdular-secrets
|
|
176
|
+
key: authMfaKey
|
|
177
|
+
optional: true
|
|
178
|
+
# Readiness also proves the database answers and that the runtime role
|
|
179
|
+
# holds neither SUPERUSER nor BYPASSRLS; liveness stays static.
|
|
180
|
+
readinessProbe:
|
|
181
|
+
httpGet:
|
|
182
|
+
path: /api/ready
|
|
183
|
+
port: http
|
|
184
|
+
initialDelaySeconds: 3
|
|
185
|
+
periodSeconds: 10
|
|
186
|
+
livenessProbe:
|
|
187
|
+
httpGet:
|
|
188
|
+
path: /api/health
|
|
189
|
+
port: http
|
|
190
|
+
initialDelaySeconds: 10
|
|
191
|
+
periodSeconds: 20
|
|
192
|
+
resources:
|
|
193
|
+
requests:
|
|
194
|
+
cpu: 100m
|
|
195
|
+
memory: 128Mi
|
|
196
|
+
limits:
|
|
197
|
+
cpu: 500m
|
|
198
|
+
memory: 512Mi
|
|
199
|
+
securityContext:
|
|
200
|
+
allowPrivilegeEscalation: false
|
|
201
|
+
readOnlyRootFilesystem: true
|
|
202
|
+
capabilities:
|
|
203
|
+
drop:
|
|
204
|
+
- ALL
|
|
205
|
+
volumeMounts:
|
|
206
|
+
- name: tmp
|
|
207
|
+
mountPath: /tmp
|
|
208
|
+
volumes:
|
|
209
|
+
- name: tmp
|
|
210
|
+
emptyDir:
|
|
211
|
+
sizeLimit: 64Mi
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Example only: not listed in kustomization.yaml. Create the real Secret out of
|
|
2
|
+
# band, for example:
|
|
3
|
+
# kubectl create secret generic flowdular-secrets \
|
|
4
|
+
# --from-literal=agentCredentialKey="$(openssl rand -base64 32)" \
|
|
5
|
+
# --from-literal=agentRunGrantKey="$(openssl rand -base64 32)" \
|
|
6
|
+
# --from-literal=automationsCredentialKey="$(openssl rand -base64 32)" \
|
|
7
|
+
# --from-literal=notificationsSecretKey="$(openssl rand -base64 32)" \
|
|
8
|
+
# --from-literal=workflowsPayloadKey="$(openssl rand -base64 32)" \
|
|
9
|
+
# --from-literal=workflowsCursorKey="$(openssl rand -base64 32)" \
|
|
10
|
+
# --from-literal=storageEncryptionKey="$(openssl rand -base64 32)" \
|
|
11
|
+
# --from-literal=connectorsSecretKey="$(openssl rand -base64 32)" \
|
|
12
|
+
# --from-literal=auditAnchorKey="$(openssl rand -base64 32)" \
|
|
13
|
+
# --from-literal=storageBucket="flowdular-objects" \
|
|
14
|
+
# --from-literal=storageRegion="eu-central-1" \
|
|
15
|
+
# --from-literal=storageAccessKeyId="..." \
|
|
16
|
+
# --from-literal=storageSecretAccessKey="..."
|
|
17
|
+
# storageEndpoint is optional: leave it unset for AWS, set it for MinIO or R2:
|
|
18
|
+
# --from-literal=storageEndpoint="https://minio.internal:9000"
|
|
19
|
+
#
|
|
20
|
+
# Rotating agentCredentialKey invalidates every stored provider credential.
|
|
21
|
+
# Rotating workflowsPayloadKey makes retained execution payloads unreadable.
|
|
22
|
+
#
|
|
23
|
+
# metricsToken is optional and is the bearer token a /api/metrics scrape must
|
|
24
|
+
# present. Without it the endpoint answers any client that reaches the port:
|
|
25
|
+
# --from-literal=metricsToken="$(openssl rand -base64 32)"
|
|
26
|
+
apiVersion: v1
|
|
27
|
+
kind: Secret
|
|
28
|
+
metadata:
|
|
29
|
+
name: flowdular-secrets
|
|
30
|
+
labels:
|
|
31
|
+
app.kubernetes.io/name: flowdular-app
|
|
32
|
+
type: Opaque
|
|
33
|
+
stringData:
|
|
34
|
+
agentCredentialKey: REPLACE_WITH_BASE64_32_BYTE_KEY
|
|
35
|
+
agentRunGrantKey: REPLACE_WITH_BASE64_32_BYTE_KEY
|
|
36
|
+
automationsCredentialKey: REPLACE_WITH_BASE64_32_BYTE_KEY
|
|
37
|
+
notificationsSecretKey: REPLACE_WITH_BASE64_32_BYTE_KEY
|
|
38
|
+
workflowsPayloadKey: REPLACE_WITH_BASE64_32_BYTE_KEY
|
|
39
|
+
workflowsCursorKey: REPLACE_WITH_BASE64_32_BYTE_KEY
|
|
40
|
+
storageEncryptionKey: REPLACE_WITH_BASE64_32_BYTE_KEY
|
|
41
|
+
connectorsSecretKey: REPLACE_WITH_BASE64_32_BYTE_KEY
|
|
42
|
+
auditAnchorKey: REPLACE_WITH_BASE64_32_BYTE_KEY
|
|
43
|
+
storageBucket: REPLACE_WITH_OBJECT_BUCKET
|
|
44
|
+
storageRegion: REPLACE_WITH_OBJECT_REGION
|
|
45
|
+
storageAccessKeyId: REPLACE_WITH_OBJECT_ACCESS_KEY_ID
|
|
46
|
+
storageSecretAccessKey: REPLACE_WITH_OBJECT_SECRET_ACCESS_KEY
|
|
47
|
+
# Optional: leave unset for AWS, set it for MinIO or R2.
|
|
48
|
+
storageEndpoint: REPLACE_WITH_OBJECT_ENDPOINT_OR_REMOVE
|
|
49
|
+
# Optional, base64url encoded.
|
|
50
|
+
authMfaKey: REPLACE_WITH_BASE64URL_32_BYTE_KEY
|
|
51
|
+
# Optional.
|
|
52
|
+
metricsToken: REPLACE_WITH_SCRAPE_TOKEN_OR_REMOVE
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"id": "example.core",
|
|
5
5
|
"package": "@app/module-example",
|
|
6
6
|
"version": "0.1.0",
|
|
7
|
+
"platformApi": "^0.1.0",
|
|
7
8
|
"profile": "full",
|
|
8
9
|
"capabilities": ["api", "database", "client", "translations"],
|
|
9
10
|
"platform": {
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
"dependencies": [
|
|
14
15
|
{
|
|
15
16
|
"id": "auth.core",
|
|
16
|
-
"range": "0.
|
|
17
|
+
"range": "^0.13.0"
|
|
17
18
|
}
|
|
18
19
|
],
|
|
19
20
|
"tenancy": "required",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DatabaseHandle } from '@flowdular/sdk/database';
|
|
2
|
-
import { runDatabaseMigrations } from '@flowdular/sdk/database';
|
|
2
|
+
import { integer, runDatabaseMigrations } from '@flowdular/sdk/database';
|
|
3
3
|
import type { Note } from '../domain/types.ts';
|
|
4
4
|
import { databaseMigrations } from './migration.ts';
|
|
5
5
|
import type { NoteRepository } from './repository.ts';
|
|
@@ -23,23 +23,13 @@ const CREATE = `INSERT INTO example_notes
|
|
|
23
23
|
(tenant_id, id, title, body, created_at)
|
|
24
24
|
VALUES ($1, $2, $3, $4, $5)`;
|
|
25
25
|
|
|
26
|
-
/* PostgreSQL returns BIGINT as a string, so every numeric read is normalized
|
|
27
|
-
before it reaches the domain. */
|
|
28
|
-
function integer(value: NoteRow['created_at']): number {
|
|
29
|
-
const normalized = Number(value);
|
|
30
|
-
if (!Number.isSafeInteger(normalized) || normalized < 0) {
|
|
31
|
-
throw new Error('The example database returned an invalid timestamp.');
|
|
32
|
-
}
|
|
33
|
-
return normalized;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
26
|
function fromRow(row: NoteRow): Note {
|
|
37
27
|
return {
|
|
38
28
|
tenantId: row.tenant_id,
|
|
39
29
|
id: row.id,
|
|
40
30
|
title: row.title,
|
|
41
31
|
body: row.body,
|
|
42
|
-
createdAt: integer(row.created_at),
|
|
32
|
+
createdAt: integer(row.created_at, 'created_at', { min: 0 }),
|
|
43
33
|
};
|
|
44
34
|
}
|
|
45
35
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "node --env-file-if-exists=.env platform/scripts/dev.mjs",
|
|
12
|
+
"sandbox": "npx @flowdular/sandbox",
|
|
12
13
|
"typecheck": "pnpm -r --if-present typecheck",
|
|
13
14
|
"test": "pnpm -r --if-present test",
|
|
14
15
|
"format": "prettier --write .",
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
"flowdular": "flowdular",
|
|
17
18
|
"cl": "flowdular",
|
|
18
19
|
"doctor": "flowdular doctor",
|
|
19
|
-
"verify": "pnpm rules:check && pnpm typecheck && pnpm test && flowdular module validate && pnpm format:check",
|
|
20
|
+
"verify": "pnpm rules:check && pnpm typecheck && pnpm test && flowdular spec validate --all && flowdular module validate && pnpm format:check",
|
|
20
21
|
"rules:generate": "rulesync generate",
|
|
21
22
|
"rules:check": "rulesync generate --check",
|
|
22
23
|
"build": "flowdular module sync --apply && pnpm --filter @app/platform build"
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"@tsrx/prettier-plugin": "0.3.120",
|
|
26
27
|
"prettier": "3.6.2",
|
|
27
|
-
"flowdular": "0.
|
|
28
|
+
"flowdular": "0.3.1",
|
|
28
29
|
"rulesync": "16.21.0"
|
|
29
30
|
}
|
|
30
31
|
}
|