@synapsor/runner 0.1.0-alpha.2 → 0.1.0-alpha.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.
- package/README.md +111 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -0
- package/dist/runner.mjs +2229 -100
- package/docs/MCP_RUNNER_IMPLEMENTATION_PLAN.md +1 -1
- package/docs/cloud-mode.md +3 -3
- package/docs/config-migrations.md +3 -3
- package/docs/demo-transcript.md +140 -52
- package/docs/first-10-minutes.md +30 -5
- package/docs/getting-started-own-database.md +27 -27
- package/docs/limitations.md +20 -0
- package/docs/local-mode.md +78 -15
- package/docs/local-ui.md +4 -4
- package/docs/mcp-audit.md +28 -7
- package/docs/mcp-client-setup.md +6 -6
- package/docs/mcp-efficiency-benchmark.md +2 -2
- package/docs/open-source-feature-inventory.md +254 -0
- package/docs/operations.md +3 -3
- package/docs/own-db-20-minutes.md +14 -14
- package/docs/recipes.md +6 -6
- package/docs/schema-inspection.md +3 -3
- package/docs/security-boundary.md +17 -0
- package/docs/shadow-mode.md +4 -4
- package/docs/troubleshooting-first-run.md +6 -6
- package/docs/writeback-executors.md +1 -1
- package/examples/reference-support-billing-app/README.md +60 -9
- package/examples/reference-support-billing-app/schema.sql +14 -1
- package/examples/reference-support-billing-app/seed.sql +12 -5
- package/examples/reference-support-billing-app/synapsor.runner.json +105 -0
- package/package.json +1 -1
|
@@ -32,7 +32,7 @@ That command runs inspection, guided config generation, and tool preview. The
|
|
|
32
32
|
rest of this page shows the same steps explicitly.
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
35
|
+
npx -y -p @synapsor/runner@alpha synapsor inspect --from-env DATABASE_URL --engine auto
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
This prints discovered tables/views, primary keys, possible tenant/scope
|
|
@@ -43,7 +43,7 @@ write credentials to the model.
|
|
|
43
43
|
For disposable staging databases, this shorter form also works:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
46
|
+
npx -y -p @synapsor/runner@alpha synapsor inspect "$DATABASE_URL" --engine auto
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Prefer `--from-env` on shared machines so URLs do not land in shell history.
|
|
@@ -51,7 +51,7 @@ Prefer `--from-env` on shared machines so URLs do not land in shell history.
|
|
|
51
51
|
## 3. Run The Guided Wizard
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
54
|
+
npx -y -p @synapsor/runner@alpha synapsor init --from-env DATABASE_URL --mode review --wizard
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
The wizard asks for:
|
|
@@ -100,9 +100,9 @@ The generated config stores environment-variable names, not database secrets.
|
|
|
100
100
|
## 5. Preview What The Model Sees
|
|
101
101
|
|
|
102
102
|
```bash
|
|
103
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
104
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
105
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
103
|
+
npx -y -p @synapsor/runner@alpha synapsor config validate --config synapsor.runner.json
|
|
104
|
+
npx -y -p @synapsor/runner@alpha synapsor doctor --config synapsor.runner.json
|
|
105
|
+
npx -y -p @synapsor/runner@alpha synapsor tools preview --config synapsor.runner.json --store ./.synapsor/local.db
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
`doctor` checks config shape, trusted context env vars, source env vars,
|
|
@@ -124,7 +124,7 @@ vars, and the semantic MCP tool boundary.
|
|
|
124
124
|
export SYNAPSOR_TENANT_ID="acme"
|
|
125
125
|
export SYNAPSOR_PRINCIPAL="local_operator"
|
|
126
126
|
|
|
127
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
127
|
+
npx -y -p @synapsor/runner@alpha synapsor mcp serve \
|
|
128
128
|
--config ./synapsor.runner.json \
|
|
129
129
|
--store ./.synapsor/local.db
|
|
130
130
|
```
|
|
@@ -138,15 +138,15 @@ model-controlled tenant authority.
|
|
|
138
138
|
If a proposal is created:
|
|
139
139
|
|
|
140
140
|
```bash
|
|
141
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
142
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
143
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
141
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals list --store ./.synapsor/local.db
|
|
142
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals show <proposal_id> --store ./.synapsor/local.db
|
|
143
|
+
npx -y -p @synapsor/runner@alpha synapsor replay show <proposal_id> --store ./.synapsor/local.db
|
|
144
144
|
```
|
|
145
145
|
|
|
146
146
|
Open the UI:
|
|
147
147
|
|
|
148
148
|
```bash
|
|
149
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
149
|
+
npx -y -p @synapsor/runner@alpha synapsor ui --tour --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
150
150
|
```
|
|
151
151
|
|
|
152
152
|
## 8. Apply Only After Review
|
|
@@ -160,12 +160,12 @@ export SYNAPSOR_DATABASE_WRITE_URL="<postgres-or-mysql-writer-url>"
|
|
|
160
160
|
Then:
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
164
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
163
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals approve <proposal_id> --store ./.synapsor/local.db --actor local_reviewer --yes
|
|
164
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals writeback-job <proposal_id> --store ./.synapsor/local.db --output job.json
|
|
165
165
|
|
|
166
166
|
SYNAPSOR_ENGINE=postgres \
|
|
167
167
|
SYNAPSOR_DATABASE_URL="$SYNAPSOR_DATABASE_WRITE_URL" \
|
|
168
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
168
|
+
npx -y -p @synapsor/runner@alpha synapsor apply --job job.json --config synapsor.runner.json --store ./.synapsor/local.db
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
If your application should own the business write, configure an `http_handler`
|
package/docs/recipes.md
CHANGED
|
@@ -9,20 +9,20 @@ tenant key, conflict column, and business limits.
|
|
|
9
9
|
List recipes:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
12
|
+
npx -y -p @synapsor/runner@alpha synapsor recipes list
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Inspect one:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
18
|
+
npx -y -p @synapsor/runner@alpha synapsor recipes show billing.late_fee_waiver
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Initialize a starter config:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
25
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
24
|
+
npx -y -p @synapsor/runner@alpha synapsor recipes init billing.late_fee_waiver --output synapsor.runner.json
|
|
25
|
+
npx -y -p @synapsor/runner@alpha synapsor config validate --config synapsor.runner.json
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Built-in recipes are JSON files under `recipes/`. They are starter data, not
|
|
@@ -31,8 +31,8 @@ domain, and initialize from your file:
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
cp recipes/billing.late_fee_waiver.json my-recipe.json
|
|
34
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
35
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
34
|
+
npx -y -p @synapsor/runner@alpha synapsor recipes show ./my-recipe.json
|
|
35
|
+
npx -y -p @synapsor/runner@alpha synapsor recipes init ./my-recipe.json --output synapsor.runner.json
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Available recipes:
|
|
@@ -7,7 +7,7 @@ Use the public `synapsor ...` runner CLI. From a source checkout, use
|
|
|
7
7
|
`./bin/synapsor ...` if the global binary is not linked yet.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
10
|
+
npx -y -p @synapsor/runner@alpha synapsor inspect \
|
|
11
11
|
--engine auto \
|
|
12
12
|
--from-env SYNAPSOR_DATABASE_READ_URL \
|
|
13
13
|
--schema public
|
|
@@ -16,7 +16,7 @@ npx -y -p @synapsor/runner@alpha synapsor-runner inspect \
|
|
|
16
16
|
JSON output:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
19
|
+
npx -y -p @synapsor/runner@alpha synapsor inspect \
|
|
20
20
|
--engine mysql \
|
|
21
21
|
--from-env SYNAPSOR_DATABASE_READ_URL \
|
|
22
22
|
--schema app \
|
|
@@ -84,5 +84,5 @@ Binary/blob/vector columns are excluded from generated default visible columns.
|
|
|
84
84
|
Use the inspection result to create `onboarding-selection.json`, then run:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
87
|
+
npx -y -p @synapsor/runner@alpha synapsor init --spec onboarding-selection.json --non-interactive
|
|
88
88
|
```
|
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
Synapsor Runner controls a narrow database path for MCP agents. It does not make
|
|
4
4
|
MCP generally secure and it does not solve prompt injection.
|
|
5
5
|
|
|
6
|
+
## Least-privilege database access
|
|
7
|
+
|
|
8
|
+
Use a read-only database user, restricted views, row-level security, and staging
|
|
9
|
+
data where appropriate. Synapsor Runner is not a replacement for database
|
|
10
|
+
permissions.
|
|
11
|
+
|
|
12
|
+
Database permissions protect the connection. Synapsor Runner shapes the
|
|
13
|
+
model-facing interface: reviewed semantic capabilities, trusted context
|
|
14
|
+
binding, evidence handles, query audit, local inspection, and proposal-first
|
|
15
|
+
writes instead of model-facing commit authority.
|
|
16
|
+
|
|
17
|
+
If all you need is restricted reads, database permissions are a good start.
|
|
18
|
+
Use Synapsor Runner when you also want the agent-facing layer: semantic tools,
|
|
19
|
+
trusted context, evidence handles, query audit, local inspection, and
|
|
20
|
+
proposal-first writes. Proposal workflows add full replay across evidence,
|
|
21
|
+
approval, writeback receipts, and events.
|
|
22
|
+
|
|
6
23
|
The model-facing MCP server exposes reviewed semantic tools such as
|
|
7
24
|
`billing.inspect_invoice` and `billing.propose_late_fee_waiver`.
|
|
8
25
|
|
package/docs/shadow-mode.md
CHANGED
|
@@ -17,7 +17,7 @@ Run a shadow config:
|
|
|
17
17
|
List shadow proposals:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
20
|
+
npx -y -p @synapsor/runner@alpha synapsor shadow list --store ./.synapsor/local.db
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Record the human action:
|
|
@@ -30,7 +30,7 @@ cat > human-action.json <<'JSON'
|
|
|
30
30
|
}
|
|
31
31
|
JSON
|
|
32
32
|
|
|
33
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
33
|
+
npx -y -p @synapsor/runner@alpha synapsor shadow record-human-action wrp_123 \
|
|
34
34
|
--store ./.synapsor/local.db \
|
|
35
35
|
--patch human-action.json \
|
|
36
36
|
--actor human_operator \
|
|
@@ -40,13 +40,13 @@ npx -y -p @synapsor/runner@alpha synapsor-runner shadow record-human-action wrp_
|
|
|
40
40
|
Compare:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
43
|
+
npx -y -p @synapsor/runner@alpha synapsor shadow compare wrp_123 --store ./.synapsor/local.db
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
Report:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
49
|
+
npx -y -p @synapsor/runner@alpha synapsor shadow report --store ./.synapsor/local.db
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
The report counts:
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Run the friendly doctor first:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
6
|
+
npx -y -p @synapsor/runner@alpha synapsor doctor --first-run
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Use JSON for automation:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
12
|
+
npx -y -p @synapsor/runner@alpha synapsor doctor --first-run --json
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Docker Missing
|
|
@@ -128,13 +128,13 @@ Own-database MCP setup needs a reviewed config before serving tools.
|
|
|
128
128
|
Fix:
|
|
129
129
|
|
|
130
130
|
```bash
|
|
131
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
131
|
+
npx -y -p @synapsor/runner@alpha synapsor init --from-env DATABASE_URL --mode review --wizard
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
Or pass an example config:
|
|
135
135
|
|
|
136
136
|
```bash
|
|
137
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
137
|
+
npx -y -p @synapsor/runner@alpha synapsor tools preview --config ./examples/mcp-postgres-billing/synapsor.runner.json --store ./.synapsor/local.db
|
|
138
138
|
```
|
|
139
139
|
|
|
140
140
|
## SQLite Store Missing
|
|
@@ -180,7 +180,7 @@ Fix:
|
|
|
180
180
|
|
|
181
181
|
```bash
|
|
182
182
|
export SYNAPSOR_DATABASE_READ_URL="<read-only-url>"
|
|
183
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
183
|
+
npx -y -p @synapsor/runner@alpha synapsor doctor --config synapsor.runner.json
|
|
184
184
|
```
|
|
185
185
|
|
|
186
186
|
## Read/Write Credential Split Failed
|
|
@@ -216,7 +216,7 @@ Fix:
|
|
|
216
216
|
Regenerate the snippet:
|
|
217
217
|
|
|
218
218
|
```bash
|
|
219
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
219
|
+
npx -y -p @synapsor/runner@alpha synapsor mcp config claude-desktop \
|
|
220
220
|
--absolute-paths \
|
|
221
221
|
--config ./synapsor.runner.json \
|
|
222
222
|
--store ./.synapsor/local.db
|
|
@@ -36,14 +36,14 @@ examples/reference-support-billing-app/scripts/run-demo.sh
|
|
|
36
36
|
Validate the reviewed contract:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
40
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
39
|
+
npx -y -p @synapsor/runner@alpha synapsor config validate --config examples/reference-support-billing-app/synapsor.runner.json
|
|
40
|
+
npx -y -p @synapsor/runner@alpha synapsor doctor --config examples/reference-support-billing-app/synapsor.runner.json
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Serve MCP:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
46
|
+
npx -y -p @synapsor/runner@alpha synapsor mcp serve \
|
|
47
47
|
--config examples/reference-support-billing-app/synapsor.runner.json \
|
|
48
48
|
--store ./tmp/reference-support-billing/local.db
|
|
49
49
|
```
|
|
@@ -54,27 +54,78 @@ The model-facing MCP tools are:
|
|
|
54
54
|
|
|
55
55
|
- `support.inspect_ticket`
|
|
56
56
|
- `support.propose_ticket_resolution`
|
|
57
|
+
- `support.inspect_customer_account`
|
|
58
|
+
- `support.propose_plan_credit`
|
|
57
59
|
- `billing.inspect_invoice`
|
|
58
60
|
- `billing.propose_late_fee_waiver`
|
|
61
|
+
- `orders.inspect_order`
|
|
62
|
+
- `orders.propose_status_change`
|
|
59
63
|
|
|
60
64
|
The model does not receive approval tools, commit tools, write credentials, raw SQL, arbitrary table names, arbitrary column names, or tenant authority.
|
|
61
65
|
|
|
66
|
+
## Safe Write Examples
|
|
67
|
+
|
|
68
|
+
After `synapsor 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 propose billing.propose_late_fee_waiver --sample
|
|
73
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals show latest
|
|
74
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals approve latest --yes
|
|
75
|
+
npx -y -p @synapsor/runner@alpha synapsor apply latest
|
|
76
|
+
npx -y -p @synapsor/runner@alpha synapsor 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 propose support.propose_plan_credit --sample
|
|
97
|
+
npx -y -p @synapsor/runner@alpha synapsor 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
|
+
|
|
62
113
|
## Review And Replay
|
|
63
114
|
|
|
64
115
|
After a proposal exists:
|
|
65
116
|
|
|
66
117
|
```bash
|
|
67
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
68
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
69
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
70
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
71
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
118
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals list --store ./tmp/reference-support-billing/local.db
|
|
119
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals approve <proposal_id> --store ./tmp/reference-support-billing/local.db --actor local_reviewer --yes
|
|
120
|
+
npx -y -p @synapsor/runner@alpha synapsor 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 apply --job ./tmp/reference-support-billing/job.json --store ./tmp/reference-support-billing/local.db
|
|
122
|
+
npx -y -p @synapsor/runner@alpha synapsor replay export <proposal_id> --store ./tmp/reference-support-billing/local.db --output ./tmp/reference-support-billing/replay.json
|
|
72
123
|
```
|
|
73
124
|
|
|
74
125
|
To inspect locally in a browser:
|
|
75
126
|
|
|
76
127
|
```bash
|
|
77
|
-
npx -y -p @synapsor/runner@alpha synapsor
|
|
128
|
+
npx -y -p @synapsor/runner@alpha synapsor ui \
|
|
78
129
|
--config examples/reference-support-billing-app/synapsor.runner.json \
|
|
79
130
|
--store ./tmp/reference-support-billing/local.db
|
|
80
131
|
```
|
|
@@ -11,6 +11,8 @@ CREATE TABLE IF NOT EXISTS public.customers (
|
|
|
11
11
|
name text NOT NULL,
|
|
12
12
|
email text,
|
|
13
13
|
plan text NOT NULL,
|
|
14
|
+
plan_credit_cents integer NOT NULL DEFAULT 0,
|
|
15
|
+
credit_reason text,
|
|
14
16
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
15
17
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
16
18
|
);
|
|
@@ -36,6 +38,15 @@ CREATE TABLE IF NOT EXISTS public.invoices (
|
|
|
36
38
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
37
39
|
);
|
|
38
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
|
+
|
|
39
50
|
DO $$
|
|
40
51
|
BEGIN
|
|
41
52
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_reader') THEN
|
|
@@ -50,6 +61,8 @@ $$;
|
|
|
50
61
|
GRANT CONNECT ON DATABASE synapsor_reference_support_billing TO synapsor_reader, synapsor_writer;
|
|
51
62
|
GRANT USAGE ON SCHEMA public TO synapsor_reader, synapsor_writer;
|
|
52
63
|
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;
|
|
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;
|
|
54
66
|
GRANT UPDATE (status, resolution_note, updated_at) ON public.support_tickets TO synapsor_writer;
|
|
55
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;
|
|
@@ -4,12 +4,12 @@ VALUES
|
|
|
4
4
|
('otherco', 'OtherCo Labs', '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z')
|
|
5
5
|
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, updated_at = EXCLUDED.updated_at;
|
|
6
6
|
|
|
7
|
-
INSERT INTO public.customers (id, tenant_id, name, email, plan, created_at, updated_at)
|
|
7
|
+
INSERT INTO public.customers (id, tenant_id, name, email, plan, plan_credit_cents, credit_reason, created_at, updated_at)
|
|
8
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;
|
|
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
13
|
|
|
14
14
|
INSERT INTO public.support_tickets (id, tenant_id, customer_id, subject, status, resolution_note, updated_at)
|
|
15
15
|
VALUES
|
|
@@ -24,3 +24,10 @@ VALUES
|
|
|
24
24
|
('INV-3002', 'acme', 'cust_acme_2', 'paid', 0, 0, NULL, '2026-06-20T14:40:00Z'),
|
|
25
25
|
('INV-9001', 'otherco', 'cust_other_1', 'overdue', 25500, 5500, NULL, '2026-06-20T14:31:08Z')
|
|
26
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;
|
|
@@ -83,6 +83,56 @@
|
|
|
83
83
|
"conflict_guard": { "column": "updated_at" },
|
|
84
84
|
"approval": { "mode": "human", "required_role": "support_lead" }
|
|
85
85
|
},
|
|
86
|
+
{
|
|
87
|
+
"name": "support.inspect_customer_account",
|
|
88
|
+
"kind": "read",
|
|
89
|
+
"source": "app_postgres",
|
|
90
|
+
"context": "local_operator",
|
|
91
|
+
"target": {
|
|
92
|
+
"schema": "public",
|
|
93
|
+
"table": "customers",
|
|
94
|
+
"primary_key": "id",
|
|
95
|
+
"tenant_key": "tenant_id"
|
|
96
|
+
},
|
|
97
|
+
"args": {
|
|
98
|
+
"customer_id": { "type": "string", "required": true, "max_length": 128 }
|
|
99
|
+
},
|
|
100
|
+
"lookup": { "id_from_arg": "customer_id" },
|
|
101
|
+
"visible_columns": ["id", "tenant_id", "name", "plan", "plan_credit_cents", "credit_reason", "updated_at"],
|
|
102
|
+
"evidence": "required",
|
|
103
|
+
"max_rows": 1
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "support.propose_plan_credit",
|
|
107
|
+
"kind": "proposal",
|
|
108
|
+
"source": "app_postgres",
|
|
109
|
+
"context": "local_operator",
|
|
110
|
+
"target": {
|
|
111
|
+
"schema": "public",
|
|
112
|
+
"table": "customers",
|
|
113
|
+
"primary_key": "id",
|
|
114
|
+
"tenant_key": "tenant_id"
|
|
115
|
+
},
|
|
116
|
+
"args": {
|
|
117
|
+
"customer_id": { "type": "string", "required": true, "max_length": 128 },
|
|
118
|
+
"credit_cents": { "type": "number", "required": true, "minimum": 0, "maximum": 5000 },
|
|
119
|
+
"reason": { "type": "string", "required": true, "max_length": 500 }
|
|
120
|
+
},
|
|
121
|
+
"lookup": { "id_from_arg": "customer_id" },
|
|
122
|
+
"visible_columns": ["id", "tenant_id", "name", "plan", "plan_credit_cents", "credit_reason", "updated_at"],
|
|
123
|
+
"evidence": "required",
|
|
124
|
+
"max_rows": 1,
|
|
125
|
+
"patch": {
|
|
126
|
+
"plan_credit_cents": { "from_arg": "credit_cents" },
|
|
127
|
+
"credit_reason": { "from_arg": "reason" }
|
|
128
|
+
},
|
|
129
|
+
"allowed_columns": ["plan_credit_cents", "credit_reason"],
|
|
130
|
+
"numeric_bounds": {
|
|
131
|
+
"plan_credit_cents": { "minimum": 0, "maximum": 5000 }
|
|
132
|
+
},
|
|
133
|
+
"conflict_guard": { "column": "updated_at" },
|
|
134
|
+
"approval": { "mode": "human", "required_role": "support_lead" }
|
|
135
|
+
},
|
|
86
136
|
{
|
|
87
137
|
"name": "billing.inspect_invoice",
|
|
88
138
|
"kind": "read",
|
|
@@ -131,6 +181,61 @@
|
|
|
131
181
|
},
|
|
132
182
|
"conflict_guard": { "column": "updated_at" },
|
|
133
183
|
"approval": { "mode": "human", "required_role": "billing_lead" }
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "orders.inspect_order",
|
|
187
|
+
"kind": "read",
|
|
188
|
+
"source": "app_postgres",
|
|
189
|
+
"context": "local_operator",
|
|
190
|
+
"target": {
|
|
191
|
+
"schema": "public",
|
|
192
|
+
"table": "orders",
|
|
193
|
+
"primary_key": "id",
|
|
194
|
+
"tenant_key": "tenant_id"
|
|
195
|
+
},
|
|
196
|
+
"args": {
|
|
197
|
+
"order_id": { "type": "string", "required": true, "max_length": 128 }
|
|
198
|
+
},
|
|
199
|
+
"lookup": { "id_from_arg": "order_id" },
|
|
200
|
+
"visible_columns": ["id", "tenant_id", "customer_id", "status", "status_change_reason", "updated_at"],
|
|
201
|
+
"evidence": "required",
|
|
202
|
+
"max_rows": 1
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"name": "orders.propose_status_change",
|
|
206
|
+
"kind": "proposal",
|
|
207
|
+
"source": "app_postgres",
|
|
208
|
+
"context": "local_operator",
|
|
209
|
+
"target": {
|
|
210
|
+
"schema": "public",
|
|
211
|
+
"table": "orders",
|
|
212
|
+
"primary_key": "id",
|
|
213
|
+
"tenant_key": "tenant_id"
|
|
214
|
+
},
|
|
215
|
+
"args": {
|
|
216
|
+
"order_id": { "type": "string", "required": true, "max_length": 128 },
|
|
217
|
+
"status": { "type": "string", "required": true, "enum": ["ready_to_ship", "canceled"] },
|
|
218
|
+
"reason": { "type": "string", "required": true, "max_length": 500 }
|
|
219
|
+
},
|
|
220
|
+
"lookup": { "id_from_arg": "order_id" },
|
|
221
|
+
"visible_columns": ["id", "tenant_id", "customer_id", "status", "status_change_reason", "updated_at"],
|
|
222
|
+
"evidence": "required",
|
|
223
|
+
"max_rows": 1,
|
|
224
|
+
"patch": {
|
|
225
|
+
"status": { "from_arg": "status" },
|
|
226
|
+
"status_change_reason": { "from_arg": "reason" }
|
|
227
|
+
},
|
|
228
|
+
"allowed_columns": ["status", "status_change_reason"],
|
|
229
|
+
"transition_guards": {
|
|
230
|
+
"status": {
|
|
231
|
+
"allowed": {
|
|
232
|
+
"paid": ["ready_to_ship", "canceled"],
|
|
233
|
+
"processing": ["ready_to_ship"]
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
"conflict_guard": { "column": "updated_at" },
|
|
238
|
+
"approval": { "mode": "human", "required_role": "orders_lead" }
|
|
134
239
|
}
|
|
135
240
|
]
|
|
136
241
|
}
|