@synapsor/runner 1.6.3 → 1.6.4

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 (97) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +72 -8
  3. package/CONTRIBUTING.md +10 -3
  4. package/README.md +61 -93
  5. package/SECURITY.md +7 -3
  6. package/THREAT_MODEL.md +37 -0
  7. package/dist/authoring.mjs +98 -13
  8. package/dist/cli.d.ts +12 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/local-ui.d.ts +60 -0
  11. package/dist/local-ui.d.ts.map +1 -1
  12. package/dist/runner.mjs +7321 -2000
  13. package/dist/runtime.mjs +528 -51
  14. package/docs/README.md +14 -0
  15. package/docs/agent-guided-setup.md +7 -7
  16. package/docs/aggregate-reads.md +14 -5
  17. package/docs/app-owned-executors.md +5 -6
  18. package/docs/auto-boundary-and-scoped-explore.md +58 -6
  19. package/docs/capability-authoring.md +2 -2
  20. package/docs/client-recipes.md +1 -1
  21. package/docs/cloud-mode.md +7 -7
  22. package/docs/conformance.md +6 -3
  23. package/docs/current-scope.md +13 -2
  24. package/docs/cursor-plugin.md +3 -3
  25. package/docs/database-enforced-scope.md +6 -0
  26. package/docs/dsl-reference.md +12 -1
  27. package/docs/fresh-developer-usability.md +6 -6
  28. package/docs/getting-started-own-database.md +41 -41
  29. package/docs/guided-onboarding.md +98 -30
  30. package/docs/limitations.md +34 -14
  31. package/docs/local-mode.md +20 -20
  32. package/docs/mcp-audit.md +14 -14
  33. package/docs/mcp-client-setup.md +11 -14
  34. package/docs/mcp-clients.md +1 -1
  35. package/docs/migrating-to-synapsor-spec.md +5 -1
  36. package/docs/openai-agents-sdk.md +2 -2
  37. package/docs/proposal-evidence-freshness.md +7 -7
  38. package/docs/recipes.md +6 -6
  39. package/docs/release-notes.md +86 -10
  40. package/docs/release-policy.md +20 -7
  41. package/docs/reversible-change-sets.md +1 -1
  42. package/docs/reviewed-database-views.md +132 -0
  43. package/docs/reviewed-relationships.md +245 -0
  44. package/docs/rfcs/006-reviewed-reversible-change-sets.md +1 -1
  45. package/docs/runner-bundles.md +7 -7
  46. package/docs/schema-api-candidates.md +1 -1
  47. package/docs/security-boundary.md +30 -0
  48. package/docs/troubleshooting-first-run.md +52 -12
  49. package/docs/use-your-own-database.md +6 -4
  50. package/docs/workbench-ask.md +279 -0
  51. package/docs/writeback-executors.md +7 -7
  52. package/examples/auto-boundary-churn/README.md +1 -1
  53. package/examples/community-solar-clean-room/README.md +7 -0
  54. package/examples/community-solar-clean-room/docker-compose.yml +16 -0
  55. package/examples/community-solar-clean-room/package.json +15 -0
  56. package/examples/community-solar-clean-room/prisma/schema.prisma +171 -0
  57. package/examples/community-solar-clean-room/seed/postgres.sql +571 -0
  58. package/examples/fitflow-guided-onboarding/README.md +2 -2
  59. package/examples/openai-agents-http/Makefile +1 -1
  60. package/examples/openai-agents-http/README.md +1 -1
  61. package/examples/openai-agents-stdio/Makefile +1 -1
  62. package/examples/openai-agents-stdio/README.md +2 -2
  63. package/examples/openai-agents-stdio/agent.py +1 -4
  64. package/examples/reference-support-billing-app/README.md +16 -16
  65. package/examples/retail-clean-room/README.md +7 -0
  66. package/examples/retail-clean-room/docker-compose.yml +16 -0
  67. package/examples/retail-clean-room/package.json +9 -0
  68. package/examples/retail-clean-room/prisma/schema.prisma +239 -0
  69. package/examples/retail-clean-room/seed/postgres.sql +781 -0
  70. package/examples/retail-clean-room/view-recipe/average-retained-revenue.synapsor.sql +21 -0
  71. package/examples/retail-clean-room/view-recipe/synapsor.runner.json +25 -0
  72. package/examples/support-plan-credit/mcp-client-examples/claude-code.sh +1 -4
  73. package/examples/support-plan-credit/mcp-client-examples/claude-desktop.json +1 -4
  74. package/examples/support-plan-credit/mcp-client-examples/codex.config.toml +1 -4
  75. package/examples/support-plan-credit/mcp-client-examples/cursor-global.mcp.json +1 -4
  76. package/examples/support-plan-credit/mcp-client-examples/cursor-project.mcp.json +1 -4
  77. package/examples/support-plan-credit/mcp-client-examples/generic-stdio.json +1 -4
  78. package/examples/support-plan-credit/mcp-client-examples/generic-stdio.mjs +1 -1
  79. package/examples/support-plan-credit/mcp-client-examples/google-adk.py +1 -1
  80. package/examples/support-plan-credit/mcp-client-examples/langchain.mjs +1 -1
  81. package/examples/support-plan-credit/mcp-client-examples/llamaindex.py +1 -1
  82. package/examples/support-plan-credit/mcp-client-examples/openai-agents-stdio.ts +1 -1
  83. package/examples/support-plan-credit/mcp-client-examples/vscode.mcp.json +1 -4
  84. package/fixtures/compatibility/published-1.6.3/manifest.json +76 -0
  85. package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/aggregate-read.synapsor.sql +21 -0
  86. package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/billing-late-fee.synapsor.sql +56 -0
  87. package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/bounded-set-multi-term.synapsor.sql +30 -0
  88. package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/principal-row-scope.synapsor.sql +23 -0
  89. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/aggregate-read/contract.json +119 -0
  90. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/approval-quorum/contract.json +44 -0
  91. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/bounded-set-threats/contract.json +115 -0
  92. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/principal-row-scope/contract.json +78 -0
  93. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/proposal-capability/contract.json +101 -0
  94. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/reversible-change-sets/contract.json +98 -0
  95. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/valid/basic-read.contract.json +60 -0
  96. package/llms.txt +10 -5
  97. package/package.json +8 -2
@@ -0,0 +1,279 @@
1
+ # Workbench Ask With Your Model
2
+
3
+ Workbench Ask is an optional local client for the reviewed Synapsor tools that
4
+ already exist. It lets a developer ask a plain-language question through
5
+ OpenAI, Anthropic, or an explicitly configured OpenAI-compatible endpoint
6
+ without installing an external MCP host first.
7
+
8
+ Ask does not generate or activate authority. It does not replace the no-model
9
+ Workbench composer, which remains the default onboarding path and requires no
10
+ model API key.
11
+
12
+ ```text
13
+ human activates a reviewed boundary or named capability
14
+ -> Workbench lists that exact model-facing tool surface
15
+ -> human selects a provider and acknowledges direct data egress
16
+ -> provider may request one of those reviewed tools
17
+ -> Runner executes the request through the normal MCP/runtime path
18
+ -> provider receives the bounded reviewed result
19
+ -> Workbench renders provider prose as untrusted output
20
+ ```
21
+
22
+ The model cannot activate a boundary, Protect a query, approve or apply a
23
+ proposal, control a worker, acknowledge attention, reconcile an outcome, or
24
+ change provider configuration.
25
+
26
+ ## Availability
27
+
28
+ Ask appears only when all of these conditions hold:
29
+
30
+ - Workbench is authenticated and bound to loopback;
31
+ - the deployment profile is explicitly `development` or `staging`;
32
+ - at least one reviewed model-facing tool is available;
33
+ - generated authority and its generation lock are current when applicable.
34
+
35
+ It is absent from production, missing/unknown profiles, non-loopback
36
+ Workbench, shared remote HTTP, and model-facing MCP configuration. An MCP
37
+ request cannot enable Ask or select its provider.
38
+
39
+ While Scoped Explore is active, Ask may use:
40
+
41
+ ```text
42
+ app.describe_data
43
+ app.explore_data
44
+ ```
45
+
46
+ After Explore is disabled, Ask may use only the activated named capabilities
47
+ in `synapsor.runner.json`, such as:
48
+
49
+ ```text
50
+ analytics.weekly_revenue_by_store
51
+ retail.propose_order_fulfillment
52
+ ```
53
+
54
+ The panel displays the exact tool names and an Ask authority digest. That digest
55
+ binds the tool surface, runtime config, deployment profile, and active
56
+ exploration digest when one exists. A material authority change invalidates
57
+ egress consent before another provider call.
58
+
59
+ ## Start Without A Model
60
+
61
+ Begin with the normal one-command staging path:
62
+
63
+ ```bash
64
+ export SYNAPSOR_TENANT_ID='<reviewed tenant>'
65
+ export SYNAPSOR_PRINCIPAL='<reviewed principal>'
66
+ npx -y @synapsor/runner start
67
+ ```
68
+
69
+ Paste the SELECT-only database URL into Runner's hidden terminal prompt, or
70
+ explicitly consent to loading only `DATABASE_URL` from a recognized project
71
+ environment file. For an intentionally exported automation path, use
72
+ `start --from-env DATABASE_URL`.
73
+
74
+ Review and activate the boundary, then run the first safe read or aggregate in
75
+ Workbench. At this point the complete no-model path is already working.
76
+
77
+ Open **Analyze**, then **Ask with your model**. Workbench shows:
78
+
79
+ - the reviewed tools the provider may request;
80
+ - the current boundary/authority digest;
81
+ - provider, model, origin, and credential source;
82
+ - that approved visible results may go directly to the provider;
83
+ - that Synapsor does not relay the request;
84
+ - that the source database is unchanged by a read or proposal call.
85
+
86
+ ## Provider Credentials
87
+
88
+ Workbench Ask does not load provider keys from `.env` files. Instant database
89
+ onboarding may separately read only a selected `DATABASE_URL` after explicit
90
+ human consent. Export a provider key in the same shell that starts Runner:
91
+
92
+ ```bash
93
+ export OPENAI_API_KEY='<provider key>'
94
+ npx -y @synapsor/runner start
95
+ ```
96
+
97
+ Provide the database URL through the same hidden/consented path above. If
98
+ Runner is already running, use the masked Workbench key field instead of
99
+ restarting only to add a provider key.
100
+
101
+ In Workbench select:
102
+
103
+ ```text
104
+ Provider: OpenAI
105
+ Credential: Read an environment variable
106
+ Environment variable name: OPENAI_API_KEY
107
+ ```
108
+
109
+ The browser sends only the environment variable name. The value stays in the
110
+ local Runner process and is not returned to the browser.
111
+
112
+ Alternatively, paste a key into the masked Workbench field. Runner holds that
113
+ value only in server memory for the current local session and clears the field
114
+ after configuration. Do not paste credentials into an agent chat.
115
+
116
+ Keys and authorization headers are not written to:
117
+
118
+ - generated DSL, JSON, locks, or config;
119
+ - the SQLite/shared ledger, evidence, receipts, or replay;
120
+ - browser local/session storage;
121
+ - logs, screenshots, telemetry, or command-line arguments.
122
+
123
+ Select **Clear** to cancel an active request, remove in-memory provider
124
+ configuration, and discard bounded conversation history. Stopping Workbench
125
+ also clears it.
126
+
127
+ ## Direct Egress
128
+
129
+ Before the first call, Workbench requires this provider-specific decision:
130
+
131
+ ```text
132
+ Your reviewed visible data may be sent directly to this provider using your
133
+ credential. Synapsor does not receive it. Fields kept out by the active
134
+ boundary are unavailable to this model.
135
+ ```
136
+
137
+ Consent is bound to the provider, model, endpoint origin, and current Ask
138
+ authority digest. Changing any of those requires a new acknowledgement.
139
+ Consent does not activate or widen database authority.
140
+
141
+ The question, reviewed tool definitions, and bounded tool results go directly
142
+ from local Runner to the selected provider. A tool result can contain reviewed
143
+ source values, so use a provider and data-handling policy appropriate for that
144
+ data. Kept-out fields are unavailable to selection, filtering, grouping,
145
+ sorting, joins, or returned results.
146
+
147
+ Runner does not persist the conversation, provider response, or returned tool
148
+ rows by default. Existing normalized query audit still records the boundary
149
+ digest, keyed plan/literal fingerprints, suppression decision, timing, and
150
+ result size without storing result values or trusted tenant/principal values.
151
+ An operator-created screenshot or external provider may retain data under its
152
+ own policy; Runner cannot erase those external copies.
153
+
154
+ ## Authority And Proposal Behavior
155
+
156
+ Provider tool calls use the official MCP SDK and the same canonical runtime as
157
+ an external client:
158
+
159
+ - typed argument validation;
160
+ - trusted tenant/principal injection outside model arguments;
161
+ - read-only transaction enforcement for Scoped Explore;
162
+ - field, relationship, suppression, extraction, and differencing limits;
163
+ - proposal construction and exact active contract digest;
164
+ - the same error envelope and source-change state.
165
+
166
+ Provider prose, tool names, arguments, and response metadata remain untrusted.
167
+ An unknown or operator-plane tool is refused. A malformed or oversized
168
+ request, repeated tool loop, changed authority, timeout, or provider failure
169
+ stops safely.
170
+
171
+ A reviewed write tool may create an inert proposal:
172
+
173
+ ```text
174
+ Proposal only
175
+ The source database did not change.
176
+ The model cannot approve or apply this proposal.
177
+ ```
178
+
179
+ The human continues through the separate Workbench/CLI operator workflow.
180
+ Ask never receives approval or commit tools.
181
+
182
+ ## Endpoint Security
183
+
184
+ Official providers use fixed endpoints:
185
+
186
+ ```text
187
+ OpenAI: https://api.openai.com/v1/chat/completions
188
+ Anthropic: https://api.anthropic.com/v1/messages
189
+ ```
190
+
191
+ Use **Custom OpenAI-compatible** for another endpoint. Remote custom endpoints
192
+ must use HTTPS. Plain HTTP is accepted only for an explicit loopback endpoint,
193
+ for example:
194
+
195
+ ```text
196
+ http://127.0.0.1:11434/v1
197
+ ```
198
+
199
+ Runner:
200
+
201
+ - rejects URL credentials, fragments, and query parameters;
202
+ - disables redirects and never forwards a key across one;
203
+ - resolves and validates DNS for every connection;
204
+ - pins the connection to the validated address while retaining TLS hostname
205
+ verification;
206
+ - blocks private, link-local, multicast, unspecified, metadata, and other
207
+ special destinations for remote mode;
208
+ - permits custom local mode only on loopback;
209
+ - bounds request/response bytes, tool schemas/results, tool calls, iterations,
210
+ history, time, and reported tokens.
211
+
212
+ No URL, model, header, or destination comes from model output.
213
+
214
+ ## Fixed Session Bounds
215
+
216
+ The current release enforces:
217
+
218
+ | Item | Bound |
219
+ | --- | ---: |
220
+ | Question | 4,000 characters |
221
+ | Model identifier | 128 characters |
222
+ | Provider request | 256 KiB |
223
+ | Provider response | 1 MiB |
224
+ | Tool schema surface | 128 KiB and 64 tools |
225
+ | One tool result | 128 KiB |
226
+ | Tool calls | 4 per provider response, 8 per turn |
227
+ | Tool-loop iterations | 6 |
228
+ | Conversation history | 4 completed turns, 16 KiB |
229
+ | Final answer | 16 KiB |
230
+ | Provider request timeout | 30 seconds |
231
+ | Reported session token usage | 50,000 tokens |
232
+
233
+ One Workbench Ask session runs one request at a time. Runner does not
234
+ automatically retry provider calls in this release. A developer may retry a
235
+ known safe failure from the UI; every retry begins with current authority
236
+ validation. Token accounting depends on usage reported by the provider and is
237
+ not a monetary spend guarantee.
238
+
239
+ ## Tested Provider Matrix
240
+
241
+ Status as of the prepared 1.6.4 source:
242
+
243
+ | Provider surface | Verification | Claim |
244
+ | --- | --- | --- |
245
+ | OpenAI `gpt-5-mini` | Live packed-Workbench run against real local PostgreSQL on 2026-07-25 | Live tested |
246
+ | Anthropic Messages/tool-use protocol | Deterministic mock server, normal/refusal/error paths | Protocol tested; no live Anthropic account run |
247
+ | Custom OpenAI-compatible loopback | Real local HTTP server plus deterministic tool/refusal/proposal paths | Tested against the documented Chat Completions subset |
248
+ | Ollama or another named compatible server | Not installed in the release environment | Unknown until tested; compatibility is not implied by the label |
249
+
250
+ The live OpenAI run used `app.describe_data` and `app.explore_data`, matched the
251
+ official MCP aggregate result, changed no source rows, and passed exact-key
252
+ artifact scans. See
253
+ `development/runner-1.6.4-community-solar-results.json`.
254
+
255
+ ## Troubleshooting
256
+
257
+ **Ask is missing:** confirm loopback Workbench, an explicit development/staging
258
+ profile, and at least one active reviewed tool. Production intentionally omits
259
+ Ask.
260
+
261
+ **Key required:** export the named variable in the process that launches
262
+ Runner, or use the session-only masked paste. Runner does not load `.env`.
263
+
264
+ **Authority changed:** inspect the new tool/digest summary and acknowledge
265
+ egress again. Do not bypass the check.
266
+
267
+ **Provider unavailable:** the reviewed boundary and no-model composer remain
268
+ usable. Check provider status, local DNS/TLS, key validity, and model access.
269
+ Runner returns a redacted failure and does not retry automatically.
270
+
271
+ **Custom endpoint refused:** use HTTPS remotely or an exact loopback HTTP URL.
272
+ Private network and metadata endpoints are intentionally blocked.
273
+
274
+ **Provider answers without a tool:** Runner refuses to present prose as a
275
+ database answer until at least one reviewed Synapsor tool succeeds or returns a
276
+ reviewed refusal.
277
+
278
+ **Proposal did not commit:** that is correct. Open the separate operator
279
+ workflow to review, approve, and guardedly apply it.
@@ -59,9 +59,9 @@ It does not claim distributed exactly-once. See
59
59
  Use the helper commands before enabling direct SQL writeback:
60
60
 
61
61
  ```bash
62
- npx -y -p @synapsor/runner synapsor-runner writeback doctor --config ./synapsor.runner.json
63
- npx -y -p @synapsor/runner synapsor-runner writeback migration --engine postgres
64
- npx -y -p @synapsor/runner synapsor-runner writeback grants --engine postgres --writer-role app_writer
62
+ npx -y @synapsor/runner writeback doctor --config ./synapsor.runner.json
63
+ npx -y @synapsor/runner writeback migration --engine postgres
64
+ npx -y @synapsor/runner writeback grants --engine postgres --writer-role app_writer
65
65
  ```
66
66
 
67
67
  `writeback doctor --check-db` connects with the configured writer credential and
@@ -106,7 +106,7 @@ literal values.
106
106
  Run after approval:
107
107
 
108
108
  ```bash
109
- npx -y -p @synapsor/runner synapsor-runner apply \
109
+ npx -y @synapsor/runner apply \
110
110
  --proposal wrp_123 \
111
111
  --config ./synapsor.runner.json \
112
112
  --store ./.synapsor/local.db
@@ -199,13 +199,13 @@ handler receipt.
199
199
  Or generate one into your app:
200
200
 
201
201
  ```bash
202
- npx -y -p @synapsor/runner synapsor-runner handler template node-fastify \
202
+ npx -y @synapsor/runner handler template node-fastify \
203
203
  --output ./synapsor-writeback-handler.mjs
204
204
 
205
- npx -y -p @synapsor/runner synapsor-runner handler template python-fastapi \
205
+ npx -y @synapsor/runner handler template python-fastapi \
206
206
  --output ./synapsor_writeback_handler.py
207
207
 
208
- npx -y -p @synapsor/runner synapsor-runner handler template command \
208
+ npx -y @synapsor/runner handler template command \
209
209
  --output ./synapsor-command-handler.mjs
210
210
  ```
211
211
 
@@ -1,6 +1,6 @@
1
1
  # Auto Boundary Churn Fixture
2
2
 
3
- This synthetic staging fixture is the measured Runner 1.6.0 golden path:
3
+ This synthetic staging fixture is the original Runner 1.6.0 focused path:
4
4
  PostgreSQL, Next.js, Prisma, Cursor-compatible local MCP, and the local
5
5
  Workbench.
6
6
 
@@ -0,0 +1,7 @@
1
+ # Community Solar Clean-Room Fixture
2
+
3
+ This synthetic PostgreSQL and Prisma application exists only for the Runner
4
+ 1.6.4 clean-room adoption gate. It contains no production data or credentials.
5
+
6
+ The measured journey starts from a copied packed artifact and this fixture in a
7
+ new temporary directory. Synapsor artifacts are not pre-generated.
@@ -0,0 +1,16 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:16
4
+ environment:
5
+ POSTGRES_DB: community_solar
6
+ POSTGRES_USER: solar_admin
7
+ POSTGRES_PASSWORD: solar_admin_password
8
+ ports:
9
+ - "127.0.0.1:55464:5432"
10
+ healthcheck:
11
+ test: ["CMD-SHELL", "test \"$(psql -U solar_admin -d community_solar -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: 60
15
+ volumes:
16
+ - ./seed/postgres.sql:/docker-entrypoint-initdb.d/001-schema.sql:ro
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "synapsor-community-solar-clean-room",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@prisma/client": "^6.0.0",
7
+ "next": "^15.0.0",
8
+ "react": "^19.0.0",
9
+ "react-dom": "^19.0.0"
10
+ },
11
+ "devDependencies": {
12
+ "prisma": "^6.0.0",
13
+ "typescript": "^5.8.0"
14
+ }
15
+ }
@@ -0,0 +1,171 @@
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 Cooperative {
11
+ id String @id
12
+ name String
13
+ regions Region[]
14
+ teams TechnicianTeam[]
15
+ technicians Technician[]
16
+ sites SolarSite[]
17
+ models InverterModel[]
18
+ workOrders WorkOrder[]
19
+ incidents Incident[]
20
+
21
+ @@map("cooperatives")
22
+ }
23
+
24
+ model Region {
25
+ id String @id
26
+ cooperativeId String @map("cooperative_id")
27
+ name String
28
+ climateZone String @map("climate_zone")
29
+ cooperative Cooperative @relation(fields: [cooperativeId], references: [id])
30
+ sites SolarSite[]
31
+ workOrders WorkOrder[]
32
+ incidents Incident[]
33
+
34
+ @@unique([cooperativeId, name])
35
+ @@map("regions")
36
+ }
37
+
38
+ model TechnicianTeam {
39
+ id String @id
40
+ cooperativeId String @map("cooperative_id")
41
+ name String
42
+ cooperative Cooperative @relation(fields: [cooperativeId], references: [id])
43
+ technicians Technician[]
44
+ workOrders WorkOrder[]
45
+
46
+ @@unique([cooperativeId, name])
47
+ @@map("technician_teams")
48
+ }
49
+
50
+ model Technician {
51
+ id String @id
52
+ cooperativeId String @map("cooperative_id")
53
+ teamId String @map("team_id")
54
+ displayName String @map("display_name")
55
+ privateNotes String @map("private_technician_notes")
56
+ cooperative Cooperative @relation(fields: [cooperativeId], references: [id])
57
+ team TechnicianTeam @relation(fields: [teamId], references: [id])
58
+ workOrders WorkOrder[]
59
+
60
+ @@map("technicians")
61
+ }
62
+
63
+ model SolarSite {
64
+ id String @id
65
+ cooperativeId String @map("cooperative_id")
66
+ regionId String @map("region_id")
67
+ name String
68
+ siteGroup String @map("site_group")
69
+ cooperative Cooperative @relation(fields: [cooperativeId], references: [id])
70
+ region Region @relation(fields: [regionId], references: [id])
71
+ inverters Inverter[]
72
+ workOrders WorkOrder[]
73
+ incidents Incident[]
74
+
75
+ @@unique([cooperativeId, name])
76
+ @@map("solar_sites")
77
+ }
78
+
79
+ model InverterModel {
80
+ id String @id
81
+ cooperativeId String @map("cooperative_id")
82
+ manufacturer String
83
+ modelName String @map("model_name")
84
+ cooperative Cooperative @relation(fields: [cooperativeId], references: [id])
85
+ inverters Inverter[]
86
+ workOrders WorkOrder[]
87
+ incidents Incident[]
88
+
89
+ @@unique([cooperativeId, manufacturer, modelName])
90
+ @@map("inverter_models")
91
+ }
92
+
93
+ model Inverter {
94
+ id String @id
95
+ cooperativeId String @map("cooperative_id")
96
+ siteId String @map("site_id")
97
+ inverterModelId String @map("inverter_model_id")
98
+ serialNumber String @map("serial_number")
99
+ accessToken String @map("access_token")
100
+ panelPosition String @map("panel_position")
101
+ site SolarSite @relation(fields: [siteId], references: [id])
102
+ model InverterModel @relation(fields: [inverterModelId], references: [id])
103
+
104
+ @@unique([cooperativeId, serialNumber])
105
+ @@map("inverters")
106
+ }
107
+
108
+ model FaultCategory {
109
+ id String @id
110
+ cooperativeId String @map("cooperative_id")
111
+ name String
112
+ severity String
113
+ incidents Incident[]
114
+ workOrders WorkOrder[]
115
+
116
+ @@unique([cooperativeId, name])
117
+ @@map("fault_categories")
118
+ }
119
+
120
+ model WorkOrder {
121
+ id String @id
122
+ cooperativeId String @map("cooperative_id")
123
+ siteId String @map("site_id")
124
+ regionId String @map("region_id")
125
+ inverterModelId String @map("inverter_model_id")
126
+ faultCategoryId String @map("fault_category_id")
127
+ assignedTechnicianId String @map("assigned_technician_id")
128
+ technicianTeamId String @map("technician_team_id")
129
+ status String
130
+ openedAt DateTime @map("opened_at") @db.Timestamptz(6)
131
+ completedAt DateTime? @map("completed_at") @db.Timestamptz(6)
132
+ downtimeMinutes Int @map("downtime_minutes")
133
+ repairDurationMinutes Int @map("repair_duration_minutes")
134
+ estimatedEnergyLossWh Int @map("estimated_energy_loss_wh")
135
+ lostRevenueCents Int @map("lost_revenue_cents")
136
+ inspectionFailed Boolean @map("inspection_failed")
137
+ version Int
138
+ privateTechnicianNotes String @map("private_technician_notes")
139
+ cooperative Cooperative @relation(fields: [cooperativeId], references: [id])
140
+ site SolarSite @relation(fields: [siteId], references: [id])
141
+ region Region @relation(fields: [regionId], references: [id])
142
+ inverterModel InverterModel @relation(fields: [inverterModelId], references: [id])
143
+ faultCategory FaultCategory @relation(fields: [faultCategoryId], references: [id])
144
+ assignedTechnician Technician @relation(fields: [assignedTechnicianId], references: [id])
145
+ technicianTeam TechnicianTeam @relation(fields: [technicianTeamId], references: [id])
146
+
147
+ @@index([cooperativeId, assignedTechnicianId, openedAt])
148
+ @@map("work_orders")
149
+ }
150
+
151
+ model Incident {
152
+ id String @id
153
+ cooperativeId String @map("cooperative_id")
154
+ siteId String @map("site_id")
155
+ regionId String @map("region_id")
156
+ inverterModelId String @map("inverter_model_id")
157
+ faultCategoryId String @map("fault_category_id")
158
+ status String
159
+ occurredAt DateTime @map("occurred_at") @db.Timestamptz(6)
160
+ resolvedAt DateTime? @map("resolved_at") @db.Timestamptz(6)
161
+ downtimeMinutes Int @map("downtime_minutes")
162
+ estimatedEnergyLossWh Int @map("estimated_energy_loss_wh")
163
+ cooperative Cooperative @relation(fields: [cooperativeId], references: [id])
164
+ site SolarSite @relation(fields: [siteId], references: [id])
165
+ region Region @relation(fields: [regionId], references: [id])
166
+ inverterModel InverterModel @relation(fields: [inverterModelId], references: [id])
167
+ faultCategory FaultCategory @relation(fields: [faultCategoryId], references: [id])
168
+
169
+ @@index([cooperativeId, occurredAt])
170
+ @@map("incidents")
171
+ }