@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.
@@ -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-runner inspect --from-env DATABASE_URL --engine auto
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-runner inspect "$DATABASE_URL" --engine auto
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-runner init --from-env DATABASE_URL --mode review --wizard
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-runner config validate --config synapsor.runner.json
104
- npx -y -p @synapsor/runner@alpha synapsor-runner doctor --config synapsor.runner.json
105
- npx -y -p @synapsor/runner@alpha synapsor-runner tools preview --config synapsor.runner.json --store ./.synapsor/local.db
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-runner mcp serve \
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-runner proposals list --store ./.synapsor/local.db
142
- npx -y -p @synapsor/runner@alpha synapsor-runner proposals show <proposal_id> --store ./.synapsor/local.db
143
- npx -y -p @synapsor/runner@alpha synapsor-runner replay show <proposal_id> --store ./.synapsor/local.db
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-runner ui --tour --config ./synapsor.runner.json --store ./.synapsor/local.db
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-runner proposals approve <proposal_id> --store ./.synapsor/local.db --actor local_reviewer --yes
164
- npx -y -p @synapsor/runner@alpha synapsor-runner proposals writeback-job <proposal_id> --store ./.synapsor/local.db --output job.json
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-runner apply --job job.json --config synapsor.runner.json --store ./.synapsor/local.db
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-runner recipes list
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-runner recipes show billing.late_fee_waiver
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-runner recipes init billing.late_fee_waiver --output synapsor.runner.json
25
- npx -y -p @synapsor/runner@alpha synapsor-runner config validate --config synapsor.runner.json
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-runner recipes show ./my-recipe.json
35
- npx -y -p @synapsor/runner@alpha synapsor-runner recipes init ./my-recipe.json --output synapsor.runner.json
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-runner inspect \
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-runner inspect \
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-runner init --spec onboarding-selection.json --non-interactive
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
 
@@ -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-runner shadow list --store ./.synapsor/local.db
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-runner shadow record-human-action wrp_123 \
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-runner shadow compare wrp_123 --store ./.synapsor/local.db
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-runner shadow report --store ./.synapsor/local.db
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-runner doctor --first-run
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-runner doctor --first-run --json
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-runner init --from-env DATABASE_URL --mode review --wizard
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-runner tools preview --config ./examples/mcp-postgres-billing/synapsor.runner.json --store ./.synapsor/local.db
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-runner doctor --config synapsor.runner.json
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-runner mcp config claude-desktop \
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
@@ -59,7 +59,7 @@ literal values.
59
59
  Run after approval:
60
60
 
61
61
  ```bash
62
- npx -y -p @synapsor/runner@alpha synapsor-runner apply \
62
+ npx -y -p @synapsor/runner@alpha synapsor apply \
63
63
  --proposal wrp_123 \
64
64
  --config ./synapsor.runner.json \
65
65
  --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-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
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-runner mcp serve \
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-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
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-runner ui \
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synapsor/runner",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "Commit-safe MCP runner for Postgres and MySQL agents",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",