@synapsor/runner 0.1.0-alpha.0 → 0.1.0-alpha.2
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 +21 -19
- package/TRADEMARKS.md +23 -0
- package/dist/cli.js +15 -8723
- package/dist/runner.mjs +8767 -0
- package/docs/MCP_RUNNER_IMPLEMENTATION_PLAN.md +187 -0
- package/docs/README.md +56 -0
- package/docs/architecture.md +65 -0
- package/docs/capability-config.md +180 -0
- package/docs/cloud-mode.md +140 -0
- package/docs/config-migrations.md +67 -0
- package/docs/demo-transcript.md +73 -0
- package/docs/dependency-license-inventory.md +35 -0
- package/docs/first-10-minutes.md +147 -0
- package/docs/getting-started-own-database.md +367 -0
- package/docs/licensing.md +38 -0
- package/docs/limitations.md +75 -0
- package/docs/local-mode.md +246 -0
- package/docs/local-ui.md +163 -0
- package/docs/mcp-audit.md +135 -0
- package/docs/mcp-client-setup.md +155 -0
- package/docs/mcp-efficiency-benchmark.md +84 -0
- package/docs/operations.md +38 -0
- package/docs/own-db-20-minutes.md +185 -0
- package/docs/production-readiness.md +39 -0
- package/docs/protocol.md +90 -0
- package/docs/recipes.md +61 -0
- package/docs/roadmap.md +13 -0
- package/docs/schema-inspection.md +88 -0
- package/docs/security-boundary.md +70 -0
- package/docs/shadow-mode.md +67 -0
- package/docs/telemetry.md +28 -0
- package/docs/threat-model.md +25 -0
- package/docs/troubleshooting-first-run.md +248 -0
- package/docs/trusted-context.md +70 -0
- package/docs/writeback-executors.md +128 -0
- package/examples/dangerous-mcp-tools.json +88 -0
- package/examples/reference-support-billing-app/README.md +86 -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 +55 -0
- package/examples/reference-support-billing-app/scripts/run-demo.sh +7 -0
- package/examples/reference-support-billing-app/seed.sql +26 -0
- package/examples/reference-support-billing-app/synapsor.runner.json +136 -0
- package/package.json +10 -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,128 @@
|
|
|
1
|
+
# Writeback Executors
|
|
2
|
+
|
|
3
|
+
Synapsor Runner separates proposal authority from execution authority.
|
|
4
|
+
|
|
5
|
+
The model-facing MCP server can inspect and propose. It cannot approve or
|
|
6
|
+
commit. After a human or trusted process approves a proposal, the local runner
|
|
7
|
+
uses a configured writeback executor.
|
|
8
|
+
|
|
9
|
+
## `sql_update`
|
|
10
|
+
|
|
11
|
+
`sql_update` is the default executor.
|
|
12
|
+
|
|
13
|
+
It applies one guarded `UPDATE` through the database adapter:
|
|
14
|
+
|
|
15
|
+
- fixed schema/table from reviewed config;
|
|
16
|
+
- fixed primary key column;
|
|
17
|
+
- tenant guard;
|
|
18
|
+
- allowed-column validation;
|
|
19
|
+
- conflict/version guard;
|
|
20
|
+
- idempotency key;
|
|
21
|
+
- affected-row check;
|
|
22
|
+
- terminal receipt in replay.
|
|
23
|
+
|
|
24
|
+
Use this when the trusted runner is allowed to update the selected business row
|
|
25
|
+
directly.
|
|
26
|
+
|
|
27
|
+
## `http_handler`
|
|
28
|
+
|
|
29
|
+
Use `http_handler` when your application/API should own business execution.
|
|
30
|
+
|
|
31
|
+
The approved proposal becomes a structured HTTP request to an internal handler.
|
|
32
|
+
The handler URL and bearer token come from environment variables, not config
|
|
33
|
+
literal values.
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"executors": {
|
|
38
|
+
"billing_api": {
|
|
39
|
+
"type": "http_handler",
|
|
40
|
+
"url_env": "SYNAPSOR_BILLING_HANDLER_URL",
|
|
41
|
+
"method": "POST",
|
|
42
|
+
"auth": {
|
|
43
|
+
"type": "bearer_env",
|
|
44
|
+
"token_env": "SYNAPSOR_BILLING_HANDLER_TOKEN"
|
|
45
|
+
},
|
|
46
|
+
"timeout_ms": 5000
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"capabilities": [
|
|
50
|
+
{
|
|
51
|
+
"name": "billing.propose_late_fee_waiver",
|
|
52
|
+
"kind": "proposal",
|
|
53
|
+
"executor": "billing_api"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Run after approval:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner apply \
|
|
63
|
+
--proposal wrp_123 \
|
|
64
|
+
--config ./synapsor.runner.json \
|
|
65
|
+
--store ./.synapsor/local.db
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The handler receives proposal fields, the exact patch, evidence metadata,
|
|
69
|
+
guards, and an idempotency key. It does not receive arbitrary model SQL or DB
|
|
70
|
+
credentials from Synapsor Runner.
|
|
71
|
+
|
|
72
|
+
Handler responses:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"status": "applied",
|
|
77
|
+
"rows_affected": 1,
|
|
78
|
+
"previous_version": "2026-06-20T14:31:08Z",
|
|
79
|
+
"new_version": "2026-06-20T14:34:19Z",
|
|
80
|
+
"source_database_mutated": true
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Allowed terminal statuses:
|
|
85
|
+
|
|
86
|
+
- `applied`
|
|
87
|
+
- `already_applied`
|
|
88
|
+
- `conflict`
|
|
89
|
+
- `failed`
|
|
90
|
+
|
|
91
|
+
Non-2xx responses and timeouts become failed execution receipts. The terminal
|
|
92
|
+
receipt is stored in replay.
|
|
93
|
+
|
|
94
|
+
Use your application/API for business logic. Use Synapsor Runner for proposal,
|
|
95
|
+
approval, evidence, policy boundary, and replay.
|
|
96
|
+
|
|
97
|
+
## `command_handler`
|
|
98
|
+
|
|
99
|
+
`command_handler` is a local integration path for scripts:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"executors": {
|
|
104
|
+
"local_billing_script": {
|
|
105
|
+
"type": "command_handler",
|
|
106
|
+
"command_env": "SYNAPSOR_BILLING_HANDLER_COMMAND",
|
|
107
|
+
"timeout_ms": 5000
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The command receives the same structured JSON request on stdin and should print
|
|
114
|
+
a JSON receipt body on stdout.
|
|
115
|
+
|
|
116
|
+
## Safety Boundary
|
|
117
|
+
|
|
118
|
+
Executor secrets are never exposed over MCP. The model never receives:
|
|
119
|
+
|
|
120
|
+
- approval tools;
|
|
121
|
+
- commit tools;
|
|
122
|
+
- write credentials;
|
|
123
|
+
- handler bearer tokens;
|
|
124
|
+
- arbitrary SQL authority;
|
|
125
|
+
- tenant/principal authority.
|
|
126
|
+
|
|
127
|
+
`MCP tool call = request/proposal authority. Trusted runner = execution
|
|
128
|
+
authority.`
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tools": [
|
|
3
|
+
{
|
|
4
|
+
"name": "execute_sql",
|
|
5
|
+
"description": "Execute arbitrary SQL against the application database.",
|
|
6
|
+
"inputSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"sql": { "type": "string" }
|
|
10
|
+
},
|
|
11
|
+
"required": ["sql"]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "run_query",
|
|
16
|
+
"description": "Run any query and return database rows.",
|
|
17
|
+
"inputSchema": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"query": { "type": "string" },
|
|
21
|
+
"table": { "type": "string" },
|
|
22
|
+
"columns": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"items": { "type": "string" }
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"required": ["query"]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "approve_refund",
|
|
32
|
+
"description": "Approve and issue a customer refund immediately.",
|
|
33
|
+
"inputSchema": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"refund_id": { "type": "string" },
|
|
37
|
+
"tenant_id": { "type": "string" },
|
|
38
|
+
"amount_cents": { "type": "number" }
|
|
39
|
+
},
|
|
40
|
+
"required": ["refund_id", "tenant_id", "amount_cents"]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "update_customer",
|
|
45
|
+
"description": "Update a customer record directly.",
|
|
46
|
+
"inputSchema": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"customer_id": { "type": "string" },
|
|
50
|
+
"tenant_id": { "type": "string" },
|
|
51
|
+
"column": { "type": "string" },
|
|
52
|
+
"value": { "type": "string" }
|
|
53
|
+
},
|
|
54
|
+
"required": ["customer_id", "tenant_id", "column", "value"]
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "delete_order",
|
|
59
|
+
"description": "Delete an order from the database.",
|
|
60
|
+
"inputSchema": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {
|
|
63
|
+
"order_id": { "type": "string" },
|
|
64
|
+
"tenant_id": { "type": "string" }
|
|
65
|
+
},
|
|
66
|
+
"required": ["order_id", "tenant_id"]
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "query_database",
|
|
71
|
+
"description": "Query arbitrary tables and columns from the database.",
|
|
72
|
+
"inputSchema": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"database": { "type": "string" },
|
|
76
|
+
"schema": { "type": "string" },
|
|
77
|
+
"table": { "type": "string" },
|
|
78
|
+
"columns": {
|
|
79
|
+
"type": "array",
|
|
80
|
+
"items": { "type": "string" }
|
|
81
|
+
},
|
|
82
|
+
"where": { "type": "string" }
|
|
83
|
+
},
|
|
84
|
+
"required": ["table"]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
- `billing.inspect_invoice`
|
|
58
|
+
- `billing.propose_late_fee_waiver`
|
|
59
|
+
|
|
60
|
+
The model does not receive approval tools, commit tools, write credentials, raw SQL, arbitrary table names, arbitrary column names, or tenant authority.
|
|
61
|
+
|
|
62
|
+
## Review And Replay
|
|
63
|
+
|
|
64
|
+
After a proposal exists:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner proposals list --store ./tmp/reference-support-billing/local.db
|
|
68
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner proposals approve <proposal_id> --store ./tmp/reference-support-billing/local.db --actor local_reviewer --yes
|
|
69
|
+
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
|
|
70
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner apply --job ./tmp/reference-support-billing/job.json --store ./tmp/reference-support-billing/local.db
|
|
71
|
+
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
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
To inspect locally in a browser:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner ui \
|
|
78
|
+
--config examples/reference-support-billing-app/synapsor.runner.json \
|
|
79
|
+
--store ./tmp/reference-support-billing/local.db
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Stop
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
docker compose -f examples/reference-support-billing-app/docker-compose.yml down -v
|
|
86
|
+
```
|
|
@@ -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,55 @@
|
|
|
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
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
15
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE TABLE IF NOT EXISTS public.support_tickets (
|
|
19
|
+
id text PRIMARY KEY,
|
|
20
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
21
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
22
|
+
subject text NOT NULL,
|
|
23
|
+
status text NOT NULL,
|
|
24
|
+
resolution_note text,
|
|
25
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS public.invoices (
|
|
29
|
+
id text PRIMARY KEY,
|
|
30
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
31
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
32
|
+
status text NOT NULL,
|
|
33
|
+
balance_cents integer NOT NULL,
|
|
34
|
+
late_fee_cents integer NOT NULL,
|
|
35
|
+
waiver_reason text,
|
|
36
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
DO $$
|
|
40
|
+
BEGIN
|
|
41
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_reader') THEN
|
|
42
|
+
CREATE ROLE synapsor_reader LOGIN PASSWORD 'synapsor_reader_password';
|
|
43
|
+
END IF;
|
|
44
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_writer') THEN
|
|
45
|
+
CREATE ROLE synapsor_writer LOGIN PASSWORD 'synapsor_writer_password';
|
|
46
|
+
END IF;
|
|
47
|
+
END
|
|
48
|
+
$$;
|
|
49
|
+
|
|
50
|
+
GRANT CONNECT ON DATABASE synapsor_reference_support_billing TO synapsor_reader, synapsor_writer;
|
|
51
|
+
GRANT USAGE ON SCHEMA public TO synapsor_reader, synapsor_writer;
|
|
52
|
+
GRANT CREATE ON SCHEMA public TO synapsor_writer;
|
|
53
|
+
GRANT SELECT ON public.tenants, public.customers, public.support_tickets, public.invoices TO synapsor_reader, synapsor_writer;
|
|
54
|
+
GRANT UPDATE (status, resolution_note, updated_at) ON public.support_tickets TO synapsor_writer;
|
|
55
|
+
GRANT UPDATE (late_fee_cents, waiver_reason, updated_at) ON public.invoices TO synapsor_writer;
|
|
@@ -0,0 +1,26 @@
|
|
|
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, created_at, updated_at)
|
|
8
|
+
VALUES
|
|
9
|
+
('cust_acme_1', 'acme', 'Acme Robotics', 'ops@example.invalid', 'enterprise', '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z'),
|
|
10
|
+
('cust_acme_2', 'acme', 'Acme Field Ops', 'field@example.invalid', 'builder', '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z'),
|
|
11
|
+
('cust_other_1', 'otherco', 'OtherCo Labs', 'ops@otherco.invalid', 'builder', '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, 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;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"mode": "review",
|
|
4
|
+
"storage": {
|
|
5
|
+
"sqlite_path": "./tmp/reference-support-billing/local.db"
|
|
6
|
+
},
|
|
7
|
+
"sources": {
|
|
8
|
+
"app_postgres": {
|
|
9
|
+
"engine": "postgres",
|
|
10
|
+
"read_url_env": "REFERENCE_POSTGRES_READ_URL",
|
|
11
|
+
"write_url_env": "REFERENCE_POSTGRES_WRITE_URL",
|
|
12
|
+
"statement_timeout_ms": 3000
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"trusted_context": {
|
|
16
|
+
"provider": "environment",
|
|
17
|
+
"values": {
|
|
18
|
+
"tenant_id_env": "SYNAPSOR_TENANT_ID",
|
|
19
|
+
"principal_env": "SYNAPSOR_PRINCIPAL"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"contexts": {
|
|
23
|
+
"local_operator": {
|
|
24
|
+
"provider": "environment",
|
|
25
|
+
"values": {
|
|
26
|
+
"tenant_id_env": "SYNAPSOR_TENANT_ID",
|
|
27
|
+
"principal_env": "SYNAPSOR_PRINCIPAL"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"capabilities": [
|
|
32
|
+
{
|
|
33
|
+
"name": "support.inspect_ticket",
|
|
34
|
+
"kind": "read",
|
|
35
|
+
"source": "app_postgres",
|
|
36
|
+
"context": "local_operator",
|
|
37
|
+
"target": {
|
|
38
|
+
"schema": "public",
|
|
39
|
+
"table": "support_tickets",
|
|
40
|
+
"primary_key": "id",
|
|
41
|
+
"tenant_key": "tenant_id"
|
|
42
|
+
},
|
|
43
|
+
"args": {
|
|
44
|
+
"ticket_id": { "type": "string", "required": true, "max_length": 128 }
|
|
45
|
+
},
|
|
46
|
+
"lookup": { "id_from_arg": "ticket_id" },
|
|
47
|
+
"visible_columns": ["id", "tenant_id", "customer_id", "subject", "status", "resolution_note", "updated_at"],
|
|
48
|
+
"evidence": "required",
|
|
49
|
+
"max_rows": 1
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "support.propose_ticket_resolution",
|
|
53
|
+
"kind": "proposal",
|
|
54
|
+
"source": "app_postgres",
|
|
55
|
+
"context": "local_operator",
|
|
56
|
+
"target": {
|
|
57
|
+
"schema": "public",
|
|
58
|
+
"table": "support_tickets",
|
|
59
|
+
"primary_key": "id",
|
|
60
|
+
"tenant_key": "tenant_id"
|
|
61
|
+
},
|
|
62
|
+
"args": {
|
|
63
|
+
"ticket_id": { "type": "string", "required": true, "max_length": 128 },
|
|
64
|
+
"resolution_note": { "type": "string", "required": true, "max_length": 1000 }
|
|
65
|
+
},
|
|
66
|
+
"lookup": { "id_from_arg": "ticket_id" },
|
|
67
|
+
"visible_columns": ["id", "tenant_id", "customer_id", "subject", "status", "resolution_note", "updated_at"],
|
|
68
|
+
"evidence": "required",
|
|
69
|
+
"max_rows": 1,
|
|
70
|
+
"patch": {
|
|
71
|
+
"status": { "fixed": "pending_review" },
|
|
72
|
+
"resolution_note": { "from_arg": "resolution_note" }
|
|
73
|
+
},
|
|
74
|
+
"allowed_columns": ["status", "resolution_note"],
|
|
75
|
+
"transition_guards": {
|
|
76
|
+
"status": {
|
|
77
|
+
"allowed": {
|
|
78
|
+
"open": ["pending_review"],
|
|
79
|
+
"pending_review": ["resolved"]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"conflict_guard": { "column": "updated_at" },
|
|
84
|
+
"approval": { "mode": "human", "required_role": "support_lead" }
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "billing.inspect_invoice",
|
|
88
|
+
"kind": "read",
|
|
89
|
+
"source": "app_postgres",
|
|
90
|
+
"context": "local_operator",
|
|
91
|
+
"target": {
|
|
92
|
+
"schema": "public",
|
|
93
|
+
"table": "invoices",
|
|
94
|
+
"primary_key": "id",
|
|
95
|
+
"tenant_key": "tenant_id"
|
|
96
|
+
},
|
|
97
|
+
"args": {
|
|
98
|
+
"invoice_id": { "type": "string", "required": true, "max_length": 128 }
|
|
99
|
+
},
|
|
100
|
+
"lookup": { "id_from_arg": "invoice_id" },
|
|
101
|
+
"visible_columns": ["id", "tenant_id", "customer_id", "status", "balance_cents", "late_fee_cents", "waiver_reason", "updated_at"],
|
|
102
|
+
"evidence": "required",
|
|
103
|
+
"max_rows": 1
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "billing.propose_late_fee_waiver",
|
|
107
|
+
"kind": "proposal",
|
|
108
|
+
"source": "app_postgres",
|
|
109
|
+
"context": "local_operator",
|
|
110
|
+
"target": {
|
|
111
|
+
"schema": "public",
|
|
112
|
+
"table": "invoices",
|
|
113
|
+
"primary_key": "id",
|
|
114
|
+
"tenant_key": "tenant_id"
|
|
115
|
+
},
|
|
116
|
+
"args": {
|
|
117
|
+
"invoice_id": { "type": "string", "required": true, "max_length": 128 },
|
|
118
|
+
"reason": { "type": "string", "required": true, "max_length": 500 }
|
|
119
|
+
},
|
|
120
|
+
"lookup": { "id_from_arg": "invoice_id" },
|
|
121
|
+
"visible_columns": ["id", "tenant_id", "customer_id", "status", "balance_cents", "late_fee_cents", "waiver_reason", "updated_at"],
|
|
122
|
+
"evidence": "required",
|
|
123
|
+
"max_rows": 1,
|
|
124
|
+
"patch": {
|
|
125
|
+
"late_fee_cents": { "fixed": 0 },
|
|
126
|
+
"waiver_reason": { "from_arg": "reason" }
|
|
127
|
+
},
|
|
128
|
+
"allowed_columns": ["late_fee_cents", "waiver_reason"],
|
|
129
|
+
"numeric_bounds": {
|
|
130
|
+
"late_fee_cents": { "minimum": 0, "maximum": 10000 }
|
|
131
|
+
},
|
|
132
|
+
"conflict_guard": { "column": "updated_at" },
|
|
133
|
+
"approval": { "mode": "human", "required_role": "billing_lead" }
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synapsor/runner",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"description": "Commit-safe MCP runner for Postgres and MySQL agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"synapsor
|
|
8
|
+
"synapsor": "dist/cli.js",
|
|
9
|
+
"synapsor-runner": "dist/cli.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
|
-
"dist/bin.cjs",
|
|
12
12
|
"dist/cli.js",
|
|
13
13
|
"dist/cli.d.ts",
|
|
14
14
|
"dist/cli.d.ts.map",
|
|
15
15
|
"dist/local-ui.d.ts",
|
|
16
16
|
"dist/local-ui.d.ts.map",
|
|
17
|
+
"dist/runner.mjs",
|
|
18
|
+
"docs/**/*.md",
|
|
19
|
+
"examples/dangerous-mcp-tools.json",
|
|
20
|
+
"examples/reference-support-billing-app/**",
|
|
21
|
+
"recipes/**/*.json",
|
|
17
22
|
"README.md",
|
|
18
23
|
"LICENSE",
|
|
19
|
-
"NOTICE"
|
|
24
|
+
"NOTICE",
|
|
25
|
+
"TRADEMARKS.md"
|
|
20
26
|
],
|
|
21
27
|
"engines": {
|
|
22
28
|
"node": ">=22.5.0"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "accounts.trial_extension",
|
|
3
|
+
"title": "Trial extension",
|
|
4
|
+
"summary": "Inspect one account and propose a bounded trial extension.",
|
|
5
|
+
"expected_table_type": "accounts or subscriptions table",
|
|
6
|
+
"required_columns": ["id", "tenant_id", "trial_ends_at", "extension_reason", "updated_at"],
|
|
7
|
+
"recommended_primary_key": "id",
|
|
8
|
+
"recommended_tenant_key": "tenant_id",
|
|
9
|
+
"recommended_conflict_column": "updated_at",
|
|
10
|
+
"visible_columns": ["id", "tenant_id", "plan", "trial_ends_at", "extension_reason", "updated_at"],
|
|
11
|
+
"allowed_write_columns": ["trial_ends_at", "extension_reason"],
|
|
12
|
+
"semantic_tools": ["accounts.inspect_account", "accounts.propose_trial_extension"],
|
|
13
|
+
"notes": [
|
|
14
|
+
"Use a handler executor if trial changes must go through your billing service."
|
|
15
|
+
],
|
|
16
|
+
"spec": {
|
|
17
|
+
"version": 1,
|
|
18
|
+
"engine": "postgres",
|
|
19
|
+
"mode": "review",
|
|
20
|
+
"schema": "public",
|
|
21
|
+
"table": "accounts",
|
|
22
|
+
"primary_key": "id",
|
|
23
|
+
"tenant_key": "tenant_id",
|
|
24
|
+
"conflict_column": "updated_at",
|
|
25
|
+
"namespace": "accounts",
|
|
26
|
+
"object_name": "account",
|
|
27
|
+
"inspect_tool_name": "accounts.inspect_account",
|
|
28
|
+
"proposal_tool_name": "accounts.propose_trial_extension",
|
|
29
|
+
"lookup_arg": "account_id",
|
|
30
|
+
"visible_columns": ["id", "tenant_id", "plan", "trial_ends_at", "extension_reason", "updated_at"],
|
|
31
|
+
"allowed_columns": ["trial_ends_at", "extension_reason"],
|
|
32
|
+
"patch": {
|
|
33
|
+
"trial_ends_at": { "from_arg": "trial_ends_at" },
|
|
34
|
+
"extension_reason": { "from_arg": "reason" }
|
|
35
|
+
},
|
|
36
|
+
"patch_args": {
|
|
37
|
+
"trial_ends_at": { "type": "string", "required": true, "max_length": 64 },
|
|
38
|
+
"reason": { "type": "string", "required": true, "max_length": 500 }
|
|
39
|
+
},
|
|
40
|
+
"approval": { "required_role": "account_admin" }
|
|
41
|
+
}
|
|
42
|
+
}
|