@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.
- package/CHANGELOG.md +57 -0
- package/LICENSE +202 -0
- package/NOTICE +15 -0
- package/README.md +154 -0
- package/dist/decision.d.ts +26 -0
- package/dist/decision.d.ts.map +1 -0
- package/dist/decision.js +28 -0
- package/dist/decision.js.map +1 -0
- package/dist/evaluate.d.ts +15 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +112 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/fetch.d.ts +61 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +126 -0
- package/dist/fetch.js.map +1 -0
- package/dist/fingerprint.d.ts +14 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +15 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +32 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +8 -0
- package/dist/log.js.map +1 -0
- package/dist/mechanical.d.ts +30 -0
- package/dist/mechanical.d.ts.map +1 -0
- package/dist/mechanical.js +35 -0
- package/dist/mechanical.js.map +1 -0
- package/dist/policy.d.ts +36 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +96 -0
- package/dist/policy.js.map +1 -0
- package/dist/proposal-ack.d.ts +111 -0
- package/dist/proposal-ack.d.ts.map +1 -0
- package/dist/proposal-ack.js +114 -0
- package/dist/proposal-ack.js.map +1 -0
- package/dist/proposal-acp.d.ts +18 -0
- package/dist/proposal-acp.d.ts.map +1 -0
- package/dist/proposal-acp.js +72 -0
- package/dist/proposal-acp.js.map +1 -0
- package/dist/proposal-ap2.d.ts +121 -0
- package/dist/proposal-ap2.d.ts.map +1 -0
- package/dist/proposal-ap2.js +112 -0
- package/dist/proposal-ap2.js.map +1 -0
- package/dist/proposal-mpp.d.ts +44 -0
- package/dist/proposal-mpp.d.ts.map +1 -0
- package/dist/proposal-mpp.js +106 -0
- package/dist/proposal-mpp.js.map +1 -0
- package/dist/proposal-universal.d.ts +239 -0
- package/dist/proposal-universal.d.ts.map +1 -0
- package/dist/proposal-universal.js +470 -0
- package/dist/proposal-universal.js.map +1 -0
- package/dist/proposal.d.ts +33 -0
- package/dist/proposal.d.ts.map +1 -0
- package/dist/proposal.js +107 -0
- package/dist/proposal.js.map +1 -0
- package/dist/transact.d.ts +24 -0
- package/dist/transact.d.ts.map +1 -0
- package/dist/transact.js +11 -0
- package/dist/transact.js.map +1 -0
- package/package.json +81 -0
- package/src/decision.ts +54 -0
- package/src/evaluate.ts +176 -0
- package/src/fetch.ts +169 -0
- package/src/fingerprint.ts +27 -0
- package/src/index.ts +68 -0
- package/src/log.ts +34 -0
- package/src/mechanical.ts +71 -0
- package/src/policy.ts +135 -0
- package/src/proposal-ack.ts +216 -0
- package/src/proposal-acp.ts +89 -0
- package/src/proposal-ap2.ts +195 -0
- package/src/proposal-mpp.ts +125 -0
- package/src/proposal-universal.ts +671 -0
- package/src/proposal.ts +170 -0
- package/src/transact.ts +34 -0
package/dist/fetch.d.ts
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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
package/dist/log.js.map
ADDED
|
@@ -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"}
|
package/dist/policy.d.ts
ADDED
|
@@ -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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"}
|