@synapsor/runner 1.5.0 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +92 -3
  2. package/README.md +96 -109
  3. package/dist/authoring.d.ts +23 -0
  4. package/dist/authoring.d.ts.map +1 -0
  5. package/dist/authoring.mjs +1318 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +4 -5
  8. package/dist/local-ui.d.ts +14 -0
  9. package/dist/local-ui.d.ts.map +1 -1
  10. package/dist/runner.mjs +10845 -5598
  11. package/dist/runtime.mjs +29 -1
  12. package/dist/shadow.d.ts +36 -0
  13. package/dist/shadow.d.ts.map +1 -0
  14. package/dist/shadow.mjs +5262 -0
  15. package/docs/README.md +25 -2
  16. package/docs/alternatives.md +122 -0
  17. package/docs/capability-authoring.md +43 -0
  18. package/docs/client-recipes.md +218 -0
  19. package/docs/contract-testing.md +9 -7
  20. package/docs/cursor-plugin.md +78 -0
  21. package/docs/effect-regression.md +39 -2
  22. package/docs/fresh-developer-usability.md +110 -0
  23. package/docs/getting-started-own-database.md +96 -2
  24. package/docs/host-compatibility.md +59 -0
  25. package/docs/local-mode.md +6 -5
  26. package/docs/mcp-audit.md +166 -2
  27. package/docs/mcp-client-setup.md +4 -0
  28. package/docs/mcp-clients.md +33 -4
  29. package/docs/oss-vs-cloud.md +3 -0
  30. package/docs/release-notes.md +90 -4
  31. package/docs/security-boundary.md +20 -5
  32. package/docs/shadow-studies.md +47 -4
  33. package/docs/troubleshooting-first-run.md +46 -0
  34. package/examples/support-billing-agent/README.md +18 -0
  35. package/examples/support-billing-agent/app/README.md +6 -0
  36. package/examples/support-billing-agent/app/contract.ts +28 -0
  37. package/examples/support-billing-agent/app/effect-adapter.mjs +23 -0
  38. package/examples/support-billing-agent/app/record-shadow-outcomes.mjs +44 -0
  39. package/examples/support-billing-agent/scripts/run-evaluation.sh +6 -5
  40. package/examples/support-plan-credit/README.md +48 -2
  41. package/examples/support-plan-credit/mcp-client-examples/README.md +23 -0
  42. package/examples/support-plan-credit/mcp-client-examples/claude-code.sh +34 -0
  43. package/examples/support-plan-credit/mcp-client-examples/codex.config.toml +24 -0
  44. package/examples/support-plan-credit/mcp-client-examples/generic-stdio.mjs +35 -0
  45. package/examples/support-plan-credit/mcp-client-examples/generic-streamable-http.mjs +31 -0
  46. package/examples/support-plan-credit/mcp-client-examples/google-adk.py +40 -0
  47. package/examples/support-plan-credit/mcp-client-examples/langchain.mjs +29 -0
  48. package/examples/support-plan-credit/mcp-client-examples/llamaindex.py +36 -0
  49. package/examples/support-plan-credit/mcp-client-examples/openai-agents-stdio.ts +1 -1
  50. package/examples/support-plan-credit/mcp-client-examples/openai-agents-streamable-http.ts +1 -1
  51. package/examples/support-plan-credit/mcp-client-examples/vscode.mcp.json +20 -0
  52. package/examples/support-plan-credit/synapsor/actions/support.propose_plan_credit.contract-tests.generated.json +221 -0
  53. package/examples/support-plan-credit/synapsor/actions/support.propose_plan_credit.ts +34 -0
  54. package/fixtures/mcp-audit/README.md +14 -0
  55. package/fixtures/mcp-audit/cursor-bypass-config.json +38 -0
  56. package/fixtures/mcp-audit/dangerous-tools-list.json +33 -0
  57. package/fixtures/mcp-audit/reviewed-proposal-tools-list.json +60 -0
  58. package/package.json +18 -3
  59. package/schemas/mcp-audit-report.schema.json +100 -1
  60. package/schemas/synapsor.contract-tests.schema.json +1 -1
@@ -10,7 +10,93 @@ npx -y -p @synapsor/runner synapsor-runner demo --quick
10
10
  The OSS runner command is `synapsor-runner`. The `synapsor` command is reserved
11
11
  for the Synapsor Cloud CLI.
12
12
 
13
- ## 1.5.0 (prepared, not published)
13
+ ## 1.5.3 (prepared, not published)
14
+
15
+ ### Intent to Safe Action
16
+
17
+ - `start --action <name> --description <intent>` creates one inert TypeScript
18
+ Safe Action scaffold from an existing reviewed read boundary. Project-scoped
19
+ instructions let a coding agent complete and validate only the draft.
20
+ - `action validate` statically parses the restricted object and emits a
21
+ digest-addressed disabled canonical draft, explanation, and deterministic
22
+ allow/deny/effect tests. It does not import adopter code, activate the action,
23
+ or alter the active MCP tool catalog.
24
+ - The secured Workbench runs one real source-unchanged staging Data PR before
25
+ activation and requires `ACTIVATE` plus the complete digest. Cloud-linked
26
+ projects use governed Cloud activation. Existing proposals remain bound to
27
+ the exact active contract digest they were created under.
28
+ - The package includes a current-format Cursor plugin with
29
+ `/synapsor-protect`, diagnostics, deterministic package verification, and
30
+ project-safe MCP wiring. Cursor host claims remain evidence-labeled; a stable
31
+ UI pass and Marketplace submission are owner gates.
32
+ - MCP audit now renders a model-authority map and opt-in bypass evidence as
33
+ text, JSON, Markdown, or SARIF. A checked-in GitHub Actions workflow and Safe
34
+ Action team CI are deterministic and non-mutating.
35
+ - Proposal-only integration recipes cover Claude Code, Codex, VS Code, OpenAI
36
+ Agents, LangChain/LangGraph, Google ADK, LlamaIndex, and generic MCP clients.
37
+ - The first two README screens, public website, article, discovery routes, and
38
+ 36-second evidence-backed demo now lead with the own-project Data PR path.
39
+
40
+ Prepared package version: `@synapsor/runner@1.5.3`. Spec, DSL, and Cloud CLI
41
+ packages are unchanged. Nothing has been published by this repository change.
42
+
43
+ ## 1.5.2 (prepared, not published)
44
+
45
+ ### First safe action in an existing application
46
+
47
+ - `start --from-env DATABASE_URL` and
48
+ `try --prove --from-env DATABASE_URL` now converge on one own-data
49
+ onboarding path. It inspects a staging database read-only, asks the developer
50
+ to choose trusted scope and one reviewed action, emits canonical files, and
51
+ never substitutes synthetic data after an own-data failure.
52
+ - The localhost workbench presents Project, Data source, Trust scope, Action,
53
+ Agent, Test, and Review plus an exact proposal Data PR. Test is complete only
54
+ after a scoped tool call records query audit, not after config validation.
55
+ - Project-scoped Cursor install/status/uninstall previews and owns only its MCP
56
+ entry, preserves other entries, and launches the exact Runner version.
57
+ Approval, apply, revert, credentials, and trusted identity stay outside MCP.
58
+ - Local activation reports measure proof, onboarding, Cursor, first read, and
59
+ first proposal without telemetry or business identifiers. Product time
60
+ excludes initial package download; cold `npx` time is reported separately
61
+ as an observed environment-specific measurement.
62
+ - Optional TypeScript authoring emits the same canonical Spec contract. Shadow
63
+ trust progression and provider-neutral effect regression remain
64
+ non-activating and preserve deterministic/external-model provenance.
65
+ - MCP audit candidates can open directly in the secured workbench, and the
66
+ release includes the host-compatibility matrix and support/billing reference
67
+ workflow.
68
+ - The package now requires Node 22.13.0 or newer, the first Node 22 release
69
+ where Runner's `node:sqlite` dependency is available without an experimental
70
+ runtime flag. Older Node versions fail immediately with an actionable error.
71
+
72
+ Prepared package version: `@synapsor/runner@1.5.2`. Spec, DSL, and Cloud CLI
73
+ packages are unchanged. Nothing has been published by this repository change.
74
+
75
+ ## 1.5.1 (prepared, not published)
76
+
77
+ ### Safe disposable state ownership
78
+
79
+ - `try --state-dir` no longer treats the supplied directory as disposable.
80
+ The supplied path is a caller-owned container and Runner uses a marked
81
+ managed child beneath it.
82
+ - Cleanup removes only known direct try-state files. Unrelated files remain in
83
+ place, and roots, home/cwd/repository paths, traversal, symlink escapes,
84
+ unmarked lookalikes, and linked managed files fail closed.
85
+ - An atomic state lease prevents concurrent runs from corrupting each other and
86
+ permits recovery from a valid lease whose process is no longer alive.
87
+ - `demo inspect --state-dir` resolves the same managed child. The default
88
+ `.synapsor/try` path safely adopts only the recognized legacy file layout.
89
+ - Explicit `--force` replacement of generated schema and MCP-audit candidate
90
+ directories now rejects protected paths, symlinked ancestors, and linked or
91
+ invalid ownership markers.
92
+ - The embedded `try` data source is consistently identified as synthetic.
93
+ - The packaged YAML parser is updated to `2.8.3` to include the upstream fix
94
+ for deeply nested collection denial of service.
95
+
96
+ Prepared package version: `@synapsor/runner@1.5.1`. Spec, DSL, and Cloud CLI
97
+ packages are unchanged. Nothing has been published by this repository change.
98
+
99
+ ## 1.5.0 (published 2026-07-20)
14
100
 
15
101
  ### Complete guarded-action developer proof
16
102
 
@@ -45,8 +131,8 @@ for the Synapsor Cloud CLI.
45
131
  immediately second, and distinguishes application scope, PostgreSQL RLS, and
46
132
  tenant-bound isolation without overstating any of them.
47
133
 
48
- Prepared package version: `@synapsor/runner@1.5.0`. Spec, DSL, and Cloud CLI
49
- packages are unchanged. Nothing has been published by this repository change.
134
+ Published package version: `@synapsor/runner@1.5.0`. Spec, DSL, and Cloud CLI
135
+ packages were unchanged.
50
136
 
51
137
  ## 1.4.123 (2026-07-17)
52
138
 
@@ -744,7 +830,7 @@ not change and must not be republished for this release.
744
830
  CDC, managed runner fleets, compliance exports, production SLA, or C++ DBMS
745
831
  internals.
746
832
  - The local store is single-node SQLite for local/dev/staging usage.
747
- - Node >= 22.5.0 is required because the local ledger uses Node's
833
+ - Node >= 22.13.0 is required because the local ledger uses Node's unflagged
748
834
  `node:sqlite` runtime. Use a supported Node runtime or the Docker-backed
749
835
  source demo path.
750
836
 
@@ -36,6 +36,7 @@ The model does not receive:
36
36
  - a generic `execute_sql` tool;
37
37
  - arbitrary table, schema, or column names;
38
38
  - database URLs or credentials;
39
+ - contract activation tools;
39
40
  - approval tools;
40
41
  - commit/writeback tools;
41
42
  - trusted tenant or principal authority as ordinary model arguments.
@@ -84,6 +85,18 @@ explicit host-level administrative boundary.
84
85
  Proposal tools read the current row through the read credential, store evidence
85
86
  and an exact before/after diff, and leave the source database unchanged.
86
87
 
88
+ Code-first Safe Actions do not create authority when a file is edited. Runner
89
+ parses the restricted TypeScript object without importing or executing it,
90
+ compiles it into a digest-addressed disabled canonical draft, and keeps the
91
+ active contract pointer unchanged. Activation is available only in the
92
+ session-token/CSRF-protected localhost Workbench after a matching real staging
93
+ proposal proves `source_database_changed:false`; the operator must confirm the
94
+ complete digest. A proposal remains pinned to the active contract digest it was
95
+ created under, so later activation cannot reinterpret an old approval. Cloud-
96
+ linked projects route activation through Cloud governance instead of the local
97
+ control. No model-facing MCP tool can validate-and-activate, approve, apply, or
98
+ revert.
99
+
87
100
  The local proposal store rejects obvious credential material before persistence:
88
101
  database URLs, bearer tokens, Synapsor runner tokens, private-key blocks, and
89
102
  secret-like fields such as password, token, API key, private key, cookie,
@@ -145,11 +158,13 @@ database identifiers such as `../private`, `id/../../tenant_id`, or
145
158
  `status; DROP TABLE tickets` before adapter execution. Local CLI file paths
146
159
  remain explicit user-provided paths; they are not model-facing authority.
147
160
 
148
- Local review can happen through the CLI or `synapsor-runner ui`. The UI is a localhost
149
- review surface with a per-run session token and CSRF protection for
150
- approve/reject actions. It does not expose raw SQL, database URLs, write
151
- credentials, approval tools, commit tools, or controls that widen reviewed
152
- tables/columns.
161
+ Local review can happen through the CLI or `synapsor-runner ui`. The UI is a
162
+ localhost review surface with a per-run session token and CSRF protection for
163
+ approve/reject and Safe Action draft controls. Activation additionally requires
164
+ a matching source-unchanged preview plus the complete digest. These operator
165
+ controls are not MCP tools. The UI does not expose raw SQL, database URLs, or
166
+ write credentials, and it does not let a model widen reviewed tables, columns,
167
+ scope, policy, or executor authority.
153
168
 
154
169
  Contract lint and tests are review aids rather than a proof of complete
155
170
  security. Capability breadth can still drift as narrow tools accumulate;
@@ -142,6 +142,41 @@ synapsor-runner shadow outcome import \
142
142
  --store ./.synapsor/local.db
143
143
  ```
144
144
 
145
+ For the normal application path, record the trusted outcome directly instead
146
+ of assembling JSONL. `@synapsor/runner/shadow` writes through the same scope,
147
+ effect-shape, and secret checks as the import command:
148
+
149
+ ```js
150
+ import { createShadowOutcomeRecorder } from "@synapsor/runner/shadow";
151
+
152
+ const recorder = createShadowOutcomeRecorder({
153
+ storePath: "./.synapsor/local.db",
154
+ studyId: "sst_support_pilot",
155
+ actor: "support_application",
156
+ source: "support-audit-log",
157
+ });
158
+
159
+ try {
160
+ recorder.record({
161
+ requestId: "request_123",
162
+ tenantId: "acme",
163
+ businessObject: "invoice",
164
+ objectId: "INV-3001",
165
+ disposition: "applied",
166
+ actualEffect: {
167
+ before: { late_fee_cents: 5500 },
168
+ after: { late_fee_cents: 0 },
169
+ patch: { late_fee_cents: 0 },
170
+ },
171
+ });
172
+ } finally {
173
+ recorder.close();
174
+ }
175
+ ```
176
+
177
+ The application owns the truth of this outcome. The helper does not read or
178
+ mutate the source database and cannot approve a shadow proposal.
179
+
145
180
  ## Review The Report
146
181
 
147
182
  ```bash
@@ -161,6 +196,16 @@ denial, unable-to-propose, stale/conflict, unmatched, and unsafe-scope cases.
161
196
  It also includes amount distributions, capability/reason breakdowns, and
162
197
  risk-ranked disagreements.
163
198
 
199
+ The report also shows the explicit trust progression:
200
+
201
+ ```text
202
+ Observe -> Compare -> Manual review -> Suggested bounded policy
203
+ ```
204
+
205
+ It reports an insufficient sample size until the minimum comparable evidence
206
+ exists. A suggested policy remains an inactive review artifact; the report
207
+ never widens or activates the contract.
208
+
164
209
  Close a completed study:
165
210
 
166
211
  ```bash
@@ -187,10 +232,8 @@ synapsor-runner shadow case import \
187
232
  --input ./examples/support-billing-agent/shadow-study/cases.jsonl \
188
233
  --store ./.synapsor/shadow-reference.db
189
234
 
190
- synapsor-runner shadow outcome import \
191
- --study sst_support_reference \
192
- --input ./examples/support-billing-agent/shadow-study/outcomes.jsonl \
193
- --store ./.synapsor/shadow-reference.db
235
+ node ./examples/support-billing-agent/app/record-shadow-outcomes.mjs \
236
+ ./.synapsor/shadow-reference.db sst_support_reference
194
237
 
195
238
  synapsor-runner shadow report \
196
239
  --study sst_support_reference \
@@ -12,6 +12,52 @@ Use JSON for automation:
12
12
  npx -y -p @synapsor/runner synapsor-runner doctor --first-run --json
13
13
  ```
14
14
 
15
+ ## Safe Action Draft Does Not Appear As A Tool
16
+
17
+ This is expected before activation. `start --action`, agent edits, `action
18
+ validate`, and `action watch` can create or refresh only a disabled draft. They
19
+ must not alter the active model-facing tools.
20
+
21
+ Check status without exposing credentials:
22
+
23
+ ```bash
24
+ synapsor-runner action status --json
25
+ synapsor-runner tools preview \
26
+ --config ./synapsor.runner.json \
27
+ --store ./.synapsor/local.db
28
+ ```
29
+
30
+ Open the secured Workbench, run the real source-unchanged staging Data PR
31
+ preview, review the complete digest, and activate it there. There is
32
+ intentionally no activation CLI command to hand to a coding agent.
33
+
34
+ ## Activated Tool Does Not Appear In Cursor
35
+
36
+ Some MCP hosts do not refresh `tools/list` for a running stdio session. First
37
+ confirm that Runner's active tool surface changed:
38
+
39
+ ```bash
40
+ synapsor-runner action status --json
41
+ synapsor-runner mcp status cursor --project --check-launch
42
+ ```
43
+
44
+ Then reconnect or restart the project MCP server as directed by the Workbench.
45
+ Do not work around a stale host session by adding approval, apply, activation,
46
+ credentials, tenant values, or raw SQL to the Cursor configuration.
47
+
48
+ ## Safe Action Validation Reports Review Placeholders
49
+
50
+ The composer fails closed while any `__REVIEW_*__` authority placeholder or
51
+ dynamic TypeScript expression remains. Review the reported field and source,
52
+ then make the authority explicit in the restricted `defineCapability({...})`
53
+ object. Runner will not infer trusted tenant/principal bindings, hidden fields,
54
+ write columns, bounds, conflict guards, approval, or executor authority from
55
+ application code.
56
+
57
+ Use the generated explanation and test manifest after validation. Do not edit
58
+ digest-addressed files under `.synapsor/drafts/` or `.synapsor/active/`; edit
59
+ the TypeScript source and validate again.
60
+
15
61
  ## Smoke Proposal Missing From Another Runner
16
62
 
17
63
  What happened:
@@ -63,6 +63,21 @@ The demo proves:
63
63
  - the shadow report compares cases with authoritative human outcomes before the
64
64
  effect fixture checks the expected business change.
65
65
 
66
+ The app-owned integration files make those two evaluation paths reusable:
67
+
68
+ - `app/contract.ts` is the code-first TypeScript form of the reviewed late-fee
69
+ action and compiles to the same canonical spec as JSON/DSL;
70
+ - `app/record-shadow-outcomes.mjs` records authoritative app outcomes through
71
+ `@synapsor/runner/shadow` without approval or source mutation;
72
+ - `app/effect-adapter.mjs` emits one deterministic, propose-only result through
73
+ the provider-neutral command adapter;
74
+ - `.github/workflows/effect-regression.yml` runs the effect check and uploads
75
+ JUnit output for relevant pull requests.
76
+
77
+ This repository result is deterministic application evidence, not an external
78
+ model benchmark. Use `--result-origin external-model` only when your adapter
79
+ actually invokes a model.
80
+
66
81
  RLS is defense in depth, not a claim that a fully compromised Runner process is
67
82
  contained. A process that can choose arbitrary trusted context or replace the
68
83
  database credential remains outside this guarantee. Production deployments
@@ -107,6 +122,9 @@ Relevant files:
107
122
  - `scripts/run-demo.sh`
108
123
  - `scripts/run-evaluation.sh`
109
124
  - `app/README.md`
125
+ - `app/contract.ts`
126
+ - `app/record-shadow-outcomes.mjs`
127
+ - `app/effect-adapter.mjs`
110
128
  - `shadow-study/cases.jsonl` and `shadow-study/outcomes.jsonl`: deterministic
111
129
  true-shadow reference data for agent-versus-authoritative-outcome reports.
112
130
 
@@ -11,3 +11,9 @@ writeback with tenant, primary-key, allowed-column, idempotency, and
11
11
  For richer app actions, such as inserting a credit ledger row and emitting an
12
12
  event, use the app-owned handler examples in `../app-owned-writeback/` and
13
13
  `../mcp-postgres-billing-app-handler/`.
14
+
15
+ `contract.ts` demonstrates the optional typed authoring frontend without
16
+ changing the canonical contract. `record-shadow-outcomes.mjs` is the normal-app
17
+ bridge for authoritative human outcomes. `effect-adapter.mjs` is a deterministic
18
+ propose-only CI adapter; it receives a bounded fixture path and emits one
19
+ canonical result. Neither helper approves, applies, or mutates the source.
@@ -0,0 +1,28 @@
1
+ import { defineCapability } from "@synapsor/runner/authoring";
2
+
3
+ export const waiveLateFee = defineCapability({
4
+ name: "billing.propose_late_fee_waiver",
5
+ kind: "proposal",
6
+ context: "local_operator",
7
+ source: "app_postgres",
8
+ subject: { resource: "billing.invoice", primary_key: "id", tenant_key: "tenant_id" },
9
+ args: {
10
+ invoice_id: { type: "string", required: true, max_length: 128 },
11
+ reason: { type: "string", required: true, max_length: 500 },
12
+ },
13
+ lookup: { id_from_arg: "invoice_id" },
14
+ visible_fields: ["id", "tenant_id", "late_fee_cents", "waiver_reason", "updated_at"],
15
+ kept_out_fields: ["card_token", "internal_risk_note"],
16
+ evidence: { required: true, query_audit: true },
17
+ max_rows: 1,
18
+ proposal: {
19
+ action: "waive_late_fee",
20
+ operation: { kind: "update" },
21
+ allowed_fields: ["late_fee_cents", "waiver_reason"],
22
+ patch: { late_fee_cents: { fixed: 0 }, waiver_reason: { from_arg: "reason" } },
23
+ numeric_bounds: { late_fee_cents: { minimum: 0, maximum: 10000 } },
24
+ conflict_guard: { column: "updated_at" },
25
+ approval: { mode: "human", required_role: "billing_lead" },
26
+ writeback: { mode: "direct_sql" },
27
+ },
28
+ });
@@ -0,0 +1,23 @@
1
+ import fs from "node:fs";
2
+
3
+ if (process.env.SYNAPSOR_EFFECT_MODE !== "propose_only") {
4
+ throw new Error("reference effect adapter requires SYNAPSOR_EFFECT_MODE=propose_only");
5
+ }
6
+ if (process.env.DATABASE_URL || process.env.SYNAPSOR_DATABASE_WRITE_URL) {
7
+ throw new Error("reference effect adapter must not receive database credentials");
8
+ }
9
+ const fixturePath = process.env.SYNAPSOR_EFFECT_FIXTURE_PATH;
10
+ if (!fixturePath) throw new Error("SYNAPSOR_EFFECT_FIXTURE_PATH is required");
11
+ const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
12
+ const expected = fixture.expected;
13
+
14
+ process.stdout.write(`${JSON.stringify({
15
+ schema_version: "synapsor.effect-result.v1",
16
+ fixture_id: fixture.fixture_id,
17
+ capability_calls: expected.capability_calls.map((name) => ({ name })),
18
+ trusted_context: expected.trusted_context,
19
+ proposal: expected.proposal,
20
+ observed_fields: Object.keys(expected.proposal.diff ?? {}).sort(),
21
+ evidence: { mode: "fixture", new_source_reads: false },
22
+ source_database_changed: false,
23
+ })}\n`);
@@ -0,0 +1,44 @@
1
+ import { createShadowOutcomeRecorder } from "@synapsor/runner/shadow";
2
+
3
+ const [storePath, studyId] = process.argv.slice(2);
4
+ if (!storePath || !studyId) {
5
+ throw new Error("usage: node record-shadow-outcomes.mjs <store-path> <study-id>");
6
+ }
7
+
8
+ const recorder = createShadowOutcomeRecorder({
9
+ storePath,
10
+ studyId,
11
+ actor: "support_reference_app",
12
+ source: "support-app-audit",
13
+ });
14
+ try {
15
+ recorder.record({
16
+ requestId: "req-waiver-exact",
17
+ tenantId: "acme",
18
+ businessObject: "invoice",
19
+ objectId: "INV-3001",
20
+ disposition: "applied",
21
+ actualEffect: {
22
+ before: { late_fee_cents: 5500, waiver_reason: null },
23
+ after: { late_fee_cents: 0, waiver_reason: "courtesy waiver" },
24
+ patch: { late_fee_cents: 0, waiver_reason: "courtesy waiver" },
25
+ },
26
+ occurredAt: "2026-07-19T11:00:00.000Z",
27
+ reference: "ticket:SUP-184",
28
+ reason: "customer qualified",
29
+ });
30
+ recorder.record({
31
+ requestId: "req-waiver-rejected",
32
+ tenantId: "acme",
33
+ businessObject: "invoice",
34
+ objectId: "INV-REJECTED",
35
+ disposition: "rejected_no_action",
36
+ occurredAt: "2026-07-19T11:01:00.000Z",
37
+ reference: "ticket:SUP-REJECTED",
38
+ reason: "customer already received a courtesy waiver",
39
+ });
40
+ } finally {
41
+ recorder.close();
42
+ }
43
+
44
+ process.stdout.write("Recorded 2 authoritative outcomes through @synapsor/runner/shadow.\n");
@@ -23,10 +23,9 @@ corepack pnpm runner shadow case import \
23
23
  --input "$ROOT_DIR/examples/support-billing-agent/shadow-study/cases.jsonl" \
24
24
  --store "$STORE_PATH" >/dev/null
25
25
 
26
- corepack pnpm runner shadow outcome import \
27
- --study sst_support_reference \
28
- --input "$ROOT_DIR/examples/support-billing-agent/shadow-study/outcomes.jsonl" \
29
- --store "$STORE_PATH" >/dev/null
26
+ corepack pnpm build:runner-package >/dev/null
27
+ node "$ROOT_DIR/examples/support-billing-agent/app/record-shadow-outcomes.mjs" \
28
+ "$STORE_PATH" sst_support_reference >/dev/null
30
29
 
31
30
  corepack pnpm runner shadow report \
32
31
  --study sst_support_reference \
@@ -54,7 +53,9 @@ NODE
54
53
 
55
54
  corepack pnpm runner effect run \
56
55
  --dataset "$ROOT_DIR/fixtures/effects/dataset.json" \
57
- --results-dir "$ROOT_DIR/fixtures/effects/results" \
56
+ --adapter node \
57
+ --adapter-arg "$ROOT_DIR/examples/support-billing-agent/app/effect-adapter.mjs" \
58
+ --result-origin deterministic-application \
58
59
  --format json \
59
60
  --out "$EFFECT_PATH" >/dev/null
60
61
 
@@ -15,7 +15,7 @@ policy-tier demonstration.
15
15
  ## Prerequisites
16
16
 
17
17
  - Docker
18
- - Node 22.5+
18
+ - Node 22.13+
19
19
  - Port `55438` available
20
20
 
21
21
  Install the stable CLI once for the commands below:
@@ -50,6 +50,42 @@ network connection or connecting to a database. From a source checkout,
50
50
  `corepack pnpm verify:adoption` runs this boundary check plus the no-database
51
51
  quick demo and MCP config validation.
52
52
 
53
+ ## Clone And Validate The Same Safe Action
54
+
55
+ The checked-in code-first action is
56
+ [`synapsor/actions/support.propose_plan_credit.ts`](synapsor/actions/support.propose_plan_credit.ts).
57
+ It is a reviewable frontend for the same canonical JSON contract, not another
58
+ runtime format. Editing it cannot change active tools.
59
+
60
+ After cloning the repository and installing dependencies, another developer can
61
+ run the deterministic team gate without a database or Cloud account:
62
+
63
+ ```bash
64
+ corepack pnpm install --frozen-lockfile
65
+ corepack pnpm verify:safe-action-team
66
+ ```
67
+
68
+ The verifier copies this example to a disposable directory, compiles the action
69
+ only as a disabled draft, compares the regenerated boundary tests with the
70
+ checked-in file, proves the config and active contract are unchanged, and emits
71
+ text, JSON, and JUnit reports under `tmp/safe-action-team-ci/`. Its static gate
72
+ covers the exact proposal effect, trusted scope, evidence requirement, approval
73
+ policy, operator separation, kept-out fields, argument bounds, and conflict
74
+ guard. It performs no approval, activation, source read, or source mutation.
75
+
76
+ The same gate runs in
77
+ [`safe-action-ci.yml`](https://github.com/Synapsor/Synapsor-Runner/blob/main/.github/workflows/safe-action-ci.yml).
78
+ CI adds a disposable PostgreSQL service and runs the generated allowed-effect,
79
+ cross-tenant-denial, and source-unchanged assertions. It uses an amount above
80
+ the auto-approval threshold, performs no approval or apply, and requires source
81
+ rows and source receipt state to remain equivalent before and after the live
82
+ suite.
83
+ For a real staging preview, each developer supplies their own
84
+ `PLAN_CREDIT_POSTGRES_READ_URL`, `PLAN_CREDIT_POSTGRES_WRITE_URL`,
85
+ `SYNAPSOR_TENANT_ID`, and `SYNAPSOR_PRINCIPAL` outside Git, then follows the
86
+ Quick Start below. Cursor receives the same proposal-only tools; activation,
87
+ approval, and apply remain outside the agent.
88
+
53
89
  ## Quick Start
54
90
 
55
91
  ```bash
@@ -199,9 +235,19 @@ shape without guessing which state should change.
199
235
  Copy the matching template from [`mcp-client-examples/`](mcp-client-examples/):
200
236
 
201
237
  - Claude Desktop: `claude-desktop.json`
238
+ - Claude Code: `claude-code.sh`
239
+ - Codex: `codex.config.toml`
202
240
  - Cursor project/global: `cursor-project.mcp.json`, `cursor-global.mcp.json`
241
+ - VS Code: `vscode.mcp.json`
203
242
  - OpenAI Agents SDK: stdio and Streamable HTTP TypeScript examples
204
- - generic MCP: stdio and Streamable HTTP JSON examples
243
+ - LangChain/LangGraph: `langchain.mjs`
244
+ - Google ADK: `google-adk.py`
245
+ - LlamaIndex: `llamaindex.py`
246
+ - generic MCP: stdio and Streamable HTTP JSON and JavaScript examples
247
+
248
+ See [Client And Framework Recipes](../../docs/client-recipes.md) for current
249
+ official references, exact proposal input, evidence labels, and the mechanical
250
+ verification gate.
205
251
 
206
252
  For Streamable HTTP, run:
207
253
 
@@ -0,0 +1,23 @@
1
+ # Support Plan Credit MCP Recipes
2
+
3
+ These files connect different MCP hosts and frameworks to the same reviewed
4
+ Runner tools:
5
+
6
+ - `support.inspect_customer`
7
+ - `support.propose_plan_credit`
8
+
9
+ Every proposal example uses:
10
+
11
+ ```json
12
+ {
13
+ "customer_id": "CUS-3001",
14
+ "credit_cents": 2500,
15
+ "reason": "SLA outage ticket SUP-481"
16
+ }
17
+ ```
18
+
19
+ The expected call result has `source_database_changed: false`. Human review,
20
+ approval, and apply stay outside every model-facing recipe.
21
+
22
+ See [Client And Framework Recipes](../../../docs/client-recipes.md) for setup,
23
+ evidence labels, official references, and verification commands.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Run from the repository root. Credentials and trusted scope are inherited
5
+ # from the shell that starts Claude Code; they are not written to .mcp.json.
6
+ claude mcp add-json --scope project synapsor "$(cat <<'JSON'
7
+ {
8
+ "type": "stdio",
9
+ "command": "npx",
10
+ "args": [
11
+ "-y",
12
+ "-p",
13
+ "@synapsor/runner",
14
+ "synapsor-runner",
15
+ "mcp",
16
+ "serve",
17
+ "--config",
18
+ "./examples/support-plan-credit/synapsor.runner.json",
19
+ "--store",
20
+ "./tmp/support-plan-credit/local.db"
21
+ ]
22
+ }
23
+ JSON
24
+ )"
25
+
26
+ claude mcp get synapsor
27
+
28
+ cat <<'PROMPT'
29
+ List the Synapsor MCP tools. Confirm there is no SQL, approval, apply, commit,
30
+ activation, or revert tool. Then call support.propose_plan_credit with
31
+ customer_id CUS-3001, credit_cents 2500, and reason "SLA outage ticket
32
+ SUP-481". Show the exact proposal effect and confirm source_database_changed is
33
+ false. Stop for human review outside Claude Code.
34
+ PROMPT
@@ -0,0 +1,24 @@
1
+ # Merge this table into a trusted project's .codex/config.toml and start Codex
2
+ # from the repository root. Credentials and trusted scope remain inherited
3
+ # environment variables; do not add their values here.
4
+ [mcp_servers.synapsor]
5
+ command = "npx"
6
+ args = [
7
+ "-y",
8
+ "-p",
9
+ "@synapsor/runner",
10
+ "synapsor-runner",
11
+ "mcp",
12
+ "serve",
13
+ "--config",
14
+ "./examples/support-plan-credit/synapsor.runner.json",
15
+ "--store",
16
+ "./tmp/support-plan-credit/local.db",
17
+ ]
18
+
19
+ # Prompt:
20
+ # List the Synapsor tools, then call support.propose_plan_credit with
21
+ # customer_id CUS-3001, credit_cents 2500, and reason "SLA outage ticket
22
+ # SUP-481". Confirm source_database_changed is false and stop for human review
23
+ # outside Codex. Never request an approval, apply, commit, activation, or revert
24
+ # MCP tool.
@@ -0,0 +1,35 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+
4
+ const transport = new StdioClientTransport({
5
+ command: "npx",
6
+ args: [
7
+ "-y", "-p", "@synapsor/runner", "synapsor-runner", "mcp", "serve",
8
+ "--config", "./examples/support-plan-credit/synapsor.runner.json",
9
+ "--store", "./tmp/support-plan-credit/local.db",
10
+ ],
11
+ env: process.env,
12
+ stderr: "inherit",
13
+ });
14
+ const client = new Client({ name: "synapsor-generic-stdio", version: "1.0.0" });
15
+
16
+ await client.connect(transport);
17
+ try {
18
+ const listed = await client.listTools();
19
+ const names = listed.tools.map((tool) => tool.name);
20
+ const forbidden = names.filter((name) => /sql|approve|apply|commit|activate|revert/i.test(name));
21
+ if (forbidden.length) throw new Error(`unsafe model-facing tools: ${forbidden.join(", ")}`);
22
+
23
+ const result = await client.callTool({
24
+ name: "support.propose_plan_credit",
25
+ arguments: {
26
+ customer_id: "CUS-3001",
27
+ credit_cents: 2500,
28
+ reason: "SLA outage ticket SUP-481",
29
+ },
30
+ });
31
+ console.log(JSON.stringify(result, null, 2));
32
+ console.error("Proposal only. Review and apply outside the model-facing MCP client.");
33
+ } finally {
34
+ await client.close();
35
+ }