@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/README.md +104 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -0
- package/dist/runner.mjs +2113 -81
- package/docs/MCP_RUNNER_IMPLEMENTATION_PLAN.md +1 -1
- package/docs/cloud-mode.md +1 -1
- package/docs/demo-transcript.md +140 -52
- package/docs/first-10-minutes.md +27 -2
- package/docs/limitations.md +20 -0
- package/docs/local-mode.md +65 -2
- package/docs/mcp-audit.md +14 -4
- package/docs/open-source-feature-inventory.md +254 -0
- package/docs/security-boundary.md +17 -0
- package/examples/reference-support-billing-app/README.md +51 -0
- package/examples/reference-support-billing-app/schema.sql +14 -1
- package/examples/reference-support-billing-app/seed.sql +12 -5
- package/examples/reference-support-billing-app/synapsor.runner.json +105 -0
- package/package.json +1 -1
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(
|
|
1428
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
1754
|
-
query_audit: this.queryAuditByEvidence(
|
|
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.
|
|
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
|
|
2802
|
-
if (!
|
|
2803
|
-
return
|
|
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
|
-
|
|
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("--
|
|
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
|
|
6761
|
-
const existing =
|
|
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.
|
|
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
|
|
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
|
-
|
|
7883
|
+
`${seeded.replay_id} captures the local proposal, evidence handle, query audit, and events.`,
|
|
7135
7884
|
"",
|
|
7136
|
-
"
|
|
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
|
|
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(
|
|
7165
|
-
`
|
|
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] =
|
|
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 "
|
|
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
|
|
8841
|
+
async function evidence(args) {
|
|
7917
8842
|
const [subcommand, ...rest] = args;
|
|
7918
|
-
if (subcommand === "
|
|
7919
|
-
if (subcommand === "
|
|
7920
|
-
if (subcommand === "
|
|
7921
|
-
|
|
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
|
|
7926
|
-
const
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
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
|
|
8039
|
-
const rows = store.listProposals(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
8161
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
8174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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,
|
|
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.
|
|
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 [--
|
|
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
|
|
10833
|
+
${cmd} replay list [--tenant acme] [--object invoice:INV-3001]
|
|
8820
10834
|
${cmd} replay show latest
|
|
8821
|
-
${cmd} replay
|
|
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]
|