@synapsor/runner 1.4.1 → 1.4.12

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.12 (prepared, not published)
4
+
5
+ ### Runtime-store smoke-call consistency
6
+
7
+ - Fixes BUG-017: `smoke call` now lets the MCP runtime resolve storage from the
8
+ complete config instead of injecting a local SQLite `ProposalStore`.
9
+ - In `runtime_store` mode, proposal, evidence, query-audit, event, and replay
10
+ records land in the authoritative shared Postgres ledger and are visible to
11
+ other Runner processes and normal approve/apply commands.
12
+ - Shared-ledger failures return a redacted, retryable availability result with
13
+ a nonzero CLI status and never create a local fallback proposal.
14
+ - Keeps local SQLite and mirror modes unchanged. The source database still
15
+ remains untouched until external approval and guarded apply.
16
+ - Adds focused no-fallback coverage and a disposable two-process fleet proof
17
+ covering ownership, approval/apply, one source receipt, replay, and safe
18
+ ledger unavailability.
19
+ - Stages only `@synapsor/runner@1.4.12`; `@synapsor/dsl` remains `1.4.1` and
20
+ `@synapsor/spec` remains `1.4.0`. Nothing is published by this change.
21
+
3
22
  ## 1.4.1 (prepared, not published)
4
23
 
5
24
  ### Canonical Bounded-Set Digest Verification
package/README.md CHANGED
@@ -215,6 +215,10 @@ remains the default. See
215
215
  [Production](docs/production.md) and the
216
216
  [Runner Config Reference](docs/runner-config-reference.md).
217
217
 
218
+ `smoke call` honors `runtime_store`: artifacts go to shared Postgres; failures
219
+ never fall back to SQLite. `--store` is compatibility plumbing. Requires
220
+ `1.4.12`+.
221
+
218
222
  For a small multi-tenant fleet, bind every capability context to verified HTTP
219
223
  claims, use `jwt_asymmetric` session auth, and share a bounded Postgres
220
224
  `runtime_store`. Never assume a global `http_claims` setting overrides an
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAYA,OAAO,EAAoP,KAAK,WAAW,EAA0H,MAAM,6BAA6B,CAAC;AAIza,OAAO,EAgBL,KAAK,qBAAqB,EAK3B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAgI,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAuD,KAAK,YAAY,EAAwB,MAAM,2BAA2B,CAAC;AAElV,OAAO,EAQL,KAAK,gBAAgB,EAEtB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAUL,KAAK,yBAAyB,EAE/B,MAAM,8BAA8B,CAAC;AAyUtC,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6D1D;AA0FD,KAAK,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE9E,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACvC,GACL,OAAO,CAAC,MAAM,CAAC,CA0YjB;AAgmGD,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAS/H;AAs4CD,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,yBAAyB,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAKxH;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,qBAAqB,EAC7B,WAAW,EAAE,yBAAyB,EACtC,OAAO,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,EAC1C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,kBAAkB,GAAG,kBAAkB,GAAG,kBAAkB,CAgH9D"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAYA,OAAO,EAAoP,KAAK,WAAW,EAA0H,MAAM,6BAA6B,CAAC;AAIza,OAAO,EAgBL,KAAK,qBAAqB,EAK3B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAgI,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAuD,KAAK,YAAY,EAAwB,MAAM,2BAA2B,CAAC;AAElV,OAAO,EAQL,KAAK,gBAAgB,EAEtB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAUL,KAAK,yBAAyB,EAE/B,MAAM,8BAA8B,CAAC;AAyUtC,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6D1D;AA0FD,KAAK,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE9E,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACvC,GACL,OAAO,CAAC,MAAM,CAAC,CA2YjB;AAqmGD,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAS/H;AAs4CD,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,yBAAyB,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAKxH;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,qBAAqB,EAC7B,WAAW,EAAE,yBAAyB,EACtC,OAAO,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,EAC1C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,kBAAkB,GAAG,kBAAkB,GAAG,kBAAkB,CAgH9D"}
package/dist/runner.mjs CHANGED
@@ -16939,7 +16939,7 @@ function isRecord7(value) {
16939
16939
  // apps/runner/package.json
16940
16940
  var package_default = {
16941
16941
  name: "@synapsor/runner",
16942
- version: "1.4.1",
16942
+ version: "1.4.12",
16943
16943
  description: "Stop giving AI agents execute_sql; expose reviewed Postgres/MySQL MCP actions with proposals, approval, writeback, and replay.",
16944
16944
  license: "Apache-2.0",
16945
16945
  type: "module",
@@ -17957,6 +17957,7 @@ async function runInitWizard(args, options = {}) {
17957
17957
  `);
17958
17958
  const smoke2 = await maybeRunGeneratedSmokeCall({
17959
17959
  config: generated.config,
17960
+ configPath: outputPath,
17960
17961
  env: options.env ?? process2.env,
17961
17962
  input: { [lookupArg]: smokeObjectId },
17962
17963
  readUrlEnv: configDatabaseUrlEnv,
@@ -18399,14 +18400,18 @@ async function maybeRunGeneratedSmokeCall(input) {
18399
18400
  ""
18400
18401
  ].join("\n");
18401
18402
  }
18402
- const store = new ProposalStore(input.storePath);
18403
- const runtime = createMcpRuntime(input.config, { store, env: input.env, readRow: input.readRow });
18403
+ const runtime = createMcpRuntime(input.config, { storePath: input.storePath, env: input.env, readRow: input.readRow });
18404
18404
  try {
18405
18405
  const result = await runtime.callTool(input.toolName, input.input);
18406
18406
  return [
18407
- "Smoke call ran successfully.",
18407
+ result.ok === false ? "Smoke call attempted but did not pass." : "Smoke call ran successfully.",
18408
18408
  "",
18409
- formatSmokeCallResult(input.toolName, input.input, result, input.storePath)
18409
+ formatSmokeCallResult(input.toolName, input.input, result, {
18410
+ configPath: input.configPath,
18411
+ storePath: input.storePath,
18412
+ storeAuthority: "local_sqlite",
18413
+ sharedPostgresSchema: "synapsor_runner"
18414
+ })
18410
18415
  ].join("\n");
18411
18416
  } catch (error) {
18412
18417
  const message2 = error instanceof Error ? error.message : String(error);
@@ -23486,8 +23491,7 @@ async function smokeCall(args) {
23486
23491
  const storePath = optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE ?? defaultStorePath;
23487
23492
  const config = await readRuntimeConfig(configPath);
23488
23493
  const env = envWithDemoDefaults(config, configPath);
23489
- const store = new ProposalStore(storePath);
23490
- const runtime = createMcpRuntime(config, { store, env });
23494
+ const runtime = createMcpRuntime(config, { storePath, env });
23491
23495
  try {
23492
23496
  const tools2 = runtime.listTools();
23493
23497
  const requestedTool = firstPositional(args);
@@ -23499,13 +23503,28 @@ async function smokeCall(args) {
23499
23503
  if (!capability && config.mode !== "cloud") throw new Error(`capability not found in ${configPath}: ${toolName}`);
23500
23504
  const input = capability ? await smokeToolInput(args, capability) : await smokeInputFromArgs(args);
23501
23505
  const result = await runtime.callTool(toolName, input);
23506
+ const ok = result.ok !== false;
23507
+ const storeAuthority = config.storage?.shared_postgres?.mode === "runtime_store" ? "shared_postgres" : "local_sqlite";
23502
23508
  if (args.includes("--json")) {
23503
- process2.stdout.write(`${JSON.stringify({ ok: true, tool: toolName, input, result, store_path: storePath }, null, 2)}
23509
+ process2.stdout.write(`${JSON.stringify({
23510
+ ok,
23511
+ tool: toolName,
23512
+ input,
23513
+ result,
23514
+ store_path: storePath,
23515
+ store_authority: storeAuthority,
23516
+ ...storeAuthority === "shared_postgres" ? { shared_postgres_schema: config.storage?.shared_postgres?.schema ?? "synapsor_runner" } : {}
23517
+ }, null, 2)}
23504
23518
  `);
23505
23519
  } else {
23506
- process2.stdout.write(formatSmokeCallResult(toolName, input, result, storePath));
23520
+ process2.stdout.write(formatSmokeCallResult(toolName, input, result, {
23521
+ configPath,
23522
+ storePath,
23523
+ storeAuthority,
23524
+ sharedPostgresSchema: config.storage?.shared_postgres?.schema ?? "synapsor_runner"
23525
+ }));
23507
23526
  }
23508
- return 0;
23527
+ return ok ? 0 : 1;
23509
23528
  } finally {
23510
23529
  await runtime.close();
23511
23530
  }
@@ -23722,13 +23741,25 @@ async function smokeInputFromArgs(args, capability) {
23722
23741
  if (sample) return {};
23723
23742
  return {};
23724
23743
  }
23725
- function formatSmokeCallResult(toolName, input, result, storePath) {
23726
- const evidenceId = stringField(result, "evidence_bundle_id");
23727
- const proposalId = stringField(result, "proposal_id");
23744
+ function formatSmokeCallResult(toolName, input, result, topology) {
23745
+ const proposal = isRecord8(result.proposal) ? result.proposal : void 0;
23746
+ const evidence2 = isRecord8(result.evidence) ? result.evidence : void 0;
23747
+ const error = isRecord8(result.error) ? result.error : void 0;
23748
+ const evidenceId = stringField(result, "evidence_bundle_id") ?? (evidence2 ? stringField(evidence2, "bundle_id") : void 0);
23749
+ const proposalId = stringField(result, "proposal_id") ?? (proposal ? stringField(proposal, "id") : void 0);
23728
23750
  const replayResource = stringField(result, "replay_resource");
23729
23751
  const sourceChanged = result.source_database_changed === true || result.source_database_mutated === true;
23752
+ const ok = result.ok !== false;
23753
+ const storeLines = topology.storeAuthority === "shared_postgres" ? [
23754
+ "Authoritative ledger:",
23755
+ `shared Postgres (${topology.sharedPostgresSchema})`,
23756
+ "",
23757
+ "Local --store path:",
23758
+ `${topology.storePath} (compatibility path only; no authoritative smoke records are written here)`,
23759
+ ""
23760
+ ] : ["Local ledger:", topology.storePath, ""];
23730
23761
  const lines = [
23731
- "Synapsor smoke call: ok",
23762
+ `Synapsor smoke call: ${ok ? "ok" : "failed"}`,
23732
23763
  "",
23733
23764
  "Tool:",
23734
23765
  toolName,
@@ -23741,20 +23772,32 @@ function formatSmokeCallResult(toolName, input, result, storePath) {
23741
23772
  "",
23742
23773
  "Evidence:",
23743
23774
  evidenceId || "(not returned)",
23744
- ""
23775
+ "",
23776
+ ...storeLines
23745
23777
  ];
23778
+ if (!ok) {
23779
+ lines.push("Error:", error ? stringField(error, "code") ?? "UNCLASSIFIED" : "UNCLASSIFIED");
23780
+ if (error?.retryable === true) {
23781
+ lines.push("Retryable:", "yes");
23782
+ const retryAfter = error.retry_after_ms;
23783
+ if (typeof retryAfter === "number") lines.push("Retry after:", `${retryAfter} ms`);
23784
+ }
23785
+ return `${lines.join("\n")}
23786
+ `;
23787
+ }
23746
23788
  if (proposalId) {
23747
23789
  lines.push("Proposal:", proposalId, "", "Replay:", replayResource || `synapsor://replay/replay_${proposalId}`, "");
23748
23790
  }
23791
+ const storeSuffix = topology.storeAuthority === "shared_postgres" ? ` --config ${topology.configPath} --store ${topology.storePath}` : ` --store ${topology.storePath}`;
23749
23792
  lines.push("Next:");
23750
- if (evidenceId) lines.push(` ${cliCommandName()} evidence show ${evidenceId} --store ${storePath}`);
23793
+ if (evidenceId) lines.push(` ${cliCommandName()} evidence show ${evidenceId}${storeSuffix}`);
23751
23794
  if (proposalId) {
23752
- lines.push(` ${cliCommandName()} proposals show ${proposalId} --store ${storePath}`);
23753
- lines.push(` ${cliCommandName()} proposals approve ${proposalId} --store ${storePath}`);
23754
- lines.push(` ${cliCommandName()} apply ${proposalId} --store ${storePath}`);
23755
- lines.push(` ${cliCommandName()} replay show --proposal ${proposalId} --store ${storePath}`);
23795
+ lines.push(` ${cliCommandName()} proposals show ${proposalId}${storeSuffix}`);
23796
+ lines.push(` ${cliCommandName()} proposals approve ${proposalId}${storeSuffix}`);
23797
+ lines.push(` ${cliCommandName()} apply ${proposalId}${storeSuffix}`);
23798
+ lines.push(` ${cliCommandName()} replay show --proposal ${proposalId}${storeSuffix}`);
23756
23799
  } else if (evidenceId) {
23757
- lines.push(` ${cliCommandName()} query-audit list --evidence ${evidenceId} --store ${storePath}`);
23800
+ lines.push(` ${cliCommandName()} query-audit list --evidence ${evidenceId}${storeSuffix}`);
23758
23801
  }
23759
23802
  return `${lines.join("\n")}
23760
23803
  `;
@@ -368,7 +368,14 @@ npx -y -p @synapsor/runner synapsor-runner smoke call --config ./synapsor.runner
368
368
 
369
369
  `smoke call` uses the same runtime as the MCP server. It records evidence and
370
370
  query audit for read tools, or creates a proposal for proposal tools, then
371
- prints the commands to inspect evidence/proposals/replay from the local store.
371
+ prints the commands to inspect evidence/proposals/replay. With the default
372
+ storage topology those records are in local SQLite. With
373
+ `storage.shared_postgres.mode = "runtime_store"`, Runner `1.4.12` and later
374
+ write them to the authoritative shared Postgres ledger and include `--config`
375
+ in follow-up commands. The supplied `--store` path is compatibility plumbing
376
+ for those CLI commands; it does not receive an orphan proposal copy. If the
377
+ shared ledger is unavailable, `smoke call` fails safely and does not fall back
378
+ to SQLite.
372
379
 
373
380
  The snippets contain the local command and args. They must not contain database
374
381
  URLs, passwords, approval tools, commit tools, or write credentials.
@@ -340,6 +340,16 @@ from `url_env`, auto-runs the shared-ledger migration, and serializes runtime
340
340
  mutations with a transaction-scoped advisory lock. The MCP tools still expose no
341
341
  database URLs or write credentials to the model.
342
342
 
343
+ `smoke call` uses the same runtime-store resolver as MCP serving. Its evidence,
344
+ query audit, proposal, events, and replay records therefore land directly in
345
+ the shared Postgres ledger and are immediately visible to another Runner using
346
+ the same config. `--store` remains accepted so the printed follow-up CLI
347
+ commands have a compatibility/bridge path, but that SQLite path is not an
348
+ authoritative copy and normally is not created by the smoke call. Shared-ledger
349
+ connection failure returns the safe availability error and a nonzero exit; it
350
+ never causes a local fallback. This consistency fix is present in Runner
351
+ `1.4.12` and later.
352
+
343
353
  `runtime_store` covers MCP serving, CLI approval/apply, and supervised worker
344
354
  runs. For CLI mutations, Runner restores the shared ledger into a temporary
345
355
  local store while holding the Postgres advisory lock, runs the existing local
@@ -10,6 +10,24 @@ 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.4.12 (prepared, not published)
14
+
15
+ ### Runtime-store smoke-call consistency
16
+
17
+ - Fixes BUG-017, where `smoke call` could put proposal artifacts in the
18
+ requested local SQLite path even though the config selected authoritative
19
+ shared Postgres `runtime_store` mode.
20
+ - Smoke calls now use the same runtime storage resolver as MCP tool calls. A
21
+ second Runner can immediately inspect the proposal, evidence, query audit,
22
+ events, and replay from the shared ledger.
23
+ - Shared-ledger unavailability fails closed with a safe retryable error,
24
+ nonzero exit status, no credential leakage, and no local orphan proposal.
25
+ - Local SQLite and mirror modes retain their existing behavior; no source row
26
+ changes before the normal external approval/apply path.
27
+
28
+ Prepared package version: `@synapsor/runner@1.4.12`.
29
+ `@synapsor/dsl` remains `1.4.1`; `@synapsor/spec` remains `1.4.0`.
30
+
13
31
  ## 1.4.1 (prepared, not published)
14
32
 
15
33
  ### Bounded-set digest compatibility patch
@@ -12,6 +12,26 @@ Use JSON for automation:
12
12
  npx -y -p @synapsor/runner synapsor-runner doctor --first-run --json
13
13
  ```
14
14
 
15
+ ## Smoke Proposal Missing From Another Runner
16
+
17
+ What happened:
18
+
19
+ `smoke call` returned a proposal id, but `proposals list --config ...` on a
20
+ second Runner cannot find it.
21
+
22
+ Fix:
23
+
24
+ 1. Verify `synapsor-runner --version` is `1.4.12` or later.
25
+ 2. Confirm both commands use a config whose
26
+ `storage.shared_postgres.mode` is `runtime_store` and whose `url_env` and
27
+ `schema` identify the same ledger.
28
+ 3. Run `store shared-postgres status --url-env <ENV> --schema <SCHEMA>`.
29
+
30
+ In `runtime_store` mode, `--store` is not the authoritative ledger. Runner does
31
+ not fall back to that SQLite path when shared Postgres is unavailable. Versions
32
+ before `1.4.12` could orphan smoke-call artifacts locally; recreate that test
33
+ proposal after upgrading.
34
+
15
35
  ## Docker Missing
16
36
 
17
37
  What happened:
@@ -64,6 +64,7 @@ INSERT INTO public.invoices (id, tenant_id, status, late_fee_cents, waiver_reaso
64
64
  ('INV-ACME', 'acme', 'overdue', 2500, NULL, '2026-07-12T12:00:00Z'),
65
65
  ('INV-GLOBEX', 'globex', 'overdue', 3100, NULL, '2026-07-12T12:00:00Z'),
66
66
  ('INV-RATECO', 'rateco', 'overdue', 900, NULL, '2026-07-12T12:00:00Z'),
67
+ ('INV-SMOKE-CALL', 'acme', 'overdue', 2100, NULL, '2026-07-12T12:00:00Z'),
67
68
  ('INV-BATCH-A', 'acme', 'overdue', 1100, NULL, '2026-07-12T12:00:00Z'),
68
69
  ('INV-BATCH-B', 'acme', 'overdue', 1300, NULL, '2026-07-12T12:00:00Z'),
69
70
  ('INV-QUORUM-RACE', 'acme', 'overdue', 1400, NULL, '2026-07-12T12:00:00Z'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synapsor/runner",
3
- "version": "1.4.1",
3
+ "version": "1.4.12",
4
4
  "description": "Stop giving AI agents execute_sql; expose reviewed Postgres/MySQL MCP actions with proposals, approval, writeback, and replay.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",