@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/dist/runner.mjs CHANGED
@@ -1196,13 +1196,89 @@ var ProposalStoreError = class extends Error {
1196
1196
  };
1197
1197
  var ProposalStore = class {
1198
1198
  db;
1199
+ path;
1199
1200
  constructor(path4 = ":memory:") {
1201
+ this.path = path4;
1200
1202
  this.db = new DatabaseSync(path4);
1201
1203
  this.migrate();
1202
1204
  }
1203
1205
  close() {
1204
1206
  this.db.close();
1205
1207
  }
1208
+ stats() {
1209
+ const pageCount = this.numberValue("PRAGMA page_count");
1210
+ const pageSize = this.numberValue("PRAGMA page_size");
1211
+ return {
1212
+ path: this.path,
1213
+ proposals: this.countTable("proposals"),
1214
+ evidence_bundles: this.countTable("evidence_bundles"),
1215
+ evidence_items: this.countTable("evidence_items"),
1216
+ query_audit: this.countTable("query_audit"),
1217
+ writeback_receipts: this.countTable("writeback_receipts"),
1218
+ writeback_jobs: this.countTable("writeback_jobs"),
1219
+ idempotency_receipts: this.countTable("idempotency_receipts"),
1220
+ replay_records: this.countTable("replay_records"),
1221
+ approvals: this.countTable("approvals"),
1222
+ proposal_events: this.countTable("proposal_events"),
1223
+ shadow_human_actions: this.countTable("shadow_human_actions"),
1224
+ page_count: pageCount,
1225
+ page_size: pageSize,
1226
+ approx_bytes: pageCount * pageSize
1227
+ };
1228
+ }
1229
+ vacuum() {
1230
+ this.db.exec("VACUUM");
1231
+ }
1232
+ pruneBefore(cutoffIso, options = {}) {
1233
+ const dryRun = options.dryRun !== false;
1234
+ const proposalIds = this.stringColumn("SELECT proposal_id FROM proposals WHERE created_at < ?", [cutoffIso], "proposal_id");
1235
+ const evidenceIds = this.evidenceIdsForPrune(cutoffIso, proposalIds);
1236
+ const deleted = {};
1237
+ const run = (table, where, params) => {
1238
+ deleted[table] = this.countWhere(table, where, params);
1239
+ if (!dryRun && deleted[table] > 0) this.db.prepare(`DELETE FROM ${table} WHERE ${where}`).run(...params);
1240
+ };
1241
+ const proposalWhere = inWhere("proposal_id", proposalIds);
1242
+ const evidenceWhere = inWhere("evidence_bundle_id", evidenceIds);
1243
+ this.transaction(() => {
1244
+ if (proposalWhere) {
1245
+ run("idempotency_receipts", proposalWhere.sql, proposalWhere.params);
1246
+ run("writeback_receipts", proposalWhere.sql, proposalWhere.params);
1247
+ run("writeback_jobs", proposalWhere.sql, proposalWhere.params);
1248
+ run("approvals", proposalWhere.sql, proposalWhere.params);
1249
+ run("proposal_events", proposalWhere.sql, proposalWhere.params);
1250
+ run("shadow_human_actions", proposalWhere.sql, proposalWhere.params);
1251
+ run("replay_records", proposalWhere.sql, proposalWhere.params);
1252
+ } else {
1253
+ for (const table of ["idempotency_receipts", "writeback_receipts", "writeback_jobs", "approvals", "proposal_events", "shadow_human_actions", "replay_records"]) {
1254
+ deleted[table] = 0;
1255
+ }
1256
+ }
1257
+ const auditClauses = [];
1258
+ const auditParams = [];
1259
+ if (proposalWhere) {
1260
+ auditClauses.push(proposalWhere.sql);
1261
+ auditParams.push(...proposalWhere.params);
1262
+ }
1263
+ if (evidenceWhere) {
1264
+ auditClauses.push(evidenceWhere.sql);
1265
+ auditParams.push(...evidenceWhere.params);
1266
+ }
1267
+ auditClauses.push("(proposal_id IS NULL AND evidence_bundle_id IS NULL AND created_at < ?)");
1268
+ auditParams.push(cutoffIso);
1269
+ run("query_audit", auditClauses.map((clause) => `(${clause})`).join(" OR "), auditParams);
1270
+ if (evidenceWhere) {
1271
+ run("evidence_items", evidenceWhere.sql, evidenceWhere.params);
1272
+ run("evidence_bundles", evidenceWhere.sql, evidenceWhere.params);
1273
+ } else {
1274
+ deleted.evidence_items = 0;
1275
+ deleted.evidence_bundles = 0;
1276
+ }
1277
+ if (proposalWhere) run("proposals", proposalWhere.sql, proposalWhere.params);
1278
+ else deleted.proposals = 0;
1279
+ });
1280
+ return { cutoff: cutoffIso, dry_run: dryRun, deleted };
1281
+ }
1206
1282
  migrate() {
1207
1283
  this.db.exec(`
1208
1284
  PRAGMA foreign_keys = ON;
@@ -1353,6 +1429,154 @@ var ProposalStore = class {
1353
1429
  INSERT OR IGNORE INTO proposal_store_schema(version, applied_at)
1354
1430
  VALUES (1, datetime('now'));
1355
1431
  `);
1432
+ this.ensureSearchColumns();
1433
+ this.backfillSearchColumns();
1434
+ this.ensureSearchIndexes();
1435
+ }
1436
+ ensureSearchColumns() {
1437
+ this.ensureColumn("proposals", "principal", "TEXT");
1438
+ this.ensureColumn("proposals", "capability", "TEXT");
1439
+ this.ensureColumn("proposals", "interaction_id", "TEXT");
1440
+ this.ensureColumn("proposals", "tool_call_id", "TEXT");
1441
+ this.ensureColumn("evidence_bundles", "principal", "TEXT");
1442
+ this.ensureColumn("evidence_bundles", "capability", "TEXT");
1443
+ this.ensureColumn("evidence_bundles", "source_id", "TEXT");
1444
+ this.ensureColumn("evidence_bundles", "source_table", "TEXT");
1445
+ this.ensureColumn("evidence_bundles", "business_object", "TEXT");
1446
+ this.ensureColumn("evidence_bundles", "object_id", "TEXT");
1447
+ this.ensureColumn("evidence_bundles", "query_fingerprint", "TEXT");
1448
+ this.ensureColumn("query_audit", "tenant_id", "TEXT");
1449
+ this.ensureColumn("query_audit", "principal", "TEXT");
1450
+ this.ensureColumn("query_audit", "capability", "TEXT");
1451
+ this.ensureColumn("query_audit", "business_object", "TEXT");
1452
+ this.ensureColumn("query_audit", "object_id", "TEXT");
1453
+ this.ensureColumn("query_audit", "primary_key_value", "TEXT");
1454
+ }
1455
+ ensureSearchIndexes() {
1456
+ this.db.exec(`
1457
+ CREATE INDEX IF NOT EXISTS idx_proposals_tenant_created ON proposals(tenant_id, created_at);
1458
+ CREATE INDEX IF NOT EXISTS idx_proposals_action_created ON proposals(action, created_at);
1459
+ CREATE INDEX IF NOT EXISTS idx_proposals_capability_created ON proposals(capability, created_at);
1460
+ CREATE INDEX IF NOT EXISTS idx_proposals_principal_created ON proposals(principal, created_at);
1461
+ CREATE INDEX IF NOT EXISTS idx_proposals_object_created ON proposals(business_object, object_id, created_at);
1462
+ CREATE INDEX IF NOT EXISTS idx_proposals_state_created ON proposals(state, created_at);
1463
+ CREATE INDEX IF NOT EXISTS idx_proposals_source_table_created ON proposals(source_id, source_table, created_at);
1464
+
1465
+ CREATE INDEX IF NOT EXISTS idx_evidence_bundles_tenant_created ON evidence_bundles(tenant_id, created_at);
1466
+ CREATE INDEX IF NOT EXISTS idx_evidence_bundles_proposal_id ON evidence_bundles(proposal_id);
1467
+ CREATE INDEX IF NOT EXISTS idx_evidence_bundles_created ON evidence_bundles(created_at);
1468
+ CREATE INDEX IF NOT EXISTS idx_evidence_bundles_capability_created ON evidence_bundles(capability, created_at);
1469
+ CREATE INDEX IF NOT EXISTS idx_evidence_bundles_principal_created ON evidence_bundles(principal, created_at);
1470
+ CREATE INDEX IF NOT EXISTS idx_evidence_bundles_object_created ON evidence_bundles(business_object, object_id, created_at);
1471
+ CREATE INDEX IF NOT EXISTS idx_evidence_bundles_source_table_created ON evidence_bundles(source_id, source_table, created_at);
1472
+ CREATE INDEX IF NOT EXISTS idx_evidence_bundles_fingerprint_created ON evidence_bundles(query_fingerprint, created_at);
1473
+
1474
+ CREATE INDEX IF NOT EXISTS idx_evidence_items_bundle_id ON evidence_items(evidence_bundle_id);
1475
+
1476
+ CREATE INDEX IF NOT EXISTS idx_query_audit_evidence_id ON query_audit(evidence_bundle_id);
1477
+ CREATE INDEX IF NOT EXISTS idx_query_audit_source_table_created ON query_audit(source_id, table_name, created_at);
1478
+ CREATE INDEX IF NOT EXISTS idx_query_audit_fingerprint_created ON query_audit(query_fingerprint, created_at);
1479
+ CREATE INDEX IF NOT EXISTS idx_query_audit_created ON query_audit(created_at);
1480
+ CREATE INDEX IF NOT EXISTS idx_query_audit_tenant_created ON query_audit(tenant_id, created_at);
1481
+ CREATE INDEX IF NOT EXISTS idx_query_audit_capability_created ON query_audit(capability, created_at);
1482
+ CREATE INDEX IF NOT EXISTS idx_query_audit_principal_created ON query_audit(principal, created_at);
1483
+ CREATE INDEX IF NOT EXISTS idx_query_audit_object_created ON query_audit(business_object, object_id, created_at);
1484
+ CREATE INDEX IF NOT EXISTS idx_query_audit_primary_key_created ON query_audit(primary_key_value, created_at);
1485
+
1486
+ CREATE INDEX IF NOT EXISTS idx_writeback_receipts_writeback_job ON writeback_receipts(writeback_job_id);
1487
+ CREATE INDEX IF NOT EXISTS idx_writeback_receipts_idempotency_key ON writeback_receipts(idempotency_key);
1488
+ CREATE INDEX IF NOT EXISTS idx_writeback_receipts_status_created ON writeback_receipts(status, created_at);
1489
+
1490
+ CREATE INDEX IF NOT EXISTS idx_replay_records_created ON replay_records(created_at);
1491
+
1492
+ CREATE INDEX IF NOT EXISTS idx_approvals_proposal_id ON approvals(proposal_id);
1493
+ CREATE INDEX IF NOT EXISTS idx_approvals_status_created ON approvals(status, created_at);
1494
+
1495
+ CREATE INDEX IF NOT EXISTS idx_proposal_events_kind_created ON proposal_events(kind, created_at);
1496
+ `);
1497
+ }
1498
+ ensureColumn(table, column, definition) {
1499
+ const columns = this.db.prepare(`PRAGMA table_info(${table})`).all();
1500
+ if (columns.some((row) => isRecord2(row) && row.name === column)) return;
1501
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
1502
+ }
1503
+ backfillSearchColumns() {
1504
+ const proposals2 = this.db.prepare("SELECT proposal_id, action, change_set_json FROM proposals").all();
1505
+ for (const row of proposals2) {
1506
+ if (!isRecord2(row)) continue;
1507
+ try {
1508
+ const changeSet = parseChangeSet(JSON.parse(String(row.change_set_json)));
1509
+ this.db.prepare("UPDATE proposals SET principal = COALESCE(principal, ?), capability = COALESCE(capability, ?) WHERE proposal_id = ?").run(changeSet.principal.id, changeSet.action, String(row.proposal_id));
1510
+ } catch {
1511
+ }
1512
+ }
1513
+ const evidenceRows = this.db.prepare("SELECT evidence_bundle_id, proposal_id, payload_json FROM evidence_bundles").all();
1514
+ for (const row of evidenceRows) {
1515
+ if (!isRecord2(row)) continue;
1516
+ const proposal = row.proposal_id == null ? void 0 : this.getProposal(String(row.proposal_id));
1517
+ let payload = {};
1518
+ try {
1519
+ payload = JSON.parse(String(row.payload_json));
1520
+ } catch {
1521
+ payload = {};
1522
+ }
1523
+ const metadata = this.evidenceMetadata({
1524
+ proposal,
1525
+ payload,
1526
+ items: this.evidenceItems(String(row.evidence_bundle_id)).map((item) => item.item)
1527
+ });
1528
+ this.db.prepare(`
1529
+ UPDATE evidence_bundles
1530
+ SET principal = COALESCE(principal, ?),
1531
+ capability = COALESCE(capability, ?),
1532
+ source_id = COALESCE(source_id, ?),
1533
+ source_table = COALESCE(source_table, ?),
1534
+ business_object = COALESCE(business_object, ?),
1535
+ object_id = COALESCE(object_id, ?),
1536
+ query_fingerprint = COALESCE(query_fingerprint, ?)
1537
+ WHERE evidence_bundle_id = ?
1538
+ `).run(
1539
+ metadata.principal ?? null,
1540
+ metadata.capability ?? null,
1541
+ metadata.source_id ?? null,
1542
+ metadata.source_table ?? null,
1543
+ metadata.business_object ?? null,
1544
+ metadata.object_id ?? null,
1545
+ metadata.query_fingerprint ?? null,
1546
+ String(row.evidence_bundle_id)
1547
+ );
1548
+ }
1549
+ const auditRows = this.db.prepare("SELECT audit_id, proposal_id, evidence_bundle_id, payload_json FROM query_audit").all();
1550
+ for (const row of auditRows) {
1551
+ if (!isRecord2(row)) continue;
1552
+ const proposal = row.proposal_id == null ? void 0 : this.getProposal(String(row.proposal_id));
1553
+ const evidence2 = row.evidence_bundle_id == null ? void 0 : this.getEvidenceBundle(String(row.evidence_bundle_id));
1554
+ let payload = {};
1555
+ try {
1556
+ payload = JSON.parse(String(row.payload_json));
1557
+ } catch {
1558
+ payload = {};
1559
+ }
1560
+ const metadata = this.queryAuditMetadata({ proposal, evidence: evidence2, payload });
1561
+ this.db.prepare(`
1562
+ UPDATE query_audit
1563
+ SET tenant_id = COALESCE(tenant_id, ?),
1564
+ principal = COALESCE(principal, ?),
1565
+ capability = COALESCE(capability, ?),
1566
+ business_object = COALESCE(business_object, ?),
1567
+ object_id = COALESCE(object_id, ?),
1568
+ primary_key_value = COALESCE(primary_key_value, ?)
1569
+ WHERE audit_id = ?
1570
+ `).run(
1571
+ metadata.tenant_id ?? null,
1572
+ metadata.principal ?? null,
1573
+ metadata.capability ?? null,
1574
+ metadata.business_object ?? null,
1575
+ metadata.object_id ?? null,
1576
+ metadata.primary_key_value ?? null,
1577
+ Number(row.audit_id)
1578
+ );
1579
+ }
1356
1580
  }
1357
1581
  createProposal(input) {
1358
1582
  const changeSet = parseChangeSet(input);
@@ -1377,6 +1601,10 @@ var ProposalStore = class {
1377
1601
  action,
1378
1602
  state,
1379
1603
  tenant_id,
1604
+ principal,
1605
+ capability,
1606
+ interaction_id,
1607
+ tool_call_id,
1380
1608
  business_object,
1381
1609
  object_id,
1382
1610
  source_kind,
@@ -1387,7 +1615,7 @@ var ProposalStore = class {
1387
1615
  change_set_json,
1388
1616
  created_at,
1389
1617
  updated_at
1390
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1618
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1391
1619
  `);
1392
1620
  this.transaction(() => {
1393
1621
  insert.run(
@@ -1397,6 +1625,10 @@ var ProposalStore = class {
1397
1625
  changeSet.action,
1398
1626
  state,
1399
1627
  changeSet.scope.tenant_id,
1628
+ changeSet.principal.id,
1629
+ changeSet.action,
1630
+ null,
1631
+ null,
1400
1632
  changeSet.scope.business_object,
1401
1633
  changeSet.scope.object_id,
1402
1634
  changeSet.source.kind,
@@ -1424,10 +1656,44 @@ var ProposalStore = class {
1424
1656
  const row = this.db.prepare("SELECT * FROM proposals WHERE proposal_id = ?").get(proposalId);
1425
1657
  return rowToProposal(row);
1426
1658
  }
1427
- listProposals(state) {
1428
- const rows = state ? this.db.prepare("SELECT * FROM proposals WHERE state = ? ORDER BY created_at DESC").all(state) : this.db.prepare("SELECT * FROM proposals ORDER BY created_at DESC").all();
1659
+ listProposals(filters) {
1660
+ if (typeof filters === "string") filters = { state: filters };
1661
+ const query = buildProposalQuery(filters ?? {});
1662
+ const rows = this.db.prepare(query.sql).all(...query.params);
1429
1663
  return rows.map((row) => rowToProposal(row)).filter((proposal) => proposal !== void 0);
1430
1664
  }
1665
+ listEvidenceBundles(filters = {}) {
1666
+ const query = buildEvidenceQuery(filters);
1667
+ const rows = this.db.prepare(query.sql).all(...query.params);
1668
+ return rows.map((row) => this.rowToEvidenceBundle(row)).filter((evidence2) => evidence2 !== void 0);
1669
+ }
1670
+ listQueryAudit(filters = {}) {
1671
+ const query = buildQueryAuditQuery(filters);
1672
+ const rows = this.db.prepare(query.sql).all(...query.params);
1673
+ return rows.map(rowToQueryAudit).filter((record) => record !== void 0);
1674
+ }
1675
+ getQueryAudit(auditId) {
1676
+ return rowToQueryAudit(this.db.prepare("SELECT * FROM query_audit WHERE audit_id = ?").get(auditId));
1677
+ }
1678
+ listReceipts(filters = {}) {
1679
+ const query = buildReceiptQuery(filters);
1680
+ const rows = this.db.prepare(query.sql).all(...query.params);
1681
+ return rows.map(rowToReceipt).filter((receipt) => receipt !== void 0);
1682
+ }
1683
+ getReceipt(receiptId) {
1684
+ return rowToReceipt(this.db.prepare("SELECT * FROM writeback_receipts WHERE receipt_id = ?").get(receiptId));
1685
+ }
1686
+ getReplayByReplayId(replayId) {
1687
+ const prefix = "replay_";
1688
+ const proposalId = replayId.startsWith(prefix) ? replayId.slice(prefix.length) : replayId;
1689
+ return this.replay(proposalId);
1690
+ }
1691
+ proposalIdForEvidence(evidenceBundleId) {
1692
+ const evidence2 = this.getEvidenceBundle(evidenceBundleId);
1693
+ if (evidence2?.proposal_id) return evidence2.proposal_id;
1694
+ const row = this.db.prepare("SELECT proposal_id FROM query_audit WHERE evidence_bundle_id = ? AND proposal_id IS NOT NULL ORDER BY created_at DESC LIMIT 1").get(evidenceBundleId);
1695
+ return isRecord2(row) && row.proposal_id != null ? String(row.proposal_id) : void 0;
1696
+ }
1431
1697
  approveProposal(proposalId, options) {
1432
1698
  const proposal = this.requireProposal(proposalId);
1433
1699
  assertWritebackAllowed(proposal, "approved");
@@ -1691,17 +1957,38 @@ var ProposalStore = class {
1691
1957
  recordEvidenceBundle(input) {
1692
1958
  assertNoSecretMaterial({ payload: input.payload, items: input.items ?? [] }, "evidence_bundle");
1693
1959
  const now = (/* @__PURE__ */ new Date()).toISOString();
1694
- if (input.proposal_id) this.requireProposal(input.proposal_id);
1960
+ const proposal = input.proposal_id ? this.requireProposal(input.proposal_id) : void 0;
1961
+ const metadata = this.evidenceMetadata({ proposal, payload: input.payload, items: input.items ?? [] });
1695
1962
  this.transaction(() => {
1696
1963
  this.db.prepare(`
1697
1964
  INSERT OR REPLACE INTO evidence_bundles (
1698
1965
  evidence_bundle_id,
1699
1966
  proposal_id,
1700
1967
  tenant_id,
1968
+ principal,
1969
+ capability,
1970
+ source_id,
1971
+ source_table,
1972
+ business_object,
1973
+ object_id,
1974
+ query_fingerprint,
1701
1975
  payload_json,
1702
1976
  created_at
1703
- ) VALUES (?, ?, ?, ?, ?)
1704
- `).run(input.evidence_bundle_id, input.proposal_id ?? null, input.tenant_id, JSON.stringify(input.payload), now);
1977
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1978
+ `).run(
1979
+ input.evidence_bundle_id,
1980
+ input.proposal_id ?? null,
1981
+ input.tenant_id,
1982
+ metadata.principal ?? null,
1983
+ metadata.capability ?? null,
1984
+ metadata.source_id ?? null,
1985
+ metadata.source_table ?? null,
1986
+ metadata.business_object ?? null,
1987
+ metadata.object_id ?? null,
1988
+ metadata.query_fingerprint ?? null,
1989
+ JSON.stringify(input.payload),
1990
+ now
1991
+ );
1705
1992
  for (const item of input.items ?? []) {
1706
1993
  this.db.prepare(`
1707
1994
  INSERT INTO evidence_items (evidence_bundle_id, item_json, created_at)
@@ -1719,21 +2006,35 @@ var ProposalStore = class {
1719
2006
  recordQueryAudit(input) {
1720
2007
  assertNoSecretMaterial(input.payload, "query_audit");
1721
2008
  const now = (/* @__PURE__ */ new Date()).toISOString();
1722
- if (input.proposal_id) this.requireProposal(input.proposal_id);
2009
+ const proposal = input.proposal_id ? this.requireProposal(input.proposal_id) : void 0;
2010
+ const evidence2 = input.evidence_bundle_id ? this.getEvidenceBundle(input.evidence_bundle_id) : void 0;
2011
+ const metadata = this.queryAuditMetadata({ proposal, evidence: evidence2, payload: input.payload });
1723
2012
  this.db.prepare(`
1724
2013
  INSERT INTO query_audit (
1725
2014
  proposal_id,
1726
2015
  evidence_bundle_id,
2016
+ tenant_id,
2017
+ principal,
2018
+ capability,
2019
+ business_object,
2020
+ object_id,
2021
+ primary_key_value,
1727
2022
  source_id,
1728
2023
  query_fingerprint,
1729
2024
  table_name,
1730
2025
  row_count,
1731
2026
  payload_json,
1732
2027
  created_at
1733
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2028
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1734
2029
  `).run(
1735
2030
  input.proposal_id ?? null,
1736
2031
  input.evidence_bundle_id ?? null,
2032
+ metadata.tenant_id ?? null,
2033
+ metadata.principal ?? null,
2034
+ metadata.capability ?? null,
2035
+ metadata.business_object ?? null,
2036
+ metadata.object_id ?? null,
2037
+ metadata.primary_key_value ?? null,
1737
2038
  input.source_id,
1738
2039
  input.query_fingerprint,
1739
2040
  input.table_name,
@@ -1744,14 +2045,24 @@ var ProposalStore = class {
1744
2045
  }
1745
2046
  getEvidenceBundle(evidenceBundleId) {
1746
2047
  const row = this.db.prepare("SELECT * FROM evidence_bundles WHERE evidence_bundle_id = ?").get(evidenceBundleId);
2048
+ return this.rowToEvidenceBundle(row);
2049
+ }
2050
+ rowToEvidenceBundle(row) {
1747
2051
  if (!isRecord2(row)) return void 0;
1748
2052
  return {
1749
2053
  evidence_bundle_id: String(row.evidence_bundle_id),
1750
2054
  proposal_id: row.proposal_id == null ? void 0 : String(row.proposal_id),
1751
2055
  tenant_id: String(row.tenant_id),
2056
+ principal: row.principal == null ? void 0 : String(row.principal),
2057
+ capability: row.capability == null ? void 0 : String(row.capability),
2058
+ source_id: row.source_id == null ? void 0 : String(row.source_id),
2059
+ source_table: row.source_table == null ? void 0 : String(row.source_table),
2060
+ business_object: row.business_object == null ? void 0 : String(row.business_object),
2061
+ object_id: row.object_id == null ? void 0 : String(row.object_id),
2062
+ query_fingerprint: row.query_fingerprint == null ? void 0 : String(row.query_fingerprint),
1752
2063
  payload: JSON.parse(String(row.payload_json)),
1753
- items: this.evidenceItems(evidenceBundleId),
1754
- query_audit: this.queryAuditByEvidence(evidenceBundleId),
2064
+ items: this.evidenceItems(String(row.evidence_bundle_id)),
2065
+ query_audit: this.queryAuditByEvidence(String(row.evidence_bundle_id)),
1755
2066
  created_at: String(row.created_at)
1756
2067
  };
1757
2068
  }
@@ -1896,6 +2207,8 @@ var ProposalStore = class {
1896
2207
  proposal_id: row.proposal_id == null ? void 0 : String(row.proposal_id),
1897
2208
  tenant_id: String(row.tenant_id),
1898
2209
  payload: JSON.parse(String(row.payload_json)),
2210
+ items: this.evidenceItems(String(row.evidence_bundle_id)),
2211
+ query_audit: this.queryAuditByEvidence(String(row.evidence_bundle_id)),
1899
2212
  created_at: String(row.created_at)
1900
2213
  });
1901
2214
  }
@@ -1915,6 +2228,81 @@ var ProposalStore = class {
1915
2228
  }
1916
2229
  return records;
1917
2230
  }
2231
+ evidenceMetadata(input) {
2232
+ if (input.proposal) {
2233
+ return {
2234
+ principal: input.proposal.change_set.principal.id,
2235
+ capability: input.proposal.action,
2236
+ source_id: input.proposal.source_id,
2237
+ source_table: `${input.proposal.source_schema}.${input.proposal.source_table}`,
2238
+ business_object: input.proposal.business_object,
2239
+ object_id: input.proposal.object_id,
2240
+ query_fingerprint: input.proposal.change_set.evidence.query_fingerprint
2241
+ };
2242
+ }
2243
+ const firstItem = input.items.find(isRecord2);
2244
+ const primaryKey = isRecord2(firstItem?.primary_key) ? firstItem.primary_key : void 0;
2245
+ const principal = stringFromPrincipal(input.payload.principal);
2246
+ const table = stringFromUnknown(input.payload.target) ?? stringFromUnknown(firstItem?.table);
2247
+ return {
2248
+ principal,
2249
+ capability: stringFromUnknown(input.payload.capability),
2250
+ source_id: stringFromUnknown(input.payload.source_id) ?? stringFromUnknown(firstItem?.source_id),
2251
+ source_table: table,
2252
+ business_object: table ? lastIdentifier(table) : void 0,
2253
+ object_id: primaryKey ? stringFromUnknown(primaryKey.value) : void 0,
2254
+ query_fingerprint: stringFromUnknown(input.payload.query_fingerprint)
2255
+ };
2256
+ }
2257
+ queryAuditMetadata(input) {
2258
+ if (input.proposal) {
2259
+ return {
2260
+ tenant_id: input.proposal.tenant_id,
2261
+ principal: input.proposal.change_set.principal.id,
2262
+ capability: input.proposal.action,
2263
+ business_object: input.proposal.business_object,
2264
+ object_id: input.proposal.object_id,
2265
+ primary_key_value: String(input.proposal.change_set.source.primary_key.value)
2266
+ };
2267
+ }
2268
+ const firstItem = input.evidence?.items.find((item) => isRecord2(item.item))?.item;
2269
+ const primaryKey = isRecord2(firstItem?.primary_key) ? firstItem.primary_key : void 0;
2270
+ return {
2271
+ tenant_id: input.evidence?.tenant_id ?? stringFromUnknown(input.payload.tenant_id),
2272
+ principal: input.evidence?.principal ?? stringFromPrincipal(input.payload.principal),
2273
+ capability: input.evidence?.capability ?? stringFromUnknown(input.payload.capability),
2274
+ business_object: input.evidence?.business_object ?? stringFromUnknown(input.payload.business_object),
2275
+ object_id: input.evidence?.object_id ?? stringFromUnknown(input.payload.object_id),
2276
+ primary_key_value: primaryKey ? stringFromUnknown(primaryKey.value) : input.evidence?.object_id ?? stringFromUnknown(input.payload.primary_key_value)
2277
+ };
2278
+ }
2279
+ countTable(table) {
2280
+ return this.countWhere(table, "1 = 1", []);
2281
+ }
2282
+ countWhere(table, where, params) {
2283
+ const row = this.db.prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE ${where}`).get(...params);
2284
+ return isRecord2(row) ? Number(row.count ?? 0) : 0;
2285
+ }
2286
+ numberValue(sql) {
2287
+ const row = this.db.prepare(sql).get();
2288
+ if (!isRecord2(row)) return 0;
2289
+ const value = Object.values(row)[0];
2290
+ return typeof value === "number" ? value : Number(value ?? 0);
2291
+ }
2292
+ stringColumn(sql, params, column) {
2293
+ return this.db.prepare(sql).all(...params).map((row) => isRecord2(row) ? row[column] : void 0).filter((value) => typeof value === "string" || typeof value === "number").map(String);
2294
+ }
2295
+ evidenceIdsForPrune(cutoffIso, proposalIds) {
2296
+ const proposalWhere = inWhere("proposal_id", proposalIds);
2297
+ if (!proposalWhere) {
2298
+ return this.stringColumn("SELECT evidence_bundle_id FROM evidence_bundles WHERE created_at < ?", [cutoffIso], "evidence_bundle_id");
2299
+ }
2300
+ return this.stringColumn(
2301
+ `SELECT evidence_bundle_id FROM evidence_bundles WHERE created_at < ? OR ${proposalWhere.sql}`,
2302
+ [cutoffIso, ...proposalWhere.params],
2303
+ "evidence_bundle_id"
2304
+ );
2305
+ }
1918
2306
  transaction(fn) {
1919
2307
  this.db.exec("BEGIN IMMEDIATE");
1920
2308
  try {
@@ -1933,6 +2321,133 @@ function stateFromChangeSet(changeSet) {
1933
2321
  if (changeSet.approval.status === "canceled") return "canceled";
1934
2322
  return "pending_review";
1935
2323
  }
2324
+ function inWhere(column, values) {
2325
+ if (values.length === 0) return void 0;
2326
+ return {
2327
+ sql: `${column} IN (${values.map(() => "?").join(", ")})`,
2328
+ params: values
2329
+ };
2330
+ }
2331
+ function buildProposalQuery(filters) {
2332
+ const clauses = [];
2333
+ const params = [];
2334
+ addEqual(clauses, params, "proposal_id", filters.proposal);
2335
+ addEqual(clauses, params, "tenant_id", filters.tenant);
2336
+ addEqual(clauses, params, "principal", filters.principal);
2337
+ addEqual(clauses, params, "source_id", filters.source);
2338
+ addTableFilter(clauses, params, "source_table", filters.table);
2339
+ addEqual(clauses, params, "state", filters.status ?? filters.state);
2340
+ addEqual(clauses, params, "action", filters.capability ?? filters.action);
2341
+ addObjectFilter(clauses, params, "business_object", "source_table", "object_id", filters.objectType, filters.objectId);
2342
+ addTimeRange(clauses, params, "created_at", filters.from, filters.to);
2343
+ return finishQuery("SELECT * FROM proposals", clauses, params, filters.limit);
2344
+ }
2345
+ function buildEvidenceQuery(filters) {
2346
+ const clauses = [];
2347
+ const params = [];
2348
+ addEqual(clauses, params, "evidence_bundle_id", filters.evidence);
2349
+ addEqual(clauses, params, "tenant_id", filters.tenant);
2350
+ addEqual(clauses, params, "principal", filters.principal);
2351
+ addEqual(clauses, params, "capability", filters.capability);
2352
+ addEqual(clauses, params, "proposal_id", filters.proposal);
2353
+ addEqual(clauses, params, "source_id", filters.source);
2354
+ addTableFilter(clauses, params, "source_table", filters.table);
2355
+ addEqual(clauses, params, "query_fingerprint", filters.queryFingerprint);
2356
+ addObjectFilter(clauses, params, "business_object", "source_table", "object_id", filters.objectType, filters.objectId);
2357
+ addTimeRange(clauses, params, "created_at", filters.from, filters.to);
2358
+ return finishQuery("SELECT * FROM evidence_bundles", clauses, params, filters.limit);
2359
+ }
2360
+ function buildQueryAuditQuery(filters) {
2361
+ const clauses = [];
2362
+ const params = [];
2363
+ addEqual(clauses, params, "tenant_id", filters.tenant);
2364
+ addEqual(clauses, params, "principal", filters.principal);
2365
+ addEqual(clauses, params, "capability", filters.capability);
2366
+ addEqual(clauses, params, "proposal_id", filters.proposal);
2367
+ addEqual(clauses, params, "evidence_bundle_id", filters.evidence);
2368
+ addEqual(clauses, params, "source_id", filters.source);
2369
+ addTableFilter(clauses, params, "table_name", filters.table);
2370
+ addObjectFilter(clauses, params, "business_object", "table_name", "object_id", filters.objectType, filters.objectId);
2371
+ addEqual(clauses, params, "primary_key_value", filters.primaryKey);
2372
+ addEqual(clauses, params, "query_fingerprint", filters.queryFingerprint);
2373
+ addTimeRange(clauses, params, "created_at", filters.from, filters.to);
2374
+ return finishQuery("SELECT * FROM query_audit", clauses, params, filters.limit);
2375
+ }
2376
+ function buildReceiptQuery(filters) {
2377
+ const clauses = [];
2378
+ const params = [];
2379
+ addEqual(clauses, params, "receipt_id", filters.receipt);
2380
+ addEqual(clauses, params, "proposal_id", filters.proposal);
2381
+ addEqual(clauses, params, "writeback_job_id", filters.writebackJob);
2382
+ addEqual(clauses, params, "idempotency_key", filters.idempotencyKey);
2383
+ addEqual(clauses, params, "status", filters.status);
2384
+ addTimeRange(clauses, params, "created_at", filters.from, filters.to);
2385
+ return finishQuery("SELECT * FROM writeback_receipts", clauses, params, filters.limit);
2386
+ }
2387
+ function addEqual(clauses, params, column, value) {
2388
+ if (!value) return;
2389
+ clauses.push(`${column} = ?`);
2390
+ params.push(value);
2391
+ }
2392
+ function addTableFilter(clauses, params, column, value) {
2393
+ if (!value) return;
2394
+ if (value.includes(".")) {
2395
+ clauses.push(`${column} = ?`);
2396
+ params.push(value);
2397
+ return;
2398
+ }
2399
+ clauses.push(`(${column} = ? OR ${column} = ?)`);
2400
+ params.push(value, `public.${value}`);
2401
+ }
2402
+ function addObjectFilter(clauses, params, typeColumn, tableColumn, idColumn, objectType, objectId) {
2403
+ if (objectId) {
2404
+ clauses.push(`${idColumn} = ?`);
2405
+ params.push(objectId);
2406
+ }
2407
+ if (!objectType) return;
2408
+ const variants = objectTypeVariants(objectType);
2409
+ const placeholders = variants.map(() => "?").join(", ");
2410
+ clauses.push(`(${typeColumn} IN (${placeholders}) OR ${tableColumn} IN (${placeholders}))`);
2411
+ params.push(...variants, ...variants);
2412
+ }
2413
+ function addTimeRange(clauses, params, column, from, to) {
2414
+ if (from) {
2415
+ clauses.push(`${column} >= ?`);
2416
+ params.push(from);
2417
+ }
2418
+ if (to) {
2419
+ clauses.push(`${column} <= ?`);
2420
+ params.push(to);
2421
+ }
2422
+ }
2423
+ function finishQuery(base, clauses, params, limit) {
2424
+ const where = clauses.length ? ` WHERE ${clauses.join(" AND ")}` : "";
2425
+ const sql = `${base}${where} ORDER BY created_at DESC${limit ? " LIMIT ?" : ""}`;
2426
+ return { sql, params: limit ? [...params, limit] : params };
2427
+ }
2428
+ function objectTypeVariants(value) {
2429
+ const variants = /* @__PURE__ */ new Set([value]);
2430
+ if (value.endsWith("s")) variants.add(value.slice(0, -1));
2431
+ else variants.add(`${value}s`);
2432
+ for (const variant of [...variants]) {
2433
+ if (!variant.includes(".")) variants.add(`public.${variant}`);
2434
+ }
2435
+ return [...variants];
2436
+ }
2437
+ function stringFromUnknown(value) {
2438
+ if (typeof value === "string" && value.trim()) return value;
2439
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
2440
+ return void 0;
2441
+ }
2442
+ function stringFromPrincipal(value) {
2443
+ if (typeof value === "string") return value;
2444
+ if (isRecord2(value)) return stringFromUnknown(value.id);
2445
+ return void 0;
2446
+ }
2447
+ function lastIdentifier(value) {
2448
+ const parts = value.split(".");
2449
+ return parts[parts.length - 1] ?? value;
2450
+ }
1936
2451
  function stateFromReceipt(receipt) {
1937
2452
  if (receipt.status === "applied" || receipt.status === "already_applied") return "applied";
1938
2453
  if (receipt.status === "conflict") return "conflict";
@@ -2007,6 +2522,10 @@ function rowToProposal(row) {
2007
2522
  action: String(row.action),
2008
2523
  state: String(row.state),
2009
2524
  tenant_id: String(row.tenant_id),
2525
+ principal: row.principal == null ? void 0 : String(row.principal),
2526
+ capability: row.capability == null ? void 0 : String(row.capability),
2527
+ interaction_id: row.interaction_id == null ? void 0 : String(row.interaction_id),
2528
+ tool_call_id: row.tool_call_id == null ? void 0 : String(row.tool_call_id),
2010
2529
  business_object: String(row.business_object),
2011
2530
  object_id: String(row.object_id),
2012
2531
  source_kind: String(row.source_kind),
@@ -2033,6 +2552,7 @@ function rowToEvent(row) {
2033
2552
  function rowToReceipt(row) {
2034
2553
  if (!isRecord2(row)) return void 0;
2035
2554
  return {
2555
+ receipt_id: Number(row.receipt_id),
2036
2556
  writeback_job_id: String(row.writeback_job_id),
2037
2557
  proposal_id: String(row.proposal_id),
2038
2558
  runner_id: String(row.runner_id),
@@ -2049,6 +2569,12 @@ function rowToQueryAudit(row) {
2049
2569
  audit_id: Number(row.audit_id),
2050
2570
  proposal_id: row.proposal_id == null ? void 0 : String(row.proposal_id),
2051
2571
  evidence_bundle_id: row.evidence_bundle_id == null ? void 0 : String(row.evidence_bundle_id),
2572
+ tenant_id: row.tenant_id == null ? void 0 : String(row.tenant_id),
2573
+ principal: row.principal == null ? void 0 : String(row.principal),
2574
+ capability: row.capability == null ? void 0 : String(row.capability),
2575
+ business_object: row.business_object == null ? void 0 : String(row.business_object),
2576
+ object_id: row.object_id == null ? void 0 : String(row.object_id),
2577
+ primary_key_value: row.primary_key_value == null ? void 0 : String(row.primary_key_value),
2052
2578
  source_id: String(row.source_id),
2053
2579
  query_fingerprint: String(row.query_fingerprint),
2054
2580
  table_name: String(row.table_name),
@@ -2158,7 +2684,7 @@ function createMcpRuntime(config, options = {}) {
2158
2684
  }
2159
2685
  function createSynapsorMcpServer(runtime) {
2160
2686
  const server = new McpServer(
2161
- { name: "synapsor-runner", version: "0.1.0-alpha.2" },
2687
+ { name: "synapsor-runner", version: "0.1.0-alpha.4" },
2162
2688
  { capabilities: { tools: {}, resources: {} } }
2163
2689
  );
2164
2690
  if (runtime.config.mode === "cloud") {
@@ -2798,9 +3324,9 @@ function readLocalResource(store, uri) {
2798
3324
  return { proposal, events: store.events(id), receipts: store.receipts(id) };
2799
3325
  }
2800
3326
  if (collection === "evidence") {
2801
- const evidence = store.getEvidenceBundle(id);
2802
- if (!evidence) throw new McpRuntimeError("RESOURCE_NOT_FOUND", `Evidence bundle not found: ${id}`);
2803
- return evidence;
3327
+ const evidence2 = store.getEvidenceBundle(id);
3328
+ if (!evidence2) throw new McpRuntimeError("RESOURCE_NOT_FOUND", `Evidence bundle not found: ${id}`);
3329
+ return evidence2;
2804
3330
  }
2805
3331
  if (collection === "replay") {
2806
3332
  const proposalId = id.startsWith("replay_") ? id.slice("replay_".length) : id;
@@ -4392,7 +4918,7 @@ async function handleRequest(input) {
4392
4918
  }
4393
4919
  if (request.method === "GET" && url.pathname === "/") {
4394
4920
  response.setHeader("set-cookie", `synapsor_ui_token=${encodeURIComponent(token)}; HttpOnly; SameSite=Strict; Path=/`);
4395
- sendHtml(response, renderShell(csrfToken, tour || url.searchParams.get("tour") === "1"));
4921
+ sendHtml(response, renderShell(csrfToken, tour || url.searchParams.get("tour") === "1", configPath, storePath));
4396
4922
  return;
4397
4923
  }
4398
4924
  if (request.method === "GET" && url.pathname === "/api/summary") {
@@ -4691,8 +5217,10 @@ function sendHtml(response, html) {
4691
5217
  response.setHeader("cache-control", "no-store");
4692
5218
  response.end(html);
4693
5219
  }
4694
- function renderShell(csrfToken, tour = false) {
5220
+ function renderShell(csrfToken, tour = false, configPath = "synapsor.runner.json", storePath = "./.synapsor/local.db") {
4695
5221
  const escapedCsrf = escapeScriptString(csrfToken);
5222
+ const escapedConfigPath = escapeScriptString(configPath);
5223
+ const escapedStorePath = escapeScriptString(storePath);
4696
5224
  const tourHtml = tour ? `
4697
5225
  <div class="card full tour">
4698
5226
  <h2>Commit-safe MCP in one loop</h2>
@@ -4822,6 +5350,8 @@ details.raw > summary { cursor:pointer; color:var(--blue); font-weight:600; font
4822
5350
  </main>
4823
5351
  <script>
4824
5352
  const csrfToken = "${escapedCsrf}";
5353
+ const configPath = "${escapedConfigPath}";
5354
+ const storePath = "${escapedStorePath}";
4825
5355
  const state = { selected: null, firstId: null };
4826
5356
  const byId = (id) => document.getElementById(id);
4827
5357
  const text = (tag, value, className = "") => { const node = document.createElement(tag); node.textContent = value == null ? "" : String(value); if (className) node.className = className; return node; };
@@ -4915,6 +5445,13 @@ function guardDrawer(gc) {
4915
5445
  d.append(kv);
4916
5446
  return d;
4917
5447
  }
5448
+ function shellQuote(value) {
5449
+ const text = String(value || "");
5450
+ return /^[A-Za-z0-9_./:@=-]+$/.test(text) ? text : "'" + text.replace(/'/g, "'\\\\''") + "'";
5451
+ }
5452
+ function trustedApplyCommand(proposalId) {
5453
+ return "synapsor apply " + shellQuote(proposalId) + " --config " + shellQuote(configPath) + " --store " + shellQuote(storePath);
5454
+ }
4918
5455
  async function loadSummary() {
4919
5456
  const payload = await api("/api/summary");
4920
5457
  const root = byId("summary"); root.replaceChildren(text("h2", "Setup summary"));
@@ -5109,6 +5646,24 @@ async function loadDetail(proposalId) {
5109
5646
  const reject = el("button", { class: "danger", text: "Reject", onclick: async () => { await api("/api/proposals/" + encodeURIComponent(proposalId) + "/reject", { method: "POST", headers: { "x-synapsor-csrf": csrfToken }, body: JSON.stringify({ actor: actor.value, reason: reason.value || "rejected from local UI", confirm: "reject" }) }); await loadProposals(); await loadDetail(proposalId); } });
5110
5647
  actions.append(approve, reject);
5111
5648
  reviewPane.append(el("div", { class: "callout", text: "You are the approval authority here \u2014 the model cannot reach these controls." }), actor, reason, actions);
5649
+ } else if (proposal.state === "approved" || proposal.state === "pending_worker") {
5650
+ const command = trustedApplyCommand(proposalId);
5651
+ const commandBox = el("div", { class: "mono", text: command, style: "display:block;margin-top:8px" });
5652
+ const copied = el("span", { class: "status-line", text: "" });
5653
+ const copy = el("button", { text: "Copy guarded apply command", onclick: async () => {
5654
+ try {
5655
+ await navigator.clipboard.writeText(command);
5656
+ copied.textContent = "Copied. Run this from a trusted terminal with write credentials.";
5657
+ } catch {
5658
+ copied.textContent = "Copy this guarded apply command and run it from a trusted terminal with write credentials.";
5659
+ }
5660
+ } });
5661
+ reviewPane.append(
5662
+ el("div", { class: "callout", text: "Apply guarded writeback from a trusted terminal. This remains outside MCP, so the model still cannot commit." }),
5663
+ commandBox,
5664
+ el("div", { class: "actions" }, [copy]),
5665
+ copied,
5666
+ );
5112
5667
  }
5113
5668
 
5114
5669
  jsonPane.append(
@@ -5162,8 +5717,97 @@ function stringOrDefault(value, fallback) {
5162
5717
  // apps/runner/src/cli.ts
5163
5718
  var adapters = { postgres: postgresAdapter, mysql: mysqlAdapter };
5164
5719
  var handlerReceiptStatuses = /* @__PURE__ */ new Set(["applied", "already_applied", "conflict", "failed"]);
5720
+ var dangerousDatabaseMcpAuditExample = {
5721
+ tools: [
5722
+ {
5723
+ name: "execute_sql",
5724
+ description: "Execute arbitrary SQL against the application database.",
5725
+ inputSchema: {
5726
+ type: "object",
5727
+ properties: {
5728
+ sql: { type: "string" }
5729
+ },
5730
+ required: ["sql"]
5731
+ }
5732
+ },
5733
+ {
5734
+ name: "run_query",
5735
+ description: "Run any query and return database rows.",
5736
+ inputSchema: {
5737
+ type: "object",
5738
+ properties: {
5739
+ query: { type: "string" },
5740
+ table: { type: "string" },
5741
+ columns: {
5742
+ type: "array",
5743
+ items: { type: "string" }
5744
+ }
5745
+ },
5746
+ required: ["query"]
5747
+ }
5748
+ },
5749
+ {
5750
+ name: "approve_refund",
5751
+ description: "Approve and issue a customer refund immediately.",
5752
+ inputSchema: {
5753
+ type: "object",
5754
+ properties: {
5755
+ refund_id: { type: "string" },
5756
+ tenant_id: { type: "string" },
5757
+ amount_cents: { type: "number" }
5758
+ },
5759
+ required: ["refund_id", "tenant_id", "amount_cents"]
5760
+ }
5761
+ },
5762
+ {
5763
+ name: "update_customer",
5764
+ description: "Update a customer record directly.",
5765
+ inputSchema: {
5766
+ type: "object",
5767
+ properties: {
5768
+ customer_id: { type: "string" },
5769
+ tenant_id: { type: "string" },
5770
+ column: { type: "string" },
5771
+ value: { type: "string" }
5772
+ },
5773
+ required: ["customer_id", "tenant_id", "column", "value"]
5774
+ }
5775
+ },
5776
+ {
5777
+ name: "delete_order",
5778
+ description: "Delete an order from the database.",
5779
+ inputSchema: {
5780
+ type: "object",
5781
+ properties: {
5782
+ order_id: { type: "string" },
5783
+ tenant_id: { type: "string" }
5784
+ },
5785
+ required: ["order_id", "tenant_id"]
5786
+ }
5787
+ },
5788
+ {
5789
+ name: "query_database",
5790
+ description: "Query arbitrary tables and columns from the database.",
5791
+ inputSchema: {
5792
+ type: "object",
5793
+ properties: {
5794
+ database: { type: "string" },
5795
+ schema: { type: "string" },
5796
+ table: { type: "string" },
5797
+ columns: {
5798
+ type: "array",
5799
+ items: { type: "string" }
5800
+ },
5801
+ where: { type: "string" }
5802
+ },
5803
+ required: ["table"]
5804
+ }
5805
+ }
5806
+ ]
5807
+ };
5165
5808
  var defaultConfigPath = "synapsor.runner.json";
5166
5809
  var defaultStorePath = "./.synapsor/local.db";
5810
+ var quickDemoStorePath = "./.synapsor/quick-demo.db";
5167
5811
  var referenceDemoDir = "examples/reference-support-billing-app";
5168
5812
  var referenceDemoConfigPath = `${referenceDemoDir}/synapsor.runner.json`;
5169
5813
  var referenceDemoContainer = "synapsor_runner_reference_support_billing";
@@ -5186,6 +5830,14 @@ async function main(argv) {
5186
5830
  usage([]);
5187
5831
  return 0;
5188
5832
  }
5833
+ if (!isKnownTopLevelCommand(command)) {
5834
+ process2.stderr.write(`Unknown command: synapsor ${command}
5835
+
5836
+ Try:
5837
+ synapsor --help
5838
+ `);
5839
+ return 2;
5840
+ }
5189
5841
  if (isHelpRequest(rest)) {
5190
5842
  usage([command, ...rest.filter((arg) => arg !== "--help" && arg !== "-h")]);
5191
5843
  return 0;
@@ -5213,9 +5865,18 @@ async function main(argv) {
5213
5865
  if (command === "benchmark") return benchmark(rest);
5214
5866
  if (command === "proposals") return proposals(rest);
5215
5867
  if (command === "replay") return replay(rest);
5868
+ if (command === "evidence") return evidence(rest);
5869
+ if (command === "query-audit") return queryAudit(rest);
5870
+ if (command === "receipts") return receipts(rest);
5871
+ if (command === "activity") return activity(rest);
5872
+ if (command === "store") return storeCommand(rest);
5216
5873
  if (command === "shadow") return shadow(rest);
5217
5874
  if (command === "ui") return ui(rest);
5218
- usage([]);
5875
+ process2.stderr.write(`Unknown command: synapsor ${command}
5876
+
5877
+ Try:
5878
+ synapsor --help
5879
+ `);
5219
5880
  return 2;
5220
5881
  }
5221
5882
  async function init(args) {
@@ -6039,6 +6700,49 @@ function formatLocalDoctorReport(report) {
6039
6700
  return `${lines.join("\n")}
6040
6701
  `;
6041
6702
  }
6703
+ function formatLocalDoctorMarkdown(report) {
6704
+ const store = report.store_stats;
6705
+ const boundaryOk = report.checks.find((check) => check.name === "mcp-tool-boundary")?.ok === true;
6706
+ const lines = [
6707
+ "# Synapsor Runner Doctor Report",
6708
+ "",
6709
+ `- Runner package: @synapsor/runner`,
6710
+ `- Node version: ${process2.versions.node}`,
6711
+ `- Config: ${report.config_path}`,
6712
+ `- Mode: ${report.mode}`,
6713
+ `- Status: ${report.ok ? "ok" : "needs attention"}`,
6714
+ "",
6715
+ "## Semantic Tools",
6716
+ "",
6717
+ ...report.tools.length ? report.tools.map((tool) => `- ${tool}`) : ["- none listed"],
6718
+ "",
6719
+ "## Safety Boundary",
6720
+ "",
6721
+ `- Raw SQL / commit tools exposed: ${boundaryOk ? "no obvious forbidden tools detected" : "needs review"}`,
6722
+ "- Database URLs, passwords, bearer tokens, and private keys are intentionally not included in this report.",
6723
+ "",
6724
+ "## Local Store",
6725
+ "",
6726
+ `- Path: ${store?.path ?? "not configured"}`,
6727
+ `- Exists: ${store?.exists ? "yes" : "no"}`,
6728
+ ...store?.exists ? [
6729
+ `- Proposals: ${store.proposals ?? 0}`,
6730
+ `- Evidence bundles: ${store.evidence ?? 0}`,
6731
+ `- Query audit records: ${store.query_audit ?? 0}`,
6732
+ `- Receipts: ${store.receipts ?? 0}`
6733
+ ] : [],
6734
+ "",
6735
+ "## Checks",
6736
+ "",
6737
+ ...report.checks.map((check) => `- ${check.level.toUpperCase()} ${check.name}: ${check.message}`),
6738
+ "",
6739
+ "## Redaction Note",
6740
+ "",
6741
+ "This report is redacted by design. Do not attach raw database URLs, passwords, API keys, bearer tokens, private keys, cookies, or customer data when sharing diagnostics."
6742
+ ];
6743
+ return `${lines.join("\n")}
6744
+ `;
6745
+ }
6042
6746
  var moduleDir = path3.dirname(fileURLToPath(import.meta.url));
6043
6747
  var packageAssetRoot = path3.resolve(moduleDir, "..");
6044
6748
  var sourceAssetRoot = path3.resolve(moduleDir, "../../..");
@@ -6391,9 +7095,16 @@ async function localDoctor(args) {
6391
7095
  mode: String(parsed.mode),
6392
7096
  config_path: configPath,
6393
7097
  checks,
6394
- tools: tools2
7098
+ tools: tools2,
7099
+ store_stats: await localDoctorStoreStats(optionalArg(args, "--store") ?? parsed.storage?.sqlite_path)
6395
7100
  };
6396
- if (args.includes("--json")) {
7101
+ if (args.includes("--report")) {
7102
+ const output = outputArg(args) ?? "synapsor-doctor.md";
7103
+ await fs3.mkdir(path3.dirname(path3.resolve(output)), { recursive: true });
7104
+ await fs3.writeFile(output, formatLocalDoctorMarkdown(report), "utf8");
7105
+ process2.stdout.write(`wrote redacted doctor report: ${output}
7106
+ `);
7107
+ } else if (args.includes("--json")) {
6397
7108
  process2.stdout.write(`${JSON.stringify(report, null, 2)}
6398
7109
  `);
6399
7110
  } else {
@@ -6401,6 +7112,23 @@ async function localDoctor(args) {
6401
7112
  }
6402
7113
  return report.ok ? 0 : 1;
6403
7114
  }
7115
+ async function localDoctorStoreStats(storePath) {
7116
+ if (!storePath || storePath === ":memory:") return { path: storePath ?? "not configured", exists: storePath === ":memory:" };
7117
+ if (!await fileExists(storePath)) return { path: storePath, exists: false };
7118
+ const store = new ProposalStore(storePath);
7119
+ try {
7120
+ return {
7121
+ path: storePath,
7122
+ exists: true,
7123
+ proposals: store.listProposals({ limit: 1e6 }).length,
7124
+ evidence: store.listEvidenceBundles({ limit: 1e6 }).length,
7125
+ query_audit: store.listQueryAudit({ limit: 1e6 }).length,
7126
+ receipts: store.listReceipts({ limit: 1e6 }).length
7127
+ };
7128
+ } finally {
7129
+ store.close();
7130
+ }
7131
+ }
6404
7132
  async function validate(args) {
6405
7133
  const job = await readJob(args);
6406
7134
  parseWritebackJob(job);
@@ -6479,21 +7207,21 @@ async function applyProposal(args, proposalId) {
6479
7207
  lease_seconds: Number(optionalArg(args, "--lease-seconds") ?? "300")
6480
7208
  });
6481
7209
  const result = await applySqlJob(job, configPath, storePath, dryRun, envWithDemoDefaults(config, configPath));
6482
- process2.stdout.write(`${JSON.stringify(result, null, 2)}
6483
- `);
7210
+ process2.stdout.write(args.includes("--json") ? `${JSON.stringify(result, null, 2)}
7211
+ ` : formatApplyResult(parseWritebackJob(job), result, dryRun, storePath));
6484
7212
  return result.status === "failed" ? 1 : 0;
6485
7213
  }
6486
7214
  const executor = executorConfig(config, executorName);
6487
7215
  if (executor.type === "http_handler") {
6488
7216
  const result = await applyHttpHandlerProposal({ store, proposalId: resolvedProposalId, proposal, executorName, executor, runnerId, dryRun, env: envWithDemoDefaults(config, configPath) });
6489
- process2.stdout.write(`${JSON.stringify(redactConfig(result), null, 2)}
6490
- `);
7217
+ process2.stdout.write(args.includes("--json") ? `${JSON.stringify(redactConfig(result), null, 2)}
7218
+ ` : formatHandlerApplyResult(result, resolvedProposalId, storePath));
6491
7219
  return result.status === "failed" ? 1 : 0;
6492
7220
  }
6493
7221
  if (executor.type === "command_handler") {
6494
7222
  const result = await applyCommandHandlerProposal({ store, proposalId: resolvedProposalId, proposal, executorName, executor, runnerId, dryRun, env: envWithDemoDefaults(config, configPath) });
6495
- process2.stdout.write(`${JSON.stringify(redactConfig(result), null, 2)}
6496
- `);
7223
+ process2.stdout.write(args.includes("--json") ? `${JSON.stringify(redactConfig(result), null, 2)}
7224
+ ` : formatHandlerApplyResult(result, resolvedProposalId, storePath));
6497
7225
  return result.status === "failed" ? 1 : 0;
6498
7226
  }
6499
7227
  throw new Error(`unsupported executor type for ${executorName}`);
@@ -6669,8 +7397,8 @@ function prepareHandlerProposal(store, proposal, runnerId) {
6669
7397
  };
6670
7398
  }
6671
7399
  function duplicateHandlerReceipt(store, proposalId) {
6672
- const receipts = store.receipts(proposalId);
6673
- const existing = receipts.find((receipt) => receipt.writeback_job_id.startsWith("hwb_"));
7400
+ const receipts2 = store.receipts(proposalId);
7401
+ const existing = receipts2.find((receipt) => receipt.writeback_job_id.startsWith("hwb_"));
6674
7402
  return existing ? { receipt: existing.receipt } : void 0;
6675
7403
  }
6676
7404
  function alreadyAppliedReceipt(receipt, runnerId) {
@@ -6749,6 +7477,107 @@ function buildHandlerReceipt(input) {
6749
7477
  function hashReceipt(input) {
6750
7478
  return `sha256:${crypto5.createHash("sha256").update(JSON.stringify(input)).digest("hex")}`;
6751
7479
  }
7480
+ function formatApplyResult(job, result, dryRun, storePath) {
7481
+ const status = writebackResultStatus(result);
7482
+ const affectedRows = writebackAffectedRows(result);
7483
+ const errorCode = writebackErrorCode(result);
7484
+ const receiptHash = writebackReceiptHash(result);
7485
+ const conflictGuardPassed = status === "conflict" && errorCode === "VERSION_CONFLICT" ? "no" : status === "applied" ? "yes" : "not completed";
7486
+ const title = status === "conflict" ? "Guarded writeback returned conflict." : status === "failed" ? "Guarded writeback failed." : dryRun ? "Guarded writeback dry run passed." : affectedRows === 0 ? "Guarded writeback already applied." : "Guarded writeback applied.";
7487
+ const lines = [
7488
+ title,
7489
+ "",
7490
+ "Checks:",
7491
+ "* proposal approved: yes",
7492
+ `* primary key matched: ${status === "conflict" && errorCode === "ROW_NOT_FOUND" ? "no" : status === "failed" ? "not completed" : "yes"}`,
7493
+ `* tenant guard matched: ${status === "conflict" && errorCode === "ROW_NOT_FOUND" ? "no" : status === "failed" ? "not completed" : "yes"}`,
7494
+ "* allowed columns only: yes",
7495
+ `* conflict guard passed: ${conflictGuardPassed}`,
7496
+ `* affected rows: ${affectedRows}`,
7497
+ `* idempotency key: ${job.idempotency_key}`,
7498
+ ""
7499
+ ];
7500
+ if (status === "conflict") {
7501
+ lines.push(
7502
+ errorCode === "VERSION_CONFLICT" ? "The row changed after the agent saw it." : "The target row was not available under the primary-key and tenant guard.",
7503
+ "",
7504
+ "Result:",
7505
+ "conflict",
7506
+ "",
7507
+ "Source DB changed by Synapsor:",
7508
+ "no",
7509
+ "",
7510
+ "Why:",
7511
+ errorCode === "VERSION_CONFLICT" ? "conflict/version guard did not match" : errorCode || "guarded writeback returned conflict",
7512
+ ""
7513
+ );
7514
+ }
7515
+ if (status === "failed") {
7516
+ lines.push("Error:", errorCode || "writeback failed", "");
7517
+ }
7518
+ lines.push(
7519
+ "Receipt:",
7520
+ receiptHash || "(stored locally)",
7521
+ "",
7522
+ "Replay:",
7523
+ `${cliCommandName()} replay ${job.proposal_id} --store ${storePath}`,
7524
+ ""
7525
+ );
7526
+ return `${lines.join("\n")}
7527
+ `;
7528
+ }
7529
+ function formatHandlerApplyResult(receipt, proposalId, storePath) {
7530
+ const title = receipt.status === "conflict" ? "App-owned writeback returned conflict." : receipt.status === "failed" ? "App-owned writeback failed." : receipt.status === "already_applied" ? "App-owned writeback already applied." : "App-owned writeback applied.";
7531
+ const lines = [
7532
+ title,
7533
+ "",
7534
+ "Checks:",
7535
+ "* proposal approved: yes",
7536
+ "* execution authority: app-owned handler outside MCP",
7537
+ `* source database changed by handler: ${receipt.source_database_mutated ? "yes" : "no"}`,
7538
+ `* affected rows: ${receipt.rows_affected}`,
7539
+ `* idempotency key: ${receipt.idempotency_key}`,
7540
+ ""
7541
+ ];
7542
+ if (receipt.status === "conflict") {
7543
+ lines.push(
7544
+ "Result:",
7545
+ "conflict",
7546
+ "",
7547
+ "Why:",
7548
+ receipt.safe_error_code || "handler returned conflict",
7549
+ ""
7550
+ );
7551
+ }
7552
+ if (receipt.status === "failed") {
7553
+ lines.push("Error:", receipt.safe_error_code || "handler writeback failed", "");
7554
+ }
7555
+ lines.push(
7556
+ "Receipt:",
7557
+ receipt.receipt_hash,
7558
+ "",
7559
+ "Replay:",
7560
+ `${cliCommandName()} replay ${proposalId} --store ${storePath}`,
7561
+ ""
7562
+ );
7563
+ return `${lines.join("\n")}
7564
+ `;
7565
+ }
7566
+ function writebackResultStatus(result) {
7567
+ return String(result.status ?? "unknown");
7568
+ }
7569
+ function writebackAffectedRows(result) {
7570
+ const value = result.affected_rows ?? result.rows_affected ?? 0;
7571
+ return Number.isFinite(Number(value)) ? Number(value) : 0;
7572
+ }
7573
+ function writebackErrorCode(result) {
7574
+ const value = result.error_code ?? result.safe_error_code;
7575
+ return typeof value === "string" && value ? value : void 0;
7576
+ }
7577
+ function writebackReceiptHash(result) {
7578
+ const value = result.result_hash ?? result.receipt_hash;
7579
+ return typeof value === "string" && value ? value : void 0;
7580
+ }
6752
7581
  function parseOptionalJson(text) {
6753
7582
  if (!text.trim()) return {};
6754
7583
  try {
@@ -6912,7 +7741,7 @@ async function cloudConnect(args) {
6912
7741
  return 1;
6913
7742
  }
6914
7743
  const runnerId = String(parsed.cloud.runner_id || process2.env.SYNAPSOR_RUNNER_ID || "synapsor_runner_local").trim();
6915
- const runnerVersion = String(parsed.cloud.runner_version || process2.env.npm_package_version || "0.1.0-alpha.2").trim();
7744
+ const runnerVersion = String(parsed.cloud.runner_version || process2.env.npm_package_version || "0.1.0-alpha.4").trim();
6916
7745
  const engines = normalizeEngines(parsed.cloud.engines);
6917
7746
  const capabilities = normalizeCapabilities(parsed.cloud.capabilities);
6918
7747
  const client = new ControlPlaneClient({
@@ -7015,8 +7844,16 @@ async function demo(args) {
7015
7844
  return prepareReferenceDemo(args);
7016
7845
  }
7017
7846
  async function quickDemo() {
7847
+ const seeded = await seedQuickDemoStore(quickDemoStorePath);
7018
7848
  process2.stdout.write([
7019
- "Synapsor Runner quick demo (fixture-only)",
7849
+ "Synapsor Runner quick demo",
7850
+ "",
7851
+ "This is a fixture-only first look. It does not start Docker, connect a database,",
7852
+ "or require an MCP client. It writes an inspectable local ledger fixture to:",
7853
+ quickDemoStorePath,
7854
+ "",
7855
+ "If you ran this through one-off npx and did not install the package, prefix",
7856
+ "follow-up commands with: npx -y -p @synapsor/runner@alpha synapsor",
7020
7857
  "",
7021
7858
  "Raw MCP shape:",
7022
7859
  "execute_sql(sql: string)",
@@ -7027,7 +7864,7 @@ async function quickDemo() {
7027
7864
  "billing.propose_late_fee_waiver(invoice_id, reason)",
7028
7865
  "",
7029
7866
  "Agent requested:",
7030
- 'billing.propose_late_fee_waiver(invoice_id="INV-3001")',
7867
+ 'billing.propose_late_fee_waiver(invoice_id="INV-3001", reason="approved support waiver")',
7031
7868
  "",
7032
7869
  "Trusted context:",
7033
7870
  "tenant_id = acme",
@@ -7043,47 +7880,215 @@ async function quickDemo() {
7043
7880
  "required outside MCP",
7044
7881
  "",
7045
7882
  "Replay:",
7046
- "available after approval/apply",
7883
+ `${seeded.replay_id} captures the local proposal, evidence handle, query audit, and events.`,
7884
+ "",
7885
+ "Inspect this local fixture:",
7886
+ `${cliCommandName()} proposals show latest --store ${quickDemoStorePath}`,
7887
+ `${cliCommandName()} evidence show ${seeded.evidence_bundle_id} --store ${quickDemoStorePath}`,
7888
+ `${cliCommandName()} activity search --object invoice:INV-3001 --store ${quickDemoStorePath}`,
7889
+ `${cliCommandName()} replay show --proposal ${seeded.proposal_id} --store ${quickDemoStorePath}`,
7047
7890
  "",
7048
- "Next:",
7891
+ "Approve the fixture outside MCP:",
7892
+ `${cliCommandName()} proposals approve latest --yes --store ${quickDemoStorePath}`,
7893
+ `${cliCommandName()} replay show latest --store ${quickDemoStorePath}`,
7894
+ "",
7895
+ "For real guarded writeback against disposable Postgres:",
7049
7896
  `${cliCommandName()} demo`,
7050
- `${cliCommandName()} audit <your-mcp-tools.json>`,
7897
+ "",
7898
+ "Audit risky MCP database tools:",
7899
+ `${cliCommandName()} audit --example dangerous-db-mcp`,
7051
7900
  ""
7052
7901
  ].join("\n"));
7053
7902
  return 0;
7054
7903
  }
7055
- async function mcpServe(args) {
7056
- const configPath = optionalArg(args, "--config") ?? process2.env.SYNAPSOR_MCP_CONFIG;
7057
- const readOnly = args.includes("--read-only");
7058
- const config = readOnly ? { ...await readRuntimeConfig(configPath ?? defaultConfigPath), mode: "read_only" } : void 0;
7059
- await serveStdio({
7060
- configPath,
7061
- storePath: optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE,
7062
- config
7063
- });
7064
- return 0;
7065
- }
7066
- async function mcpAudit(args) {
7067
- const json = args.includes("--json");
7068
- const target2 = firstPositional(args);
7069
- if (!target2) {
7070
- throw new Error("mcp audit requires <target>");
7904
+ async function seedQuickDemoStore(storePath) {
7905
+ const resolved = path3.resolve(storePath);
7906
+ await fs3.mkdir(path3.dirname(resolved), { recursive: true });
7907
+ await fs3.rm(resolved, { force: true });
7908
+ const store = new ProposalStore(resolved);
7909
+ try {
7910
+ const changeSet = quickDemoChangeSet();
7911
+ const proposal = store.createProposal(changeSet);
7912
+ store.recordEvidenceBundle({
7913
+ evidence_bundle_id: changeSet.evidence.bundle_id,
7914
+ proposal_id: proposal.proposal_id,
7915
+ tenant_id: changeSet.scope.tenant_id,
7916
+ payload: {
7917
+ capability: changeSet.action,
7918
+ proposal_id: proposal.proposal_id,
7919
+ source_id: changeSet.source.source_id,
7920
+ target: `${changeSet.source.schema}.${changeSet.source.table}`,
7921
+ principal: changeSet.principal.id,
7922
+ tenant_id: changeSet.scope.tenant_id,
7923
+ business_object: changeSet.scope.business_object,
7924
+ object_id: changeSet.scope.object_id,
7925
+ query_fingerprint: changeSet.evidence.query_fingerprint,
7926
+ source_database_changed: false
7927
+ },
7928
+ items: [
7929
+ {
7930
+ kind: "external_row",
7931
+ source_id: changeSet.source.source_id,
7932
+ table: `${changeSet.source.schema}.${changeSet.source.table}`,
7933
+ primary_key: changeSet.source.primary_key,
7934
+ tenant: changeSet.guards.tenant,
7935
+ visible_row: changeSet.before
7936
+ },
7937
+ {
7938
+ kind: "proposal_diff",
7939
+ before: changeSet.before,
7940
+ patch: changeSet.patch,
7941
+ after: changeSet.after
7942
+ }
7943
+ ]
7944
+ });
7945
+ store.recordQueryAudit({
7946
+ proposal_id: proposal.proposal_id,
7947
+ evidence_bundle_id: changeSet.evidence.bundle_id,
7948
+ source_id: changeSet.source.source_id,
7949
+ query_fingerprint: changeSet.evidence.query_fingerprint,
7950
+ table_name: `${changeSet.source.schema}.${changeSet.source.table}`,
7951
+ row_count: 1,
7952
+ payload: {
7953
+ capability: changeSet.action,
7954
+ tenant_bound: true,
7955
+ statement_template: "SELECT id, tenant_id, updated_at, late_fee_cents FROM invoices WHERE id = ? AND tenant_id = ? LIMIT 1",
7956
+ parameters_redacted: true
7957
+ }
7958
+ });
7959
+ store.replay(proposal.proposal_id);
7960
+ return {
7961
+ proposal_id: proposal.proposal_id,
7962
+ evidence_bundle_id: changeSet.evidence.bundle_id,
7963
+ replay_id: `replay_${proposal.proposal_id}`
7964
+ };
7965
+ } finally {
7966
+ store.close();
7071
7967
  }
7072
- const timeoutMs = Number(optionalArg(args, "--timeout-ms") ?? "5000");
7073
- const payload = await readMcpAuditTarget(target2, args, timeoutMs);
7074
- const report = auditMcpManifest(payload, { target: target2 });
7075
- process2.stdout.write(json ? `${JSON.stringify(report, null, 2)}
7076
- ` : formatMcpAuditReport(report));
7077
- return 0;
7078
7968
  }
7079
- async function propose(args) {
7080
- const capabilityName = firstPositional(args);
7081
- if (!capabilityName) throw new Error("propose requires <capability-name>");
7082
- const configPath = optionalArg(args, "--config") ?? defaultConfigPath;
7083
- const storePath = optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE ?? defaultStorePath;
7084
- const config = await readRuntimeConfig(configPath);
7085
- const capability = (config.capabilities ?? []).find((item) => item.name === capabilityName);
7086
- if (!capability) throw new Error(`proposal capability not found: ${capabilityName}`);
7969
+ function quickDemoChangeSet() {
7970
+ const base = {
7971
+ schema_version: protocolVersions.changeSet,
7972
+ proposal_id: "wrp_quick_INV_3001",
7973
+ proposal_version: 1,
7974
+ action: "billing.propose_late_fee_waiver",
7975
+ mode: "review_required",
7976
+ principal: {
7977
+ id: "support.agent",
7978
+ source: "trusted_session"
7979
+ },
7980
+ scope: {
7981
+ tenant_id: "acme",
7982
+ business_object: "invoice",
7983
+ object_id: "INV-3001"
7984
+ },
7985
+ source: {
7986
+ kind: "external_postgres",
7987
+ source_id: "app_postgres",
7988
+ schema: "public",
7989
+ table: "invoices",
7990
+ primary_key: {
7991
+ column: "id",
7992
+ value: "INV-3001"
7993
+ }
7994
+ },
7995
+ before: {
7996
+ id: "INV-3001",
7997
+ tenant_id: "acme",
7998
+ updated_at: "2026-06-23T09:00:00Z",
7999
+ late_fee_cents: 5500
8000
+ },
8001
+ patch: {
8002
+ late_fee_cents: 0
8003
+ },
8004
+ after: {
8005
+ id: "INV-3001",
8006
+ tenant_id: "acme",
8007
+ updated_at: "2026-06-23T09:00:00Z",
8008
+ late_fee_cents: 0
8009
+ },
8010
+ guards: {
8011
+ tenant: {
8012
+ column: "tenant_id",
8013
+ value: "acme"
8014
+ },
8015
+ allowed_columns: ["late_fee_cents"],
8016
+ expected_version: {
8017
+ column: "updated_at",
8018
+ value: "2026-06-23T09:00:00Z"
8019
+ }
8020
+ },
8021
+ evidence: {
8022
+ bundle_id: "ev_quick_INV_3001",
8023
+ query_fingerprint: "sha256:quick-demo-invoice-read",
8024
+ items: [
8025
+ {
8026
+ kind: "external_row",
8027
+ source_id: "app_postgres",
8028
+ table: "public.invoices",
8029
+ primary_key: { column: "id", value: "INV-3001" }
8030
+ }
8031
+ ]
8032
+ },
8033
+ approval: {
8034
+ status: "pending",
8035
+ required_role: "local_operator"
8036
+ },
8037
+ writeback: {
8038
+ status: "not_applied",
8039
+ mode: "trusted_worker_required"
8040
+ },
8041
+ source_database_mutated: false,
8042
+ integrity: {
8043
+ proposal_hash: "sha256:placeholder"
8044
+ },
8045
+ created_at: "2026-06-23T09:00:00Z"
8046
+ };
8047
+ return {
8048
+ ...base,
8049
+ integrity: {
8050
+ proposal_hash: hashReceipt({ ...base, integrity: void 0 })
8051
+ }
8052
+ };
8053
+ }
8054
+ async function mcpServe(args) {
8055
+ const configPath = optionalArg(args, "--config") ?? process2.env.SYNAPSOR_MCP_CONFIG;
8056
+ const readOnly = args.includes("--read-only");
8057
+ const config = readOnly ? { ...await readRuntimeConfig(configPath ?? defaultConfigPath), mode: "read_only" } : void 0;
8058
+ await serveStdio({
8059
+ configPath,
8060
+ storePath: optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE,
8061
+ config
8062
+ });
8063
+ return 0;
8064
+ }
8065
+ async function mcpAudit(args) {
8066
+ const format = optionalArg(args, "--format") ?? (args.includes("--json") ? "json" : "text");
8067
+ if (!["text", "json", "markdown"].includes(format)) {
8068
+ throw new Error("audit --format must be text, json, or markdown");
8069
+ }
8070
+ const example = optionalArg(args, "--example");
8071
+ const target2 = example ? `example:${example}` : firstPositional(args);
8072
+ if (!target2) {
8073
+ throw new Error("mcp audit requires <target> or --example dangerous-db-mcp");
8074
+ }
8075
+ const timeoutMs = Number(optionalArg(args, "--timeout-ms") ?? "5000");
8076
+ const payload = example ? builtInMcpAuditExample(example) : await readMcpAuditTarget(target2, args, timeoutMs);
8077
+ const report = auditMcpManifest(payload, { target: target2 });
8078
+ if (format === "json") process2.stdout.write(`${JSON.stringify(report, null, 2)}
8079
+ `);
8080
+ else if (format === "markdown") process2.stdout.write(formatMcpAuditMarkdown(report));
8081
+ else process2.stdout.write(formatMcpAuditReport(report));
8082
+ return 0;
8083
+ }
8084
+ async function propose(args) {
8085
+ const capabilityName = firstPositional(args);
8086
+ if (!capabilityName) throw new Error("propose requires <capability-name>");
8087
+ const configPath = optionalArg(args, "--config") ?? defaultConfigPath;
8088
+ const storePath = optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE ?? defaultStorePath;
8089
+ const config = await readRuntimeConfig(configPath);
8090
+ const capability = (config.capabilities ?? []).find((item) => item.name === capabilityName);
8091
+ if (!capability) throw new Error(`proposal capability not found: ${capabilityName}`);
7087
8092
  if (capability.kind !== "proposal") throw new Error(`${capabilityName} is a ${capability.kind} capability. Use a proposal capability with ${cliCommandName()} propose.`);
7088
8093
  const input = await proposalInput(args, capability);
7089
8094
  const env = envWithDemoDefaults(config, configPath);
@@ -7106,8 +8111,9 @@ async function audit(args) {
7106
8111
  const url = optionalArg(args, "--url");
7107
8112
  const stdio = optionalArg(args, "--stdio");
7108
8113
  const mcpConfig2 = optionalArg(args, "--mcp-config");
7109
- const target2 = url ?? (stdio ? `stdio:${stdio}` : mcpConfig2 ?? firstPositional(args));
7110
- if (!target2) throw new Error("audit requires <target>, --mcp-config <path>, --stdio <command>, or --url <url>");
8114
+ const example = optionalArg(args, "--example");
8115
+ const target2 = example ? `example:${example}` : url ?? (stdio ? `stdio:${stdio}` : mcpConfig2 ?? firstPositional(args));
8116
+ if (!target2) throw new Error("audit requires <target>, --example dangerous-db-mcp, --mcp-config <path>, --stdio <command>, or --url <url>");
7111
8117
  const forwarded = args.filter((arg, index) => {
7112
8118
  const previous = args[index - 1];
7113
8119
  return !["--url", "--stdio", "--mcp-config"].includes(arg) && !["--url", "--stdio", "--mcp-config"].includes(previous ?? "");
@@ -7137,13 +8143,13 @@ function sampleInputForCapability(capability) {
7137
8143
  const input = {};
7138
8144
  for (const [name, spec] of Object.entries(capability.args)) {
7139
8145
  if (name === capability.lookup.id_from_arg) input[name] = sampleIdForCapability(capability, name);
7140
- else if (/reason/i.test(name)) input[name] = "approved support waiver";
8146
+ else if (/reason/i.test(name)) input[name] = sampleReasonForCapability(capability);
7141
8147
  else if (/resolution/i.test(name)) input[name] = "Resolved after reviewing policy evidence.";
8148
+ else if (spec.enum?.length) input[name] = spec.enum[0];
7142
8149
  else if (/status/i.test(name)) input[name] = "pending_review";
7143
8150
  else if (/amount|cents|fee|credit|balance/i.test(name)) input[name] = typeof spec.maximum === "number" ? Math.min(spec.maximum, 1e3) : 0;
7144
8151
  else if (spec.type === "number") input[name] = spec.minimum ?? 1;
7145
8152
  else if (spec.type === "boolean") input[name] = true;
7146
- else if (spec.enum?.length) input[name] = spec.enum[0];
7147
8153
  else input[name] = `sample_${name}`;
7148
8154
  }
7149
8155
  const missing = Object.entries(capability.args).filter(([, spec]) => spec.required !== false).filter(([name]) => input[name] === void 0).map(([name]) => name);
@@ -7154,12 +8160,20 @@ function sampleInputForCapability(capability) {
7154
8160
  }
7155
8161
  function sampleIdForCapability(capability, argName) {
7156
8162
  const text = `${capability.name} ${capability.target.table} ${argName}`.toLowerCase();
8163
+ const arg = argName.toLowerCase();
7157
8164
  if (/invoice|billing/.test(text)) return "INV-3001";
8165
+ if (/account|customer/.test(arg) || /accounts|customers/.test(text)) return "cust_acme_1";
7158
8166
  if (/ticket|support/.test(text)) return "T-1042";
7159
- if (/order/.test(text)) return "ord_1001";
7160
- if (/account|customer/.test(text)) return "cust_acme_1";
8167
+ if (/order/.test(text)) return "O-1001";
7161
8168
  return "sample_1";
7162
8169
  }
8170
+ function sampleReasonForCapability(capability) {
8171
+ const text = `${capability.name} ${capability.target.table}`.toLowerCase();
8172
+ if (/order|status_change/.test(text)) return "payment cleared and ready for the next status";
8173
+ if (/credit|customer|account/.test(text)) return "support goodwill credit";
8174
+ if (/late_fee|waiver|billing|invoice/.test(text)) return "approved support waiver";
8175
+ return "reviewed and approved by support";
8176
+ }
7163
8177
  function formatProposeResult(capabilityName, result, storePath) {
7164
8178
  const proposalId = String(result.proposal_id ?? "");
7165
8179
  const evidenceId = String(result.evidence_bundle_id ?? "");
@@ -7723,6 +8737,10 @@ async function readMcpAuditTarget(target2, args, timeoutMs) {
7723
8737
  }
7724
8738
  return parsed;
7725
8739
  }
8740
+ function builtInMcpAuditExample(example) {
8741
+ if (example === "dangerous-db-mcp") return dangerousDatabaseMcpAuditExample;
8742
+ throw new Error(`unknown audit example: ${example}. Available examples: dangerous-db-mcp`);
8743
+ }
7726
8744
  function isRunnerConfigLike(value) {
7727
8745
  return isRecord6(value) && value.version === 1 && Array.isArray(value.capabilities);
7728
8746
  }
@@ -7813,12 +8831,50 @@ async function proposals(args) {
7813
8831
  }
7814
8832
  async function replay(args) {
7815
8833
  const [subcommand, ...rest] = args;
8834
+ if (subcommand === "list") return replayList(rest);
7816
8835
  if (subcommand && !["show", "export"].includes(subcommand)) return replayShow(args);
7817
8836
  if (subcommand === "show") return replayShow(rest);
7818
8837
  if (subcommand === "export") return replayExport(rest);
7819
8838
  usage(["replay"]);
7820
8839
  return 2;
7821
8840
  }
8841
+ async function evidence(args) {
8842
+ const [subcommand, ...rest] = args;
8843
+ if (subcommand === "show") return evidenceShow(rest);
8844
+ if (subcommand === "list") return evidenceList(rest);
8845
+ if (subcommand === "export") return evidenceExport(rest);
8846
+ usage(["evidence"]);
8847
+ return 2;
8848
+ }
8849
+ async function queryAudit(args) {
8850
+ const [subcommand, ...rest] = args;
8851
+ if (subcommand === "list") return queryAuditList(rest);
8852
+ if (subcommand === "show") return queryAuditShow(rest);
8853
+ if (subcommand === "export") return queryAuditExport(rest);
8854
+ usage(["query-audit"]);
8855
+ return 2;
8856
+ }
8857
+ async function receipts(args) {
8858
+ const [subcommand, ...rest] = args;
8859
+ if (subcommand === "list") return receiptsList(rest);
8860
+ if (subcommand === "show") return receiptsShow(rest);
8861
+ usage(["receipts"]);
8862
+ return 2;
8863
+ }
8864
+ async function activity(args) {
8865
+ const [subcommand, ...rest] = args;
8866
+ if (subcommand === "search") return activitySearch(rest);
8867
+ usage(["activity"]);
8868
+ return 2;
8869
+ }
8870
+ async function storeCommand(args) {
8871
+ const [subcommand, ...rest] = args;
8872
+ if (subcommand === "stats") return storeStats(rest);
8873
+ if (subcommand === "vacuum") return storeVacuum(rest);
8874
+ if (subcommand === "prune") return storePrune(rest);
8875
+ usage(["store"]);
8876
+ return 2;
8877
+ }
7822
8878
  async function shadow(args) {
7823
8879
  const [subcommand, ...rest] = args;
7824
8880
  if (subcommand === "list") return shadowList(rest);
@@ -7939,10 +8995,11 @@ async function shadowReport(args) {
7939
8995
  }
7940
8996
  }
7941
8997
  async function proposalsList(args) {
8998
+ assertKnownOptions(args, proposalListAllowedOptions, "proposals list");
7942
8999
  const store = await openLocalStore(args);
7943
9000
  try {
7944
- const state = optionalArg(args, "--state");
7945
- const rows = store.listProposals(state);
9001
+ const filters = proposalFiltersFromArgs(args);
9002
+ const rows = store.listProposals(filters);
7946
9003
  if (args.includes("--json")) {
7947
9004
  process2.stdout.write(`${JSON.stringify({ proposals: rows }, null, 2)}
7948
9005
  `);
@@ -7968,12 +9025,13 @@ async function proposalsShow(args) {
7968
9025
  const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
7969
9026
  const proposal = store.getProposal(resolvedProposalId);
7970
9027
  if (!proposal) throw new Error(`proposal not found: ${resolvedProposalId}`);
7971
- const payload = { proposal, events: store.events(resolvedProposalId), receipts: store.receipts(resolvedProposalId) };
9028
+ const evidence2 = store.getEvidenceBundle(proposal.change_set.evidence.bundle_id);
9029
+ const payload = { proposal, events: store.events(resolvedProposalId), receipts: store.receipts(resolvedProposalId), evidence: evidence2 };
7972
9030
  if (args.includes("--json")) {
7973
9031
  process2.stdout.write(`${JSON.stringify(payload, null, 2)}
7974
9032
  `);
7975
9033
  } else {
7976
- process2.stdout.write(formatProposalDetail(proposal));
9034
+ process2.stdout.write(formatProposalDetail(proposal, evidence2?.items.length));
7977
9035
  for (const event of payload.events) {
7978
9036
  process2.stdout.write(`event ${event.event_id}: ${event.kind} by ${event.actor} at ${event.created_at}
7979
9037
  `);
@@ -7992,7 +9050,8 @@ async function proposalsApprove(args) {
7992
9050
  const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
7993
9051
  const proposal = requireLocalProposal(store, resolvedProposalId);
7994
9052
  if (!args.includes("--json")) {
7995
- process2.stdout.write(formatProposalDetail(proposal));
9053
+ const evidence2 = store.getEvidenceBundle(proposal.change_set.evidence.bundle_id);
9054
+ process2.stdout.write(formatProposalDetail(proposal, evidence2?.items.length));
7996
9055
  }
7997
9056
  await confirmDangerousAction(args, `Approve proposal ${resolvedProposalId} for guarded writeback?`);
7998
9057
  const updated = store.approveProposal(resolvedProposalId, {
@@ -8019,7 +9078,8 @@ async function proposalsReject(args) {
8019
9078
  const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
8020
9079
  const proposal = requireLocalProposal(store, resolvedProposalId);
8021
9080
  if (!args.includes("--json")) {
8022
- process2.stdout.write(formatProposalDetail(proposal));
9081
+ const evidence2 = store.getEvidenceBundle(proposal.change_set.evidence.bundle_id);
9082
+ process2.stdout.write(formatProposalDetail(proposal, evidence2?.items.length));
8023
9083
  }
8024
9084
  await confirmDangerousAction(args, `Reject proposal ${resolvedProposalId}?`);
8025
9085
  const updated = store.rejectProposal(resolvedProposalId, {
@@ -8063,28 +9123,180 @@ async function proposalsWritebackJob(args) {
8063
9123
  store.close();
8064
9124
  }
8065
9125
  }
8066
- async function replayShow(args) {
8067
- const proposalId = positional(args, 0);
8068
- if (!proposalId) throw new Error("replay show requires <proposal_id>");
9126
+ async function evidenceList(args) {
9127
+ assertKnownOptions(args, evidenceListAllowedOptions, "evidence list");
8069
9128
  const store = await openLocalStore(args);
8070
9129
  try {
8071
- const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
8072
- const replayRecord = store.replay(resolvedProposalId);
9130
+ const rows = store.listEvidenceBundles(evidenceFiltersFromArgs(args));
8073
9131
  if (args.includes("--json")) {
8074
- process2.stdout.write(`${JSON.stringify(replayRecord, null, 2)}
9132
+ process2.stdout.write(`${JSON.stringify({ evidence: rows }, null, 2)}
8075
9133
  `);
9134
+ } else if (rows.length === 0) {
9135
+ process2.stdout.write("No evidence bundles found.\n");
8076
9136
  } else {
8077
- process2.stdout.write(`Replay ${replayRecord.replay_id}
9137
+ for (const bundle of rows) process2.stdout.write(formatEvidenceSummary(bundle));
9138
+ }
9139
+ return 0;
9140
+ } finally {
9141
+ store.close();
9142
+ }
9143
+ }
9144
+ async function evidenceShow(args) {
9145
+ assertKnownOptions(args, showAllowedOptions, "evidence show");
9146
+ const evidenceId = positional(args, 0);
9147
+ if (!evidenceId) throw new Error("evidence show requires <evidence_bundle_id>");
9148
+ const store = await openLocalStore(args);
9149
+ try {
9150
+ const evidence2 = store.getEvidenceBundle(evidenceId);
9151
+ if (!evidence2) throw new Error(`evidence bundle not found: ${evidenceId}`);
9152
+ if (args.includes("--json")) process2.stdout.write(`${JSON.stringify(evidence2, null, 2)}
9153
+ `);
9154
+ else process2.stdout.write(formatEvidenceDetail(evidence2));
9155
+ return 0;
9156
+ } finally {
9157
+ store.close();
9158
+ }
9159
+ }
9160
+ async function evidenceExport(args) {
9161
+ assertKnownOptions(args, exportAllowedOptions, "evidence export");
9162
+ const evidenceId = positional(args, 0) ?? optionalArg(args, "--evidence");
9163
+ if (!evidenceId) throw new Error("evidence export requires <evidence_bundle_id>");
9164
+ const output = outputArg(args);
9165
+ if (!output) throw new Error("evidence export requires --output <path>");
9166
+ const format = exportFormat(args);
9167
+ const store = await openLocalStore(args);
9168
+ try {
9169
+ const evidence2 = store.getEvidenceBundle(evidenceId);
9170
+ if (!evidence2) throw new Error(`evidence bundle not found: ${evidenceId}`);
9171
+ const text = format === "json" ? `${JSON.stringify(evidence2, null, 2)}
9172
+ ` : formatEvidenceMarkdown(evidence2);
9173
+ await fs3.mkdir(path3.dirname(path3.resolve(output)), { recursive: true });
9174
+ await fs3.writeFile(output, text, "utf8");
9175
+ process2.stdout.write(`exported ${evidence2.evidence_bundle_id} to ${output}
9176
+ `);
9177
+ return 0;
9178
+ } finally {
9179
+ store.close();
9180
+ }
9181
+ }
9182
+ async function queryAuditList(args) {
9183
+ assertKnownOptions(args, queryAuditListAllowedOptions, "query-audit list");
9184
+ const store = await openLocalStore(args);
9185
+ try {
9186
+ const rows = store.listQueryAudit(queryAuditFiltersFromArgs(args));
9187
+ if (args.includes("--json")) process2.stdout.write(`${JSON.stringify({ query_audit: rows }, null, 2)}
9188
+ `);
9189
+ else if (rows.length === 0) process2.stdout.write("No query audit records found.\n");
9190
+ else for (const row of rows) process2.stdout.write(formatQueryAuditSummary(row));
9191
+ return 0;
9192
+ } finally {
9193
+ store.close();
9194
+ }
9195
+ }
9196
+ async function queryAuditShow(args) {
9197
+ assertKnownOptions(args, showAllowedOptions, "query-audit show");
9198
+ const auditId = Number(positional(args, 0));
9199
+ if (!Number.isInteger(auditId) || auditId <= 0) throw new Error("query-audit show requires <audit_id>");
9200
+ const store = await openLocalStore(args);
9201
+ try {
9202
+ const row = store.getQueryAudit(auditId);
9203
+ if (!row) throw new Error(`query audit record not found: ${auditId}`);
9204
+ if (args.includes("--json")) process2.stdout.write(`${JSON.stringify(row, null, 2)}
8078
9205
  `);
8079
- process2.stdout.write(formatProposalDetail(replayRecord.proposal));
8080
- process2.stdout.write(`events: ${replayRecord.events.length}
9206
+ else process2.stdout.write(formatQueryAuditDetail(row));
9207
+ return 0;
9208
+ } finally {
9209
+ store.close();
9210
+ }
9211
+ }
9212
+ async function queryAuditExport(args) {
9213
+ assertKnownOptions(args, exportAllowedOptions, "query-audit export");
9214
+ const auditId = Number(positional(args, 0) ?? optionalArg(args, "--audit"));
9215
+ if (!Number.isInteger(auditId) || auditId <= 0) throw new Error("query-audit export requires <audit_id>");
9216
+ const output = outputArg(args);
9217
+ if (!output) throw new Error("query-audit export requires --output <path>");
9218
+ const format = exportFormat(args, ["json"]);
9219
+ const store = await openLocalStore(args);
9220
+ try {
9221
+ const row = store.getQueryAudit(auditId);
9222
+ if (!row) throw new Error(`query audit record not found: ${auditId}`);
9223
+ await fs3.mkdir(path3.dirname(path3.resolve(output)), { recursive: true });
9224
+ await fs3.writeFile(output, `${JSON.stringify(row, null, 2)}
9225
+ `, "utf8");
9226
+ process2.stdout.write(`exported query audit ${auditId} to ${output}
8081
9227
  `);
8082
- process2.stdout.write(`receipts: ${replayRecord.receipts.length}
9228
+ return 0;
9229
+ } finally {
9230
+ store.close();
9231
+ }
9232
+ }
9233
+ async function receiptsList(args) {
9234
+ assertKnownOptions(args, receiptListAllowedOptions, "receipts list");
9235
+ const store = await openLocalStore(args);
9236
+ try {
9237
+ const rows = store.listReceipts(receiptFiltersFromArgs(args));
9238
+ if (args.includes("--json")) process2.stdout.write(`${JSON.stringify({ receipts: rows }, null, 2)}
9239
+ `);
9240
+ else if (rows.length === 0) process2.stdout.write("No writeback receipts found.\n");
9241
+ else for (const receipt of rows) process2.stdout.write(formatReceiptSummary(receipt));
9242
+ return 0;
9243
+ } finally {
9244
+ store.close();
9245
+ }
9246
+ }
9247
+ async function receiptsShow(args) {
9248
+ assertKnownOptions(args, showAllowedOptions, "receipts show");
9249
+ const receiptId = Number(positional(args, 0));
9250
+ if (!Number.isInteger(receiptId) || receiptId <= 0) throw new Error("receipts show requires <receipt_id>");
9251
+ const store = await openLocalStore(args);
9252
+ try {
9253
+ const receipt = store.getReceipt(receiptId);
9254
+ if (!receipt) throw new Error(`writeback receipt not found: ${receiptId}`);
9255
+ if (args.includes("--json")) process2.stdout.write(`${JSON.stringify(receipt, null, 2)}
8083
9256
  `);
8084
- process2.stdout.write(`evidence bundles: ${replayRecord.evidence.length}
9257
+ else process2.stdout.write(formatReceiptDetail(receipt));
9258
+ return 0;
9259
+ } finally {
9260
+ store.close();
9261
+ }
9262
+ }
9263
+ async function replayList(args) {
9264
+ assertKnownOptions(args, replayListAllowedOptions, "replay list");
9265
+ const store = await openLocalStore(args);
9266
+ try {
9267
+ const filters = proposalFiltersFromReplayArgs(args, store);
9268
+ const proposals2 = store.listProposals(filters);
9269
+ const rows = proposals2.map((proposal) => ({
9270
+ replay_id: `replay_${proposal.proposal_id}`,
9271
+ proposal_id: proposal.proposal_id,
9272
+ created_at: proposal.created_at,
9273
+ state: proposal.state,
9274
+ tenant_id: proposal.tenant_id,
9275
+ principal: proposal.principal ?? proposal.change_set.principal.id,
9276
+ capability: proposal.action,
9277
+ business_object: proposal.business_object,
9278
+ object_id: proposal.object_id
9279
+ }));
9280
+ if (args.includes("--json")) process2.stdout.write(`${JSON.stringify({ replays: rows }, null, 2)}
8085
9281
  `);
8086
- process2.stdout.write(`query audit records: ${replayRecord.query_audit.length}
9282
+ else if (rows.length === 0) process2.stdout.write("No replay records found.\n");
9283
+ else for (const row of rows) process2.stdout.write(formatReplaySummary(row));
9284
+ return 0;
9285
+ } finally {
9286
+ store.close();
9287
+ }
9288
+ }
9289
+ async function replayShow(args) {
9290
+ assertKnownOptions(args, replayShowAllowedOptions, "replay show");
9291
+ const store = await openLocalStore(args);
9292
+ try {
9293
+ const resolvedProposalId = resolveReplayProposalId(args, store);
9294
+ const replayRecord = store.replay(resolvedProposalId);
9295
+ if (args.includes("--json")) {
9296
+ process2.stdout.write(`${JSON.stringify(replayRecord, null, 2)}
8087
9297
  `);
9298
+ } else {
9299
+ process2.stdout.write(formatReplayDetail(replayRecord));
8088
9300
  }
8089
9301
  return 0;
8090
9302
  } finally {
@@ -8092,17 +9304,18 @@ async function replayShow(args) {
8092
9304
  }
8093
9305
  }
8094
9306
  async function replayExport(args) {
8095
- const proposalId = positional(args, 0);
8096
- if (!proposalId) throw new Error("replay export requires <proposal_id>");
9307
+ assertKnownOptions(args, replayExportAllowedOptions, "replay export");
8097
9308
  const output = outputArg(args);
8098
9309
  if (!output) throw new Error("replay export requires --output <path>");
9310
+ const format = exportFormat(args);
8099
9311
  const store = await openLocalStore(args);
8100
9312
  try {
8101
- const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
9313
+ const resolvedProposalId = resolveReplayProposalId(args, store);
8102
9314
  const replayRecord = store.replay(resolvedProposalId);
9315
+ const text = format === "json" ? `${JSON.stringify(replayRecord, null, 2)}
9316
+ ` : formatReplayMarkdown(replayRecord);
8103
9317
  await fs3.mkdir(path3.dirname(path3.resolve(output)), { recursive: true });
8104
- await fs3.writeFile(output, `${JSON.stringify(replayRecord, null, 2)}
8105
- `, "utf8");
9318
+ await fs3.writeFile(output, text, "utf8");
8106
9319
  process2.stdout.write(`exported ${replayRecord.replay_id} to ${output}
8107
9320
  `);
8108
9321
  return 0;
@@ -8110,9 +9323,421 @@ async function replayExport(args) {
8110
9323
  store.close();
8111
9324
  }
8112
9325
  }
9326
+ async function activitySearch(args) {
9327
+ assertKnownOptions(args, activitySearchAllowedOptions, "activity search");
9328
+ const store = await openLocalStore(args);
9329
+ try {
9330
+ const proposalFilters = proposalFiltersFromActivityArgs(args, store);
9331
+ const evidenceFilters = evidenceFiltersFromActivityArgs(args, store);
9332
+ const queryAuditFilters = queryAuditFiltersFromActivityArgs(args, store);
9333
+ const receiptFilters = receiptFiltersFromActivityArgs(args, store);
9334
+ const proposals2 = store.listProposals(proposalFilters);
9335
+ const evidenceRows = store.listEvidenceBundles(evidenceFilters);
9336
+ const queryAuditRows = store.listQueryAudit(queryAuditFilters);
9337
+ const receiptsRows = store.listReceipts(receiptFilters);
9338
+ const proposalIds = new Set(proposals2.map((proposal) => proposal.proposal_id));
9339
+ const evidenceIds = new Set(evidenceRows.map((evidence2) => evidence2.evidence_bundle_id));
9340
+ const results = proposals2.map((proposal) => activityFromProposal(proposal));
9341
+ for (const evidence2 of evidenceRows) {
9342
+ if (evidence2.proposal_id && proposalIds.has(evidence2.proposal_id)) continue;
9343
+ results.push(activityFromEvidence(evidence2));
9344
+ }
9345
+ for (const audit2 of queryAuditRows) {
9346
+ const proposalId = stringField(audit2, "proposal_id");
9347
+ const evidenceId = stringField(audit2, "evidence_bundle_id");
9348
+ if (proposalId && proposalIds.has(proposalId)) continue;
9349
+ if (evidenceId && evidenceIds.has(evidenceId)) continue;
9350
+ results.push(activityFromQueryAudit(audit2));
9351
+ }
9352
+ for (const receipt of receiptsRows) {
9353
+ if (proposalIds.has(receipt.proposal_id)) continue;
9354
+ results.push(activityFromReceipt(receipt));
9355
+ }
9356
+ const sorted = results.sort((left, right) => String(right.created_at ?? "").localeCompare(String(left.created_at ?? ""))).slice(0, limitFromArgs(args));
9357
+ if (args.includes("--json")) {
9358
+ process2.stdout.write(`${JSON.stringify({ interactions: sorted }, null, 2)}
9359
+ `);
9360
+ } else if (sorted.length === 0) {
9361
+ process2.stdout.write("No local interactions found.\n");
9362
+ } else {
9363
+ process2.stdout.write(`Found ${sorted.length} local interaction${sorted.length === 1 ? "" : "s"}
9364
+
9365
+ `);
9366
+ sorted.forEach((item, index) => process2.stdout.write(formatActivityItem(item, index + 1)));
9367
+ }
9368
+ return 0;
9369
+ } finally {
9370
+ store.close();
9371
+ }
9372
+ }
9373
+ async function storeStats(args) {
9374
+ assertKnownOptions(args, storeStatsAllowedOptions, "store stats");
9375
+ const store = await openLocalStore(args);
9376
+ try {
9377
+ const stats = store.stats();
9378
+ if (args.includes("--json")) process2.stdout.write(`${JSON.stringify(stats, null, 2)}
9379
+ `);
9380
+ else process2.stdout.write(formatStoreStats(stats));
9381
+ return 0;
9382
+ } finally {
9383
+ store.close();
9384
+ }
9385
+ }
9386
+ async function storeVacuum(args) {
9387
+ assertKnownOptions(args, storeVacuumAllowedOptions, "store vacuum");
9388
+ const store = await openLocalStore(args);
9389
+ try {
9390
+ const before = store.stats();
9391
+ store.vacuum();
9392
+ const after = store.stats();
9393
+ if (args.includes("--json")) process2.stdout.write(`${JSON.stringify({ before, after }, null, 2)}
9394
+ `);
9395
+ else process2.stdout.write(`vacuumed local store ${before.path}
9396
+ approx bytes: ${before.approx_bytes} -> ${after.approx_bytes}
9397
+ `);
9398
+ return 0;
9399
+ } finally {
9400
+ store.close();
9401
+ }
9402
+ }
9403
+ async function storePrune(args) {
9404
+ assertKnownOptions(args, storePruneAllowedOptions, "store prune");
9405
+ const olderThan = optionalArg(args, "--older-than");
9406
+ if (!olderThan) throw new Error("store prune requires --older-than <duration>, for example --older-than 30d");
9407
+ if (args.includes("--yes") && args.includes("--dry-run")) throw new Error("store prune accepts either --dry-run or --yes, not both");
9408
+ const cutoff = cutoffFromOlderThan(olderThan);
9409
+ const dryRun = !args.includes("--yes");
9410
+ const store = await openLocalStore(args);
9411
+ try {
9412
+ const result = store.pruneBefore(cutoff, { dryRun });
9413
+ if (args.includes("--json")) process2.stdout.write(`${JSON.stringify(result, null, 2)}
9414
+ `);
9415
+ else process2.stdout.write(formatStorePrune(result));
9416
+ return 0;
9417
+ } finally {
9418
+ store.close();
9419
+ }
9420
+ }
9421
+ var commonReadOptions = /* @__PURE__ */ new Set(["--store", "--json"]);
9422
+ var showAllowedOptions = /* @__PURE__ */ new Set([...commonReadOptions]);
9423
+ var exportAllowedOptions = /* @__PURE__ */ new Set([...commonReadOptions, "--output", "--out", "--format", "--evidence", "--audit"]);
9424
+ var proposalListAllowedOptions = /* @__PURE__ */ new Set([
9425
+ ...commonReadOptions,
9426
+ "--tenant",
9427
+ "--principal",
9428
+ "--capability",
9429
+ "--action",
9430
+ "--object",
9431
+ "--object-type",
9432
+ "--object-id",
9433
+ "--status",
9434
+ "--state",
9435
+ "--source",
9436
+ "--table",
9437
+ "--from",
9438
+ "--to",
9439
+ "--limit"
9440
+ ]);
9441
+ var evidenceListAllowedOptions = /* @__PURE__ */ new Set([
9442
+ ...commonReadOptions,
9443
+ "--tenant",
9444
+ "--principal",
9445
+ "--capability",
9446
+ "--proposal",
9447
+ "--object",
9448
+ "--object-type",
9449
+ "--object-id",
9450
+ "--source",
9451
+ "--table",
9452
+ "--query-fingerprint",
9453
+ "--from",
9454
+ "--to",
9455
+ "--limit"
9456
+ ]);
9457
+ var queryAuditListAllowedOptions = /* @__PURE__ */ new Set([
9458
+ ...commonReadOptions,
9459
+ "--tenant",
9460
+ "--proposal",
9461
+ "--evidence",
9462
+ "--source",
9463
+ "--table",
9464
+ "--primary-key",
9465
+ "--query-fingerprint",
9466
+ "--from",
9467
+ "--to",
9468
+ "--limit"
9469
+ ]);
9470
+ var receiptListAllowedOptions = /* @__PURE__ */ new Set([
9471
+ ...commonReadOptions,
9472
+ "--proposal",
9473
+ "--writeback-job",
9474
+ "--idempotency-key",
9475
+ "--status",
9476
+ "--from",
9477
+ "--to",
9478
+ "--limit"
9479
+ ]);
9480
+ var replayShowAllowedOptions = /* @__PURE__ */ new Set([...commonReadOptions, "--proposal", "--replay", "--evidence"]);
9481
+ var replayExportAllowedOptions = /* @__PURE__ */ new Set([...replayShowAllowedOptions, "--output", "--out", "--format"]);
9482
+ var replayListAllowedOptions = /* @__PURE__ */ new Set([
9483
+ ...commonReadOptions,
9484
+ "--tenant",
9485
+ "--principal",
9486
+ "--capability",
9487
+ "--proposal",
9488
+ "--evidence",
9489
+ "--receipt",
9490
+ "--object",
9491
+ "--object-type",
9492
+ "--object-id",
9493
+ "--status",
9494
+ "--state",
9495
+ "--from",
9496
+ "--to",
9497
+ "--limit"
9498
+ ]);
9499
+ var activitySearchAllowedOptions = /* @__PURE__ */ new Set([
9500
+ ...commonReadOptions,
9501
+ "--tenant",
9502
+ "--principal",
9503
+ "--capability",
9504
+ "--object",
9505
+ "--object-type",
9506
+ "--object-id",
9507
+ "--proposal",
9508
+ "--evidence",
9509
+ "--replay",
9510
+ "--receipt",
9511
+ "--source",
9512
+ "--table",
9513
+ "--query-fingerprint",
9514
+ "--status",
9515
+ "--state",
9516
+ "--from",
9517
+ "--to",
9518
+ "--limit"
9519
+ ]);
9520
+ var storeStatsAllowedOptions = /* @__PURE__ */ new Set([...commonReadOptions]);
9521
+ var storeVacuumAllowedOptions = /* @__PURE__ */ new Set([...commonReadOptions]);
9522
+ var storePruneAllowedOptions = /* @__PURE__ */ new Set([...commonReadOptions, "--older-than", "--dry-run", "--yes"]);
9523
+ function assertKnownOptions(args, allowed, commandName) {
9524
+ for (const arg of args) {
9525
+ if (!arg.startsWith("--")) continue;
9526
+ const option = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
9527
+ if (option === "--help" || option === "-h") continue;
9528
+ if (!allowed.has(option)) throw new Error(`Unknown option for ${commandName}: ${option}`);
9529
+ }
9530
+ }
9531
+ function proposalFiltersFromArgs(args) {
9532
+ const object = objectFilterFromArgs(args);
9533
+ return {
9534
+ proposal: optionalArg(args, "--proposal"),
9535
+ tenant: optionalArg(args, "--tenant"),
9536
+ principal: optionalArg(args, "--principal"),
9537
+ capability: optionalArg(args, "--capability"),
9538
+ action: optionalArg(args, "--action"),
9539
+ objectType: optionalArg(args, "--object-type") ?? object.type,
9540
+ objectId: optionalArg(args, "--object-id") ?? object.id,
9541
+ status: optionalArg(args, "--status"),
9542
+ state: optionalArg(args, "--state"),
9543
+ source: optionalArg(args, "--source"),
9544
+ table: optionalArg(args, "--table"),
9545
+ from: optionalArg(args, "--from"),
9546
+ to: optionalArg(args, "--to"),
9547
+ limit: limitFromArgs(args)
9548
+ };
9549
+ }
9550
+ function evidenceFiltersFromArgs(args) {
9551
+ const object = objectFilterFromArgs(args);
9552
+ return {
9553
+ evidence: optionalArg(args, "--evidence"),
9554
+ tenant: optionalArg(args, "--tenant"),
9555
+ principal: optionalArg(args, "--principal"),
9556
+ capability: optionalArg(args, "--capability"),
9557
+ proposal: optionalArg(args, "--proposal"),
9558
+ objectType: optionalArg(args, "--object-type") ?? object.type,
9559
+ objectId: optionalArg(args, "--object-id") ?? object.id,
9560
+ source: optionalArg(args, "--source"),
9561
+ table: optionalArg(args, "--table"),
9562
+ queryFingerprint: optionalArg(args, "--query-fingerprint"),
9563
+ from: optionalArg(args, "--from"),
9564
+ to: optionalArg(args, "--to"),
9565
+ limit: limitFromArgs(args)
9566
+ };
9567
+ }
9568
+ function queryAuditFiltersFromArgs(args) {
9569
+ const object = objectFilterFromArgs(args);
9570
+ return {
9571
+ tenant: optionalArg(args, "--tenant"),
9572
+ principal: optionalArg(args, "--principal"),
9573
+ capability: optionalArg(args, "--capability"),
9574
+ proposal: optionalArg(args, "--proposal"),
9575
+ evidence: optionalArg(args, "--evidence"),
9576
+ source: optionalArg(args, "--source"),
9577
+ table: optionalArg(args, "--table"),
9578
+ objectType: optionalArg(args, "--object-type") ?? object.type,
9579
+ objectId: optionalArg(args, "--object-id") ?? object.id,
9580
+ primaryKey: optionalArg(args, "--primary-key"),
9581
+ queryFingerprint: optionalArg(args, "--query-fingerprint"),
9582
+ from: optionalArg(args, "--from"),
9583
+ to: optionalArg(args, "--to"),
9584
+ limit: limitFromArgs(args)
9585
+ };
9586
+ }
9587
+ function receiptFiltersFromArgs(args) {
9588
+ return {
9589
+ receipt: optionalArg(args, "--receipt"),
9590
+ proposal: optionalArg(args, "--proposal"),
9591
+ writebackJob: optionalArg(args, "--writeback-job"),
9592
+ idempotencyKey: optionalArg(args, "--idempotency-key"),
9593
+ status: optionalArg(args, "--status"),
9594
+ from: optionalArg(args, "--from"),
9595
+ to: optionalArg(args, "--to"),
9596
+ limit: limitFromArgs(args)
9597
+ };
9598
+ }
9599
+ function proposalFiltersFromReplayArgs(args, store) {
9600
+ return proposalFiltersFromActivityArgs(args, store);
9601
+ }
9602
+ function proposalFiltersFromActivityArgs(args, store) {
9603
+ const object = objectFilterFromArgs(args);
9604
+ const linkedProposal = linkedProposalFilter(args, store);
9605
+ return {
9606
+ proposal: optionalArg(args, "--proposal") ?? linkedProposal,
9607
+ tenant: optionalArg(args, "--tenant"),
9608
+ principal: optionalArg(args, "--principal"),
9609
+ capability: optionalArg(args, "--capability"),
9610
+ action: optionalArg(args, "--capability"),
9611
+ objectType: optionalArg(args, "--object-type") ?? object.type,
9612
+ objectId: optionalArg(args, "--object-id") ?? object.id,
9613
+ status: optionalArg(args, "--status"),
9614
+ state: optionalArg(args, "--state"),
9615
+ source: optionalArg(args, "--source"),
9616
+ table: optionalArg(args, "--table"),
9617
+ from: optionalArg(args, "--from"),
9618
+ to: optionalArg(args, "--to"),
9619
+ limit: limitFromArgs(args)
9620
+ };
9621
+ }
9622
+ function evidenceFiltersFromActivityArgs(args, store) {
9623
+ const object = objectFilterFromArgs(args);
9624
+ const linkedProposal = linkedProposalFilter(args, store, { includeEvidence: false });
9625
+ return {
9626
+ evidence: optionalArg(args, "--evidence"),
9627
+ tenant: optionalArg(args, "--tenant"),
9628
+ principal: optionalArg(args, "--principal"),
9629
+ capability: optionalArg(args, "--capability"),
9630
+ proposal: optionalArg(args, "--proposal") ?? linkedProposal,
9631
+ objectType: optionalArg(args, "--object-type") ?? object.type,
9632
+ objectId: optionalArg(args, "--object-id") ?? object.id,
9633
+ source: optionalArg(args, "--source"),
9634
+ table: optionalArg(args, "--table"),
9635
+ queryFingerprint: optionalArg(args, "--query-fingerprint"),
9636
+ from: optionalArg(args, "--from"),
9637
+ to: optionalArg(args, "--to"),
9638
+ limit: limitFromArgs(args)
9639
+ };
9640
+ }
9641
+ function queryAuditFiltersFromActivityArgs(args, store) {
9642
+ const object = objectFilterFromArgs(args);
9643
+ const linkedProposal = linkedProposalFilter(args, store, { includeEvidence: false });
9644
+ return {
9645
+ tenant: optionalArg(args, "--tenant"),
9646
+ principal: optionalArg(args, "--principal"),
9647
+ capability: optionalArg(args, "--capability"),
9648
+ proposal: optionalArg(args, "--proposal") ?? linkedProposal,
9649
+ evidence: optionalArg(args, "--evidence"),
9650
+ source: optionalArg(args, "--source"),
9651
+ table: optionalArg(args, "--table"),
9652
+ objectType: optionalArg(args, "--object-type") ?? object.type,
9653
+ objectId: optionalArg(args, "--object-id") ?? object.id,
9654
+ queryFingerprint: optionalArg(args, "--query-fingerprint"),
9655
+ from: optionalArg(args, "--from"),
9656
+ to: optionalArg(args, "--to"),
9657
+ limit: limitFromArgs(args)
9658
+ };
9659
+ }
9660
+ function receiptFiltersFromActivityArgs(args, store) {
9661
+ const linkedProposal = linkedProposalFilter(args, store, { includeReceipt: false });
9662
+ return {
9663
+ receipt: optionalArg(args, "--receipt"),
9664
+ proposal: optionalArg(args, "--proposal") ?? linkedProposal,
9665
+ status: optionalArg(args, "--status") ?? optionalArg(args, "--state"),
9666
+ from: optionalArg(args, "--from"),
9667
+ to: optionalArg(args, "--to"),
9668
+ limit: limitFromArgs(args)
9669
+ };
9670
+ }
9671
+ function linkedProposalFilter(args, store, options = {}) {
9672
+ const noLinkedProposal = "__synapsor_no_linked_proposal__";
9673
+ const replay2 = optionalArg(args, "--replay");
9674
+ if (replay2) return proposalIdFromReplayId(replay2);
9675
+ if (!store) return void 0;
9676
+ if (options.includeEvidence !== false) {
9677
+ const evidence2 = optionalArg(args, "--evidence");
9678
+ if (evidence2) return store.proposalIdForEvidence(evidence2) ?? noLinkedProposal;
9679
+ }
9680
+ if (options.includeReceipt !== false) {
9681
+ const receiptValue = optionalArg(args, "--receipt");
9682
+ if (receiptValue) {
9683
+ const receiptId = Number(receiptValue);
9684
+ if (!Number.isInteger(receiptId) || receiptId <= 0) throw new Error("--receipt must be a positive receipt id");
9685
+ return store.getReceipt(receiptId)?.proposal_id ?? noLinkedProposal;
9686
+ }
9687
+ }
9688
+ return void 0;
9689
+ }
9690
+ function objectFilterFromArgs(args) {
9691
+ const value = optionalArg(args, "--object");
9692
+ if (!value) return {};
9693
+ const separator = value.indexOf(":");
9694
+ if (separator <= 0 || separator === value.length - 1) {
9695
+ throw new Error("--object must use type:id, for example invoice:INV-3001");
9696
+ }
9697
+ return { type: value.slice(0, separator), id: value.slice(separator + 1) };
9698
+ }
9699
+ function limitFromArgs(args) {
9700
+ const value = optionalArg(args, "--limit");
9701
+ if (!value) return 20;
9702
+ const parsed = Number(value);
9703
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new Error("--limit must be a positive integer");
9704
+ return Math.min(parsed, 200);
9705
+ }
9706
+ function exportFormat(args, supported = ["json", "markdown"]) {
9707
+ const format = optionalArg(args, "--format") ?? "json";
9708
+ if (!supported.includes(format)) {
9709
+ throw new Error(`unsupported export format: ${format}. Supported formats: ${supported.join(", ")}`);
9710
+ }
9711
+ return format;
9712
+ }
9713
+ function resolveReplayProposalId(args, store) {
9714
+ const explicitProposal = optionalArg(args, "--proposal");
9715
+ if (explicitProposal) return resolveProposalIdFromStore(explicitProposal, store);
9716
+ const explicitReplay = optionalArg(args, "--replay");
9717
+ if (explicitReplay) return proposalIdFromReplayId(explicitReplay);
9718
+ const explicitEvidence = optionalArg(args, "--evidence");
9719
+ if (explicitEvidence) {
9720
+ const proposalId = store.proposalIdForEvidence(explicitEvidence);
9721
+ if (!proposalId) throw new Error(`evidence bundle ${explicitEvidence} is not linked to a replayable proposal`);
9722
+ return proposalId;
9723
+ }
9724
+ const value = positional(args, 0);
9725
+ if (!value) throw new Error("replay show requires <proposal_id>, --proposal <proposal_id>, --replay <replay_id>, or --evidence <evidence_bundle_id>");
9726
+ if (value === "latest") return resolveProposalIdFromStore(value, store);
9727
+ if (value.startsWith("replay_")) return proposalIdFromReplayId(value);
9728
+ if (value.startsWith("ev_")) throw new Error(`Use --evidence ${value} to replay from an evidence bundle.`);
9729
+ return resolveProposalIdFromStore(value, store);
9730
+ }
9731
+ function proposalIdFromReplayId(replayId) {
9732
+ if (!replayId.startsWith("replay_")) throw new Error(`invalid replay id: ${replayId}`);
9733
+ const proposalId = replayId.slice("replay_".length);
9734
+ if (!proposalId) throw new Error(`invalid replay id: ${replayId}`);
9735
+ return proposalId;
9736
+ }
8113
9737
  async function openLocalStore(args) {
8114
9738
  const storePath = optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE ?? "./.synapsor/local.db";
8115
9739
  if (storePath !== ":memory:") {
9740
+ if (!await fileExists(storePath)) throw missingLocalStoreError(storePath);
8116
9741
  await fs3.mkdir(path3.dirname(path3.resolve(storePath)), { recursive: true });
8117
9742
  }
8118
9743
  return new ProposalStore(storePath);
@@ -8157,6 +9782,7 @@ function requireLocalProposal(store, proposalId) {
8157
9782
  }
8158
9783
  async function resolveProposalId(proposalId, storePath) {
8159
9784
  if (proposalId !== "latest") return proposalId;
9785
+ if (storePath !== ":memory:" && !await fileExists(storePath)) throw missingLocalStoreError(storePath);
8160
9786
  const store = new ProposalStore(storePath);
8161
9787
  try {
8162
9788
  return resolveProposalIdFromStore(proposalId, store);
@@ -8170,6 +9796,15 @@ function resolveProposalIdFromStore(proposalId, store) {
8170
9796
  if (!latest) throw new Error("no proposals found in the local store");
8171
9797
  return latest.proposal_id;
8172
9798
  }
9799
+ function missingLocalStoreError(storePath) {
9800
+ return new Error([
9801
+ `No local Synapsor proposal store was found at ${storePath}.`,
9802
+ "Run:",
9803
+ `${cliCommandName()} demo`,
9804
+ "or pass:",
9805
+ "--store /path/to/local.db"
9806
+ ].join("\n"));
9807
+ }
8173
9808
  async function readRuntimeConfig(configPath) {
8174
9809
  const parsed = JSON.parse(await fs3.readFile(configPath, "utf8"));
8175
9810
  return parsed;
@@ -8320,24 +9955,36 @@ function firstPositional(args) {
8320
9955
  "--allowed-columns",
8321
9956
  "--approval-role",
8322
9957
  "--actor",
9958
+ "--action",
9959
+ "--audit",
8323
9960
  "--bearer-env",
9961
+ "--capability",
8324
9962
  "--config",
8325
9963
  "--conflict-column",
8326
9964
  "--database-url-env",
8327
9965
  "--destination",
8328
9966
  "--engine",
9967
+ "--evidence",
9968
+ "--example",
9969
+ "--format",
8329
9970
  "--from",
8330
9971
  "--from-env",
8331
9972
  "--host",
9973
+ "--idempotency-key",
8332
9974
  "--input",
8333
9975
  "--job",
8334
9976
  "--lease-seconds",
9977
+ "--limit",
8335
9978
  "--lookup-arg",
8336
9979
  "--mode",
8337
9980
  "--mcp-config",
8338
9981
  "--namespace",
8339
9982
  "--numeric-bound",
9983
+ "--object",
9984
+ "--object-id",
9985
+ "--object-type",
8340
9986
  "--object-name",
9987
+ "--older-than",
8341
9988
  "--output",
8342
9989
  "--out",
8343
9990
  "--patch-fixed",
@@ -8345,22 +9992,31 @@ function firstPositional(args) {
8345
9992
  "--port",
8346
9993
  "--primary-key",
8347
9994
  "--principal-env",
9995
+ "--proposal",
8348
9996
  "--project",
9997
+ "--query-fingerprint",
8349
9998
  "--reason",
8350
9999
  "--recipe",
10000
+ "--receipt",
10001
+ "--replay",
8351
10002
  "--runner",
8352
10003
  "--schema",
8353
10004
  "--source-name",
10005
+ "--source",
8354
10006
  "--state",
10007
+ "--status",
8355
10008
  "--stdio",
8356
10009
  "--store",
8357
10010
  "--table",
10011
+ "--tenant",
8358
10012
  "--tenant-env",
8359
10013
  "--tenant-key",
8360
10014
  "--timeout-ms",
10015
+ "--to",
8361
10016
  "--transition-guard",
8362
10017
  "--url",
8363
10018
  "--visible-columns",
10019
+ "--writeback-job",
8364
10020
  "--write-url-env"
8365
10021
  ]);
8366
10022
  for (let index = 0; index < args.length; index += 1) {
@@ -8436,15 +10092,16 @@ function parseJsonRpcResponse(stdout, id) {
8436
10092
  }
8437
10093
  function formatProposalSummary(proposal) {
8438
10094
  return [
8439
- `${proposal.proposal_id} ${proposal.state} ${proposal.action}`,
10095
+ `${proposal.created_at} ${proposal.proposal_id} ${proposal.state} ${proposal.action}`,
10096
+ ` object: ${proposal.business_object}:${proposal.object_id}`,
8440
10097
  ` target: ${proposal.source_kind}:${proposal.source_id}/${proposal.source_schema}.${proposal.source_table}/${proposal.object_id}`,
8441
10098
  ` tenant: ${proposal.tenant_id} source changed: ${proposal.source_database_mutated ? "yes" : "no"}`
8442
10099
  ].join("\n") + "\n";
8443
10100
  }
8444
- function formatProposalDetail(proposal) {
10101
+ function formatProposalDetail(proposal, storedEvidenceItemCount) {
8445
10102
  const changeSet = proposal.change_set;
8446
10103
  const conflictGuard = changeSet.guards.expected_version;
8447
- const evidenceItems = changeSet.evidence.items?.length ?? 0;
10104
+ const evidenceItems = storedEvidenceItemCount ?? changeSet.evidence.items?.length ?? 0;
8448
10105
  const approvalStatus = currentApprovalStatus(proposal);
8449
10106
  const writebackStatus = currentWritebackStatus(proposal);
8450
10107
  return [
@@ -8471,6 +10128,386 @@ function formatProposalDetail(proposal) {
8471
10128
  })
8472
10129
  ].join("\n") + "\n";
8473
10130
  }
10131
+ function formatEvidenceSummary(evidence2) {
10132
+ return [
10133
+ `${evidence2.created_at} ${evidence2.evidence_bundle_id}`,
10134
+ ` tenant: ${evidence2.tenant_id} capability: ${evidence2.capability ?? "unknown"} proposal: ${evidence2.proposal_id ?? "none"}`,
10135
+ ` source: ${evidence2.source_id ?? "unknown"}/${evidence2.source_table ?? "unknown"} object: ${evidence2.business_object ?? "object"}:${evidence2.object_id ?? "unknown"}`
10136
+ ].join("\n") + "\n";
10137
+ }
10138
+ function formatEvidenceDetail(evidence2) {
10139
+ const audit2 = evidence2.query_audit[0];
10140
+ const lines = [
10141
+ `Evidence bundle: ${evidence2.evidence_bundle_id}`,
10142
+ `Tenant: ${evidence2.tenant_id}`,
10143
+ `Proposal: ${evidence2.proposal_id ?? "none"}`,
10144
+ `Principal: ${evidence2.principal ?? "unknown"}`,
10145
+ `Capability: ${evidence2.capability ?? "unknown"}`,
10146
+ `Source: ${evidence2.source_id ?? "unknown"}`,
10147
+ `Table: ${evidence2.source_table ?? "unknown"}`,
10148
+ `Query fingerprint: ${evidence2.query_fingerprint ?? stringField(audit2, "query_fingerprint") ?? "unknown"}`,
10149
+ `Rows captured: ${evidence2.items.length}`,
10150
+ `Created at: ${evidence2.created_at}`,
10151
+ "Projection: captured visible fields only; credentials and secret-looking values are rejected before persistence.",
10152
+ "",
10153
+ "Items:",
10154
+ ...evidence2.items.flatMap((item, index) => formatEvidenceItem(item, index + 1)),
10155
+ "",
10156
+ "Related:",
10157
+ ...evidence2.proposal_id ? [` ${cliCommandName()} proposals show ${evidence2.proposal_id}`, ` ${cliCommandName()} replay show --proposal ${evidence2.proposal_id}`] : [],
10158
+ ` ${cliCommandName()} query-audit list --evidence ${evidence2.evidence_bundle_id}`
10159
+ ];
10160
+ return `${lines.join("\n")}
10161
+ `;
10162
+ }
10163
+ function formatEvidenceItem(item, index) {
10164
+ const payload = isRecord6(item.item) ? item.item : item;
10165
+ const visibleRow = isRecord6(payload.visible_row) ? payload.visible_row : payload;
10166
+ const title = stringField(payload, "kind") ?? "item";
10167
+ const primaryKey = isRecord6(payload.primary_key) ? payload.primary_key : void 0;
10168
+ const heading = primaryKey ? `* ${title} ${formatScalar(primaryKey.value)}` : `* ${title} ${index}`;
10169
+ const rows = Object.entries(visibleRow).filter(([key]) => !["kind", "source_id", "table", "primary_key", "tenant"].includes(key)).slice(0, 12).map(([key, value]) => ` ${key}: ${formatScalar(value)}`);
10170
+ return [heading, ...rows.length ? rows : [" (no scalar preview fields)"]];
10171
+ }
10172
+ function formatEvidenceMarkdown(evidence2) {
10173
+ return [
10174
+ `# Evidence ${evidence2.evidence_bundle_id}`,
10175
+ "",
10176
+ `- Tenant: ${evidence2.tenant_id}`,
10177
+ `- Proposal: ${evidence2.proposal_id ?? "none"}`,
10178
+ `- Principal: ${evidence2.principal ?? "unknown"}`,
10179
+ `- Capability: ${evidence2.capability ?? "unknown"}`,
10180
+ `- Source: ${evidence2.source_id ?? "unknown"}`,
10181
+ `- Table: ${evidence2.source_table ?? "unknown"}`,
10182
+ `- Query fingerprint: ${evidence2.query_fingerprint ?? "unknown"}`,
10183
+ `- Created at: ${evidence2.created_at}`,
10184
+ "",
10185
+ "## Captured Items",
10186
+ "",
10187
+ "```json",
10188
+ JSON.stringify(evidence2.items, null, 2),
10189
+ "```",
10190
+ "",
10191
+ "## Query Audit",
10192
+ "",
10193
+ "```json",
10194
+ JSON.stringify(evidence2.query_audit, null, 2),
10195
+ "```"
10196
+ ].join("\n") + "\n";
10197
+ }
10198
+ function formatQueryAuditSummary(row) {
10199
+ return [
10200
+ `${row.created_at} audit ${row.audit_id}`,
10201
+ ` source: ${row.source_id}/${row.table_name} rows: ${row.row_count} query: ${row.query_fingerprint}`,
10202
+ ` proposal: ${row.proposal_id ?? "none"} evidence: ${row.evidence_bundle_id ?? "none"}`
10203
+ ].join("\n") + "\n";
10204
+ }
10205
+ function formatQueryAuditDetail(row) {
10206
+ const payload = isRecord6(row.payload) ? row.payload : {};
10207
+ return [
10208
+ `Query audit: ${row.audit_id}`,
10209
+ `Created at: ${row.created_at}`,
10210
+ `Source: ${row.source_id}`,
10211
+ `Table: ${row.table_name}`,
10212
+ `Rows: ${row.row_count}`,
10213
+ `Query fingerprint: ${row.query_fingerprint}`,
10214
+ `Proposal: ${row.proposal_id ?? "none"}`,
10215
+ `Evidence: ${row.evidence_bundle_id ?? "none"}`,
10216
+ `Tenant: ${row.tenant_id ?? "unknown"}`,
10217
+ `Capability: ${row.capability ?? payload.capability ?? "unknown"}`,
10218
+ `Parameters redacted: ${payload.parameters_redacted === true ? "yes" : "unknown"}`,
10219
+ "",
10220
+ "Payload:",
10221
+ JSON.stringify(payload, null, 2)
10222
+ ].join("\n") + "\n";
10223
+ }
10224
+ function formatReceiptSummary(receipt) {
10225
+ return [
10226
+ `${receipt.created_at} receipt ${receipt.receipt_id} ${receipt.status}`,
10227
+ ` proposal: ${receipt.proposal_id} job: ${receipt.writeback_job_id}`,
10228
+ ` idempotency: ${receipt.idempotency_key} source changed: ${receipt.source_database_mutated ? "yes" : "no"}`
10229
+ ].join("\n") + "\n";
10230
+ }
10231
+ function formatReceiptDetail(receipt) {
10232
+ return [
10233
+ `Receipt: ${receipt.receipt_id}`,
10234
+ `Proposal: ${receipt.proposal_id}`,
10235
+ `Writeback job: ${receipt.writeback_job_id}`,
10236
+ `Runner: ${receipt.runner_id}`,
10237
+ `Status: ${receipt.status}`,
10238
+ `Idempotency key: ${receipt.idempotency_key}`,
10239
+ `Source database mutated: ${receipt.source_database_mutated ? "yes" : "no"}`,
10240
+ `Rows affected: ${receipt.receipt.rows_affected}`,
10241
+ `Safe error: ${receipt.receipt.safe_error_code ?? "none"}`,
10242
+ `Receipt hash: ${receipt.receipt.receipt_hash}`,
10243
+ `Created at: ${receipt.created_at}`,
10244
+ "",
10245
+ "Related:",
10246
+ ` ${cliCommandName()} replay show --proposal ${receipt.proposal_id}`
10247
+ ].join("\n") + "\n";
10248
+ }
10249
+ function formatReplaySummary(row) {
10250
+ return [
10251
+ `${row.created_at} ${row.replay_id}`,
10252
+ ` proposal: ${row.proposal_id} status: ${row.state}`,
10253
+ ` tenant: ${row.tenant_id} capability: ${row.capability} object: ${row.business_object}:${row.object_id}`
10254
+ ].join("\n") + "\n";
10255
+ }
10256
+ function formatReplayDetail(replay2) {
10257
+ const evidenceItems = replay2.evidence.reduce((count, item) => {
10258
+ const evidence2 = item;
10259
+ return count + (Array.isArray(evidence2.items) ? evidence2.items.length : 0);
10260
+ }, 0);
10261
+ return [
10262
+ `Replay ${replay2.replay_id}`,
10263
+ formatProposalDetail(replay2.proposal, evidenceItems).trimEnd(),
10264
+ `events: ${replay2.events.length}`,
10265
+ ...replay2.events.map((event) => ` ${event.kind} by ${event.actor} at ${event.created_at}`),
10266
+ `receipts: ${replay2.receipts.length}`,
10267
+ ...replay2.receipts.map((receipt) => ` receipt ${receipt.receipt_id}: ${receipt.status} job ${receipt.writeback_job_id}`),
10268
+ `evidence bundles: ${replay2.evidence.length}`,
10269
+ ...replay2.evidence.map((evidence2) => ` ${evidence2.evidence_bundle_id ?? "unknown"}`),
10270
+ `query audit records: ${replay2.query_audit.length}`,
10271
+ ...replay2.query_audit.map((record) => ` audit ${record.audit_id}: ${record.source_id}/${record.table_name} rows ${record.row_count}`)
10272
+ ].join("\n") + "\n";
10273
+ }
10274
+ function formatReplayMarkdown(replay2) {
10275
+ const proposal = replay2.proposal;
10276
+ const principal = proposal.change_set.principal.id;
10277
+ const approvalEvents = replay2.events.filter((event) => /approved|rejected|canceled/i.test(event.kind));
10278
+ const evidenceLines = replay2.evidence.length > 0 ? replay2.evidence.flatMap((evidence2) => {
10279
+ const record = evidence2;
10280
+ const payload = isRecord6(record.payload) ? record.payload : {};
10281
+ const sourceId = stringField(payload, "source_id") ?? proposal.source_id;
10282
+ const table = stringField(payload, "target") ?? `${proposal.source_schema}.${proposal.source_table}`;
10283
+ const queryFingerprint = stringField(payload, "query_fingerprint") ?? proposal.change_set.evidence.query_fingerprint;
10284
+ return [
10285
+ `- evidence: ${record.evidence_bundle_id ?? proposal.change_set.evidence.bundle_id}`,
10286
+ ` - source: ${sourceId}.${table}`,
10287
+ ` - query fingerprint: ${queryFingerprint}`,
10288
+ ` - rows captured: ${Array.isArray(record.items) ? record.items.length : 0}`
10289
+ ];
10290
+ }) : [`- evidence: ${proposal.change_set.evidence.bundle_id}`, ` - source: ${proposal.source_id}.${proposal.source_schema}.${proposal.source_table}`, ` - query fingerprint: ${proposal.change_set.evidence.query_fingerprint}`, " - rows captured: 0"];
10291
+ const receiptLines = replay2.receipts.length > 0 ? replay2.receipts.flatMap((receipt) => [
10292
+ `- receipt: ${receipt.receipt_id}`,
10293
+ ` - status: ${receipt.status}`,
10294
+ ` - affected rows: ${receipt.receipt.rows_affected}`,
10295
+ ` - idempotency key: ${receipt.idempotency_key}`,
10296
+ ` - source database mutated: ${receipt.source_database_mutated ? "yes" : "no"}`,
10297
+ ...receipt.receipt.safe_error_code ? [` - safe error: ${receipt.receipt.safe_error_code}`] : []
10298
+ ]) : ["- no writeback receipt recorded yet"];
10299
+ return [
10300
+ "# Synapsor Replay",
10301
+ "",
10302
+ `Proposal: ${proposal.proposal_id}`,
10303
+ `Capability: ${proposal.action}`,
10304
+ `Tenant: ${proposal.tenant_id}`,
10305
+ `Object: ${proposal.business_object}:${proposal.object_id}`,
10306
+ `Status: ${proposal.state}`,
10307
+ "",
10308
+ "## What The Agent Requested",
10309
+ "",
10310
+ `The model-facing capability requested \`${proposal.action}\` for ${proposal.business_object}:${proposal.object_id}.`,
10311
+ "The source database was not mutated when the proposal was created.",
10312
+ "",
10313
+ "## Trusted Context",
10314
+ "",
10315
+ `tenant_id = ${proposal.tenant_id}`,
10316
+ `principal = ${principal}`,
10317
+ `principal_source = ${proposal.change_set.principal.source}`,
10318
+ "",
10319
+ "## Evidence",
10320
+ "",
10321
+ ...evidenceLines,
10322
+ "",
10323
+ "## Proposed Diff",
10324
+ "",
10325
+ ...Object.keys(proposal.change_set.patch).map((column) => `- ${column}: ${JSON.stringify(proposal.change_set.before[column])} -> ${JSON.stringify(proposal.change_set.after[column])}`),
10326
+ "",
10327
+ "## Approval",
10328
+ "",
10329
+ ...approvalEvents.length > 0 ? approvalEvents.map((event) => `- ${event.kind} by ${event.actor} at ${event.created_at}`) : [`- ${proposal.change_set.approval.status}${proposal.change_set.approval.required_role ? `; required role: ${proposal.change_set.approval.required_role}` : ""}`],
10330
+ "",
10331
+ "## Guarded Writeback",
10332
+ "",
10333
+ ...receiptLines,
10334
+ "",
10335
+ "## Query Audit",
10336
+ "",
10337
+ ...replay2.query_audit.map((record) => `- audit ${record.audit_id}: ${record.source_id}/${record.table_name} rows ${record.row_count} fingerprint ${record.query_fingerprint}`),
10338
+ "",
10339
+ "## Replay Note",
10340
+ "",
10341
+ "This is local captured interaction replay, not external database time travel. It reconstructs what the runner recorded: trusted context, evidence handles, proposal diff, approval events, query audit, and writeback receipts."
10342
+ ].join("\n") + "\n";
10343
+ }
10344
+ function activityFromProposal(proposal) {
10345
+ return {
10346
+ kind: "proposal",
10347
+ created_at: proposal.created_at,
10348
+ capability: proposal.action,
10349
+ tenant: proposal.tenant_id,
10350
+ principal: proposal.principal ?? proposal.change_set.principal.id,
10351
+ object: `${proposal.business_object}:${proposal.object_id}`,
10352
+ proposal: proposal.proposal_id,
10353
+ evidence: proposal.change_set.evidence.bundle_id,
10354
+ status: proposal.state,
10355
+ replay: `replay_${proposal.proposal_id}`,
10356
+ source: proposal.source_id,
10357
+ table: `${proposal.source_schema}.${proposal.source_table}`
10358
+ };
10359
+ }
10360
+ function activityFromEvidence(evidence2) {
10361
+ return {
10362
+ kind: "evidence",
10363
+ created_at: evidence2.created_at,
10364
+ capability: evidence2.capability,
10365
+ tenant: evidence2.tenant_id,
10366
+ principal: evidence2.principal,
10367
+ object: evidence2.business_object && evidence2.object_id ? `${evidence2.business_object}:${evidence2.object_id}` : void 0,
10368
+ proposal: evidence2.proposal_id,
10369
+ evidence: evidence2.evidence_bundle_id,
10370
+ status: "evidence_recorded",
10371
+ source: evidence2.source_id,
10372
+ table: evidence2.source_table
10373
+ };
10374
+ }
10375
+ function activityFromQueryAudit(audit2) {
10376
+ const businessObject = stringField(audit2, "business_object");
10377
+ const objectId = stringField(audit2, "object_id") ?? stringField(audit2, "primary_key_value");
10378
+ return {
10379
+ kind: "query-audit",
10380
+ created_at: stringField(audit2, "created_at"),
10381
+ capability: stringField(audit2, "capability"),
10382
+ tenant: stringField(audit2, "tenant_id"),
10383
+ principal: stringField(audit2, "principal"),
10384
+ object: businessObject && objectId ? `${businessObject}:${objectId}` : void 0,
10385
+ proposal: stringField(audit2, "proposal_id"),
10386
+ evidence: stringField(audit2, "evidence_bundle_id"),
10387
+ status: "query_audited",
10388
+ source: stringField(audit2, "source_id"),
10389
+ table: stringField(audit2, "table_name"),
10390
+ query_audit: stringField(audit2, "audit_id"),
10391
+ query_fingerprint: stringField(audit2, "query_fingerprint")
10392
+ };
10393
+ }
10394
+ function activityFromReceipt(receipt) {
10395
+ return {
10396
+ kind: "receipt",
10397
+ created_at: receipt.created_at,
10398
+ proposal: receipt.proposal_id,
10399
+ receipt: receipt.receipt_id,
10400
+ status: receipt.status,
10401
+ replay: `replay_${receipt.proposal_id}`,
10402
+ source_database_mutated: receipt.source_database_mutated
10403
+ };
10404
+ }
10405
+ function formatActivityItem(item, index) {
10406
+ const lines = [
10407
+ `${index}. ${item.created_at}`,
10408
+ ` kind: ${item.kind}`,
10409
+ ...item.capability ? [` capability: ${item.capability}`] : [],
10410
+ ...item.tenant ? [` tenant: ${item.tenant}`] : [],
10411
+ ...item.object ? [` object: ${item.object}`] : [],
10412
+ ...item.proposal ? [` proposal: ${item.proposal}`] : [],
10413
+ ...item.evidence ? [` evidence: ${item.evidence}`] : [],
10414
+ ...item.query_audit ? [` query audit: ${item.query_audit}`] : [],
10415
+ ...item.query_fingerprint ? [` query fingerprint: ${item.query_fingerprint}`] : [],
10416
+ ...item.receipt ? [` receipt: ${item.receipt}`] : [],
10417
+ ...item.status ? [` status: ${item.status}`] : [],
10418
+ ...item.replay ? [` replay: ${item.replay}`] : [],
10419
+ ""
10420
+ ];
10421
+ return lines.join("\n");
10422
+ }
10423
+ function formatStoreStats(stats) {
10424
+ return [
10425
+ `Local store: ${stats.path}`,
10426
+ `Approx size: ${stats.approx_bytes} bytes`,
10427
+ `Proposals: ${stats.proposals}`,
10428
+ `Evidence bundles: ${stats.evidence_bundles}`,
10429
+ `Evidence items: ${stats.evidence_items}`,
10430
+ `Query audit records: ${stats.query_audit}`,
10431
+ `Writeback receipts: ${stats.writeback_receipts}`,
10432
+ `Writeback jobs: ${stats.writeback_jobs}`,
10433
+ `Idempotency receipts: ${stats.idempotency_receipts}`,
10434
+ `Replay records: ${stats.replay_records}`,
10435
+ `Approvals: ${stats.approvals}`,
10436
+ `Proposal events: ${stats.proposal_events}`,
10437
+ `Shadow human actions: ${stats.shadow_human_actions}`
10438
+ ].join("\n") + "\n";
10439
+ }
10440
+ function formatStorePrune(result) {
10441
+ const lines = [
10442
+ `Local store prune ${result.dry_run ? "dry run" : "complete"}`,
10443
+ `Cutoff: ${result.cutoff}`,
10444
+ "",
10445
+ "Rows:",
10446
+ ...Object.entries(result.deleted).map(([table, count]) => ` ${table}: ${count}`)
10447
+ ];
10448
+ if (result.dry_run) {
10449
+ lines.push("", "No rows were deleted. Rerun with --yes to apply this prune.");
10450
+ }
10451
+ return `${lines.join("\n")}
10452
+ `;
10453
+ }
10454
+ function cutoffFromOlderThan(value) {
10455
+ const match = value.match(/^(\d+)([smhd])$/i);
10456
+ if (!match) throw new Error("--older-than must use a duration such as 30d, 12h, 90m, or 0d");
10457
+ const amount = Number(match[1]);
10458
+ const unit = (match[2] ?? "d").toLowerCase();
10459
+ const multiplier = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
10460
+ return new Date(Date.now() - amount * multiplier).toISOString();
10461
+ }
10462
+ function formatMcpAuditMarkdown(report) {
10463
+ const lines = [
10464
+ "# Synapsor MCP Database Risk Review",
10465
+ "",
10466
+ `- Target: ${report.target}`,
10467
+ `- Generated at: ${report.generated_at}`,
10468
+ `- Tools inspected: ${report.summary.tools_inspected}`,
10469
+ `- Findings: HIGH ${report.summary.high} | MEDIUM ${report.summary.medium} | LOW ${report.summary.low}`,
10470
+ `- Total findings: ${report.summary.total_findings}`,
10471
+ "",
10472
+ `> ${report.disclaimer}`,
10473
+ ""
10474
+ ];
10475
+ if (report.findings.length === 0) {
10476
+ lines.push("No obvious database-commit risks were detected in the static manifest.", "");
10477
+ lines.push("This does not prove the MCP server or its tools are secure.", "");
10478
+ } else {
10479
+ lines.push("## Findings", "");
10480
+ for (const finding of report.findings) {
10481
+ lines.push(`### ${finding.severity}: ${finding.code}${finding.tool ? ` (${finding.tool})` : ""}`);
10482
+ lines.push("");
10483
+ lines.push(finding.message);
10484
+ lines.push("");
10485
+ if (finding.evidence.length > 0) {
10486
+ lines.push("Evidence:");
10487
+ for (const evidence2 of finding.evidence) lines.push(`- ${evidence2}`);
10488
+ lines.push("");
10489
+ }
10490
+ lines.push(`Recommendation: ${finding.recommendation}`);
10491
+ lines.push("");
10492
+ }
10493
+ }
10494
+ lines.push("## Safer Shape", "");
10495
+ lines.push("- expose semantic inspect/propose tools instead of raw SQL;");
10496
+ lines.push("- bind tenant/principal from trusted context;");
10497
+ lines.push("- keep approval outside MCP;");
10498
+ lines.push("- apply approved changes through guarded writeback;");
10499
+ lines.push("- keep replay/evidence handles for later review.");
10500
+ lines.push("");
10501
+ return `${lines.join("\n")}
10502
+ `;
10503
+ }
10504
+ function stringField(record, key) {
10505
+ if (!isRecord6(record)) return void 0;
10506
+ const value = record[key];
10507
+ if (typeof value === "string") return value;
10508
+ if (typeof value === "number") return String(value);
10509
+ return void 0;
10510
+ }
8474
10511
  function currentApprovalStatus(proposal) {
8475
10512
  if (proposal.state === "rejected") return "rejected";
8476
10513
  if (proposal.state === "canceled") return "canceled";
@@ -8601,7 +10638,7 @@ function starterCloudConfig() {
8601
10638
  base_url_env: "SYNAPSOR_CLOUD_BASE_URL",
8602
10639
  runner_token_env: "SYNAPSOR_RUNNER_TOKEN",
8603
10640
  runner_id: "synapsor_runner_local",
8604
- runner_version: "0.1.0-alpha.2",
10641
+ runner_version: "0.1.0-alpha.4",
8605
10642
  project_id: "token_scope",
8606
10643
  adapter_id: "mcp.your_adapter",
8607
10644
  source_id: "src_replace_me",
@@ -8624,6 +10661,37 @@ function normalizeCapabilities(value) {
8624
10661
  function isHelpRequest(args) {
8625
10662
  return args.includes("--help") || args.includes("-h");
8626
10663
  }
10664
+ function isKnownTopLevelCommand(command) {
10665
+ return (/* @__PURE__ */ new Set([
10666
+ "help",
10667
+ "init",
10668
+ "inspect",
10669
+ "config",
10670
+ "doctor",
10671
+ "validate",
10672
+ "apply",
10673
+ "propose",
10674
+ "audit",
10675
+ "start",
10676
+ "runner",
10677
+ "cloud",
10678
+ "mcp",
10679
+ "tools",
10680
+ "onboard",
10681
+ "demo",
10682
+ "recipes",
10683
+ "benchmark",
10684
+ "proposals",
10685
+ "replay",
10686
+ "evidence",
10687
+ "query-audit",
10688
+ "receipts",
10689
+ "activity",
10690
+ "store",
10691
+ "shadow",
10692
+ "ui"
10693
+ ])).has(command);
10694
+ }
8627
10695
  function cliCommandName() {
8628
10696
  if (process2.env.SYNAPSOR_RUNNER_COMMAND_NAME) return process2.env.SYNAPSOR_RUNNER_COMMAND_NAME;
8629
10697
  const invoked = path3.basename(process2.argv[1] ?? "");
@@ -8648,6 +10716,11 @@ Commands:
8648
10716
  propose Create a local evidence-backed proposal
8649
10717
  audit Review MCP/database tool risk
8650
10718
  proposals Review, approve, or reject proposals
10719
+ evidence Inspect local evidence bundles
10720
+ query-audit Inspect local query audit records
10721
+ receipts Inspect guarded writeback receipts
10722
+ activity Search local evidence/replay ledger
10723
+ store Inspect and maintain the local SQLite ledger
8651
10724
  apply Apply an approved proposal with guarded writeback
8652
10725
  replay Show what happened
8653
10726
  demo Start the local commit-safety demo
@@ -8675,6 +10748,7 @@ Generate a reviewed Synapsor Runner contract. Defaults to read-only in the wizar
8675
10748
  mcp: `Usage:
8676
10749
  ${cmd} mcp serve --config ./synapsor.runner.json --store ./.synapsor/local.db
8677
10750
  ${cmd} mcp config --absolute-paths --config ./synapsor.runner.json --store ./.synapsor/local.db
10751
+ ${cmd} mcp audit --example dangerous-db-mcp
8678
10752
  ${cmd} mcp audit ./tools-list.json
8679
10753
 
8680
10754
  MCP clients see semantic tools. They do not receive raw SQL, write credentials, approval tools, or commit tools.
@@ -8694,23 +10768,60 @@ Print MCP client configuration that references the local runner command, not dat
8694
10768
  ${cmd} propose <capability-name> --input ./input.json
8695
10769
  ${cmd} propose <capability-name> --json '{"invoice_id":"INV-3001","reason":"support-approved waiver"}'
8696
10770
 
10771
+ Examples after running ${cmd} demo:
10772
+ ${cmd} propose billing.propose_late_fee_waiver --sample
10773
+ ${cmd} propose support.propose_plan_credit --sample
10774
+ ${cmd} propose orders.propose_status_change --sample
10775
+
8697
10776
  Create the same evidence-backed proposal the MCP tool would create. The source database is not mutated.
8698
10777
  `,
8699
10778
  audit: `Usage:
10779
+ ${cmd} audit --example dangerous-db-mcp
10780
+ ${cmd} audit --example dangerous-db-mcp --format json
10781
+ ${cmd} audit --example dangerous-db-mcp --format markdown
8700
10782
  ${cmd} audit ./synapsor.runner.json
8701
10783
  ${cmd} audit --mcp-config ./claude_desktop_config.json
8702
10784
  ${cmd} audit --stdio "node ./server.js"
8703
10785
  ${cmd} audit --url http://localhost:3000/mcp
8704
10786
 
8705
10787
  Static MCP/database risk review only. This is not a security guarantee.
10788
+ `,
10789
+ doctor: `Usage:
10790
+ ${cmd} doctor --config synapsor.runner.json
10791
+ ${cmd} doctor --config synapsor.runner.json --json
10792
+ ${cmd} doctor --config synapsor.runner.json --report --redact --output synapsor-doctor.md
10793
+ ${cmd} doctor --first-run
10794
+
10795
+ Validate local config, environment bindings, semantic tool boundary, source metadata when reachable, and local store stats. Reports are redacted; do not paste secrets into issues.
8706
10796
  `,
8707
10797
  proposals: `Usage:
8708
- ${cmd} proposals list [--store ./.synapsor/local.db]
10798
+ ${cmd} proposals list [--tenant acme] [--capability billing.propose_late_fee_waiver] [--object invoice:INV-3001] [--status applied]
8709
10799
  ${cmd} proposals show latest
8710
10800
  ${cmd} proposals approve latest --yes
8711
10801
  ${cmd} proposals reject latest --reason "..."
8712
10802
 
8713
10803
  Review decisions happen outside the model-facing MCP tool surface.
10804
+ `,
10805
+ evidence: `Usage:
10806
+ ${cmd} evidence list [--tenant acme] [--capability billing.inspect_invoice] [--object invoice:INV-3001]
10807
+ ${cmd} evidence show ev_...
10808
+ ${cmd} evidence export ev_... --format json --output evidence.json
10809
+ ${cmd} evidence export ev_... --format markdown --output evidence.md
10810
+
10811
+ Inspect captured local evidence bundles and query-audit links without rerunning external DB reads.
10812
+ `,
10813
+ "query-audit": `Usage:
10814
+ ${cmd} query-audit list [--evidence ev_...] [--source app_postgres] [--table invoices]
10815
+ ${cmd} query-audit show <audit_id>
10816
+ ${cmd} query-audit export <audit_id> --format json --output audit.json
10817
+
10818
+ Inspect local query fingerprints, table names, row counts, and redacted-parameter metadata.
10819
+ `,
10820
+ receipts: `Usage:
10821
+ ${cmd} receipts list [--proposal wrp_...] [--status applied]
10822
+ ${cmd} receipts show <receipt_id>
10823
+
10824
+ Inspect guarded writeback receipts recorded by the trusted runner path.
8714
10825
  `,
8715
10826
  apply: `Usage:
8716
10827
  ${cmd} apply latest [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
@@ -8719,18 +10830,36 @@ Review decisions happen outside the model-facing MCP tool surface.
8719
10830
  Apply an approved proposal through guarded writeback. Requires a trusted write credential.
8720
10831
  `,
8721
10832
  replay: `Usage:
8722
- ${cmd} replay latest [--store ./.synapsor/local.db]
10833
+ ${cmd} replay list [--tenant acme] [--object invoice:INV-3001]
8723
10834
  ${cmd} replay show latest
8724
- ${cmd} replay export latest --output replay.json
10835
+ ${cmd} replay show --proposal wrp_...
10836
+ ${cmd} replay show --replay replay_wrp_...
10837
+ ${cmd} replay show --evidence ev_...
10838
+ ${cmd} replay export --proposal wrp_... --format json --output replay.json
10839
+ ${cmd} replay export --proposal wrp_... --format markdown --output replay.md
8725
10840
 
8726
10841
  Show evidence, proposal events, receipts, and replay state without rerunning side effects.
10842
+ `,
10843
+ activity: `Usage:
10844
+ ${cmd} activity search --tenant acme --object invoice:INV-3001
10845
+ ${cmd} activity search --capability billing.propose_late_fee_waiver --from 2026-06-01 --to 2026-06-23
10846
+
10847
+ Search the local SQLite evidence/replay ledger across proposals, evidence, query audit, receipts, and replay records.
10848
+ `,
10849
+ store: `Usage:
10850
+ ${cmd} store stats --store ./.synapsor/local.db
10851
+ ${cmd} store vacuum --store ./.synapsor/local.db
10852
+ ${cmd} store prune --store ./.synapsor/local.db --older-than 30d --dry-run
10853
+ ${cmd} store prune --store ./.synapsor/local.db --older-than 30d --yes
10854
+
10855
+ Local store maintenance only. Prune defaults to dry-run and never touches your source Postgres/MySQL database.
8727
10856
  `,
8728
10857
  demo: `Usage:
8729
10858
  ${cmd} demo [--force]
8730
10859
  ${cmd} demo --quick
8731
10860
 
8732
10861
  Start a disposable local Postgres demo and write ./synapsor.runner.json for the first-run flow.
8733
- Use --quick for a fixture-only 15-second explanation with no Docker startup.
10862
+ Use --quick for a fixture-only 15-second explanation and local ledger seed with no Docker startup.
8734
10863
  `,
8735
10864
  ui: `Usage:
8736
10865
  ${cmd} ui [--tour] [--config synapsor.runner.json] [--store ./.synapsor/local.db]