@integraledger/agent-guard 0.9.0

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 (80) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +15 -0
  4. package/README.md +154 -0
  5. package/dist/decision.d.ts +26 -0
  6. package/dist/decision.d.ts.map +1 -0
  7. package/dist/decision.js +28 -0
  8. package/dist/decision.js.map +1 -0
  9. package/dist/evaluate.d.ts +15 -0
  10. package/dist/evaluate.d.ts.map +1 -0
  11. package/dist/evaluate.js +112 -0
  12. package/dist/evaluate.js.map +1 -0
  13. package/dist/fetch.d.ts +61 -0
  14. package/dist/fetch.d.ts.map +1 -0
  15. package/dist/fetch.js +126 -0
  16. package/dist/fetch.js.map +1 -0
  17. package/dist/fingerprint.d.ts +14 -0
  18. package/dist/fingerprint.d.ts.map +1 -0
  19. package/dist/fingerprint.js +15 -0
  20. package/dist/fingerprint.js.map +1 -0
  21. package/dist/index.d.ts +15 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +15 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/log.d.ts +32 -0
  26. package/dist/log.d.ts.map +1 -0
  27. package/dist/log.js +8 -0
  28. package/dist/log.js.map +1 -0
  29. package/dist/mechanical.d.ts +30 -0
  30. package/dist/mechanical.d.ts.map +1 -0
  31. package/dist/mechanical.js +35 -0
  32. package/dist/mechanical.js.map +1 -0
  33. package/dist/policy.d.ts +36 -0
  34. package/dist/policy.d.ts.map +1 -0
  35. package/dist/policy.js +96 -0
  36. package/dist/policy.js.map +1 -0
  37. package/dist/proposal-ack.d.ts +111 -0
  38. package/dist/proposal-ack.d.ts.map +1 -0
  39. package/dist/proposal-ack.js +114 -0
  40. package/dist/proposal-ack.js.map +1 -0
  41. package/dist/proposal-acp.d.ts +18 -0
  42. package/dist/proposal-acp.d.ts.map +1 -0
  43. package/dist/proposal-acp.js +72 -0
  44. package/dist/proposal-acp.js.map +1 -0
  45. package/dist/proposal-ap2.d.ts +121 -0
  46. package/dist/proposal-ap2.d.ts.map +1 -0
  47. package/dist/proposal-ap2.js +112 -0
  48. package/dist/proposal-ap2.js.map +1 -0
  49. package/dist/proposal-mpp.d.ts +44 -0
  50. package/dist/proposal-mpp.d.ts.map +1 -0
  51. package/dist/proposal-mpp.js +106 -0
  52. package/dist/proposal-mpp.js.map +1 -0
  53. package/dist/proposal-universal.d.ts +239 -0
  54. package/dist/proposal-universal.d.ts.map +1 -0
  55. package/dist/proposal-universal.js +470 -0
  56. package/dist/proposal-universal.js.map +1 -0
  57. package/dist/proposal.d.ts +33 -0
  58. package/dist/proposal.d.ts.map +1 -0
  59. package/dist/proposal.js +107 -0
  60. package/dist/proposal.js.map +1 -0
  61. package/dist/transact.d.ts +24 -0
  62. package/dist/transact.d.ts.map +1 -0
  63. package/dist/transact.js +11 -0
  64. package/dist/transact.js.map +1 -0
  65. package/package.json +81 -0
  66. package/src/decision.ts +54 -0
  67. package/src/evaluate.ts +176 -0
  68. package/src/fetch.ts +169 -0
  69. package/src/fingerprint.ts +27 -0
  70. package/src/index.ts +68 -0
  71. package/src/log.ts +34 -0
  72. package/src/mechanical.ts +71 -0
  73. package/src/policy.ts +135 -0
  74. package/src/proposal-ack.ts +216 -0
  75. package/src/proposal-acp.ts +89 -0
  76. package/src/proposal-ap2.ts +195 -0
  77. package/src/proposal-mpp.ts +125 -0
  78. package/src/proposal-universal.ts +671 -0
  79. package/src/proposal.ts +170 -0
  80. package/src/transact.ts +34 -0
@@ -0,0 +1,61 @@
1
+ export interface FetchedTerms {
2
+ readonly bytes: Uint8Array;
3
+ readonly format: string;
4
+ readonly fetchedAt: string;
5
+ }
6
+ export interface TermsFetcher {
7
+ fetch(url: string): Promise<FetchedTerms>;
8
+ }
9
+ /** One resolved address for a host. Mirrors `node:dns/promises` `lookup(host, { all: true })`. */
10
+ export interface ResolvedAddress {
11
+ readonly address: string;
12
+ readonly family: number;
13
+ }
14
+ /**
15
+ * The host-resolution port. REQUIRED, and deliberately not defaulted: the buyer fetches a URL the SELLER
16
+ * chose, so this is the guard between an agent's HTTP client and the buyer's own network — a config that
17
+ * can omit it silently omits the guard. Node deployments pass `nodeDnsLookup`; a Workers deployment passes
18
+ * its own (or one that throws, stating it relies on the platform's private-range egress blocking). The
19
+ * port MUST return literal-IP hosts unchanged, as `node:dns` does.
20
+ */
21
+ export type HostLookup = (host: string) => Promise<readonly ResolvedAddress[]>;
22
+ export interface CachingFetcherConfig {
23
+ readonly httpFetch: typeof fetch;
24
+ readonly now: () => string;
25
+ /** Resolves a hostname to the addresses the unicast guard checks. See `HostLookup` — no default. */
26
+ readonly lookup: HostLookup;
27
+ /** LCP §2.6: clients SHOULD NOT cache beyond this; default 24h. Re-fetch after it is a MUST (enforced here). */
28
+ readonly maxAgeSeconds?: number;
29
+ /** Fail-loud byte ceiling (DoS guard); default 1 MiB. */
30
+ readonly maxBytes?: number;
31
+ /** Max distinct URLs retained (bounded memory / anti-DoS); default 256. Oldest inserted entry evicted past it. */
32
+ readonly maxEntries?: number;
33
+ }
34
+ /**
35
+ * `node:dns/promises` as a `HostLookup`. Imported lazily so the module graph stays runtime-neutral (the
36
+ * gate is client-side and may run where `node:dns` does not exist) — a Workers build that never calls this
37
+ * never pulls it in.
38
+ */
39
+ export declare const nodeDnsLookup: HostLookup;
40
+ /**
41
+ * HTTPS-only, cached (LCP §2.6), size-capped, SSRF-guarded terms fetcher. The MUST (re-fetch after expiry) is
42
+ * unconditional; the SHOULD (≤24h) is the default cap.
43
+ *
44
+ * SSRF posture — this is the BUYER's exposure, because the URL comes off the seller's 402 challenge:
45
+ * - HTTPS scheme required;
46
+ * - `redirect: "error"`, so a redirect to a non-HTTPS or internal host fails loudly rather than being
47
+ * followed silently;
48
+ * - EVERY resolved address for the host must be public unicast (`evidence.isUnicastPublic` — the one
49
+ * implementation of that predicate; this package does not re-derive it). The check runs on every
50
+ * network fetch, not once per URL, so a cache expiry re-opens it rather than grandfathering a host.
51
+ * - the byte cap is enforced while STREAMING, plus a `Content-Length` pre-check. Reading the whole body
52
+ * and measuring afterwards protects the check but not the memory, and a chunked response with no
53
+ * declared length is exactly the case the cap exists for.
54
+ *
55
+ * HONEST LIMITATION — DNS rebinding, carried verbatim from `evidence`'s resolver: the guard validates what
56
+ * `lookup` returns, but `httpFetch` performs its own resolution and nothing pins the connection to the
57
+ * validated address. This is real defense-in-depth (the naive name → private IP case is blocked outright),
58
+ * NOT a complete guarantee; closing it needs connection-level IP pinning inside the injected `httpFetch`.
59
+ */
60
+ export declare function makeCachingFetcher(cfg: CachingFetcherConfig): TermsFetcher;
61
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AACD,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC3C;AAED,kGAAkG;AAClG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AACD;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC;AAE/E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,OAAO,KAAK,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,oGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,gHAAgH;IAChH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,kHAAkH;IAClH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAGD;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,UAG3B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,oBAAoB,GAAG,YAAY,CAmG1E"}
package/dist/fetch.js ADDED
@@ -0,0 +1,126 @@
1
+ import { isUnicastPublic } from "@integraledger/lcp-evidence";
2
+ const DAY = 86_400;
3
+ /**
4
+ * `node:dns/promises` as a `HostLookup`. Imported lazily so the module graph stays runtime-neutral (the
5
+ * gate is client-side and may run where `node:dns` does not exist) — a Workers build that never calls this
6
+ * never pulls it in.
7
+ */
8
+ export const nodeDnsLookup = async (host) => {
9
+ const dns = await import("node:dns/promises");
10
+ return dns.lookup(host, { all: true });
11
+ };
12
+ /**
13
+ * HTTPS-only, cached (LCP §2.6), size-capped, SSRF-guarded terms fetcher. The MUST (re-fetch after expiry) is
14
+ * unconditional; the SHOULD (≤24h) is the default cap.
15
+ *
16
+ * SSRF posture — this is the BUYER's exposure, because the URL comes off the seller's 402 challenge:
17
+ * - HTTPS scheme required;
18
+ * - `redirect: "error"`, so a redirect to a non-HTTPS or internal host fails loudly rather than being
19
+ * followed silently;
20
+ * - EVERY resolved address for the host must be public unicast (`evidence.isUnicastPublic` — the one
21
+ * implementation of that predicate; this package does not re-derive it). The check runs on every
22
+ * network fetch, not once per URL, so a cache expiry re-opens it rather than grandfathering a host.
23
+ * - the byte cap is enforced while STREAMING, plus a `Content-Length` pre-check. Reading the whole body
24
+ * and measuring afterwards protects the check but not the memory, and a chunked response with no
25
+ * declared length is exactly the case the cap exists for.
26
+ *
27
+ * HONEST LIMITATION — DNS rebinding, carried verbatim from `evidence`'s resolver: the guard validates what
28
+ * `lookup` returns, but `httpFetch` performs its own resolution and nothing pins the connection to the
29
+ * validated address. This is real defense-in-depth (the naive name → private IP case is blocked outright),
30
+ * NOT a complete guarantee; closing it needs connection-level IP pinning inside the injected `httpFetch`.
31
+ */
32
+ export function makeCachingFetcher(cfg) {
33
+ const maxAge = cfg.maxAgeSeconds ?? DAY;
34
+ const maxBytes = cfg.maxBytes ?? 1024 * 1024;
35
+ const maxEntries = cfg.maxEntries ?? 256;
36
+ const cache = new Map();
37
+ const secs = (iso) => Math.floor(Date.parse(iso) / 1000);
38
+ async function assertPublicHost(url) {
39
+ // BRACKETS ARE URL SYNTAX, NOT ADDRESS SYNTAX, and stripping them here is what lets an IPv6 literal
40
+ // reach the guard at all. `new URL("https://[::1]/x").hostname` is `"[::1]"`, and `node:dns` answers
41
+ // that with ENOTFOUND — so the request was refused for the right outcome and the WRONG REASON: the
42
+ // buyer's record named a DNS failure where the truth was an SSRF refusal, and that message is what a
43
+ // buyer would report to a seller.
44
+ //
45
+ // It also refused hosts it should allow. `isUnicastPublic` parses the bracketed form correctly, but the
46
+ // lookup never returned an address for it, so a seller publishing a legitimate PUBLIC IPv6 literal was
47
+ // refused with the same ENOTFOUND as a loopback one.
48
+ //
49
+ // Unbracketed, `node:dns` returns a literal unchanged — `::1` comes back as `{address:"::1",family:6}`,
50
+ // exactly as `127.0.0.1` does — which is the behaviour the `HostLookup` contract already requires of
51
+ // every port. Normalising here rather than inside `nodeDnsLookup` means a Workers or Deno port inherits
52
+ // the fix instead of having to repeat it.
53
+ // ONE regex rather than `startsWith("[") && endsWith("]")`, because a matching pair is ONE fact and the
54
+ // conjunction spelt it as two. `new URL()` cannot produce a hostname with only one bracket, so neither
55
+ // conjunct was individually observable — mutating either left behaviour identical, which is a survivor
56
+ // no test can honestly kill. Expressing the pair as a single match makes the check say what it means and
57
+ // leaves nothing unobservable behind it.
58
+ const host = /^\[(.+)\]$/.exec(url.hostname)?.[1] ?? url.hostname;
59
+ const addrs = await cfg.lookup(host);
60
+ if (addrs.length === 0)
61
+ throw new Error(`no address for ${url.hostname} — refused (SSRF guard)`);
62
+ for (const { address } of addrs)
63
+ if (!isUnicastPublic(address))
64
+ throw new Error(`${url.hostname} resolves to a non-public address (${address}) — refused (SSRF guard)`);
65
+ }
66
+ async function readCapped(res) {
67
+ const body = res.body;
68
+ if (body === null)
69
+ return new Uint8Array(0);
70
+ const reader = body.getReader();
71
+ const chunks = [];
72
+ let total = 0;
73
+ for (;;) {
74
+ const { done, value } = await reader.read();
75
+ if (done)
76
+ break;
77
+ total += value.length;
78
+ if (total > maxBytes) {
79
+ await reader.cancel();
80
+ throw new Error(`terms exceed ${maxBytes} bytes (fail-loud)`);
81
+ }
82
+ chunks.push(value);
83
+ }
84
+ const out = new Uint8Array(total);
85
+ let o = 0;
86
+ for (const c of chunks) {
87
+ out.set(c, o);
88
+ o += c.length;
89
+ }
90
+ return out;
91
+ }
92
+ return {
93
+ async fetch(url) {
94
+ if (!url.startsWith("https://"))
95
+ throw new Error(`terms url must be HTTPS (SSRF posture): ${url}`);
96
+ const nowS = secs(cfg.now());
97
+ const hit = cache.get(url);
98
+ if (hit !== undefined && nowS - hit.at < maxAge)
99
+ return hit.terms; // SHOULD: serve fresh from cache
100
+ // Re-validated on every network fetch — an expiring cache entry must not grandfather a host.
101
+ await assertPublicHost(new URL(url));
102
+ const res = await cfg.httpFetch(url, { redirect: "error" }); // MUST: re-fetch after expiry; never follow redirects
103
+ if (!res.ok)
104
+ throw new Error(`terms fetch failed: ${res.status} ${url}`);
105
+ const declared = res.headers.get("content-length");
106
+ if (declared !== null &&
107
+ Number.isFinite(Number(declared)) &&
108
+ Number(declared) > maxBytes)
109
+ throw new Error(`terms declare ${declared} bytes > ${maxBytes} (fail-loud): ${url}`);
110
+ const buf = await readCapped(res);
111
+ const terms = {
112
+ bytes: buf,
113
+ format: res.headers.get("content-type") ?? "application/octet-stream",
114
+ fetchedAt: cfg.now(),
115
+ };
116
+ cache.set(url, { at: nowS, terms });
117
+ if (cache.size > maxEntries) {
118
+ const oldest = cache.keys().next().value;
119
+ if (oldest !== undefined)
120
+ cache.delete(oldest);
121
+ }
122
+ return terms;
123
+ },
124
+ };
125
+ }
126
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAqC9D,MAAM,GAAG,GAAG,MAAM,CAAC;AAEnB;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAe,KAAK,EAAE,IAAY,EAAE,EAAE;IAC9D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC9C,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;AACzC,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAyB;IAC1D,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC;IACxC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,IAAI,GAAG,IAAI,CAAC;IAC7C,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+C,CAAC;IACrE,MAAM,IAAI,GAAG,CAAC,GAAW,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAEzE,KAAK,UAAU,gBAAgB,CAAC,GAAQ;QACtC,oGAAoG;QACpG,qGAAqG;QACrG,mGAAmG;QACnG,qGAAqG;QACrG,kCAAkC;QAClC,EAAE;QACF,wGAAwG;QACxG,uGAAuG;QACvG,qDAAqD;QACrD,EAAE;QACF,wGAAwG;QACxG,qGAAqG;QACrG,wGAAwG;QACxG,0CAA0C;QAC1C,wGAAwG;QACxG,uGAAuG;QACvG,uGAAuG;QACvG,yGAAyG;QACzG,yCAAyC;QACzC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC;QAClE,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,QAAQ,yBAAyB,CAAC,CAAC;QAC3E,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,KAAK;YAC7B,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;gBAC3B,MAAM,IAAI,KAAK,CACb,GAAG,GAAG,CAAC,QAAQ,sCAAsC,OAAO,0BAA0B,CACvF,CAAC;IACR,CAAC;IAED,KAAK,UAAU,UAAU,CAAC,GAAa;QACrC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,MAAM,MAAM,GAAiB,EAAE,CAAC;QAChC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;YACtB,IAAI,KAAK,GAAG,QAAQ,EAAE,CAAC;gBACrB,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,gBAAgB,QAAQ,oBAAoB,CAAC,CAAC;YAChE,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACd,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,GAAW;YACrB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,2CAA2C,GAAG,EAAE,CAAC,CAAC;YACpE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;YAC7B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,GAAG,KAAK,SAAS,IAAI,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,MAAM;gBAAE,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,iCAAiC;YAEpG,6FAA6F;YAC7F,MAAM,gBAAgB,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAErC,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,sDAAsD;YACnH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;YACzE,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YACnD,IACE,QAAQ,KAAK,IAAI;gBACjB,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACjC,MAAM,CAAC,QAAQ,CAAC,GAAG,QAAQ;gBAE3B,MAAM,IAAI,KAAK,CACb,iBAAiB,QAAQ,YAAY,QAAQ,iBAAiB,GAAG,EAAE,CACpE,CAAC;YACJ,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,KAAK,GAAiB;gBAC1B,KAAK,EAAE,GAAG;gBACV,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,0BAA0B;gBACrE,SAAS,EAAE,GAAG,CAAC,GAAG,EAAE;aACrB,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACpC,IAAI,KAAK,CAAC,IAAI,GAAG,UAAU,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;gBACzC,IAAI,MAAM,KAAK,SAAS;oBAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACjD,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { HaltClass } from "@integraledger/lcp-binding-core";
2
+ export type FingerprintOutcome = {
3
+ readonly status: "proved";
4
+ readonly atrHash: `0x${string}`;
5
+ } | {
6
+ readonly status: "failed";
7
+ readonly haltClass: HaltClass;
8
+ readonly recomputed: `0x${string}`;
9
+ readonly advertised: string;
10
+ };
11
+ /** The LCP §5.3 core: recompute SHA-256 over the fetched terms bytes and compare to the advertised atrHash. A
12
+ * mismatch is `failed(verification-failure)` — the caller MUST halt before invoking any signing key. */
13
+ export declare function recomputeAndCompare(bytes: Uint8Array, advertisedAtrHash: string): Promise<FingerprintOutcome>;
14
+ //# sourceMappingURL=fingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,MAAM,EAAE,CAAA;CAAE,GAC9D;IACE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,KAAK,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEN;yGACyG;AACzG,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,UAAU,EACjB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,kBAAkB,CAAC,CAU7B"}
@@ -0,0 +1,15 @@
1
+ import { hashAtr } from "@integraledger/lcp-kernel";
2
+ /** The LCP §5.3 core: recompute SHA-256 over the fetched terms bytes and compare to the advertised atrHash. A
3
+ * mismatch is `failed(verification-failure)` — the caller MUST halt before invoking any signing key. */
4
+ export async function recomputeAndCompare(bytes, advertisedAtrHash) {
5
+ const recomputed = await hashAtr(bytes);
6
+ return recomputed.toLowerCase() === advertisedAtrHash.toLowerCase()
7
+ ? { status: "proved", atrHash: recomputed }
8
+ : {
9
+ status: "failed",
10
+ haltClass: "verification-failure",
11
+ recomputed,
12
+ advertised: advertisedAtrHash,
13
+ };
14
+ }
15
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAUpD;yGACyG;AACzG,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAiB,EACjB,iBAAyB;IAEzB,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,OAAO,UAAU,CAAC,WAAW,EAAE,KAAK,iBAAiB,CAAC,WAAW,EAAE;QACjE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE;QAC3C,CAAC,CAAC;YACE,MAAM,EAAE,QAAQ;YAChB,SAAS,EAAE,sBAAsB;YACjC,UAAU;YACV,UAAU,EAAE,iBAAiB;SAC9B,CAAC;AACR,CAAC"}
@@ -0,0 +1,15 @@
1
+ export { type Disposition, decideOutcome, foldDispositions, type GateDecision, type StepStatus, } from "./decision.js";
2
+ export { evaluate, type GatePorts } from "./evaluate.js";
3
+ export { type CachingFetcherConfig, type FetchedTerms, type HostLookup, makeCachingFetcher, nodeDnsLookup, type ResolvedAddress, type TermsFetcher, } from "./fetch.js";
4
+ export { type FingerprintOutcome, recomputeAndCompare } from "./fingerprint.js";
5
+ export { InMemoryOrc4Log, type Orc4Entry, type Orc4Log } from "./log.js";
6
+ export { type MechanicalPorts, verifySettled } from "./mechanical.js";
7
+ export { type BuyerPolicy, evaluatePolicy, type PolicyResult, } from "./policy.js";
8
+ export { type GateProposal, type ProposalContext, parseProposalFromChallenge, } from "./proposal.js";
9
+ export { ACK_DID_METHODS, type AckReceiptContribution, type AckReceiptFacts, ackReceiptContribution, parseAckReceipt, } from "./proposal-ack.js";
10
+ export { parseProposalFromAcpCheckout } from "./proposal-acp.js";
11
+ export { AP2_HALT_POINT, type Ap2HaltPoint, type Ap2ProposalContext, type Ap2Step, assertBeforeAp2HaltPoint, isAp2SigningStep, parseProposalFromAp2Envelope, } from "./proposal-ap2.js";
12
+ export { parseProposalFromMppRequest } from "./proposal-mpp.js";
13
+ export { ACP_SESSION_STATUS, type AdvertisedTerms, type AdvertisedTermsUrl, detectProtocol, matchProtocols, PROPOSAL_PARSERS, PROTOCOL_DISCRIMINANTS, type ProposalParser, type ProtocolDiscriminant, parseableProtocols, parseProposalUniversal, readAdvertisedTerms, VI_OPEN_MANDATE_VCT, } from "./proposal-universal.js";
14
+ export { type GuardedSigner, type TransactResult, transact, } from "./transact.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,aAAa,EACb,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,UAAU,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,kBAAkB,EAClB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,KAAK,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AACzE,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EACL,KAAK,WAAW,EAChB,cAAc,EACd,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,sBAAsB,EACtB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,OAAO,EACZ,wBAAwB,EACxB,gBAAgB,EAChB,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,QAAQ,GACT,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ export { decideOutcome, foldDispositions, } from "./decision.js";
2
+ export { evaluate } from "./evaluate.js";
3
+ export { makeCachingFetcher, nodeDnsLookup, } from "./fetch.js";
4
+ export { recomputeAndCompare } from "./fingerprint.js";
5
+ export { InMemoryOrc4Log } from "./log.js";
6
+ export { verifySettled } from "./mechanical.js";
7
+ export { evaluatePolicy, } from "./policy.js";
8
+ export { parseProposalFromChallenge, } from "./proposal.js";
9
+ export { ACK_DID_METHODS, ackReceiptContribution, parseAckReceipt, } from "./proposal-ack.js";
10
+ export { parseProposalFromAcpCheckout } from "./proposal-acp.js";
11
+ export { AP2_HALT_POINT, assertBeforeAp2HaltPoint, isAp2SigningStep, parseProposalFromAp2Envelope, } from "./proposal-ap2.js";
12
+ export { parseProposalFromMppRequest } from "./proposal-mpp.js";
13
+ export { ACP_SESSION_STATUS, detectProtocol, matchProtocols, PROPOSAL_PARSERS, PROTOCOL_DISCRIMINANTS, parseableProtocols, parseProposalUniversal, readAdvertisedTerms, VI_OPEN_MANDATE_VCT, } from "./proposal-universal.js";
14
+ export { transact, } from "./transact.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,gBAAgB,GAGjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAkB,MAAM,eAAe,CAAC;AACzD,OAAO,EAIL,kBAAkB,EAClB,aAAa,GAGd,MAAM,YAAY,CAAC;AACpB,OAAO,EAA2B,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,eAAe,EAAgC,MAAM,UAAU,CAAC;AACzE,OAAO,EAAwB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAEL,cAAc,GAEf,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,eAAe,EAGf,sBAAsB,EACtB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,cAAc,EAId,wBAAwB,EACxB,gBAAgB,EAChB,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACL,kBAAkB,EAGlB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EAGtB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAGL,QAAQ,GACT,MAAM,eAAe,CAAC"}
package/dist/log.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import type { HaltClass } from "@integraledger/lcp-binding-core";
2
+ export interface Orc4Entry {
3
+ readonly timestamp: string;
4
+ readonly decision: "proceed" | "decline" | "escalate";
5
+ readonly haltClass?: HaltClass;
6
+ readonly code: string;
7
+ readonly detail: string;
8
+ readonly atrHash?: string;
9
+ /**
10
+ * The discovery document the proposal pointed at — the terms this decision was reached about.
11
+ *
12
+ * WITHOUT IT AN ENTRY NAMES A VERDICT AND NOT ITS SUBJECT. `atrHash` alone does not identify the
13
+ * document: on the decision that matters most — a fingerprint mismatch — the advertised hash is by
14
+ * construction NOT the hash of what was served, so a reader holding only that value cannot say which
15
+ * document disagreed with it. A seller told "your terms did not match" and given a hash it cannot
16
+ * reproduce learns nothing it can act on; given the URL, it can fetch the bytes and see.
17
+ *
18
+ * Optional for the same reason `atrHash` is: this is the sink's input type, and a producer other than
19
+ * `evaluate` — a mechanical verification, say — may legitimately have no document to name. `evaluate`
20
+ * always sets it.
21
+ */
22
+ readonly legalContextUrl?: string;
23
+ }
24
+ export interface Orc4Log {
25
+ append(e: Orc4Entry): void;
26
+ }
27
+ /** A real, complete in-memory ORC-4 sink (not a mock) — a deployment injects a durable one. */
28
+ export declare class InMemoryOrc4Log implements Orc4Log {
29
+ readonly entries: Orc4Entry[];
30
+ append(e: Orc4Entry): void;
31
+ }
32
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;IACtD,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AACD,MAAM,WAAW,OAAO;IACtB,MAAM,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CAC5B;AACD,+FAA+F;AAC/F,qBAAa,eAAgB,YAAW,OAAO;IAC7C,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,CAAM;IACnC,MAAM,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,CAEzB;CACF"}
package/dist/log.js ADDED
@@ -0,0 +1,8 @@
1
+ /** A real, complete in-memory ORC-4 sink (not a mock) — a deployment injects a durable one. */
2
+ export class InMemoryOrc4Log {
3
+ entries = [];
4
+ append(e) {
5
+ this.entries.push(e);
6
+ }
7
+ }
8
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AA2BA,+FAA+F;AAC/F,MAAM,OAAO,eAAe;IACjB,OAAO,GAAgB,EAAE,CAAC;IACnC,MAAM,CAAC,CAAY;QACjB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,30 @@
1
+ import type { Assurance } from "@integraledger/lcp-authority";
2
+ import type { SettlementRef, VerifierPorts, WeldAdapter } from "@integraledger/lcp-binding-core";
3
+ import { type TransactionClass, type VerificationReport } from "@integraledger/lcp-verify";
4
+ /** Inputs for the post-settlement mechanical verify. `verifierPorts` (chain + artifacts) is INJECTED, which is
5
+ * what keeps the gate viem-free. `atrBytes` is the fetched ATR; `coverage` matches verify's shape. */
6
+ export interface MechanicalPorts {
7
+ readonly verifierPorts: VerifierPorts;
8
+ readonly atrBytes: Uint8Array;
9
+ readonly asOf: string;
10
+ readonly coverage: {
11
+ ports: string[];
12
+ bindings: string[];
13
+ };
14
+ readonly claimedClass?: TransactionClass;
15
+ /**
16
+ * The parties as the buyer resolved them (IDN-1/3). TC-1 and up require the attribution rung, so a
17
+ * record verified without this reads honestly as unverified — the gate KNOWS both sides at this point
18
+ * (it holds the seller's stated assurance from the proposal and it is itself the payer), so it says so.
19
+ */
20
+ readonly identity: {
21
+ readonly sellerAssurance: Assurance;
22
+ readonly payer: string;
23
+ };
24
+ }
25
+ /** Turn a settled receipt into a TC-2/TC-3 *transaction* record: recover the on-chain atrHash via the injected
26
+ * binding adapter, then run `verify({depth:"mechanical"})` — `verified` is raised honestly iff the ATR bytes
27
+ * hash to the recovered atrHash (and the class-required steps hold). A refused recovery leaves settledAtrHash
28
+ * absent → the fingerprint is indeterminate → verified stays false. */
29
+ export declare function verifySettled(ref: SettlementRef, adapter: Pick<WeldAdapter, "recover">, ports: MechanicalPorts): Promise<VerificationReport>;
30
+ //# sourceMappingURL=mechanical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mechanical.d.ts","sourceRoot":"","sources":["../src/mechanical.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACZ,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EAExB,MAAM,2BAA2B,CAAC;AAEnC;uGACuG;AACvG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC3D,QAAQ,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;QACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED;;;wEAGwE;AACxE,wBAAsB,aAAa,CACjC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,EACrC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,kBAAkB,CAAC,CA8B7B"}
@@ -0,0 +1,35 @@
1
+ import { verify, } from "@integraledger/lcp-verify";
2
+ /** Turn a settled receipt into a TC-2/TC-3 *transaction* record: recover the on-chain atrHash via the injected
3
+ * binding adapter, then run `verify({depth:"mechanical"})` — `verified` is raised honestly iff the ATR bytes
4
+ * hash to the recovered atrHash (and the class-required steps hold). A refused recovery leaves settledAtrHash
5
+ * absent → the fingerprint is indeterminate → verified stays false. */
6
+ export async function verifySettled(ref, adapter, ports) {
7
+ const recovered = await adapter.recover(ref, ports.verifierPorts);
8
+ // The buyer resolves the seller at the assurance the proposal stated, and itself at the level the rail
9
+ // actually supports: a wallet signature. Stating the low level honestly is conformant (IDN-3); claiming
10
+ // a level the rail cannot support would not be.
11
+ const identity = {
12
+ seller: {
13
+ subject: "seller",
14
+ assurance: ports.identity.sellerAssurance,
15
+ chain: [{ via: "domain-control" }],
16
+ },
17
+ buyer: {
18
+ subject: ports.identity.payer,
19
+ assurance: "wallet-signature-only",
20
+ chain: [{ via: "key" }],
21
+ },
22
+ };
23
+ const base = {
24
+ depth: "mechanical",
25
+ atrBytes: ports.atrBytes,
26
+ coverage: ports.coverage,
27
+ claimedClass: ports.claimedClass ?? "TC-2",
28
+ asOf: ports.asOf,
29
+ // The settlement the buyer just made IS the enumeration it can honestly attest to (PAY).
30
+ settlements: [ref],
31
+ identity,
32
+ };
33
+ return verify("ok" in recovered ? { ...base, settledAtrHash: recovered.value } : base);
34
+ }
35
+ //# sourceMappingURL=mechanical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mechanical.js","sourceRoot":"","sources":["../src/mechanical.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,MAAM,GACP,MAAM,2BAA2B,CAAC;AAqBnC;;;wEAGwE;AACxE,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAkB,EAClB,OAAqC,EACrC,KAAsB;IAEtB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAClE,uGAAuG;IACvG,wGAAwG;IACxG,gDAAgD;IAChD,MAAM,QAAQ,GAAmB;QAC/B,MAAM,EAAE;YACN,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,eAAe;YACzC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC;SACnC;QACD,KAAK,EAAE;YACL,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK;YAC7B,SAAS,EAAE,uBAAuB;YAClC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;SACxB;KACF,CAAC;IACF,MAAM,IAAI,GAAG;QACX,KAAK,EAAE,YAAqB;QAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,YAAY,EAAE,KAAK,CAAC,YAAY,IAAK,MAA2B;QAChE,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,yFAAyF;QACzF,WAAW,EAAE,CAAC,GAAG,CAAC;QAClB,QAAQ;KACT,CAAC;IACF,OAAO,MAAM,CACX,IAAI,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CACxE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { Assurance } from "@integraledger/lcp-authority";
2
+ import type { LegalContextJson } from "@integraledger/lcp-discovery";
3
+ import type { Disposition } from "./decision.js";
4
+ /**
5
+ * The buyer's stated policy.
6
+ *
7
+ * **`onNotAttempted` is the only gap disposition here, and its absent sibling is the point.** An earlier
8
+ * shape also required `onIndeterminate`, which `evaluate` never read — and could not have, because no step
9
+ * it runs yields `indeterminate`: the fingerprint proves or fails, and the record parses or does not. A
10
+ * required field promising a disposition the gate cannot reach is worse than no field, because a buyer
11
+ * reads it as a control. Consumers folding their own step ladders use `decideOutcome`, which carries its
12
+ * own four-valued gaps shape and does handle `indeterminate`.
13
+ */
14
+ export interface BuyerPolicy {
15
+ readonly requiredLevel: 1 | 2 | 3 | 4;
16
+ readonly acceptableJurisdictions: readonly string[] | "any";
17
+ readonly acceptableDisputeMethods: readonly string[] | "any";
18
+ readonly maxCommitment: Readonly<Record<string, string>>;
19
+ readonly forbiddenClauseCategories: readonly string[];
20
+ readonly requiredAssurance: Assurance | "any";
21
+ readonly onNotAttempted: Disposition;
22
+ }
23
+ export type PolicyResult = {
24
+ readonly ok: true;
25
+ } | {
26
+ readonly ok: false;
27
+ readonly code: string;
28
+ readonly detail: string;
29
+ };
30
+ /** Evaluate the buyer policy on the TYPED legal-context envelope + the typed offer — NEVER on prose (LCP §12.7).
31
+ * Every check reads a structured field; the function's inputs cannot carry the natural-language body. */
32
+ export declare function evaluatePolicy(policy: BuyerPolicy, terms: LegalContextJson, offer: {
33
+ amount: string;
34
+ unit: string;
35
+ }): PolicyResult;
36
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;;;;;GASG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,QAAQ,CAAC,uBAAuB,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC;IAC5D,QAAQ,CAAC,wBAAwB,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC;IAC7D,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,yBAAyB,EAAE,SAAS,MAAM,EAAE,CAAC;IACtD,QAAQ,CAAC,iBAAiB,EAAE,SAAS,GAAG,KAAK,CAAC;IAC9C,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;CACtC;AACD,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAO3E;0GAC0G;AAC1G,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,gBAAgB,EACvB,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACtC,YAAY,CAgGd"}
package/dist/policy.js ADDED
@@ -0,0 +1,96 @@
1
+ const accepts = (allow, v) => (allow === "any" ? true : v !== undefined && allow.includes(v));
2
+ /** Evaluate the buyer policy on the TYPED legal-context envelope + the typed offer — NEVER on prose (LCP §12.7).
3
+ * Every check reads a structured field; the function's inputs cannot carry the natural-language body. */
4
+ export function evaluatePolicy(policy, terms, offer) {
5
+ // NARROWED, not cast — the same treatment `clauseCategories` gets below, and for the same reason. The
6
+ // cast that stood here asserted `{ jurisdiction?: string; method?: string }` about a value the discovery
7
+ // parser never checked, which made the two fields policy actually reads the two the type system was not
8
+ // checking. It failed closed, so this was never a bypass: a seller publishing `"disputeResolution":
9
+ // "arbitration"` yielded `undefined` for both members and declined. It declined for the WRONG REASON —
10
+ // "jurisdiction (none) not acceptable" tells a seller its jurisdiction is unacceptable when its actual
11
+ // defect is that the field is a string. A gate whose refusals misdescribe the defect cannot be acted on,
12
+ // and being right by accident is not the same as being right.
13
+ const rawDr = terms.disputeResolution;
14
+ if (rawDr !== undefined &&
15
+ (typeof rawDr !== "object" || rawDr === null || Array.isArray(rawDr)))
16
+ return {
17
+ ok: false,
18
+ code: "policy/malformed-dispute-resolution",
19
+ detail: `seller's disputeResolution is ${Array.isArray(rawDr) ? "an array" : rawDr === null ? "null" : typeof rawDr}, not an object`,
20
+ };
21
+ const drRecord = (rawDr ?? {});
22
+ // A member that is present and not a string is the seller's defect too, named rather than silently read
23
+ // as absent. Absent stays absent: that is the ordinary "no jurisdiction declared" path, and it is the
24
+ // buyer's stated policy that decides whether absence is acceptable.
25
+ for (const member of ["jurisdiction", "method"]) {
26
+ const v = drRecord[member];
27
+ if (v !== undefined && typeof v !== "string")
28
+ return {
29
+ ok: false,
30
+ code: "policy/malformed-dispute-resolution",
31
+ detail: `seller's disputeResolution.${member} is ${typeof v}, not a string`,
32
+ };
33
+ }
34
+ // NOT optionally chained, and the narrowing above is why. The cast that stood here produced a possibly
35
+ // absent object, so every read was `dr?.member`; the loop has now proved both members are a string or
36
+ // absent, so the object is always there and `?.` could never short-circuit. Mutation testing is what
37
+ // surfaced it — four optional-chaining mutants survived because no input could reach the branch they
38
+ // removed, which is the signature of a branch that cannot happen.
39
+ const jurisdiction = drRecord["jurisdiction"];
40
+ const method = drRecord["method"];
41
+ if (!accepts(policy.acceptableJurisdictions, jurisdiction))
42
+ return {
43
+ ok: false,
44
+ code: "policy/jurisdiction",
45
+ detail: `jurisdiction ${jurisdiction ?? "(none)"} not acceptable`,
46
+ };
47
+ if (!accepts(policy.acceptableDisputeMethods, method))
48
+ return {
49
+ ok: false,
50
+ code: "policy/dispute-method",
51
+ detail: `dispute method ${method ?? "(none)"} not acceptable`,
52
+ };
53
+ // `Object.hasOwn`, never a bare index read. `offer.unit` is SELLER-DERIVED — on the ACP path it is the
54
+ // session's `currency`, an unconstrained string — so a bare read answers from `Object.prototype` for
55
+ // `constructor`, `toString`, `valueOf`, `hasOwnProperty` or `__proto__`. Each returns a non-undefined
56
+ // non-string, slips past the `undefined` check below, and reaches `BigInt()`, which throws a SyntaxError
57
+ // out of `evaluate` naming nothing a buyer can act on. An own-property read makes every one of them the
58
+ // ordinary "no cap declared" decline.
59
+ const cap = Object.hasOwn(policy.maxCommitment, offer.unit)
60
+ ? policy.maxCommitment[offer.unit]
61
+ : undefined;
62
+ if (typeof cap !== "string")
63
+ return {
64
+ ok: false,
65
+ code: "policy/unit",
66
+ detail: `no cap declared for unit ${offer.unit}`,
67
+ };
68
+ if (BigInt(offer.amount) > BigInt(cap))
69
+ return {
70
+ ok: false,
71
+ code: "policy/over-cap",
72
+ detail: `offer ${offer.amount} exceeds cap ${cap} (${offer.unit})`,
73
+ };
74
+ // NARROWED, not cast. `clauseCategories` is not in the discovery schema at all — it arrives through the
75
+ // record's loose index signature as `unknown`, so it is whatever the SELLER wrote. A cast asserting
76
+ // `readonly string[]` was a claim about a hostile value: a seller publishing `"clauseCategories":
77
+ // "arbitration"` made `cats.find` a TypeError that escaped `evaluate` entirely, past its contract to
78
+ // RETURN a GateDecision. A malformed field is now the seller's defect, named as such and declined.
79
+ const raw = terms.clauseCategories;
80
+ if (raw !== undefined && !Array.isArray(raw))
81
+ return {
82
+ ok: false,
83
+ code: "policy/malformed-clause-categories",
84
+ detail: `seller's clauseCategories is ${typeof raw}, not an array of category strings`,
85
+ };
86
+ const cats = raw ?? [];
87
+ const forbidden = cats.find((c) => typeof c === "string" && policy.forbiddenClauseCategories.includes(c));
88
+ if (forbidden !== undefined)
89
+ return {
90
+ ok: false,
91
+ code: "policy/forbidden-clause",
92
+ detail: `forbidden clause category: ${forbidden}`,
93
+ };
94
+ return { ok: true };
95
+ }
96
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}