@synapsor/runner 1.6.1 → 1.6.3
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 +98 -3
- package/README.md +33 -19
- package/SECURITY.md +10 -2
- package/THREAT_MODEL.md +27 -4
- package/dist/authoring.mjs +53 -3
- package/dist/cli.d.ts +19 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/local-ui.d.ts +85 -1
- package/dist/local-ui.d.ts.map +1 -1
- package/dist/runner.mjs +33542 -20382
- package/dist/runtime.mjs +3581 -237
- package/dist/shadow.mjs +2423 -65
- package/docs/README.md +22 -2
- package/docs/agent-guided-setup.md +239 -0
- package/docs/approval-roles-and-operator-identity.md +353 -0
- package/docs/auto-boundary-and-scoped-explore.md +32 -6
- package/docs/capability-authoring.md +19 -0
- package/docs/current-scope.md +20 -1
- package/docs/cursor-plugin.md +2 -2
- package/docs/dsl-json-parity.md +75 -0
- package/docs/dsl-reference.md +10 -0
- package/docs/fresh-developer-usability.md +155 -80
- package/docs/getting-started-own-database.md +5 -4
- package/docs/guarded-crud-writeback.md +19 -0
- package/docs/guided-onboarding.md +331 -0
- package/docs/human-attention-notifications.md +367 -0
- package/docs/limitations.md +19 -3
- package/docs/local-mode.md +16 -2
- package/docs/migrating-to-synapsor-spec.md +38 -0
- package/docs/production.md +39 -0
- package/docs/release-notes.md +87 -4
- package/docs/runner-config-reference.md +132 -0
- package/docs/running-a-runner-fleet.md +16 -1
- package/docs/security-boundary.md +24 -0
- package/docs/store-lifecycle.md +20 -1
- package/docs/supervised-automatic-apply.md +267 -0
- package/docs/troubleshooting-first-run.md +35 -0
- package/examples/app-owned-writeback/command-handler.mjs +0 -0
- package/examples/fitflow-guided-onboarding/README.md +43 -0
- package/examples/fitflow-guided-onboarding/app/page.tsx +8 -0
- package/examples/fitflow-guided-onboarding/docker-compose.yml +16 -0
- package/examples/fitflow-guided-onboarding/package.json +18 -0
- package/examples/fitflow-guided-onboarding/prisma/schema.prisma +98 -0
- package/examples/fitflow-guided-onboarding/seed/postgres.sql +401 -0
- package/examples/mcp-postgres-billing-app-handler/scripts/run-demo.sh +0 -0
- package/examples/operator-oidc/issuer.mjs +188 -0
- package/examples/reference-support-billing-app/scripts/run-demo.sh +0 -0
- package/examples/support-billing-agent/scripts/run-demo.sh +0 -0
- package/examples/support-billing-agent/scripts/run-evaluation.sh +0 -0
- package/examples/support-plan-credit/README.md +6 -2
- package/package.json +11 -9
- package/schemas/schema-candidate-review.schema.json +42 -0
- package/schemas/synapsor.runner.schema.json +227 -4
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Operator-Supervised Automatic Apply
|
|
2
|
+
|
|
3
|
+
Synapsor separates two decisions that are often incorrectly combined:
|
|
4
|
+
|
|
5
|
+
1. Who or what may approve a proposal?
|
|
6
|
+
2. Who or what may execute an approved proposal?
|
|
7
|
+
|
|
8
|
+
The model answers neither question. It supplies a bounded request through a
|
|
9
|
+
reviewed capability. Human-reviewed policy may approve that exact immutable
|
|
10
|
+
proposal, and a separately operated Runner worker may apply it only after
|
|
11
|
+
revalidating the complete guarded-write boundary.
|
|
12
|
+
|
|
13
|
+
Supervised automatic apply is additive and disabled by default. Upgrading does
|
|
14
|
+
not start a worker, change existing `AUTO APPROVE` behavior, or apply an
|
|
15
|
+
already-approved proposal.
|
|
16
|
+
|
|
17
|
+
## The Four Supported Modes
|
|
18
|
+
|
|
19
|
+
| Approval | Execution | Behavior |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Human | Manual | Default: a qualified operator approves and explicitly runs `apply`. |
|
|
22
|
+
| Human | Supervised worker | A qualified operator approves; the trusted worker may apply. |
|
|
23
|
+
| Policy auto-approval | Manual | Reviewed policy approves; an operator still runs `apply`. This is the legacy `AUTO APPROVE` behavior. |
|
|
24
|
+
| Policy auto-approval | Supervised worker | Reviewed policy approves; an independently enabled worker may guardedly apply. |
|
|
25
|
+
|
|
26
|
+
The default remains human approval plus manual apply.
|
|
27
|
+
|
|
28
|
+
## Dual Opt-In
|
|
29
|
+
|
|
30
|
+
Automatic execution requires both:
|
|
31
|
+
|
|
32
|
+
1. Contract permission, included in the canonical contract digest.
|
|
33
|
+
2. Deployment permission for the exact capability and exact active digest.
|
|
34
|
+
|
|
35
|
+
Public DSL (Domain-Specific Language):
|
|
36
|
+
|
|
37
|
+
```sql
|
|
38
|
+
CREATE CAPABILITY billing.propose_small_credit
|
|
39
|
+
...
|
|
40
|
+
AUTO APPROVE WHEN amount_cents <= 2500
|
|
41
|
+
LIMIT 20 PER DAY
|
|
42
|
+
ALLOW SUPERVISED WORKER APPLY;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The final clause compiles to:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"execution": {
|
|
50
|
+
"supervised_worker": "allowed"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That permission alone applies nothing. The operator must separately configure
|
|
56
|
+
an exact deployment allowlist:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"supervised_worker": {
|
|
61
|
+
"enabled": true,
|
|
62
|
+
"profile": "production",
|
|
63
|
+
"capabilities": [
|
|
64
|
+
{
|
|
65
|
+
"capability": "billing.propose_small_credit",
|
|
66
|
+
"contract_digest": "sha256:REPLACE_WITH_THE_ACTIVE_DIGEST",
|
|
67
|
+
"mode": "supervised_worker",
|
|
68
|
+
"concurrency": 1,
|
|
69
|
+
"queue_limit": 100,
|
|
70
|
+
"lease_seconds": 60,
|
|
71
|
+
"max_attempts": 5,
|
|
72
|
+
"proposal_ttl_seconds": 3600,
|
|
73
|
+
"rate_limit": {
|
|
74
|
+
"executions": 20,
|
|
75
|
+
"window_seconds": 60
|
|
76
|
+
},
|
|
77
|
+
"write_url_env": "BILLING_POSTGRES_WRITE_URL",
|
|
78
|
+
"worker_identity": "billing_worker",
|
|
79
|
+
"control_role": "runner_operator",
|
|
80
|
+
"require_least_privilege_writer": true,
|
|
81
|
+
"writer_posture_fingerprint": "sha256:REPLACE_WITH_REVIEWED_POSTURE_DIGEST"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Use environment-variable names in config, never database URLs or credentials.
|
|
89
|
+
Production also requires verified `signed_key` or `jwt_oidc` operator identity
|
|
90
|
+
for worker controls.
|
|
91
|
+
|
|
92
|
+
If either opt-in is absent, stale, revoked, or mismatched, the proposal remains
|
|
93
|
+
unapplied. Enabling the worker globally does not enable another capability or a
|
|
94
|
+
new contract version.
|
|
95
|
+
|
|
96
|
+
## Initial Eligible Write Shapes
|
|
97
|
+
|
|
98
|
+
The hardened supervised path is intentionally narrower than manual apply. It
|
|
99
|
+
accepts only direct-SQL, single-row proposal capabilities whose complete effect
|
|
100
|
+
Runner can validate:
|
|
101
|
+
|
|
102
|
+
- `UPDATE` with trusted tenant scope and an exact non-weak conflict/version
|
|
103
|
+
guard;
|
|
104
|
+
- `INSERT` with trusted tenant scope and a reviewed deterministic
|
|
105
|
+
deduplication key.
|
|
106
|
+
|
|
107
|
+
It currently refuses:
|
|
108
|
+
|
|
109
|
+
- `DELETE`;
|
|
110
|
+
- reversible actions;
|
|
111
|
+
- bounded-set and other multi-row writes;
|
|
112
|
+
- app-owned executors or external effects;
|
|
113
|
+
- arbitrary or model-generated SQL;
|
|
114
|
+
- a missing trusted tenant boundary;
|
|
115
|
+
- an unavailable receipt authority;
|
|
116
|
+
- a shared, privileged, owner, `BYPASSRLS`, or otherwise unverifiable writer
|
|
117
|
+
when hardened posture is required.
|
|
118
|
+
|
|
119
|
+
These operations remain available through their documented manual review/apply
|
|
120
|
+
or app-owned executor paths. The narrower worker eligibility does not remove
|
|
121
|
+
existing Runner write capabilities.
|
|
122
|
+
|
|
123
|
+
## What The Worker Rechecks
|
|
124
|
+
|
|
125
|
+
Immediately before leasing and applying, Runner rechecks:
|
|
126
|
+
|
|
127
|
+
- proposal state, immutable hash, version, approval, expiry, and exact active
|
|
128
|
+
contract digest;
|
|
129
|
+
- contract permission, deployment allowlist, worker identity, control state,
|
|
130
|
+
profile, generation lock, and current approval-policy snapshot;
|
|
131
|
+
- trusted tenant and principal scope;
|
|
132
|
+
- target row and required supporting-evidence freshness;
|
|
133
|
+
- current source version and exact reviewed before-state;
|
|
134
|
+
- operation, allowed columns, bounds, transitions, row count, and expected
|
|
135
|
+
effect;
|
|
136
|
+
- approval and execution count/value limits under concurrency;
|
|
137
|
+
- writer credential separation, least-privilege posture, grants, and
|
|
138
|
+
PostgreSQL RLS posture where configured;
|
|
139
|
+
- operation identity, deduplication, receipt authority, and active lease.
|
|
140
|
+
|
|
141
|
+
A policy-approved proposal does not gain permanent authority. If the target row
|
|
142
|
+
or mandatory supporting evidence changes before execution, the worker fails
|
|
143
|
+
closed with the established conflict/freshness outcome. A refreshed diff is a
|
|
144
|
+
new proposal with a new hash and needs its own approval.
|
|
145
|
+
|
|
146
|
+
Manual and supervised execution converge on the same guarded apply
|
|
147
|
+
implementation. The queue is durable work coordination, not a second mutation
|
|
148
|
+
engine.
|
|
149
|
+
|
|
150
|
+
## Run And Operate The Worker
|
|
151
|
+
|
|
152
|
+
Validate first:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
synapsor-runner config validate --config ./synapsor.runner.json
|
|
156
|
+
synapsor-runner doctor --config ./synapsor.runner.json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Run one supervised claim:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
synapsor-runner worker run --supervised --once --yes \
|
|
163
|
+
--worker-id billing_worker \
|
|
164
|
+
--config ./synapsor.runner.json
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Run continuously:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
synapsor-runner worker run --supervised --yes \
|
|
171
|
+
--worker-id billing_worker \
|
|
172
|
+
--config ./synapsor.runner.json
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Inspect without copying proposal IDs:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
synapsor-runner worker status --config ./synapsor.runner.json
|
|
179
|
+
synapsor-runner attention show --config ./synapsor.runner.json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Operator controls:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
synapsor-runner worker pause --yes --config ./synapsor.runner.json
|
|
186
|
+
synapsor-runner worker resume --yes --config ./synapsor.runner.json
|
|
187
|
+
synapsor-runner worker drain --yes --config ./synapsor.runner.json
|
|
188
|
+
|
|
189
|
+
synapsor-runner worker disable billing.propose_small_credit \
|
|
190
|
+
--digest sha256:EXACT_DIGEST --yes --config ./synapsor.runner.json
|
|
191
|
+
synapsor-runner worker enable billing.propose_small_credit \
|
|
192
|
+
--digest sha256:EXACT_DIGEST --yes --config ./synapsor.runner.json
|
|
193
|
+
synapsor-runner worker revoke billing.propose_small_credit \
|
|
194
|
+
--digest sha256:EXACT_DIGEST --yes --config ./synapsor.runner.json
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`revoke` is terminal for that digest. Pause, drain, disable, and revoke stop new
|
|
198
|
+
leasing but preserve queued proposals and durable history. They do not interrupt
|
|
199
|
+
a transaction that already committed.
|
|
200
|
+
|
|
201
|
+
Dead-letter recovery remains explicit and identity-verified:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
synapsor-runner worker dead-letter list --config ./synapsor.runner.json
|
|
205
|
+
synapsor-runner worker dead-letter show wrp_... --config ./synapsor.runner.json
|
|
206
|
+
synapsor-runner worker dead-letter requeue wrp_... --retry-budget 3 --yes \
|
|
207
|
+
--config ./synapsor.runner.json --identity alice --identity-key ./alice.pem
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Use the secured Workbench for the same queue, pause/drain, exact-digest,
|
|
211
|
+
dead-letter, and reconciliation controls.
|
|
212
|
+
|
|
213
|
+
## Retry And Crash Boundary
|
|
214
|
+
|
|
215
|
+
Runner may retry only a classified transient failure with a proven non-commit
|
|
216
|
+
outcome. Version conflict, stale authority, scope failure, limit failure,
|
|
217
|
+
validation failure, and receipt mismatch do not retry automatically.
|
|
218
|
+
|
|
219
|
+
Delivery may repeat; mutation must not. Worker leases are fenced, and source
|
|
220
|
+
operation identity plus receipts prevent duplicate effects.
|
|
221
|
+
|
|
222
|
+
When the database outcome is unknown, Runner does not guess:
|
|
223
|
+
|
|
224
|
+
- source-receipt mode resolves a known committed operation from the source-side
|
|
225
|
+
receipt;
|
|
226
|
+
- genuinely ambiguous runner-ledger outcomes enter
|
|
227
|
+
`reconciliation_required`;
|
|
228
|
+
- automatic retry stops until a verified operator inspects and resolves the
|
|
229
|
+
established reconciliation record.
|
|
230
|
+
|
|
231
|
+
This is safe retry and explicit ambiguity handling, not a claim of distributed
|
|
232
|
+
exactly-once execution.
|
|
233
|
+
|
|
234
|
+
## Optional Supervision-Health Gate
|
|
235
|
+
|
|
236
|
+
An exact capability policy may name `required_attention_sinks`:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"required_attention_sinks": ["operations"]
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
When present, the worker leaves eligible work queued until every named,
|
|
245
|
+
enabled sink has a healthy delivery record. Sink health never approves a
|
|
246
|
+
proposal and never bypasses either execution opt-in. This gate is off unless a
|
|
247
|
+
capability explicitly names required sinks.
|
|
248
|
+
|
|
249
|
+
See [Human Attention And Notifications](human-attention-notifications.md) for
|
|
250
|
+
quiet defaults, signed webhooks, routing, and the Workbench inbox.
|
|
251
|
+
|
|
252
|
+
## Accurate Model-Facing Claim
|
|
253
|
+
|
|
254
|
+
Without supervised execution:
|
|
255
|
+
|
|
256
|
+
> This call creates a proposal and does not change the source database.
|
|
257
|
+
|
|
258
|
+
With exact supervised execution enabled:
|
|
259
|
+
|
|
260
|
+
> This call creates a proposal. If it satisfies the reviewed automatic-approval
|
|
261
|
+
> policy, a separately trusted Runner worker may automatically apply it without
|
|
262
|
+
> a per-request human click. The model cannot approve, apply, start the worker,
|
|
263
|
+
> or change that policy.
|
|
264
|
+
|
|
265
|
+
The model can therefore cause a bounded request to enter a pre-authorized
|
|
266
|
+
production pipeline. It still cannot choose the authority, approve the
|
|
267
|
+
proposal, control the worker, or commit a mutation directly.
|
|
@@ -12,6 +12,41 @@ Use JSON for automation:
|
|
|
12
12
|
npx -y -p @synapsor/runner synapsor-runner doctor --first-run --json
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
## Guided Recovery Contract
|
|
16
|
+
|
|
17
|
+
Runner 1.6.3 failures should tell you what failed, why the boundary stopped,
|
|
18
|
+
what state remains, and one next action. Do not delete the project or add
|
|
19
|
+
`--force` merely to recover.
|
|
20
|
+
|
|
21
|
+
| Failure | State preserved | One next action |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Database connection or metadata inspection failed | Existing project/review files and source rows | Fix the exported URL/network without printing the credential, then rerun `npx -y @synapsor/runner@latest start --from-env DATABASE_URL`. |
|
|
24
|
+
| Read role is writable, owner, superuser, `BYPASSRLS`, or unverifiable | Disabled metadata draft; no source-row Explore | Supply a verifiably SELECT-only non-owner staging role, then rerun the same `start` command. |
|
|
25
|
+
| Schema or project choice is ambiguous | Existing files; no authority activation | Rerun with the exact reviewed schema, for example `npx -y @synapsor/runner@latest start --from-env DATABASE_URL --schema public`. |
|
|
26
|
+
| Tenant or principal scope is unresolved | Conservative blocked resource decisions | Open the same Workbench URL and resolve the highlighted scope exception. |
|
|
27
|
+
| Sensitive field remains unresolved | Field stays kept out; active tools unchanged | Open Workbench **Exceptions** and record one reviewed field decision. |
|
|
28
|
+
| Row identifier is missing/composite/ambiguous | Resource remains blocked | Select a source-proven single-column primary/unique identity or keep the resource blocked. |
|
|
29
|
+
| Trusted context environment is missing | Boundary and ledger remain intact; query did not run | Export the named tenant/principal variable locally, then rerun the displayed Try action. |
|
|
30
|
+
| Generated output already exists | Existing files are not overwritten | Rerun the original `start` command and choose **Resume existing review**. |
|
|
31
|
+
| Generation lock is stale | Existing active named capability and review history | Run `synapsor-runner boundary diff --json`, then choose **Rescan and review changes**. |
|
|
32
|
+
| Config JSON is malformed | Config and source database are unchanged | Correct the reported file/line/column, then run `synapsor-runner config validate --config ./synapsor.runner.json --json`. |
|
|
33
|
+
| Config mode is missing/invalid | Config and source database are unchanged | Set `mode` to `read_only`, `shadow`, `review`, or `cloud`, then rerun `synapsor-runner config validate --json`. |
|
|
34
|
+
| Config contains an unknown field | Config and source database are unchanged | Remove or correct the reported JSON path, then rerun `synapsor-runner config validate --json`. |
|
|
35
|
+
| Workbench port is occupied | Review files, ledger, and source database | Rerun `synapsor-runner ui --open`; the default selects a free loopback port. |
|
|
36
|
+
| Writeback setup fails | Config and reviewed plan; transactional setup rolls back | Rerun `synapsor-runner writeback setup --profile staging --json` and review the reported prerequisite. |
|
|
37
|
+
| Writer role/setup URL is missing | No DDL or grant was applied | Rerun the preview with `--writer-role <role> --setup-url-env <ADMIN_URL_ENV>`. |
|
|
38
|
+
| No supported write candidate exists | Read boundary remains active; no write authority | Use **Add a safe action** on a writable base table with a proven identity/version field, or retain read-only mode. |
|
|
39
|
+
| MCP client installation is unavailable | Project and reviewed authority; client config unchanged | Run `synapsor-runner mcp config --absolute-paths` and use the generic stdio snippet manually. |
|
|
40
|
+
| Supervised work remains queued | Approved proposal and queue state; source unchanged | Run `synapsor-runner worker status --json`, then follow its exact digest, posture, policy, freshness, limit, pause, or required-sink finding. |
|
|
41
|
+
| Required attention sink is unhealthy | Approved proposal remains queued; no source mutation | Repair the operator-owned sink, run `synapsor-runner notifications test --sink <id>`, then dispatch and let the worker revalidate from the beginning. |
|
|
42
|
+
| Notification delivery is dead-lettered | Authoritative event and proposal state are preserved | Repair/test the sink, then run `synapsor-runner notifications replay latest --yes --reason "sink repaired"` with the configured verified operator identity; this resends only the redacted event. |
|
|
43
|
+
| Apply outcome is UNKNOWN | Intent, receipt evidence, and queue state are preserved | Open the latest critical attention item and use the verified reconciliation flow; never rerun the mutation blindly. |
|
|
44
|
+
|
|
45
|
+
For machine output, a process-level failure emits one redacted JSON object to
|
|
46
|
+
stdout and diagnostics to stderr. `recovery.source_database_changed` is `null`
|
|
47
|
+
when a generic exception cannot establish an operation-specific mutation
|
|
48
|
+
outcome; inspect the durable receipt/reconciliation state rather than guessing.
|
|
49
|
+
|
|
15
50
|
## Fresh Start Did Not Enter Auto Boundary
|
|
16
51
|
|
|
17
52
|
Auto Boundary is the default only for a fresh interactive `start --from-env`
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# FitFlow Guided Onboarding Fixture
|
|
2
|
+
|
|
3
|
+
FitFlow is a synthetic multi-tenant fitness application used to verify the
|
|
4
|
+
Runner 1.6.3 first-time developer journey.
|
|
5
|
+
|
|
6
|
+
Its live PostgreSQL schema contains 41 relations: six seeded core relations,
|
|
7
|
+
34 realistic surrounding application subsystems, and one readiness marker.
|
|
8
|
+
The broader catalog proves that review by exception can narrow a real
|
|
9
|
+
application-sized schema to a small active agent pack without blind bulk
|
|
10
|
+
approval.
|
|
11
|
+
|
|
12
|
+
The fixture deliberately separates two reviewed authority packs:
|
|
13
|
+
|
|
14
|
+
- `organization_analytics`: organization-scoped, aggregate-only analysis over
|
|
15
|
+
check-ins and locations;
|
|
16
|
+
- `trainer_members`: exact member reads and proposal-first member updates,
|
|
17
|
+
restricted to the assigned trainer.
|
|
18
|
+
|
|
19
|
+
The source includes planted fields that must never become visible by default:
|
|
20
|
+
`payment_method`, `home_address`, and `medical_waiver_notes`.
|
|
21
|
+
|
|
22
|
+
The PostgreSQL fixture provides separate roles:
|
|
23
|
+
|
|
24
|
+
- `fitflow_analytics_reader`: read-only, organization-scoped analytics;
|
|
25
|
+
- `fitflow_trainer_reader`: read-only, organization and trainer scoped;
|
|
26
|
+
- `fitflow_writer`: guarded member updates under the same trusted scope;
|
|
27
|
+
- `fitflow_setup`: development/staging receipt-table setup only.
|
|
28
|
+
|
|
29
|
+
Start the database:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
docker compose up -d --wait postgres
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Use the organization analytics role for the first onboarding journey:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export DATABASE_URL='postgresql://fitflow_analytics_reader:fitflow_analytics_reader_password@127.0.0.1:55463/fitflow'
|
|
39
|
+
export SYNAPSOR_TENANT_ID='org-fitflow'
|
|
40
|
+
npx -y @synapsor/runner@latest start --from-env DATABASE_URL
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Use only synthetic data. The fixture is not a production deployment template.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16
|
|
4
|
+
environment:
|
|
5
|
+
POSTGRES_DB: fitflow
|
|
6
|
+
POSTGRES_USER: fitflow_admin
|
|
7
|
+
POSTGRES_PASSWORD: fitflow_admin_password
|
|
8
|
+
ports:
|
|
9
|
+
- "127.0.0.1:55463:5432"
|
|
10
|
+
healthcheck:
|
|
11
|
+
test: ["CMD-SHELL", "test \"$(psql -U fitflow_admin -d fitflow -tAc \"SELECT pg_postmaster_start_time() > initialized_at FROM public.synapsor_fixture_ready LIMIT 1\" 2>/dev/null | xargs)\" = t"]
|
|
12
|
+
interval: 1s
|
|
13
|
+
timeout: 3s
|
|
14
|
+
retries: 40
|
|
15
|
+
volumes:
|
|
16
|
+
- ./seed/postgres.sql:/docker-entrypoint-initdb.d/001-schema.sql:ro
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "synapsor-fitflow-guided-onboarding",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@prisma/client": "^6.0.0",
|
|
10
|
+
"next": "^15.0.0",
|
|
11
|
+
"react": "^19.0.0",
|
|
12
|
+
"react-dom": "^19.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"prisma": "^6.0.0",
|
|
16
|
+
"typescript": "^5.8.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "postgresql"
|
|
7
|
+
url = env("DATABASE_URL")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
model Organization {
|
|
11
|
+
id String @id
|
|
12
|
+
name String
|
|
13
|
+
locations Location[]
|
|
14
|
+
trainers Trainer[]
|
|
15
|
+
members Member[]
|
|
16
|
+
classes Class[]
|
|
17
|
+
checkIns CheckIn[]
|
|
18
|
+
|
|
19
|
+
@@map("organizations")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
model Location {
|
|
23
|
+
id String @id
|
|
24
|
+
organizationId String @map("organization_id")
|
|
25
|
+
name String
|
|
26
|
+
region String
|
|
27
|
+
organization Organization @relation(fields: [organizationId], references: [id])
|
|
28
|
+
members Member[]
|
|
29
|
+
classes Class[]
|
|
30
|
+
checkIns CheckIn[]
|
|
31
|
+
|
|
32
|
+
@@unique([organizationId, name])
|
|
33
|
+
@@map("locations")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
model Trainer {
|
|
37
|
+
id String @id
|
|
38
|
+
organizationId String @map("organization_id")
|
|
39
|
+
displayName String @map("display_name")
|
|
40
|
+
organization Organization @relation(fields: [organizationId], references: [id])
|
|
41
|
+
members Member[]
|
|
42
|
+
classes Class[]
|
|
43
|
+
|
|
44
|
+
@@map("trainers")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
model Member {
|
|
48
|
+
id String @id
|
|
49
|
+
organizationId String @map("organization_id")
|
|
50
|
+
locationId String @map("location_id")
|
|
51
|
+
assignedTrainerId String @map("assigned_trainer_id")
|
|
52
|
+
membershipStatus String @map("membership_status")
|
|
53
|
+
membershipTier String @map("membership_tier")
|
|
54
|
+
loyaltyBalance Int @map("loyalty_balance")
|
|
55
|
+
version Int
|
|
56
|
+
paymentMethod String @map("payment_method")
|
|
57
|
+
homeAddress String @map("home_address")
|
|
58
|
+
medicalWaiverNotes String @map("medical_waiver_notes")
|
|
59
|
+
organization Organization @relation(fields: [organizationId], references: [id])
|
|
60
|
+
location Location @relation(fields: [locationId], references: [id])
|
|
61
|
+
assignedTrainer Trainer @relation(fields: [assignedTrainerId], references: [id])
|
|
62
|
+
checkIns CheckIn[]
|
|
63
|
+
|
|
64
|
+
@@index([organizationId, assignedTrainerId])
|
|
65
|
+
@@map("members")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
model Class {
|
|
69
|
+
id String @id
|
|
70
|
+
organizationId String @map("organization_id")
|
|
71
|
+
locationId String @map("location_id")
|
|
72
|
+
trainerId String @map("trainer_id")
|
|
73
|
+
classType String @map("class_type")
|
|
74
|
+
startsAt DateTime @map("starts_at") @db.Timestamptz(6)
|
|
75
|
+
organization Organization @relation(fields: [organizationId], references: [id])
|
|
76
|
+
location Location @relation(fields: [locationId], references: [id])
|
|
77
|
+
trainer Trainer @relation(fields: [trainerId], references: [id])
|
|
78
|
+
checkIns CheckIn[]
|
|
79
|
+
|
|
80
|
+
@@map("classes")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
model CheckIn {
|
|
84
|
+
id String @id
|
|
85
|
+
organizationId String @map("organization_id")
|
|
86
|
+
locationId String @map("location_id")
|
|
87
|
+
memberId String @map("member_id")
|
|
88
|
+
classId String @map("class_id")
|
|
89
|
+
outcome String
|
|
90
|
+
checkedInAt DateTime @map("checked_in_at") @db.Timestamptz(6)
|
|
91
|
+
organization Organization @relation(fields: [organizationId], references: [id])
|
|
92
|
+
location Location @relation(fields: [locationId], references: [id])
|
|
93
|
+
member Member @relation(fields: [memberId], references: [id])
|
|
94
|
+
fitnessClass Class @relation(fields: [classId], references: [id])
|
|
95
|
+
|
|
96
|
+
@@index([organizationId, checkedInAt])
|
|
97
|
+
@@map("check_ins")
|
|
98
|
+
}
|