midsummer-sol 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.js +200 -6
  2. package/package.json +1 -1
  3. package/sol-mcp.js +1743 -164
  4. package/sol.js +2240 -299
package/index.js CHANGED
@@ -1423,16 +1423,76 @@ function trustAuthor(op, trust = {}) {
1423
1423
  }
1424
1424
 
1425
1425
  // src/prov.ts
1426
+ var TC_SEP = "\x01";
1427
+ var TC_TAG = "tc1";
1428
+ function encodeTool(p) {
1429
+ const surface = p.tool?.trim() || "";
1430
+ if (!p.toolCall && !p.testRunId)
1431
+ return surface || undefined;
1432
+ const segs = [surface, TC_TAG];
1433
+ const tc = p.toolCall;
1434
+ segs.push(`name=${tc?.name ?? ""}`, `args=${tc?.argsHash ?? ""}`, `inv=${tc?.invocationId ?? ""}`, `test=${p.testRunId ?? ""}`);
1435
+ return segs.join(TC_SEP);
1436
+ }
1437
+ function decodeTool(tool) {
1438
+ if (!tool)
1439
+ return {};
1440
+ const parts = tool.split(TC_SEP);
1441
+ if (parts.length < 2 || parts[1] !== TC_TAG)
1442
+ return { tool: tool || undefined };
1443
+ const surface = parts[0] || undefined;
1444
+ const kv = new Map;
1445
+ for (const seg of parts.slice(2)) {
1446
+ const eq2 = seg.indexOf("=");
1447
+ if (eq2 >= 0)
1448
+ kv.set(seg.slice(0, eq2), seg.slice(eq2 + 1));
1449
+ }
1450
+ const name = kv.get("name") || "";
1451
+ const argsHash = kv.get("args") || "";
1452
+ const invocationId = kv.get("inv") || "";
1453
+ const testRunId = kv.get("test") || undefined;
1454
+ const out = { tool: surface };
1455
+ if (name || argsHash || invocationId)
1456
+ out.toolCall = { name, argsHash, invocationId };
1457
+ if (testRunId)
1458
+ out.testRunId = testRunId;
1459
+ return out;
1460
+ }
1461
+ function parseToolCall(raw) {
1462
+ if (!raw)
1463
+ return;
1464
+ let v;
1465
+ try {
1466
+ v = JSON.parse(raw);
1467
+ } catch {
1468
+ return;
1469
+ }
1470
+ if (!v || typeof v !== "object")
1471
+ return;
1472
+ const o = v;
1473
+ const name = typeof o.name === "string" ? o.name : "";
1474
+ const argsHash = typeof o.argsHash === "string" ? o.argsHash : "";
1475
+ const invocationId = typeof o.invocationId === "string" ? o.invocationId : "";
1476
+ if (!name || !argsHash || !invocationId)
1477
+ return;
1478
+ return { name, argsHash, invocationId };
1479
+ }
1426
1480
  function provenanceFromEnv(env = process.env) {
1427
1481
  const kind = env.SOL_KIND === "agent" ? "agent" : "human";
1482
+ const tool = encodeTool({
1483
+ tool: env.SOL_TOOL,
1484
+ toolCall: parseToolCall(env.SOL_TOOL_CALL),
1485
+ testRunId: env.SOL_TEST_RUN_ID || undefined
1486
+ });
1428
1487
  const node = buildProvNode({
1429
1488
  agentId: env.SOL_AGENT_ID || (kind === "agent" ? env.SOL_ACTOR : undefined),
1430
1489
  sessionId: env.SOL_SESSION,
1431
1490
  model: env.SOL_MODEL,
1432
1491
  intent: env.SOL_INTENT,
1433
- tool: env.SOL_TOOL
1492
+ tool
1434
1493
  });
1435
- return node ? { kind, node } : { kind };
1494
+ const opRunId = env.SOL_OPERATION_ID || undefined;
1495
+ return { kind, ...node ? { node } : {}, ...opRunId ? { opRunId } : {} };
1436
1496
  }
1437
1497
  function buildProvNode(fields) {
1438
1498
  const clean = { kind: "prov" };
@@ -1461,6 +1521,9 @@ function authorLabel(op, reader) {
1461
1521
  const bits = [];
1462
1522
  if (p?.sessionId)
1463
1523
  bits.push(`session ${p.sessionId}`);
1524
+ const tp = p ? decodeTool(p.tool) : {};
1525
+ if (tp.toolCall)
1526
+ bits.push(`via ${tp.toolCall.name}`);
1464
1527
  const tail = p?.intent ? ` — intent: ${p.intent}` : "";
1465
1528
  return `${who}${bits.length ? ` (${bits.join(", ")})` : ""}${tail}`;
1466
1529
  }
@@ -1470,10 +1533,21 @@ function provJson(op, reader, key, trust) {
1470
1533
  return;
1471
1534
  const out = { kind: op.kind ?? "human" };
1472
1535
  if (p) {
1473
- for (const k of ["agentId", "sessionId", "model", "intent", "tool", "rationale", "parent"])
1536
+ for (const k of ["agentId", "sessionId", "model", "intent", "rationale", "parent"])
1474
1537
  if (p[k])
1475
1538
  out[k] = p[k];
1476
1539
  }
1540
+ if (p?.tool) {
1541
+ const tp = decodeTool(p.tool);
1542
+ if (tp.tool)
1543
+ out.tool = tp.tool;
1544
+ if (tp.toolCall)
1545
+ out.toolCall = tp.toolCall;
1546
+ if (tp.testRunId)
1547
+ out.testRunId = tp.testRunId;
1548
+ }
1549
+ if (op.opRunId !== undefined)
1550
+ out.opRunId = op.opRunId;
1477
1551
  if (op.sig !== undefined) {
1478
1552
  const t = trustAuthor(op, trust);
1479
1553
  out.signed = true;
@@ -1531,7 +1605,7 @@ function signTrailerValue(op, trust) {
1531
1605
  return `${t.fingerprint} verified`;
1532
1606
  }
1533
1607
  }
1534
- function provTrailers(op, reader, key, trust) {
1608
+ function provTrailers(op, reader, key, trust, reviewCount) {
1535
1609
  const p = reader ? provOf(op, reader) : undefined;
1536
1610
  const lines2 = [];
1537
1611
  const signed = trustAuthor(op, trust);
@@ -1545,6 +1619,13 @@ function provTrailers(op, reader, key, trust) {
1545
1619
  lines2.push(`Sol-Session: ${p.sessionId}`);
1546
1620
  if (p?.model)
1547
1621
  lines2.push(`Sol-Model: ${p.model}`);
1622
+ const tp = decodeTool(p?.tool);
1623
+ if (tp.toolCall)
1624
+ lines2.push(`Sol-ToolCall: ${tp.toolCall.name} args=${tp.toolCall.argsHash} inv=${tp.toolCall.invocationId}`);
1625
+ if (tp.testRunId)
1626
+ lines2.push(`Sol-TestRun: ${tp.testRunId}`);
1627
+ if (op.opRunId)
1628
+ lines2.push(`Sol-Operation: ${op.opRunId}`);
1548
1629
  const intent = p?.intent || (isAgent ? op.message?.trim() || undefined : undefined);
1549
1630
  if (intent)
1550
1631
  lines2.push(`Sol-Intent: ${intent}`);
@@ -1553,15 +1634,40 @@ function provTrailers(op, reader, key, trust) {
1553
1634
  lines2.push(`Sol-Sign: ${sign}`);
1554
1635
  if (op.att !== undefined && !(key && !verifyAtt(key, op)))
1555
1636
  lines2.push(`Sol-Attested: pushedBy=${op.pushedBy ?? "?"}`);
1637
+ if (reviewCount && reviewCount > 0)
1638
+ lines2.push(`Sol-Attestations: ${reviewCount}`);
1556
1639
  return lines2;
1557
1640
  }
1558
1641
  async function captureProv(sink, env = process.env) {
1559
- const { kind, node } = provenanceFromEnv(env);
1642
+ const { kind, node, opRunId } = provenanceFromEnv(env);
1560
1643
  const out = {};
1561
1644
  if (kind === "agent")
1562
1645
  out.kind = "agent";
1563
1646
  if (node)
1564
1647
  out.prov = await sink.put(node);
1648
+ if (opRunId)
1649
+ out.opRunId = opRunId;
1650
+ return out;
1651
+ }
1652
+ function toolProvOf(op, reader) {
1653
+ const p = reader ? provOf(op, reader) : undefined;
1654
+ return p ? decodeTool(p.tool) : {};
1655
+ }
1656
+ function toAttestationRecords(op, reader, issuer = "sol") {
1657
+ const tp = toolProvOf(op, reader);
1658
+ const out = [];
1659
+ if (tp.toolCall) {
1660
+ out.push({
1661
+ kind: "audit",
1662
+ issuer,
1663
+ verdict: "pass",
1664
+ summary: `tool-call ${tp.toolCall.name} (inv ${tp.toolCall.invocationId}, args ${tp.toolCall.argsHash})`,
1665
+ at: op.at
1666
+ });
1667
+ }
1668
+ if (tp.testRunId) {
1669
+ out.push({ kind: "check", issuer, verdict: "pending", summary: `test-run ${tp.testRunId}`, at: op.at });
1670
+ }
1565
1671
  return out;
1566
1672
  }
1567
1673
 
@@ -1613,10 +1719,25 @@ class AsyncRepo {
1613
1719
  await this.log.append(by === this.actor ? this.sign(entry) : entry);
1614
1720
  }
1615
1721
  provFor;
1722
+ pendingToolCall;
1723
+ setToolCall(tc) {
1724
+ this.pendingToolCall = tc;
1725
+ }
1616
1726
  async provenance(by) {
1617
1727
  if (by !== this.actor)
1618
1728
  return {};
1619
- return this.provFor ??= captureProv(this.store);
1729
+ const base = await (this.provFor ??= captureProv(this.store));
1730
+ const tc = this.pendingToolCall;
1731
+ if (!tc)
1732
+ return base;
1733
+ this.pendingToolCall = undefined;
1734
+ const { kind, node } = provenanceFromEnv();
1735
+ const session = node ?? { kind: "prov" };
1736
+ const surface = decodeTool(session.tool);
1737
+ const tool = encodeTool({ tool: surface.tool, toolCall: tc, testRunId: surface.testRunId });
1738
+ const merged = { ...session, kind: "prov", tool };
1739
+ const prov = await this.store.put(merged);
1740
+ return { ...kind === "agent" ? { kind: "agent" } : {}, prov, ...base.opRunId ? { opRunId: base.opRunId } : {} };
1620
1741
  }
1621
1742
  async currentRoot() {
1622
1743
  const head = await this.log.head();
@@ -1818,6 +1939,9 @@ class SolWorkspace {
1818
1939
  this.actor = actor;
1819
1940
  this.repo = new AsyncRepo(store, log, actor);
1820
1941
  }
1942
+ setToolCall(tc) {
1943
+ this.repo.setToolCall(tc);
1944
+ }
1821
1945
  async write(path, content) {
1822
1946
  return this.repo.writeFile(safePath(path), content);
1823
1947
  }
@@ -2844,7 +2968,64 @@ class Ledger {
2844
2968
  return [...this.bySubject.get(subject) ?? []];
2845
2969
  }
2846
2970
  }
2971
+ function canonicalReview(a) {
2972
+ return JSON.stringify({
2973
+ kind: a.kind,
2974
+ issuer: a.issuer,
2975
+ reviewerKind: a.reviewerKind,
2976
+ verdict: a.verdict,
2977
+ commitSha: a.commitSha,
2978
+ findings: a.findings ?? [],
2979
+ at: a.at
2980
+ });
2981
+ }
2982
+ function signReview(key, a) {
2983
+ return createHmac2("sha256", key).update(canonicalReview(a)).digest("base64");
2984
+ }
2985
+ function verifyReview(key, a, sig = a.signature) {
2986
+ if (!sig)
2987
+ return false;
2988
+ const expected = Buffer.from(signReview(key, a));
2989
+ const got = Buffer.from(sig);
2990
+ return expected.length === got.length && timingSafeEqual3(expected, got);
2991
+ }
2992
+ function buildReviewerAttestation(key, a) {
2993
+ const full = { kind: "review", ...a, at: a.at ?? Date.now() };
2994
+ full.signature = signReview(key, full);
2995
+ return full;
2996
+ }
2997
+ function reviewSubject(changeId, commitSha) {
2998
+ return `review:${changeId}:${commitSha}`;
2999
+ }
3000
+
3001
+ class ReviewerAttestationLedger {
3002
+ byChange = new Map;
3003
+ append(changeId, a) {
3004
+ if (!a.signature)
3005
+ throw new Error("reviewer attestation must be signed before it is recorded");
3006
+ const list = this.byChange.get(changeId) ?? [];
3007
+ list.push(a);
3008
+ this.byChange.set(changeId, list);
3009
+ }
3010
+ provenance(changeId) {
3011
+ return [...this.byChange.get(changeId) ?? []];
3012
+ }
3013
+ forCommit(changeId, commitSha) {
3014
+ return this.provenance(changeId).filter((a) => a.commitSha === commitSha);
3015
+ }
3016
+ verifiedCount(key, changeId, commitSha) {
3017
+ const recs = commitSha ? this.forCommit(changeId, commitSha) : this.provenance(changeId);
3018
+ return recs.filter((a) => verifyReview(key, a)).length;
3019
+ }
3020
+ }
2847
3021
  // src/check.ts
3022
+ function gateFromResult(head, runner, r, at = Date.now()) {
3023
+ return { head, verdict: r.verdict, checkName: runner.id, summary: r.summary, findings: r.findings, at };
3024
+ }
3025
+ function pendingGate(head, at = Date.now()) {
3026
+ return { head, verdict: "pending", at };
3027
+ }
3028
+
2848
3029
  class CheckCache {
2849
3030
  cache = new Map;
2850
3031
  _hits = 0;
@@ -2871,6 +3052,7 @@ function toAttestation(runner, r) {
2871
3052
  export {
2872
3053
  writeFile,
2873
3054
  wrapCekTo,
3055
+ verifyReview,
2874
3056
  verifyPubkeySig,
2875
3057
  verifyOp,
2876
3058
  verifyAuthor,
@@ -2878,9 +3060,12 @@ export {
2878
3060
  verifyAtt,
2879
3061
  unwrapCekWith,
2880
3062
  trustAuthor,
3063
+ toolProvOf,
3064
+ toAttestationRecords,
2881
3065
  toAttestation,
2882
3066
  signerFrom,
2883
3067
  signTag,
3068
+ signReview,
2884
3069
  signPubkey,
2885
3070
  signOp,
2886
3071
  signAuthor,
@@ -2894,6 +3079,7 @@ export {
2894
3079
  safePath,
2895
3080
  runCommand,
2896
3081
  rotate,
3082
+ reviewSubject,
2897
3083
  recoveryKeyPair,
2898
3084
  readFile,
2899
3085
  reachableFrom,
@@ -2907,6 +3093,8 @@ export {
2907
3093
  provTrailers,
2908
3094
  provOf,
2909
3095
  provJson,
3096
+ pendingGate,
3097
+ parseToolCall,
2910
3098
  pageOps,
2911
3099
  openWithRecovery,
2912
3100
  openContent,
@@ -2927,6 +3115,7 @@ export {
2927
3115
  getTree,
2928
3116
  generateRecoveryCode,
2929
3117
  generateKeypair,
3118
+ gateFromResult,
2930
3119
  garbage,
2931
3120
  formatLog,
2932
3121
  fingerprintOf,
@@ -2934,21 +3123,25 @@ export {
2934
3123
  fileAt,
2935
3124
  exportFiles,
2936
3125
  entryKindAt,
3126
+ encodeTool,
2937
3127
  emptyRoot,
2938
3128
  duplicateExportCheck,
2939
3129
  diffTrees,
2940
3130
  diffFile,
2941
3131
  deriveIdentity,
2942
3132
  deleteFile,
3133
+ decodeTool,
2943
3134
  converge,
2944
3135
  compact,
2945
3136
  clone,
2946
3137
  ciphertextOf,
2947
3138
  captureProv,
3139
+ canonicalReview,
2948
3140
  canonicalOp,
2949
3141
  canonicalAuthor,
2950
3142
  canonicalAttestation,
2951
3143
  canonicalAtt,
3144
+ buildReviewerAttestation,
2952
3145
  buildProvNode,
2953
3146
  blobHashAt,
2954
3147
  blame,
@@ -2963,6 +3156,7 @@ export {
2963
3156
  SealedClient,
2964
3157
  SealRegistry,
2965
3158
  SEALED,
3159
+ ReviewerAttestationLedger,
2966
3160
  Repo,
2967
3161
  RefAcls,
2968
3162
  MemoryOpLog,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "midsummer-sol",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Sol — agent-native version control (a new git). CLI, MCP server, and no-filesystem SDK.",
5
5
  "bin": { "sol": "./sol.js", "sol-mcp": "./sol-mcp.js", "sol-secret-mcp": "./sol-secret-mcp.js" },
6
6
  "main": "./index.js",