@synapsor/runner 0.1.0-alpha.1 → 0.1.0-alpha.11
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/README.md +426 -19
- package/TRADEMARKS.md +23 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +20 -8723
- package/dist/runner.mjs +12958 -0
- package/docs/README.md +53 -0
- package/docs/app-owned-executors.md +21 -0
- package/docs/cloud-mode.md +24 -0
- package/docs/current-scope.md +24 -0
- package/docs/dependency-license-inventory.md +35 -0
- package/docs/getting-started-own-database.md +460 -0
- package/docs/http-mcp.md +276 -0
- package/docs/licensing.md +36 -0
- package/docs/limitations.md +95 -0
- package/docs/local-mode.md +351 -0
- package/docs/mcp-audit.md +152 -0
- package/docs/mcp-client-setup.md +270 -0
- package/docs/openai-agents-sdk.md +57 -0
- package/docs/recipes.md +61 -0
- package/docs/release-notes.md +158 -0
- package/docs/security-boundary.md +94 -0
- package/docs/troubleshooting-first-run.md +248 -0
- package/docs/use-your-own-database.md +18 -0
- package/docs/writeback-executors.md +220 -0
- package/examples/app-owned-writeback/README.md +120 -0
- package/examples/app-owned-writeback/business-actions.md +221 -0
- package/examples/app-owned-writeback/command-handler.mjs +46 -0
- package/examples/app-owned-writeback/node-fastify-handler.mjs +55 -0
- package/examples/app-owned-writeback/python-fastapi-handler.py +57 -0
- package/examples/dangerous-mcp-tools.json +88 -0
- package/examples/mcp-postgres-billing-app-handler/README.md +82 -0
- package/examples/mcp-postgres-billing-app-handler/app-handler.mjs +197 -0
- package/examples/mcp-postgres-billing-app-handler/docker-compose.yml +13 -0
- package/examples/mcp-postgres-billing-app-handler/schema.sql +59 -0
- package/examples/mcp-postgres-billing-app-handler/scripts/run-demo.sh +99 -0
- package/examples/mcp-postgres-billing-app-handler/seed.sql +39 -0
- package/examples/mcp-postgres-billing-app-handler/synapsor.runner.json +157 -0
- package/examples/openai-agents-http/README.md +64 -0
- package/examples/openai-agents-http/agent.py +54 -0
- package/examples/openai-agents-http/requirements.txt +1 -0
- package/examples/openai-agents-stdio/README.md +66 -0
- package/examples/openai-agents-stdio/agent.py +72 -0
- package/examples/openai-agents-stdio/requirements.txt +1 -0
- package/examples/reference-support-billing-app/README.md +137 -0
- package/examples/reference-support-billing-app/docker-compose.yml +13 -0
- package/examples/reference-support-billing-app/mcp-client.generic.json +11 -0
- package/examples/reference-support-billing-app/schema.sql +68 -0
- package/examples/reference-support-billing-app/scripts/run-demo.sh +7 -0
- package/examples/reference-support-billing-app/seed.sql +33 -0
- package/examples/reference-support-billing-app/synapsor.runner.json +241 -0
- package/fixtures/benchmark/mcp-efficiency.json +53 -0
- package/fixtures/benchmark/mcp-efficiency.txt +25 -0
- package/fixtures/protocol/MANIFEST.json +54 -0
- package/fixtures/protocol/change-set.late-fee-waiver.v1.json +72 -0
- package/fixtures/protocol/execution-receipt.applied.v1.json +14 -0
- package/fixtures/protocol/execution-receipt.conflict.v1.json +15 -0
- package/fixtures/protocol/runner-registration.v1.json +22 -0
- package/fixtures/protocol/writeback-job.late-fee-waiver.v1.json +44 -0
- package/package.json +14 -4
- package/recipes/accounts.trial_extension.json +42 -0
- package/recipes/billing.late_fee_waiver.json +46 -0
- package/recipes/credits.account_credit.json +45 -0
- package/recipes/orders.refund_review.json +57 -0
- package/recipes/support.ticket_resolution.json +51 -0
- package/dist/bin.cjs +0 -13
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from agents import Agent, Runner
|
|
7
|
+
from agents.mcp import MCPServerStreamableHttp
|
|
8
|
+
except ImportError as exc:
|
|
9
|
+
raise SystemExit(
|
|
10
|
+
"This example requires the OpenAI Agents SDK with Streamable HTTP MCP support. "
|
|
11
|
+
"Install with: pip install -r requirements.txt"
|
|
12
|
+
) from exc
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def main() -> None:
|
|
16
|
+
required = ["OPENAI_API_KEY", "SYNAPSOR_RUNNER_HTTP_URL", "SYNAPSOR_RUNNER_HTTP_TOKEN"]
|
|
17
|
+
missing = [name for name in required if not os.environ.get(name)]
|
|
18
|
+
if missing:
|
|
19
|
+
raise SystemExit(f"Missing required environment variables: {', '.join(missing)}")
|
|
20
|
+
|
|
21
|
+
invoice_id = os.environ.get("SYNAPSOR_INVOICE_ID", "INV-3001")
|
|
22
|
+
mcp_url = os.environ["SYNAPSOR_RUNNER_HTTP_URL"]
|
|
23
|
+
token = os.environ["SYNAPSOR_RUNNER_HTTP_TOKEN"]
|
|
24
|
+
|
|
25
|
+
async with MCPServerStreamableHttp(
|
|
26
|
+
params={
|
|
27
|
+
"url": mcp_url,
|
|
28
|
+
"headers": {"Authorization": f"Bearer {token}"},
|
|
29
|
+
"timeout": 15,
|
|
30
|
+
}
|
|
31
|
+
) as mcp_server:
|
|
32
|
+
agent = Agent(
|
|
33
|
+
name="Synapsor Streamable HTTP MCP demo agent",
|
|
34
|
+
instructions=(
|
|
35
|
+
"Use Synapsor MCP tools to inspect scoped database data. "
|
|
36
|
+
"Do not claim that you can run SQL, approve proposals, or commit writes."
|
|
37
|
+
),
|
|
38
|
+
mcp_servers=[mcp_server],
|
|
39
|
+
)
|
|
40
|
+
result = await Runner.run(
|
|
41
|
+
agent,
|
|
42
|
+
(
|
|
43
|
+
f"Inspect invoice {invoice_id} using Synapsor. "
|
|
44
|
+
"Explain what you saw and whether you have write authority."
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
print(result.final_output)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
try:
|
|
52
|
+
asyncio.run(main())
|
|
53
|
+
except KeyboardInterrupt:
|
|
54
|
+
sys.exit(130)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openai-agents>=0.1.0
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# OpenAI Agents SDK + Synapsor Runner over stdio
|
|
2
|
+
|
|
3
|
+
This example shows an OpenAI Agents SDK app launching Synapsor Runner as a
|
|
4
|
+
local stdio MCP server.
|
|
5
|
+
|
|
6
|
+
Use stdio when the agent process can start the MCP server on the same machine.
|
|
7
|
+
The model sees Synapsor semantic tools through OpenAI-safe aliases such as
|
|
8
|
+
`billing__inspect_invoice`. Runner keeps the canonical Synapsor capability
|
|
9
|
+
name, such as `billing.inspect_invoice`, in MCP metadata and maps calls back to
|
|
10
|
+
it. The model
|
|
11
|
+
does not receive raw SQL, database URLs, write credentials, approval tools, or
|
|
12
|
+
commit tools.
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
Generate `synapsor.runner.json` first:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner demo
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
or connect your own staging database:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner onboard db --from-env DATABASE_URL
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then install the Python dependencies:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python -m venv .venv
|
|
32
|
+
. .venv/bin/activate
|
|
33
|
+
pip install -r requirements.txt
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Run
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export OPENAI_API_KEY="..."
|
|
40
|
+
export DATABASE_URL="<postgres-or-mysql-read-url>"
|
|
41
|
+
export SYNAPSOR_TENANT_ID="acme"
|
|
42
|
+
export SYNAPSOR_PRINCIPAL="openai_agent_demo"
|
|
43
|
+
|
|
44
|
+
python agent.py
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Optional env:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export SYNAPSOR_CONFIG="./synapsor.runner.json"
|
|
51
|
+
export SYNAPSOR_STORE="./.synapsor/local.db"
|
|
52
|
+
export SYNAPSOR_TOOL="billing.inspect_invoice"
|
|
53
|
+
export SYNAPSOR_INVOICE_ID="INV-3001"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Expected behavior:
|
|
57
|
+
|
|
58
|
+
- the agent can inspect the scoped invoice through Synapsor using an
|
|
59
|
+
OpenAI-safe tool alias;
|
|
60
|
+
- Runner maps the alias back to the canonical Synapsor capability;
|
|
61
|
+
- the agent cannot run SQL;
|
|
62
|
+
- the agent cannot approve or commit writes;
|
|
63
|
+
- evidence/query audit are saved in the local Runner store.
|
|
64
|
+
|
|
65
|
+
If your installed OpenAI Agents SDK does not expose `MCPServerStdio`, update the
|
|
66
|
+
SDK or use the Streamable HTTP example.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from agents import Agent, Runner
|
|
7
|
+
from agents.mcp import MCPServerStdio
|
|
8
|
+
except ImportError as exc:
|
|
9
|
+
raise SystemExit(
|
|
10
|
+
"This example requires the OpenAI Agents SDK with MCP stdio support. "
|
|
11
|
+
"Install with: pip install -r requirements.txt"
|
|
12
|
+
) from exc
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def main() -> None:
|
|
16
|
+
config_path = os.environ.get("SYNAPSOR_CONFIG", "./synapsor.runner.json")
|
|
17
|
+
store_path = os.environ.get("SYNAPSOR_STORE", "./.synapsor/local.db")
|
|
18
|
+
invoice_id = os.environ.get("SYNAPSOR_INVOICE_ID", "INV-3001")
|
|
19
|
+
|
|
20
|
+
required = ["OPENAI_API_KEY", "DATABASE_URL", "SYNAPSOR_TENANT_ID", "SYNAPSOR_PRINCIPAL"]
|
|
21
|
+
missing = [name for name in required if not os.environ.get(name)]
|
|
22
|
+
if missing:
|
|
23
|
+
raise SystemExit(f"Missing required environment variables: {', '.join(missing)}")
|
|
24
|
+
|
|
25
|
+
params = {
|
|
26
|
+
"command": "npx",
|
|
27
|
+
"args": [
|
|
28
|
+
"-y",
|
|
29
|
+
"-p",
|
|
30
|
+
"@synapsor/runner@alpha",
|
|
31
|
+
"synapsor-runner",
|
|
32
|
+
"mcp",
|
|
33
|
+
"serve",
|
|
34
|
+
"--config",
|
|
35
|
+
config_path,
|
|
36
|
+
"--store",
|
|
37
|
+
store_path,
|
|
38
|
+
"--alias-mode",
|
|
39
|
+
"openai",
|
|
40
|
+
],
|
|
41
|
+
"env": {
|
|
42
|
+
**os.environ,
|
|
43
|
+
"DATABASE_URL": os.environ["DATABASE_URL"],
|
|
44
|
+
"SYNAPSOR_TENANT_ID": os.environ["SYNAPSOR_TENANT_ID"],
|
|
45
|
+
"SYNAPSOR_PRINCIPAL": os.environ["SYNAPSOR_PRINCIPAL"],
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async with MCPServerStdio(params=params) as mcp_server:
|
|
50
|
+
agent = Agent(
|
|
51
|
+
name="Synapsor stdio MCP demo agent",
|
|
52
|
+
instructions=(
|
|
53
|
+
"Use Synapsor MCP tools to inspect scoped database data. "
|
|
54
|
+
"Do not claim that you can run SQL, approve proposals, or commit writes."
|
|
55
|
+
),
|
|
56
|
+
mcp_servers=[mcp_server],
|
|
57
|
+
)
|
|
58
|
+
result = await Runner.run(
|
|
59
|
+
agent,
|
|
60
|
+
(
|
|
61
|
+
f"Inspect invoice {invoice_id} using Synapsor. "
|
|
62
|
+
"Explain what you saw and whether you have write authority."
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
print(result.final_output)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
try:
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
except KeyboardInterrupt:
|
|
72
|
+
sys.exit(130)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openai-agents>=0.1.0
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Reference Support/Billing App
|
|
2
|
+
|
|
3
|
+
This is the main local reference app for Synapsor Runner.
|
|
4
|
+
|
|
5
|
+
It shows the commit-safe MCP loop against a disposable Postgres app database:
|
|
6
|
+
|
|
7
|
+
1. The MCP model sees semantic tools, not `execute_sql`.
|
|
8
|
+
2. Reads are scoped by trusted `SYNAPSOR_TENANT_ID` and `SYNAPSOR_PRINCIPAL`.
|
|
9
|
+
3. Proposal tools create exact diffs and leave the source database unchanged.
|
|
10
|
+
4. Approval happens through the local CLI or UI, outside MCP.
|
|
11
|
+
5. The trusted runner applies one guarded update with tenant, allowed-column, idempotency, and `updated_at` conflict checks.
|
|
12
|
+
6. Replay exports evidence, diff, approval, writeback receipt, and conflict outcome.
|
|
13
|
+
|
|
14
|
+
The app fixture is split into `schema.sql` and `seed.sql` so the database shape
|
|
15
|
+
and demo data are easy to inspect.
|
|
16
|
+
|
|
17
|
+
## Start
|
|
18
|
+
|
|
19
|
+
From the repository root:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
docker compose -f examples/reference-support-billing-app/docker-compose.yml up -d
|
|
23
|
+
|
|
24
|
+
export REFERENCE_POSTGRES_READ_URL="postgresql://synapsor_reader:synapsor_reader_password@localhost:55435/synapsor_reference_support_billing"
|
|
25
|
+
export REFERENCE_POSTGRES_WRITE_URL="postgresql://synapsor_writer:synapsor_writer_password@localhost:55435/synapsor_reference_support_billing"
|
|
26
|
+
export SYNAPSOR_TENANT_ID="acme"
|
|
27
|
+
export SYNAPSOR_PRINCIPAL="local_support_operator"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or run the complete success/conflict/replay smoke:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
examples/reference-support-billing-app/scripts/run-demo.sh
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Validate the reviewed contract:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner config validate --config examples/reference-support-billing-app/synapsor.runner.json
|
|
40
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner doctor --config examples/reference-support-billing-app/synapsor.runner.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Serve MCP:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner mcp serve \
|
|
47
|
+
--config examples/reference-support-billing-app/synapsor.runner.json \
|
|
48
|
+
--store ./tmp/reference-support-billing/local.db
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Tools
|
|
52
|
+
|
|
53
|
+
The model-facing MCP tools are:
|
|
54
|
+
|
|
55
|
+
- `support.inspect_ticket`
|
|
56
|
+
- `support.propose_ticket_resolution`
|
|
57
|
+
- `support.inspect_customer_account`
|
|
58
|
+
- `support.propose_plan_credit`
|
|
59
|
+
- `billing.inspect_invoice`
|
|
60
|
+
- `billing.propose_late_fee_waiver`
|
|
61
|
+
- `orders.inspect_order`
|
|
62
|
+
- `orders.propose_status_change`
|
|
63
|
+
|
|
64
|
+
The model does not receive approval tools, commit tools, write credentials, raw SQL, arbitrary table names, arbitrary column names, or tenant authority.
|
|
65
|
+
|
|
66
|
+
## Safe Write Examples
|
|
67
|
+
|
|
68
|
+
After `synapsor-runner demo` or after starting this fixture manually, try the same
|
|
69
|
+
proposal-first loop without connecting an MCP client:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner propose billing.propose_late_fee_waiver --sample
|
|
73
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner proposals show latest
|
|
74
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner proposals approve latest --yes
|
|
75
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner apply latest
|
|
76
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner replay latest
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Expected safety output:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
Source DB changed:
|
|
83
|
+
no
|
|
84
|
+
|
|
85
|
+
Guarded writeback applied.
|
|
86
|
+
* proposal approved: yes
|
|
87
|
+
* primary key matched: yes
|
|
88
|
+
* tenant guard matched: yes
|
|
89
|
+
* allowed columns only: yes
|
|
90
|
+
* conflict guard passed: yes
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Other proposal examples use the same review/apply/replay path:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner propose support.propose_plan_credit --sample
|
|
97
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner propose orders.propose_status_change --sample
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Safety guarantees:
|
|
101
|
+
|
|
102
|
+
- proposals store evidence, a before/after diff, trusted tenant/principal
|
|
103
|
+
context, and query audit before any write;
|
|
104
|
+
- approval stays outside the model-facing MCP tool surface;
|
|
105
|
+
- writeback is single-row, primary-key targeted, tenant guarded,
|
|
106
|
+
allowed-column checked, idempotent, and conflict guarded;
|
|
107
|
+
- stale rows return `conflict` and are replayable.
|
|
108
|
+
|
|
109
|
+
Current limitation: v0.1 is intentionally local and single-user. It does not
|
|
110
|
+
provide hosted RBAC, workflow DAGs, branches, settlement policies, auto-merge,
|
|
111
|
+
or production-scale runner orchestration.
|
|
112
|
+
|
|
113
|
+
## Review And Replay
|
|
114
|
+
|
|
115
|
+
After a proposal exists:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner proposals list --store ./tmp/reference-support-billing/local.db
|
|
119
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner proposals approve <proposal_id> --store ./tmp/reference-support-billing/local.db --actor local_reviewer --yes
|
|
120
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner proposals writeback-job <proposal_id> --store ./tmp/reference-support-billing/local.db --output ./tmp/reference-support-billing/job.json
|
|
121
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner apply --job ./tmp/reference-support-billing/job.json --store ./tmp/reference-support-billing/local.db
|
|
122
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner replay export <proposal_id> --store ./tmp/reference-support-billing/local.db --output ./tmp/reference-support-billing/replay.json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
To inspect locally in a browser:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner ui \
|
|
129
|
+
--config examples/reference-support-billing-app/synapsor.runner.json \
|
|
130
|
+
--store ./tmp/reference-support-billing/local.db
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Stop
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
docker compose -f examples/reference-support-billing-app/docker-compose.yml down -v
|
|
137
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16
|
|
4
|
+
container_name: synapsor_runner_reference_support_billing
|
|
5
|
+
environment:
|
|
6
|
+
POSTGRES_DB: synapsor_reference_support_billing
|
|
7
|
+
POSTGRES_USER: synapsor_admin
|
|
8
|
+
POSTGRES_PASSWORD: synapsor_admin_password
|
|
9
|
+
ports:
|
|
10
|
+
- "55435:5432"
|
|
11
|
+
volumes:
|
|
12
|
+
- ./schema.sql:/docker-entrypoint-initdb.d/001_schema.sql:ro
|
|
13
|
+
- ./seed.sql:/docker-entrypoint-initdb.d/002_seed.sql:ro
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS public.tenants (
|
|
2
|
+
id text PRIMARY KEY,
|
|
3
|
+
name text NOT NULL,
|
|
4
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
5
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
CREATE TABLE IF NOT EXISTS public.customers (
|
|
9
|
+
id text PRIMARY KEY,
|
|
10
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
11
|
+
name text NOT NULL,
|
|
12
|
+
email text,
|
|
13
|
+
plan text NOT NULL,
|
|
14
|
+
plan_credit_cents integer NOT NULL DEFAULT 0,
|
|
15
|
+
credit_reason text,
|
|
16
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
17
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS public.support_tickets (
|
|
21
|
+
id text PRIMARY KEY,
|
|
22
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
23
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
24
|
+
subject text NOT NULL,
|
|
25
|
+
status text NOT NULL,
|
|
26
|
+
resolution_note text,
|
|
27
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE TABLE IF NOT EXISTS public.invoices (
|
|
31
|
+
id text PRIMARY KEY,
|
|
32
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
33
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
34
|
+
status text NOT NULL,
|
|
35
|
+
balance_cents integer NOT NULL,
|
|
36
|
+
late_fee_cents integer NOT NULL,
|
|
37
|
+
waiver_reason text,
|
|
38
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS public.orders (
|
|
42
|
+
id text PRIMARY KEY,
|
|
43
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
44
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
45
|
+
status text NOT NULL,
|
|
46
|
+
status_change_reason text,
|
|
47
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
DO $$
|
|
51
|
+
BEGIN
|
|
52
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_reader') THEN
|
|
53
|
+
CREATE ROLE synapsor_reader LOGIN PASSWORD 'synapsor_reader_password';
|
|
54
|
+
END IF;
|
|
55
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_writer') THEN
|
|
56
|
+
CREATE ROLE synapsor_writer LOGIN PASSWORD 'synapsor_writer_password';
|
|
57
|
+
END IF;
|
|
58
|
+
END
|
|
59
|
+
$$;
|
|
60
|
+
|
|
61
|
+
GRANT CONNECT ON DATABASE synapsor_reference_support_billing TO synapsor_reader, synapsor_writer;
|
|
62
|
+
GRANT USAGE ON SCHEMA public TO synapsor_reader, synapsor_writer;
|
|
63
|
+
GRANT CREATE ON SCHEMA public TO synapsor_writer;
|
|
64
|
+
GRANT SELECT ON public.tenants, public.customers, public.support_tickets, public.invoices, public.orders TO synapsor_reader, synapsor_writer;
|
|
65
|
+
GRANT UPDATE (plan_credit_cents, credit_reason, updated_at) ON public.customers TO synapsor_writer;
|
|
66
|
+
GRANT UPDATE (status, resolution_note, updated_at) ON public.support_tickets TO synapsor_writer;
|
|
67
|
+
GRANT UPDATE (late_fee_cents, waiver_reason, updated_at) ON public.invoices TO synapsor_writer;
|
|
68
|
+
GRANT UPDATE (status, status_change_reason, updated_at) ON public.orders TO synapsor_writer;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
INSERT INTO public.tenants (id, name, created_at, updated_at)
|
|
2
|
+
VALUES
|
|
3
|
+
('acme', 'Acme Robotics', '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z'),
|
|
4
|
+
('otherco', 'OtherCo Labs', '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z')
|
|
5
|
+
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, updated_at = EXCLUDED.updated_at;
|
|
6
|
+
|
|
7
|
+
INSERT INTO public.customers (id, tenant_id, name, email, plan, plan_credit_cents, credit_reason, created_at, updated_at)
|
|
8
|
+
VALUES
|
|
9
|
+
('cust_acme_1', 'acme', 'Acme Robotics', 'ops@example.invalid', 'enterprise', 0, NULL, '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z'),
|
|
10
|
+
('cust_acme_2', 'acme', 'Acme Field Ops', 'field@example.invalid', 'builder', 0, NULL, '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z'),
|
|
11
|
+
('cust_other_1', 'otherco', 'OtherCo Labs', 'ops@otherco.invalid', 'builder', 0, NULL, '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z')
|
|
12
|
+
ON CONFLICT (id) DO UPDATE SET tenant_id = EXCLUDED.tenant_id, name = EXCLUDED.name, email = EXCLUDED.email, plan = EXCLUDED.plan, plan_credit_cents = EXCLUDED.plan_credit_cents, credit_reason = EXCLUDED.credit_reason, updated_at = EXCLUDED.updated_at;
|
|
13
|
+
|
|
14
|
+
INSERT INTO public.support_tickets (id, tenant_id, customer_id, subject, status, resolution_note, updated_at)
|
|
15
|
+
VALUES
|
|
16
|
+
('T-1042', 'acme', 'cust_acme_1', 'Late fee waiver request for INV-3001', 'open', NULL, '2026-06-20T12:00:00Z'),
|
|
17
|
+
('T-1043', 'acme', 'cust_acme_2', 'Duplicate card charge question', 'open', NULL, '2026-06-20T12:05:00Z'),
|
|
18
|
+
('T-9001', 'otherco', 'cust_other_1', 'OtherCo private billing ticket', 'open', NULL, '2026-06-20T12:00:00Z')
|
|
19
|
+
ON CONFLICT (id) DO UPDATE SET tenant_id = EXCLUDED.tenant_id, customer_id = EXCLUDED.customer_id, subject = EXCLUDED.subject, status = EXCLUDED.status, resolution_note = EXCLUDED.resolution_note, updated_at = EXCLUDED.updated_at;
|
|
20
|
+
|
|
21
|
+
INSERT INTO public.invoices (id, tenant_id, customer_id, status, balance_cents, late_fee_cents, waiver_reason, updated_at)
|
|
22
|
+
VALUES
|
|
23
|
+
('INV-3001', 'acme', 'cust_acme_1', 'overdue', 25500, 5500, NULL, '2026-06-20T14:31:08Z'),
|
|
24
|
+
('INV-3002', 'acme', 'cust_acme_2', 'paid', 0, 0, NULL, '2026-06-20T14:40:00Z'),
|
|
25
|
+
('INV-9001', 'otherco', 'cust_other_1', 'overdue', 25500, 5500, NULL, '2026-06-20T14:31:08Z')
|
|
26
|
+
ON CONFLICT (id) DO UPDATE SET tenant_id = EXCLUDED.tenant_id, customer_id = EXCLUDED.customer_id, status = EXCLUDED.status, balance_cents = EXCLUDED.balance_cents, late_fee_cents = EXCLUDED.late_fee_cents, waiver_reason = EXCLUDED.waiver_reason, updated_at = EXCLUDED.updated_at;
|
|
27
|
+
|
|
28
|
+
INSERT INTO public.orders (id, tenant_id, customer_id, status, status_change_reason, updated_at)
|
|
29
|
+
VALUES
|
|
30
|
+
('O-1001', 'acme', 'cust_acme_1', 'paid', NULL, '2026-06-20T13:00:00Z'),
|
|
31
|
+
('O-1002', 'acme', 'cust_acme_2', 'processing', NULL, '2026-06-20T13:05:00Z'),
|
|
32
|
+
('O-9001', 'otherco', 'cust_other_1', 'paid', NULL, '2026-06-20T13:00:00Z')
|
|
33
|
+
ON CONFLICT (id) DO UPDATE SET tenant_id = EXCLUDED.tenant_id, customer_id = EXCLUDED.customer_id, status = EXCLUDED.status, status_change_reason = EXCLUDED.status_change_reason, updated_at = EXCLUDED.updated_at;
|