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.
Files changed (103) hide show
  1. package/agent-template/.agents/skills/agent-tool-design/SKILL.md +1 -1
  2. package/agent-template/.agents/skills/auth-security-review/SKILL.md +1 -1
  3. package/agent-template/.agents/skills/cli-extension/SKILL.md +1 -1
  4. package/agent-template/.agents/skills/deploy-operate/SKILL.md +114 -0
  5. package/agent-template/.agents/skills/module-new/SKILL.md +29 -0
  6. package/agent-template/.agents/skills/module-update/SKILL.md +9 -1
  7. package/agent-template/.agents/skills/spec-interview/SKILL.md +114 -0
  8. package/agent-template/.agents/skills/ux-design/SKILL.md +34 -3
  9. package/agent-template/.ai/README.md +2 -1
  10. package/agent-template/.ai/agents/sandbox/business-manager.md +5 -1
  11. package/agent-template/.ai/blueprints/author-spec/README.md +1 -1
  12. package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +44 -0
  13. package/agent-template/.ai/blueprints/author-spec/steps.yaml +5 -5
  14. package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +99 -3
  15. package/agent-template/.ai/blueprints/edit-module/gates.yaml +4 -0
  16. package/agent-template/.ai/blueprints/edit-module/required-files.yaml +9 -0
  17. package/agent-template/.ai/blueprints/new-module/gates.yaml +4 -0
  18. package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +2 -2
  19. package/agent-template/.ai/blueprints/release/gates.yaml +4 -0
  20. package/agent-template/.ai/platform-capabilities.md +128 -0
  21. package/agent-template/.ai/policies/capabilities.yaml +130 -3
  22. package/agent-template/.ai/policies/path-ownership.yaml +5 -2
  23. package/agent-template/.ai/policies/task-budgets.yaml +5 -3
  24. package/agent-template/.ai/references/catalog/module.json +4 -4
  25. package/agent-template/.ai/references/catalog/package.json +2 -2
  26. package/agent-template/.ai/references/catalog/spec/module.yaml +5 -3
  27. package/agent-template/.ai/references/catalog/src/platform.ts +2 -0
  28. package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +89 -1
  29. package/agent-template/.ai/references/catalog/src/services/data-classes.ts +47 -0
  30. package/agent-template/.ai/references/catalog/src/services/database-repository.ts +98 -1
  31. package/agent-template/.ai/references/catalog/src/services/repository.ts +22 -1
  32. package/agent-template/.ai/references/catalog/tests/data-classes.test.ts +157 -0
  33. package/agent-template/.ai/references/catalog.provenance.json +12 -10
  34. package/agent-template/.ai/rules/flowdular.md +4 -0
  35. package/agent-template/.ai/skills/README.md +10 -0
  36. package/agent-template/.ai/skills/agent-tool-design/SKILL.md +1 -2
  37. package/agent-template/.ai/skills/auth-security-review/SKILL.md +1 -1
  38. package/agent-template/.ai/skills/business-agent-design/SKILL.md +0 -1
  39. package/agent-template/.ai/skills/cli-extension/SKILL.md +1 -1
  40. package/agent-template/.ai/skills/deploy-operate/SKILL.md +119 -0
  41. package/agent-template/.ai/skills/module-new/SKILL.md +29 -3
  42. package/agent-template/.ai/skills/module-update/SKILL.md +9 -3
  43. package/agent-template/.ai/skills/perf-audit/SKILL.md +0 -1
  44. package/agent-template/.ai/skills/release-eject-pr/SKILL.md +0 -1
  45. package/agent-template/.ai/skills/spec-interview/SKILL.md +120 -0
  46. package/agent-template/.ai/skills/test-hardening/SKILL.md +1 -0
  47. package/agent-template/.ai/skills/ux-design/SKILL.md +34 -3
  48. package/agent-template/.ai/skills/variables/SKILL.md +0 -2
  49. package/agent-template/.ai/skills/workflow-development/SKILL.md +0 -1
  50. package/agent-template/.claude/skills/agent-tool-design/SKILL.md +1 -1
  51. package/agent-template/.claude/skills/auth-security-review/SKILL.md +1 -1
  52. package/agent-template/.claude/skills/cli-extension/SKILL.md +1 -1
  53. package/agent-template/.claude/skills/deploy-operate/SKILL.md +114 -0
  54. package/agent-template/.claude/skills/module-new/SKILL.md +29 -0
  55. package/agent-template/.claude/skills/module-update/SKILL.md +9 -1
  56. package/agent-template/.claude/skills/spec-interview/SKILL.md +114 -0
  57. package/agent-template/.claude/skills/ux-design/SKILL.md +34 -3
  58. package/agent-template/AGENTS.md +4 -0
  59. package/agent-template/CLAUDE.md +4 -0
  60. package/agent-template/docs/adr/0003-module-settings.md +1 -1
  61. package/agent-template/docs/adr/0006-agentic-workflows.md +24 -21
  62. package/agent-template/docs/agent-contract.md +2 -2
  63. package/agent-template/docs/cli-extensions.md +82 -0
  64. package/agent-template/docs/cli.md +195 -0
  65. package/agent-template/docs/configuration.md +593 -36
  66. package/agent-template/docs/design-system.md +185 -31
  67. package/agent-template/docs/getting-started.md +118 -0
  68. package/agent-template/docs/module-distribution.md +96 -0
  69. package/agent-template/docs/module-web-surfaces.md +221 -0
  70. package/agent-template/docs/modules.md +216 -0
  71. package/agent-template/docs/operations.md +545 -0
  72. package/agent-template/docs/sandbox.md +212 -0
  73. package/agent-template/platform/scripts/build.mjs +11 -0
  74. package/dist/bin.js +29 -0
  75. package/package.json +1 -1
  76. package/template/default/.dockerignore +14 -0
  77. package/template/default/.env.example +96 -0
  78. package/template/default/README.md +37 -1
  79. package/template/default/flowdular.json +15 -4
  80. package/template/default/infra/README.md +116 -0
  81. package/template/default/infra/docker/Dockerfile +37 -0
  82. package/template/default/infra/docker/compose.yaml +158 -0
  83. package/template/default/infra/docker/postgres/10-roles.sh +31 -0
  84. package/template/default/infra/docker/postgres/tls-init.sh +28 -0
  85. package/template/default/infra/kubernetes/database-secret.example.yaml +15 -0
  86. package/template/default/infra/kubernetes/deployment.yaml +211 -0
  87. package/template/default/infra/kubernetes/kustomization.yaml +9 -0
  88. package/template/default/infra/kubernetes/secrets.example.yaml +52 -0
  89. package/template/default/infra/kubernetes/service.yaml +13 -0
  90. package/template/default/modules/example/module.json +2 -1
  91. package/template/default/modules/example/package.json +1 -1
  92. package/template/default/modules/example/spec/module.yaml +1 -1
  93. package/template/default/modules/example/src/services/database-repository.ts +2 -12
  94. package/template/default/package.json +3 -2
  95. package/template/default/platform/octane.config.ts +99 -9
  96. package/template/default/platform/package.json +1 -1
  97. package/template/default/platform/src/generated/modules.client.ts +26 -2
  98. package/template/default/platform/src/generated/modules.server.ts +241 -10
  99. package/template/default/platform/src/server/health.ts +47 -0
  100. package/template/default/platform/src/server/metrics.ts +100 -0
  101. package/template/default/platform/src/server/storage.ts +172 -0
  102. package/template/default/platform/src/server/tracing.ts +85 -0
  103. 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,9 @@
1
+ apiVersion: kustomize.config.k8s.io/v1beta1
2
+ kind: Kustomization
3
+ resources:
4
+ - deployment.yaml
5
+ - service.yaml
6
+ images:
7
+ # Replace with the image you publish.
8
+ - name: ghcr.io/OWNER/REPOSITORY
9
+ newTag: 0.1.0
@@ -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
@@ -0,0 +1,13 @@
1
+ apiVersion: v1
2
+ kind: Service
3
+ metadata:
4
+ name: flowdular-app
5
+ labels:
6
+ app.kubernetes.io/name: flowdular-app
7
+ spec:
8
+ selector:
9
+ app.kubernetes.io/name: flowdular-app
10
+ ports:
11
+ - name: http
12
+ port: 80
13
+ targetPort: http
@@ -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.10.0"
17
+ "range": "^0.13.0"
17
18
  }
18
19
  ],
19
20
  "tenancy": "required",
@@ -16,7 +16,7 @@
16
16
  "dependencies": {
17
17
  "octane": "0.1.51",
18
18
  "segment-state": "0.2.1",
19
- "@flowdular/sdk": "0.2.4"
19
+ "@flowdular/sdk": "0.3.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@tsrx/typescript-plugin": "0.3.120",
@@ -12,7 +12,7 @@ capabilities:
12
12
  - translations
13
13
  dependencies:
14
14
  - id: auth.core
15
- range: 0.10.0
15
+ range: ^0.13.0
16
16
  tenancy: required
17
17
  locales:
18
18
  - en
@@ -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.2.4",
28
+ "flowdular": "0.3.1",
28
29
  "rulesync": "16.21.0"
29
30
  }
30
31
  }