@synapsor/runner 0.1.0-alpha.3 → 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.3" },
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(
@@ -5252,6 +5807,7 @@ var dangerousDatabaseMcpAuditExample = {
5252
5807
  };
5253
5808
  var defaultConfigPath = "synapsor.runner.json";
5254
5809
  var defaultStorePath = "./.synapsor/local.db";
5810
+ var quickDemoStorePath = "./.synapsor/quick-demo.db";
5255
5811
  var referenceDemoDir = "examples/reference-support-billing-app";
5256
5812
  var referenceDemoConfigPath = `${referenceDemoDir}/synapsor.runner.json`;
5257
5813
  var referenceDemoContainer = "synapsor_runner_reference_support_billing";
@@ -5274,6 +5830,14 @@ async function main(argv) {
5274
5830
  usage([]);
5275
5831
  return 0;
5276
5832
  }
5833
+ if (!isKnownTopLevelCommand(command)) {
5834
+ process2.stderr.write(`Unknown command: synapsor ${command}
5835
+
5836
+ Try:
5837
+ synapsor --help
5838
+ `);
5839
+ return 2;
5840
+ }
5277
5841
  if (isHelpRequest(rest)) {
5278
5842
  usage([command, ...rest.filter((arg) => arg !== "--help" && arg !== "-h")]);
5279
5843
  return 0;
@@ -5301,9 +5865,18 @@ async function main(argv) {
5301
5865
  if (command === "benchmark") return benchmark(rest);
5302
5866
  if (command === "proposals") return proposals(rest);
5303
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);
5304
5873
  if (command === "shadow") return shadow(rest);
5305
5874
  if (command === "ui") return ui(rest);
5306
- usage([]);
5875
+ process2.stderr.write(`Unknown command: synapsor ${command}
5876
+
5877
+ Try:
5878
+ synapsor --help
5879
+ `);
5307
5880
  return 2;
5308
5881
  }
5309
5882
  async function init(args) {
@@ -6127,6 +6700,49 @@ function formatLocalDoctorReport(report) {
6127
6700
  return `${lines.join("\n")}
6128
6701
  `;
6129
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
+ }
6130
6746
  var moduleDir = path3.dirname(fileURLToPath(import.meta.url));
6131
6747
  var packageAssetRoot = path3.resolve(moduleDir, "..");
6132
6748
  var sourceAssetRoot = path3.resolve(moduleDir, "../../..");
@@ -6479,9 +7095,16 @@ async function localDoctor(args) {
6479
7095
  mode: String(parsed.mode),
6480
7096
  config_path: configPath,
6481
7097
  checks,
6482
- tools: tools2
7098
+ tools: tools2,
7099
+ store_stats: await localDoctorStoreStats(optionalArg(args, "--store") ?? parsed.storage?.sqlite_path)
6483
7100
  };
6484
- 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")) {
6485
7108
  process2.stdout.write(`${JSON.stringify(report, null, 2)}
6486
7109
  `);
6487
7110
  } else {
@@ -6489,6 +7112,23 @@ async function localDoctor(args) {
6489
7112
  }
6490
7113
  return report.ok ? 0 : 1;
6491
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
+ }
6492
7132
  async function validate(args) {
6493
7133
  const job = await readJob(args);
6494
7134
  parseWritebackJob(job);
@@ -6567,21 +7207,21 @@ async function applyProposal(args, proposalId) {
6567
7207
  lease_seconds: Number(optionalArg(args, "--lease-seconds") ?? "300")
6568
7208
  });
6569
7209
  const result = await applySqlJob(job, configPath, storePath, dryRun, envWithDemoDefaults(config, configPath));
6570
- process2.stdout.write(`${JSON.stringify(result, null, 2)}
6571
- `);
7210
+ process2.stdout.write(args.includes("--json") ? `${JSON.stringify(result, null, 2)}
7211
+ ` : formatApplyResult(parseWritebackJob(job), result, dryRun, storePath));
6572
7212
  return result.status === "failed" ? 1 : 0;
6573
7213
  }
6574
7214
  const executor = executorConfig(config, executorName);
6575
7215
  if (executor.type === "http_handler") {
6576
7216
  const result = await applyHttpHandlerProposal({ store, proposalId: resolvedProposalId, proposal, executorName, executor, runnerId, dryRun, env: envWithDemoDefaults(config, configPath) });
6577
- process2.stdout.write(`${JSON.stringify(redactConfig(result), null, 2)}
6578
- `);
7217
+ process2.stdout.write(args.includes("--json") ? `${JSON.stringify(redactConfig(result), null, 2)}
7218
+ ` : formatHandlerApplyResult(result, resolvedProposalId, storePath));
6579
7219
  return result.status === "failed" ? 1 : 0;
6580
7220
  }
6581
7221
  if (executor.type === "command_handler") {
6582
7222
  const result = await applyCommandHandlerProposal({ store, proposalId: resolvedProposalId, proposal, executorName, executor, runnerId, dryRun, env: envWithDemoDefaults(config, configPath) });
6583
- process2.stdout.write(`${JSON.stringify(redactConfig(result), null, 2)}
6584
- `);
7223
+ process2.stdout.write(args.includes("--json") ? `${JSON.stringify(redactConfig(result), null, 2)}
7224
+ ` : formatHandlerApplyResult(result, resolvedProposalId, storePath));
6585
7225
  return result.status === "failed" ? 1 : 0;
6586
7226
  }
6587
7227
  throw new Error(`unsupported executor type for ${executorName}`);
@@ -6757,8 +7397,8 @@ function prepareHandlerProposal(store, proposal, runnerId) {
6757
7397
  };
6758
7398
  }
6759
7399
  function duplicateHandlerReceipt(store, proposalId) {
6760
- const receipts = store.receipts(proposalId);
6761
- 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_"));
6762
7402
  return existing ? { receipt: existing.receipt } : void 0;
6763
7403
  }
6764
7404
  function alreadyAppliedReceipt(receipt, runnerId) {
@@ -6837,6 +7477,107 @@ function buildHandlerReceipt(input) {
6837
7477
  function hashReceipt(input) {
6838
7478
  return `sha256:${crypto5.createHash("sha256").update(JSON.stringify(input)).digest("hex")}`;
6839
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
+ }
6840
7581
  function parseOptionalJson(text) {
6841
7582
  if (!text.trim()) return {};
6842
7583
  try {
@@ -7000,7 +7741,7 @@ async function cloudConnect(args) {
7000
7741
  return 1;
7001
7742
  }
7002
7743
  const runnerId = String(parsed.cloud.runner_id || process2.env.SYNAPSOR_RUNNER_ID || "synapsor_runner_local").trim();
7003
- const runnerVersion = String(parsed.cloud.runner_version || process2.env.npm_package_version || "0.1.0-alpha.3").trim();
7744
+ const runnerVersion = String(parsed.cloud.runner_version || process2.env.npm_package_version || "0.1.0-alpha.4").trim();
7004
7745
  const engines = normalizeEngines(parsed.cloud.engines);
7005
7746
  const capabilities = normalizeCapabilities(parsed.cloud.capabilities);
7006
7747
  const client = new ControlPlaneClient({
@@ -7103,8 +7844,16 @@ async function demo(args) {
7103
7844
  return prepareReferenceDemo(args);
7104
7845
  }
7105
7846
  async function quickDemo() {
7847
+ const seeded = await seedQuickDemoStore(quickDemoStorePath);
7106
7848
  process2.stdout.write([
7107
- "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",
7108
7857
  "",
7109
7858
  "Raw MCP shape:",
7110
7859
  "execute_sql(sql: string)",
@@ -7115,7 +7864,7 @@ async function quickDemo() {
7115
7864
  "billing.propose_late_fee_waiver(invoice_id, reason)",
7116
7865
  "",
7117
7866
  "Agent requested:",
7118
- 'billing.propose_late_fee_waiver(invoice_id="INV-3001")',
7867
+ 'billing.propose_late_fee_waiver(invoice_id="INV-3001", reason="approved support waiver")',
7119
7868
  "",
7120
7869
  "Trusted context:",
7121
7870
  "tenant_id = acme",
@@ -7131,15 +7880,177 @@ async function quickDemo() {
7131
7880
  "required outside MCP",
7132
7881
  "",
7133
7882
  "Replay:",
7134
- "available after approval/apply",
7883
+ `${seeded.replay_id} captures the local proposal, evidence handle, query audit, and events.`,
7135
7884
  "",
7136
- "Next:",
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}`,
7890
+ "",
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:",
7137
7896
  `${cliCommandName()} demo`,
7897
+ "",
7898
+ "Audit risky MCP database tools:",
7138
7899
  `${cliCommandName()} audit --example dangerous-db-mcp`,
7139
7900
  ""
7140
7901
  ].join("\n"));
7141
7902
  return 0;
7142
7903
  }
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();
7967
+ }
7968
+ }
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
+ }
7143
8054
  async function mcpServe(args) {
7144
8055
  const configPath = optionalArg(args, "--config") ?? process2.env.SYNAPSOR_MCP_CONFIG;
7145
8056
  const readOnly = args.includes("--read-only");
@@ -7152,7 +8063,10 @@ async function mcpServe(args) {
7152
8063
  return 0;
7153
8064
  }
7154
8065
  async function mcpAudit(args) {
7155
- const json = args.includes("--json");
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
+ }
7156
8070
  const example = optionalArg(args, "--example");
7157
8071
  const target2 = example ? `example:${example}` : firstPositional(args);
7158
8072
  if (!target2) {
@@ -7161,8 +8075,10 @@ async function mcpAudit(args) {
7161
8075
  const timeoutMs = Number(optionalArg(args, "--timeout-ms") ?? "5000");
7162
8076
  const payload = example ? builtInMcpAuditExample(example) : await readMcpAuditTarget(target2, args, timeoutMs);
7163
8077
  const report = auditMcpManifest(payload, { target: target2 });
7164
- process2.stdout.write(json ? `${JSON.stringify(report, null, 2)}
7165
- ` : formatMcpAuditReport(report));
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));
7166
8082
  return 0;
7167
8083
  }
7168
8084
  async function propose(args) {
@@ -7227,13 +8143,13 @@ function sampleInputForCapability(capability) {
7227
8143
  const input = {};
7228
8144
  for (const [name, spec] of Object.entries(capability.args)) {
7229
8145
  if (name === capability.lookup.id_from_arg) input[name] = sampleIdForCapability(capability, name);
7230
- else if (/reason/i.test(name)) input[name] = "approved support waiver";
8146
+ else if (/reason/i.test(name)) input[name] = sampleReasonForCapability(capability);
7231
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];
7232
8149
  else if (/status/i.test(name)) input[name] = "pending_review";
7233
8150
  else if (/amount|cents|fee|credit|balance/i.test(name)) input[name] = typeof spec.maximum === "number" ? Math.min(spec.maximum, 1e3) : 0;
7234
8151
  else if (spec.type === "number") input[name] = spec.minimum ?? 1;
7235
8152
  else if (spec.type === "boolean") input[name] = true;
7236
- else if (spec.enum?.length) input[name] = spec.enum[0];
7237
8153
  else input[name] = `sample_${name}`;
7238
8154
  }
7239
8155
  const missing = Object.entries(capability.args).filter(([, spec]) => spec.required !== false).filter(([name]) => input[name] === void 0).map(([name]) => name);
@@ -7244,12 +8160,20 @@ function sampleInputForCapability(capability) {
7244
8160
  }
7245
8161
  function sampleIdForCapability(capability, argName) {
7246
8162
  const text = `${capability.name} ${capability.target.table} ${argName}`.toLowerCase();
8163
+ const arg = argName.toLowerCase();
7247
8164
  if (/invoice|billing/.test(text)) return "INV-3001";
8165
+ if (/account|customer/.test(arg) || /accounts|customers/.test(text)) return "cust_acme_1";
7248
8166
  if (/ticket|support/.test(text)) return "T-1042";
7249
- if (/order/.test(text)) return "ord_1001";
7250
- if (/account|customer/.test(text)) return "cust_acme_1";
8167
+ if (/order/.test(text)) return "O-1001";
7251
8168
  return "sample_1";
7252
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
+ }
7253
8177
  function formatProposeResult(capabilityName, result, storePath) {
7254
8178
  const proposalId = String(result.proposal_id ?? "");
7255
8179
  const evidenceId = String(result.evidence_bundle_id ?? "");
@@ -7907,26 +8831,64 @@ async function proposals(args) {
7907
8831
  }
7908
8832
  async function replay(args) {
7909
8833
  const [subcommand, ...rest] = args;
8834
+ if (subcommand === "list") return replayList(rest);
7910
8835
  if (subcommand && !["show", "export"].includes(subcommand)) return replayShow(args);
7911
8836
  if (subcommand === "show") return replayShow(rest);
7912
8837
  if (subcommand === "export") return replayExport(rest);
7913
8838
  usage(["replay"]);
7914
8839
  return 2;
7915
8840
  }
7916
- async function shadow(args) {
8841
+ async function evidence(args) {
7917
8842
  const [subcommand, ...rest] = args;
7918
- if (subcommand === "list") return shadowList(rest);
7919
- if (subcommand === "record-human-action") return shadowRecordHumanAction(rest);
7920
- if (subcommand === "compare") return shadowCompare(rest);
7921
- if (subcommand === "report") return shadowReport(rest);
7922
- usage(["shadow"]);
8843
+ if (subcommand === "show") return evidenceShow(rest);
8844
+ if (subcommand === "list") return evidenceList(rest);
8845
+ if (subcommand === "export") return evidenceExport(rest);
8846
+ usage(["evidence"]);
7923
8847
  return 2;
7924
8848
  }
7925
- async function ui(args) {
7926
- const portArg = optionalArg(args, "--port");
7927
- const server = await startLocalUiServer({
7928
- configPath: optionalArg(args, "--config") ?? "synapsor.runner.json",
7929
- storePath: optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE ?? "./.synapsor/local.db",
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
+ }
8878
+ async function shadow(args) {
8879
+ const [subcommand, ...rest] = args;
8880
+ if (subcommand === "list") return shadowList(rest);
8881
+ if (subcommand === "record-human-action") return shadowRecordHumanAction(rest);
8882
+ if (subcommand === "compare") return shadowCompare(rest);
8883
+ if (subcommand === "report") return shadowReport(rest);
8884
+ usage(["shadow"]);
8885
+ return 2;
8886
+ }
8887
+ async function ui(args) {
8888
+ const portArg = optionalArg(args, "--port");
8889
+ const server = await startLocalUiServer({
8890
+ configPath: optionalArg(args, "--config") ?? "synapsor.runner.json",
8891
+ storePath: optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE ?? "./.synapsor/local.db",
7930
8892
  host: optionalArg(args, "--host") ?? "127.0.0.1",
7931
8893
  port: portArg ? Number(portArg) : 0,
7932
8894
  allowRemoteBind: args.includes("--allow-remote-bind"),
@@ -8033,10 +8995,11 @@ async function shadowReport(args) {
8033
8995
  }
8034
8996
  }
8035
8997
  async function proposalsList(args) {
8998
+ assertKnownOptions(args, proposalListAllowedOptions, "proposals list");
8036
8999
  const store = await openLocalStore(args);
8037
9000
  try {
8038
- const state = optionalArg(args, "--state");
8039
- const rows = store.listProposals(state);
9001
+ const filters = proposalFiltersFromArgs(args);
9002
+ const rows = store.listProposals(filters);
8040
9003
  if (args.includes("--json")) {
8041
9004
  process2.stdout.write(`${JSON.stringify({ proposals: rows }, null, 2)}
8042
9005
  `);
@@ -8062,12 +9025,13 @@ async function proposalsShow(args) {
8062
9025
  const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
8063
9026
  const proposal = store.getProposal(resolvedProposalId);
8064
9027
  if (!proposal) throw new Error(`proposal not found: ${resolvedProposalId}`);
8065
- 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 };
8066
9030
  if (args.includes("--json")) {
8067
9031
  process2.stdout.write(`${JSON.stringify(payload, null, 2)}
8068
9032
  `);
8069
9033
  } else {
8070
- process2.stdout.write(formatProposalDetail(proposal));
9034
+ process2.stdout.write(formatProposalDetail(proposal, evidence2?.items.length));
8071
9035
  for (const event of payload.events) {
8072
9036
  process2.stdout.write(`event ${event.event_id}: ${event.kind} by ${event.actor} at ${event.created_at}
8073
9037
  `);
@@ -8086,7 +9050,8 @@ async function proposalsApprove(args) {
8086
9050
  const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
8087
9051
  const proposal = requireLocalProposal(store, resolvedProposalId);
8088
9052
  if (!args.includes("--json")) {
8089
- 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));
8090
9055
  }
8091
9056
  await confirmDangerousAction(args, `Approve proposal ${resolvedProposalId} for guarded writeback?`);
8092
9057
  const updated = store.approveProposal(resolvedProposalId, {
@@ -8113,7 +9078,8 @@ async function proposalsReject(args) {
8113
9078
  const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
8114
9079
  const proposal = requireLocalProposal(store, resolvedProposalId);
8115
9080
  if (!args.includes("--json")) {
8116
- 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));
8117
9083
  }
8118
9084
  await confirmDangerousAction(args, `Reject proposal ${resolvedProposalId}?`);
8119
9085
  const updated = store.rejectProposal(resolvedProposalId, {
@@ -8157,28 +9123,180 @@ async function proposalsWritebackJob(args) {
8157
9123
  store.close();
8158
9124
  }
8159
9125
  }
8160
- async function replayShow(args) {
8161
- const proposalId = positional(args, 0);
8162
- if (!proposalId) throw new Error("replay show requires <proposal_id>");
9126
+ async function evidenceList(args) {
9127
+ assertKnownOptions(args, evidenceListAllowedOptions, "evidence list");
8163
9128
  const store = await openLocalStore(args);
8164
9129
  try {
8165
- const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
8166
- const replayRecord = store.replay(resolvedProposalId);
9130
+ const rows = store.listEvidenceBundles(evidenceFiltersFromArgs(args));
8167
9131
  if (args.includes("--json")) {
8168
- process2.stdout.write(`${JSON.stringify(replayRecord, null, 2)}
9132
+ process2.stdout.write(`${JSON.stringify({ evidence: rows }, null, 2)}
8169
9133
  `);
9134
+ } else if (rows.length === 0) {
9135
+ process2.stdout.write("No evidence bundles found.\n");
8170
9136
  } else {
8171
- 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)}
8172
9153
  `);
8173
- process2.stdout.write(formatProposalDetail(replayRecord.proposal));
8174
- process2.stdout.write(`events: ${replayRecord.events.length}
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}
8175
9176
  `);
8176
- process2.stdout.write(`receipts: ${replayRecord.receipts.length}
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)}
8177
9188
  `);
8178
- process2.stdout.write(`evidence bundles: ${replayRecord.evidence.length}
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)}
8179
9205
  `);
8180
- process2.stdout.write(`query audit records: ${replayRecord.query_audit.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}
8181
9227
  `);
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)}
9256
+ `);
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)}
9281
+ `);
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)}
9297
+ `);
9298
+ } else {
9299
+ process2.stdout.write(formatReplayDetail(replayRecord));
8182
9300
  }
8183
9301
  return 0;
8184
9302
  } finally {
@@ -8186,17 +9304,18 @@ async function replayShow(args) {
8186
9304
  }
8187
9305
  }
8188
9306
  async function replayExport(args) {
8189
- const proposalId = positional(args, 0);
8190
- if (!proposalId) throw new Error("replay export requires <proposal_id>");
9307
+ assertKnownOptions(args, replayExportAllowedOptions, "replay export");
8191
9308
  const output = outputArg(args);
8192
9309
  if (!output) throw new Error("replay export requires --output <path>");
9310
+ const format = exportFormat(args);
8193
9311
  const store = await openLocalStore(args);
8194
9312
  try {
8195
- const resolvedProposalId = resolveProposalIdFromStore(proposalId, store);
9313
+ const resolvedProposalId = resolveReplayProposalId(args, store);
8196
9314
  const replayRecord = store.replay(resolvedProposalId);
9315
+ const text = format === "json" ? `${JSON.stringify(replayRecord, null, 2)}
9316
+ ` : formatReplayMarkdown(replayRecord);
8197
9317
  await fs3.mkdir(path3.dirname(path3.resolve(output)), { recursive: true });
8198
- await fs3.writeFile(output, `${JSON.stringify(replayRecord, null, 2)}
8199
- `, "utf8");
9318
+ await fs3.writeFile(output, text, "utf8");
8200
9319
  process2.stdout.write(`exported ${replayRecord.replay_id} to ${output}
8201
9320
  `);
8202
9321
  return 0;
@@ -8204,9 +9323,421 @@ async function replayExport(args) {
8204
9323
  store.close();
8205
9324
  }
8206
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
+ }
8207
9737
  async function openLocalStore(args) {
8208
9738
  const storePath = optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE ?? "./.synapsor/local.db";
8209
9739
  if (storePath !== ":memory:") {
9740
+ if (!await fileExists(storePath)) throw missingLocalStoreError(storePath);
8210
9741
  await fs3.mkdir(path3.dirname(path3.resolve(storePath)), { recursive: true });
8211
9742
  }
8212
9743
  return new ProposalStore(storePath);
@@ -8251,6 +9782,7 @@ function requireLocalProposal(store, proposalId) {
8251
9782
  }
8252
9783
  async function resolveProposalId(proposalId, storePath) {
8253
9784
  if (proposalId !== "latest") return proposalId;
9785
+ if (storePath !== ":memory:" && !await fileExists(storePath)) throw missingLocalStoreError(storePath);
8254
9786
  const store = new ProposalStore(storePath);
8255
9787
  try {
8256
9788
  return resolveProposalIdFromStore(proposalId, store);
@@ -8264,6 +9796,15 @@ function resolveProposalIdFromStore(proposalId, store) {
8264
9796
  if (!latest) throw new Error("no proposals found in the local store");
8265
9797
  return latest.proposal_id;
8266
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
+ }
8267
9808
  async function readRuntimeConfig(configPath) {
8268
9809
  const parsed = JSON.parse(await fs3.readFile(configPath, "utf8"));
8269
9810
  return parsed;
@@ -8414,25 +9955,36 @@ function firstPositional(args) {
8414
9955
  "--allowed-columns",
8415
9956
  "--approval-role",
8416
9957
  "--actor",
9958
+ "--action",
9959
+ "--audit",
8417
9960
  "--bearer-env",
9961
+ "--capability",
8418
9962
  "--config",
8419
9963
  "--conflict-column",
8420
9964
  "--database-url-env",
8421
9965
  "--destination",
8422
9966
  "--engine",
9967
+ "--evidence",
8423
9968
  "--example",
9969
+ "--format",
8424
9970
  "--from",
8425
9971
  "--from-env",
8426
9972
  "--host",
9973
+ "--idempotency-key",
8427
9974
  "--input",
8428
9975
  "--job",
8429
9976
  "--lease-seconds",
9977
+ "--limit",
8430
9978
  "--lookup-arg",
8431
9979
  "--mode",
8432
9980
  "--mcp-config",
8433
9981
  "--namespace",
8434
9982
  "--numeric-bound",
9983
+ "--object",
9984
+ "--object-id",
9985
+ "--object-type",
8435
9986
  "--object-name",
9987
+ "--older-than",
8436
9988
  "--output",
8437
9989
  "--out",
8438
9990
  "--patch-fixed",
@@ -8440,22 +9992,31 @@ function firstPositional(args) {
8440
9992
  "--port",
8441
9993
  "--primary-key",
8442
9994
  "--principal-env",
9995
+ "--proposal",
8443
9996
  "--project",
9997
+ "--query-fingerprint",
8444
9998
  "--reason",
8445
9999
  "--recipe",
10000
+ "--receipt",
10001
+ "--replay",
8446
10002
  "--runner",
8447
10003
  "--schema",
8448
10004
  "--source-name",
10005
+ "--source",
8449
10006
  "--state",
10007
+ "--status",
8450
10008
  "--stdio",
8451
10009
  "--store",
8452
10010
  "--table",
10011
+ "--tenant",
8453
10012
  "--tenant-env",
8454
10013
  "--tenant-key",
8455
10014
  "--timeout-ms",
10015
+ "--to",
8456
10016
  "--transition-guard",
8457
10017
  "--url",
8458
10018
  "--visible-columns",
10019
+ "--writeback-job",
8459
10020
  "--write-url-env"
8460
10021
  ]);
8461
10022
  for (let index = 0; index < args.length; index += 1) {
@@ -8531,15 +10092,16 @@ function parseJsonRpcResponse(stdout, id) {
8531
10092
  }
8532
10093
  function formatProposalSummary(proposal) {
8533
10094
  return [
8534
- `${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}`,
8535
10097
  ` target: ${proposal.source_kind}:${proposal.source_id}/${proposal.source_schema}.${proposal.source_table}/${proposal.object_id}`,
8536
10098
  ` tenant: ${proposal.tenant_id} source changed: ${proposal.source_database_mutated ? "yes" : "no"}`
8537
10099
  ].join("\n") + "\n";
8538
10100
  }
8539
- function formatProposalDetail(proposal) {
10101
+ function formatProposalDetail(proposal, storedEvidenceItemCount) {
8540
10102
  const changeSet = proposal.change_set;
8541
10103
  const conflictGuard = changeSet.guards.expected_version;
8542
- const evidenceItems = changeSet.evidence.items?.length ?? 0;
10104
+ const evidenceItems = storedEvidenceItemCount ?? changeSet.evidence.items?.length ?? 0;
8543
10105
  const approvalStatus = currentApprovalStatus(proposal);
8544
10106
  const writebackStatus = currentWritebackStatus(proposal);
8545
10107
  return [
@@ -8566,6 +10128,386 @@ function formatProposalDetail(proposal) {
8566
10128
  })
8567
10129
  ].join("\n") + "\n";
8568
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
+ }
8569
10511
  function currentApprovalStatus(proposal) {
8570
10512
  if (proposal.state === "rejected") return "rejected";
8571
10513
  if (proposal.state === "canceled") return "canceled";
@@ -8696,7 +10638,7 @@ function starterCloudConfig() {
8696
10638
  base_url_env: "SYNAPSOR_CLOUD_BASE_URL",
8697
10639
  runner_token_env: "SYNAPSOR_RUNNER_TOKEN",
8698
10640
  runner_id: "synapsor_runner_local",
8699
- runner_version: "0.1.0-alpha.3",
10641
+ runner_version: "0.1.0-alpha.4",
8700
10642
  project_id: "token_scope",
8701
10643
  adapter_id: "mcp.your_adapter",
8702
10644
  source_id: "src_replace_me",
@@ -8719,6 +10661,37 @@ function normalizeCapabilities(value) {
8719
10661
  function isHelpRequest(args) {
8720
10662
  return args.includes("--help") || args.includes("-h");
8721
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
+ }
8722
10695
  function cliCommandName() {
8723
10696
  if (process2.env.SYNAPSOR_RUNNER_COMMAND_NAME) return process2.env.SYNAPSOR_RUNNER_COMMAND_NAME;
8724
10697
  const invoked = path3.basename(process2.argv[1] ?? "");
@@ -8743,6 +10716,11 @@ Commands:
8743
10716
  propose Create a local evidence-backed proposal
8744
10717
  audit Review MCP/database tool risk
8745
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
8746
10724
  apply Apply an approved proposal with guarded writeback
8747
10725
  replay Show what happened
8748
10726
  demo Start the local commit-safety demo
@@ -8790,24 +10768,60 @@ Print MCP client configuration that references the local runner command, not dat
8790
10768
  ${cmd} propose <capability-name> --input ./input.json
8791
10769
  ${cmd} propose <capability-name> --json '{"invoice_id":"INV-3001","reason":"support-approved waiver"}'
8792
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
+
8793
10776
  Create the same evidence-backed proposal the MCP tool would create. The source database is not mutated.
8794
10777
  `,
8795
10778
  audit: `Usage:
8796
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
8797
10782
  ${cmd} audit ./synapsor.runner.json
8798
10783
  ${cmd} audit --mcp-config ./claude_desktop_config.json
8799
10784
  ${cmd} audit --stdio "node ./server.js"
8800
10785
  ${cmd} audit --url http://localhost:3000/mcp
8801
10786
 
8802
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.
8803
10796
  `,
8804
10797
  proposals: `Usage:
8805
- ${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]
8806
10799
  ${cmd} proposals show latest
8807
10800
  ${cmd} proposals approve latest --yes
8808
10801
  ${cmd} proposals reject latest --reason "..."
8809
10802
 
8810
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.
8811
10825
  `,
8812
10826
  apply: `Usage:
8813
10827
  ${cmd} apply latest [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
@@ -8816,18 +10830,36 @@ Review decisions happen outside the model-facing MCP tool surface.
8816
10830
  Apply an approved proposal through guarded writeback. Requires a trusted write credential.
8817
10831
  `,
8818
10832
  replay: `Usage:
8819
- ${cmd} replay latest [--store ./.synapsor/local.db]
10833
+ ${cmd} replay list [--tenant acme] [--object invoice:INV-3001]
8820
10834
  ${cmd} replay show latest
8821
- ${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
8822
10840
 
8823
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.
8824
10856
  `,
8825
10857
  demo: `Usage:
8826
10858
  ${cmd} demo [--force]
8827
10859
  ${cmd} demo --quick
8828
10860
 
8829
10861
  Start a disposable local Postgres demo and write ./synapsor.runner.json for the first-run flow.
8830
- 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.
8831
10863
  `,
8832
10864
  ui: `Usage:
8833
10865
  ${cmd} ui [--tour] [--config synapsor.runner.json] [--store ./.synapsor/local.db]