protect-mcp 0.7.6 → 0.9.2

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.
@@ -320,6 +320,7 @@ function signDecision(entry) {
320
320
  if (entry.timing) payload.timing = entry.timing;
321
321
  if (entry.swarm) payload.swarm = entry.swarm;
322
322
  if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
323
+ if (entry.enrichment) payload.enrichment = entry.enrichment;
323
324
  if (entry.action_readback) payload.action_readback = entry.action_readback;
324
325
  if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
325
326
  const result = artifactsModule.createSignedArtifact(
@@ -703,6 +704,423 @@ function buildActionReadback(tool, input) {
703
704
  };
704
705
  }
705
706
 
707
+ // node_modules/@noble/hashes/esm/utils.js
708
+ function isBytes(a) {
709
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
710
+ }
711
+ function abytes(b, ...lengths) {
712
+ if (!isBytes(b))
713
+ throw new Error("Uint8Array expected");
714
+ if (lengths.length > 0 && !lengths.includes(b.length))
715
+ throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
716
+ }
717
+ function aexists(instance, checkFinished = true) {
718
+ if (instance.destroyed)
719
+ throw new Error("Hash instance has been destroyed");
720
+ if (checkFinished && instance.finished)
721
+ throw new Error("Hash#digest() has already been called");
722
+ }
723
+ function aoutput(out, instance) {
724
+ abytes(out);
725
+ const min = instance.outputLen;
726
+ if (out.length < min) {
727
+ throw new Error("digestInto() expects output buffer of length at least " + min);
728
+ }
729
+ }
730
+ function clean(...arrays) {
731
+ for (let i = 0; i < arrays.length; i++) {
732
+ arrays[i].fill(0);
733
+ }
734
+ }
735
+ function createView(arr) {
736
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
737
+ }
738
+ function rotr(word, shift) {
739
+ return word << 32 - shift | word >>> shift;
740
+ }
741
+ var hasHexBuiltin = /* @__PURE__ */ (() => (
742
+ // @ts-ignore
743
+ typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
744
+ ))();
745
+ var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
746
+ function bytesToHex(bytes) {
747
+ abytes(bytes);
748
+ if (hasHexBuiltin)
749
+ return bytes.toHex();
750
+ let hex = "";
751
+ for (let i = 0; i < bytes.length; i++) {
752
+ hex += hexes[bytes[i]];
753
+ }
754
+ return hex;
755
+ }
756
+ function utf8ToBytes(str) {
757
+ if (typeof str !== "string")
758
+ throw new Error("string expected");
759
+ return new Uint8Array(new TextEncoder().encode(str));
760
+ }
761
+ function toBytes(data) {
762
+ if (typeof data === "string")
763
+ data = utf8ToBytes(data);
764
+ abytes(data);
765
+ return data;
766
+ }
767
+ var Hash = class {
768
+ };
769
+ function createHasher(hashCons) {
770
+ const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
771
+ const tmp = hashCons();
772
+ hashC.outputLen = tmp.outputLen;
773
+ hashC.blockLen = tmp.blockLen;
774
+ hashC.create = () => hashCons();
775
+ return hashC;
776
+ }
777
+
778
+ // node_modules/@noble/hashes/esm/_md.js
779
+ function setBigUint64(view, byteOffset, value, isLE) {
780
+ if (typeof view.setBigUint64 === "function")
781
+ return view.setBigUint64(byteOffset, value, isLE);
782
+ const _32n = BigInt(32);
783
+ const _u32_max = BigInt(4294967295);
784
+ const wh = Number(value >> _32n & _u32_max);
785
+ const wl = Number(value & _u32_max);
786
+ const h = isLE ? 4 : 0;
787
+ const l = isLE ? 0 : 4;
788
+ view.setUint32(byteOffset + h, wh, isLE);
789
+ view.setUint32(byteOffset + l, wl, isLE);
790
+ }
791
+ function Chi(a, b, c) {
792
+ return a & b ^ ~a & c;
793
+ }
794
+ function Maj(a, b, c) {
795
+ return a & b ^ a & c ^ b & c;
796
+ }
797
+ var HashMD = class extends Hash {
798
+ constructor(blockLen, outputLen, padOffset, isLE) {
799
+ super();
800
+ this.finished = false;
801
+ this.length = 0;
802
+ this.pos = 0;
803
+ this.destroyed = false;
804
+ this.blockLen = blockLen;
805
+ this.outputLen = outputLen;
806
+ this.padOffset = padOffset;
807
+ this.isLE = isLE;
808
+ this.buffer = new Uint8Array(blockLen);
809
+ this.view = createView(this.buffer);
810
+ }
811
+ update(data) {
812
+ aexists(this);
813
+ data = toBytes(data);
814
+ abytes(data);
815
+ const { view, buffer, blockLen } = this;
816
+ const len = data.length;
817
+ for (let pos = 0; pos < len; ) {
818
+ const take = Math.min(blockLen - this.pos, len - pos);
819
+ if (take === blockLen) {
820
+ const dataView = createView(data);
821
+ for (; blockLen <= len - pos; pos += blockLen)
822
+ this.process(dataView, pos);
823
+ continue;
824
+ }
825
+ buffer.set(data.subarray(pos, pos + take), this.pos);
826
+ this.pos += take;
827
+ pos += take;
828
+ if (this.pos === blockLen) {
829
+ this.process(view, 0);
830
+ this.pos = 0;
831
+ }
832
+ }
833
+ this.length += data.length;
834
+ this.roundClean();
835
+ return this;
836
+ }
837
+ digestInto(out) {
838
+ aexists(this);
839
+ aoutput(out, this);
840
+ this.finished = true;
841
+ const { buffer, view, blockLen, isLE } = this;
842
+ let { pos } = this;
843
+ buffer[pos++] = 128;
844
+ clean(this.buffer.subarray(pos));
845
+ if (this.padOffset > blockLen - pos) {
846
+ this.process(view, 0);
847
+ pos = 0;
848
+ }
849
+ for (let i = pos; i < blockLen; i++)
850
+ buffer[i] = 0;
851
+ setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
852
+ this.process(view, 0);
853
+ const oview = createView(out);
854
+ const len = this.outputLen;
855
+ if (len % 4)
856
+ throw new Error("_sha2: outputLen should be aligned to 32bit");
857
+ const outLen = len / 4;
858
+ const state = this.get();
859
+ if (outLen > state.length)
860
+ throw new Error("_sha2: outputLen bigger than state");
861
+ for (let i = 0; i < outLen; i++)
862
+ oview.setUint32(4 * i, state[i], isLE);
863
+ }
864
+ digest() {
865
+ const { buffer, outputLen } = this;
866
+ this.digestInto(buffer);
867
+ const res = buffer.slice(0, outputLen);
868
+ this.destroy();
869
+ return res;
870
+ }
871
+ _cloneInto(to) {
872
+ to || (to = new this.constructor());
873
+ to.set(...this.get());
874
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
875
+ to.destroyed = destroyed;
876
+ to.finished = finished;
877
+ to.length = length;
878
+ to.pos = pos;
879
+ if (length % blockLen)
880
+ to.buffer.set(buffer);
881
+ return to;
882
+ }
883
+ clone() {
884
+ return this._cloneInto();
885
+ }
886
+ };
887
+ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
888
+ 1779033703,
889
+ 3144134277,
890
+ 1013904242,
891
+ 2773480762,
892
+ 1359893119,
893
+ 2600822924,
894
+ 528734635,
895
+ 1541459225
896
+ ]);
897
+
898
+ // node_modules/@noble/hashes/esm/sha2.js
899
+ var SHA256_K = /* @__PURE__ */ Uint32Array.from([
900
+ 1116352408,
901
+ 1899447441,
902
+ 3049323471,
903
+ 3921009573,
904
+ 961987163,
905
+ 1508970993,
906
+ 2453635748,
907
+ 2870763221,
908
+ 3624381080,
909
+ 310598401,
910
+ 607225278,
911
+ 1426881987,
912
+ 1925078388,
913
+ 2162078206,
914
+ 2614888103,
915
+ 3248222580,
916
+ 3835390401,
917
+ 4022224774,
918
+ 264347078,
919
+ 604807628,
920
+ 770255983,
921
+ 1249150122,
922
+ 1555081692,
923
+ 1996064986,
924
+ 2554220882,
925
+ 2821834349,
926
+ 2952996808,
927
+ 3210313671,
928
+ 3336571891,
929
+ 3584528711,
930
+ 113926993,
931
+ 338241895,
932
+ 666307205,
933
+ 773529912,
934
+ 1294757372,
935
+ 1396182291,
936
+ 1695183700,
937
+ 1986661051,
938
+ 2177026350,
939
+ 2456956037,
940
+ 2730485921,
941
+ 2820302411,
942
+ 3259730800,
943
+ 3345764771,
944
+ 3516065817,
945
+ 3600352804,
946
+ 4094571909,
947
+ 275423344,
948
+ 430227734,
949
+ 506948616,
950
+ 659060556,
951
+ 883997877,
952
+ 958139571,
953
+ 1322822218,
954
+ 1537002063,
955
+ 1747873779,
956
+ 1955562222,
957
+ 2024104815,
958
+ 2227730452,
959
+ 2361852424,
960
+ 2428436474,
961
+ 2756734187,
962
+ 3204031479,
963
+ 3329325298
964
+ ]);
965
+ var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
966
+ var SHA256 = class extends HashMD {
967
+ constructor(outputLen = 32) {
968
+ super(64, outputLen, 8, false);
969
+ this.A = SHA256_IV[0] | 0;
970
+ this.B = SHA256_IV[1] | 0;
971
+ this.C = SHA256_IV[2] | 0;
972
+ this.D = SHA256_IV[3] | 0;
973
+ this.E = SHA256_IV[4] | 0;
974
+ this.F = SHA256_IV[5] | 0;
975
+ this.G = SHA256_IV[6] | 0;
976
+ this.H = SHA256_IV[7] | 0;
977
+ }
978
+ get() {
979
+ const { A, B, C, D, E, F, G, H } = this;
980
+ return [A, B, C, D, E, F, G, H];
981
+ }
982
+ // prettier-ignore
983
+ set(A, B, C, D, E, F, G, H) {
984
+ this.A = A | 0;
985
+ this.B = B | 0;
986
+ this.C = C | 0;
987
+ this.D = D | 0;
988
+ this.E = E | 0;
989
+ this.F = F | 0;
990
+ this.G = G | 0;
991
+ this.H = H | 0;
992
+ }
993
+ process(view, offset) {
994
+ for (let i = 0; i < 16; i++, offset += 4)
995
+ SHA256_W[i] = view.getUint32(offset, false);
996
+ for (let i = 16; i < 64; i++) {
997
+ const W15 = SHA256_W[i - 15];
998
+ const W2 = SHA256_W[i - 2];
999
+ const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
1000
+ const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
1001
+ SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
1002
+ }
1003
+ let { A, B, C, D, E, F, G, H } = this;
1004
+ for (let i = 0; i < 64; i++) {
1005
+ const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
1006
+ const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
1007
+ const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
1008
+ const T2 = sigma0 + Maj(A, B, C) | 0;
1009
+ H = G;
1010
+ G = F;
1011
+ F = E;
1012
+ E = D + T1 | 0;
1013
+ D = C;
1014
+ C = B;
1015
+ B = A;
1016
+ A = T1 + T2 | 0;
1017
+ }
1018
+ A = A + this.A | 0;
1019
+ B = B + this.B | 0;
1020
+ C = C + this.C | 0;
1021
+ D = D + this.D | 0;
1022
+ E = E + this.E | 0;
1023
+ F = F + this.F | 0;
1024
+ G = G + this.G | 0;
1025
+ H = H + this.H | 0;
1026
+ this.set(A, B, C, D, E, F, G, H);
1027
+ }
1028
+ roundClean() {
1029
+ clean(SHA256_W);
1030
+ }
1031
+ destroy() {
1032
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
1033
+ clean(this.buffer);
1034
+ }
1035
+ };
1036
+ var sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
1037
+
1038
+ // node_modules/@noble/hashes/esm/sha256.js
1039
+ var sha2562 = sha256;
1040
+
1041
+ // src/receipt-enrichment.ts
1042
+ var ENRICHMENT_VERSION = 1;
1043
+ function canonicalJson(value) {
1044
+ const seen = /* @__PURE__ */ new WeakSet();
1045
+ const enc = (v) => {
1046
+ if (v === null || v === void 0) return "null";
1047
+ const t = typeof v;
1048
+ if (t === "number") return Number.isFinite(v) ? JSON.stringify(v) : "null";
1049
+ if (t === "boolean" || t === "string") return JSON.stringify(v);
1050
+ if (t === "bigint") return JSON.stringify(v.toString());
1051
+ if (t === "function" || t === "symbol") return "null";
1052
+ if (Array.isArray(v)) return "[" + v.map(enc).join(",") + "]";
1053
+ if (t === "object") {
1054
+ const o = v;
1055
+ if (seen.has(o)) return '"[circular]"';
1056
+ seen.add(o);
1057
+ const body = Object.keys(o).sort().map((k) => JSON.stringify(k) + ":" + enc(o[k])).join(",");
1058
+ seen.delete(o);
1059
+ return "{" + body + "}";
1060
+ }
1061
+ return "null";
1062
+ };
1063
+ return enc(value);
1064
+ }
1065
+ function sha256Hex(s) {
1066
+ return bytesToHex(sha2562(new TextEncoder().encode(s)));
1067
+ }
1068
+ var RULES = [
1069
+ { cap: "exec.shell", tool: /bash|shell|exec|terminal|run_command|command/ },
1070
+ { cap: "fs.read", tool: /(^|[_.])(read|cat|glob|grep|search|ls|view|list_files|open)/ },
1071
+ { cap: "fs.write", tool: /write|create_file|save|append|edit|patch|replace|update_file|multiedit|notebook/ },
1072
+ { cap: "fs.delete", tool: /delete|remove|unlink|trash|(^|[_.])rm/ },
1073
+ { cap: "net.egress", tool: /fetch|http|curl|wget|request|download|browse|navigate|webfetch|web_search|scrape/ },
1074
+ { cap: "vcs", tool: /(^|[_.])git/, text: /\bgit\s+(commit|push|pull|clone|reset|checkout|branch|rebase|merge|tag)\b/ },
1075
+ { cap: "package.install", text: /\b(npm|pnpm|yarn)\s+(i|install|add)\b|\bpip3?\s+install\b|\bgo\s+get\b|\bcargo\s+add\b|\bbrew\s+install\b|\bapt(-get)?\s+install\b|\bgem\s+install\b/ },
1076
+ { cap: "secret.adjacent", text: /\.env\b|secret|credential|passwd|password|api[_-]?key|private[_-]?key|\.pem\b|\.key\b|id_rsa|bearer\s|aws_(access|secret)|authorization/ },
1077
+ { cap: "destructive", text: /rm\s+-[a-z]*[rf]|\brmdir\b|drop\s+table|truncate\s+table|delete\s+from|reset\s+--hard|--force\b|\bmkfs\b|\bdd\s+if=|shutdown|reboot|kill\s+-9|>\s*\/dev\/sd/ },
1078
+ { cap: "financial", text: /\b(order|trade|buy|sell|transfer|wire|payment|withdraw|deposit|swap|invoice|charge|refund|settle)\b/ },
1079
+ { cap: "data.query", text: /\bselect\s+[\s\S]+\bfrom\b|\binsert\s+into\b|\bupdate\s+[\s\S]+\bset\b|\bdelete\s+from\b/ }
1080
+ ];
1081
+ function deriveCapabilities(tool, input) {
1082
+ const t = String(tool || "").toLowerCase();
1083
+ let text = "";
1084
+ try {
1085
+ text = canonicalJson(input).toLowerCase();
1086
+ } catch {
1087
+ }
1088
+ const caps = /* @__PURE__ */ new Set();
1089
+ for (const r of RULES) {
1090
+ if (r.tool && r.tool.test(t)) caps.add(r.cap);
1091
+ if (r.text && r.text.test(text)) caps.add(r.cap);
1092
+ }
1093
+ return Array.from(caps).sort();
1094
+ }
1095
+ function deriveResource(input) {
1096
+ const o = input && typeof input === "object" ? input : {};
1097
+ const path = o.file_path ?? o.path ?? o.filePath ?? o.notebook_path ?? o.filename;
1098
+ if (typeof path === "string" && path.trim()) return { kind: "path", digest: sha256Hex(path.replace(/\\/g, "/")) };
1099
+ const url = o.url ?? o.uri ?? o.endpoint ?? o.href;
1100
+ if (typeof url === "string" && url.trim()) {
1101
+ try {
1102
+ return { kind: "host", digest: sha256Hex(new URL(url).host.toLowerCase()) };
1103
+ } catch {
1104
+ }
1105
+ }
1106
+ const cmd = o.command ?? o.cmd ?? o.script;
1107
+ if (typeof cmd === "string" && cmd.trim()) {
1108
+ const first = cmd.trim().split(/\s+/)[0];
1109
+ if (first) return { kind: "command", digest: sha256Hex(first) };
1110
+ }
1111
+ return void 0;
1112
+ }
1113
+ function buildEnrichment(tool, input) {
1114
+ const e = {
1115
+ v: ENRICHMENT_VERSION,
1116
+ input_digest: sha256Hex(canonicalJson(input ?? {})),
1117
+ capabilities: deriveCapabilities(tool, input)
1118
+ };
1119
+ const resource = deriveResource(input);
1120
+ if (resource) e.resource = resource;
1121
+ return e;
1122
+ }
1123
+
706
1124
  // src/hook-server.ts
707
1125
  var DEFAULT_PORT = 9377;
708
1126
  var LOG_FILE = ".protect-mcp-log.jsonl";
@@ -775,6 +1193,9 @@ async function handlePreToolUse(input, state) {
775
1193
  });
776
1194
  const payloadDigest = computePayloadDigest(input.toolInput);
777
1195
  const actionReadback = buildActionReadback(toolName, input.toolInput || {});
1196
+ const enrichment = buildEnrichment(toolName, input.toolInput || {});
1197
+ const inflightRec = state.inflightTools.get(requestId);
1198
+ if (inflightRec) inflightRec.enrichment = enrichment;
778
1199
  const swarm = {
779
1200
  ...state.swarmContext,
780
1201
  ...input.agentId && { agent_id: input.agentId },
@@ -1194,6 +1615,8 @@ function emitDecisionLog(state, entry) {
1194
1615
  ...entry.sandbox_state && { sandbox_state: entry.sandbox_state },
1195
1616
  ...entry.plan_receipt_id && { plan_receipt_id: entry.plan_receipt_id }
1196
1617
  };
1618
+ const enr = state.inflightTools.get(log.request_id)?.enrichment;
1619
+ if (enr) log.enrichment = enr;
1197
1620
  process.stderr.write(`[PROTECT_MCP] ${JSON.stringify(log)}
1198
1621
  `);
1199
1622
  try {
@@ -1615,3 +2038,8 @@ function normalizeHookInput(raw) {
1615
2038
  *
1616
2039
  * @license MIT
1617
2040
  */
2041
+ /*! Bundled license information:
2042
+
2043
+ @noble/hashes/esm/utils.js:
2044
+ (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
2045
+ */
@@ -1,7 +1,11 @@
1
1
  import {
2
2
  startHookServer
3
- } from "./chunk-6E2DHBAR.mjs";
4
- import "./chunk-WIPWNWMJ.mjs";
3
+ } from "./chunk-744JMCY4.mjs";
4
+ import "./chunk-KMNXHGGT.mjs";
5
+ import "./chunk-AYNQIEN7.mjs";
6
+ import "./chunk-IDUH2O4Q.mjs";
7
+ import "./chunk-JIDDQUSQ.mjs";
8
+ import "./chunk-D733KAPG.mjs";
5
9
  import "./chunk-PQJP2ZCI.mjs";
6
10
  export {
7
11
  startHookServer
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ProtectGateway
3
- } from "./chunk-VTPZ4G5I.mjs";
4
- import "./chunk-WIPWNWMJ.mjs";
3
+ } from "./chunk-GHR65WVD.mjs";
4
+ import "./chunk-IDUH2O4Q.mjs";
5
5
  import "./chunk-PQJP2ZCI.mjs";
6
6
 
7
7
  // src/http-transport.ts
package/dist/index.d.mts CHANGED
@@ -3,6 +3,20 @@ export { BUILTIN_PATTERNS, HookPattern, generateHookSettings, generateSampleCeda
3
3
  export { createSandboxServer } from './demo-server.mjs';
4
4
  import 'node:http';
5
5
 
6
+ interface ReceiptEnrichment {
7
+ /** Rule/schema version, so derivations stay reproducible as rules evolve. */
8
+ v: number;
9
+ /** SHA-256 (hex) of the canonical tool input. */
10
+ input_digest: string;
11
+ /** Sorted, deterministic capability tags (heuristic organisation labels). */
12
+ capabilities: string[];
13
+ /** Hashed coarse resource for clustering, when one is derivable. */
14
+ resource?: {
15
+ kind: 'path' | 'host' | 'command';
16
+ digest: string;
17
+ };
18
+ }
19
+
6
20
  interface ProtectPolicy {
7
21
  tools: Record<string, ToolPolicy>;
8
22
  /** Default trust tier for unidentified agents (default: "unknown") */
@@ -188,6 +202,8 @@ interface DecisionLog {
188
202
  sandbox_state?: 'enabled' | 'disabled' | 'unavailable';
189
203
  /** Plan receipt reference — links tool calls back to the approved plan */
190
204
  plan_receipt_id?: string;
205
+ /** Deterministic enrichment (input digest, capability tags, hashed resource) */
206
+ enrichment?: ReceiptEnrichment;
191
207
  /** Hook event that triggered this log entry */
192
208
  hook_event?: HookEventName;
193
209
  /** Redacted exact-action readback shown to humans before approving */
package/dist/index.d.ts CHANGED
@@ -3,6 +3,20 @@ export { BUILTIN_PATTERNS, HookPattern, generateHookSettings, generateSampleCeda
3
3
  export { createSandboxServer } from './demo-server.js';
4
4
  import 'node:http';
5
5
 
6
+ interface ReceiptEnrichment {
7
+ /** Rule/schema version, so derivations stay reproducible as rules evolve. */
8
+ v: number;
9
+ /** SHA-256 (hex) of the canonical tool input. */
10
+ input_digest: string;
11
+ /** Sorted, deterministic capability tags (heuristic organisation labels). */
12
+ capabilities: string[];
13
+ /** Hashed coarse resource for clustering, when one is derivable. */
14
+ resource?: {
15
+ kind: 'path' | 'host' | 'command';
16
+ digest: string;
17
+ };
18
+ }
19
+
6
20
  interface ProtectPolicy {
7
21
  tools: Record<string, ToolPolicy>;
8
22
  /** Default trust tier for unidentified agents (default: "unknown") */
@@ -188,6 +202,8 @@ interface DecisionLog {
188
202
  sandbox_state?: 'enabled' | 'disabled' | 'unavailable';
189
203
  /** Plan receipt reference — links tool calls back to the approved plan */
190
204
  plan_receipt_id?: string;
205
+ /** Deterministic enrichment (input digest, capability tags, hashed resource) */
206
+ enrichment?: ReceiptEnrichment;
191
207
  /** Hook event that triggered this log entry */
192
208
  hook_event?: HookEventName;
193
209
  /** Redacted exact-action readback shown to humans before approving */