@ashlar-blue/x402-trust 0.3.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -0
  3. package/dist/attested-directory.d.ts +78 -0
  4. package/dist/attested-directory.d.ts.map +1 -0
  5. package/dist/attested-directory.js +163 -0
  6. package/dist/attested-directory.js.map +1 -0
  7. package/dist/badges.d.ts +131 -0
  8. package/dist/badges.d.ts.map +1 -0
  9. package/dist/badges.js +213 -0
  10. package/dist/badges.js.map +1 -0
  11. package/dist/catalog-mcp.d.ts +145 -0
  12. package/dist/catalog-mcp.d.ts.map +1 -0
  13. package/dist/catalog-mcp.js +409 -0
  14. package/dist/catalog-mcp.js.map +1 -0
  15. package/dist/census-reconcile.d.ts +69 -0
  16. package/dist/census-reconcile.d.ts.map +1 -0
  17. package/dist/census-reconcile.js +162 -0
  18. package/dist/census-reconcile.js.map +1 -0
  19. package/dist/directory-classify.d.ts +47 -0
  20. package/dist/directory-classify.d.ts.map +1 -0
  21. package/dist/directory-classify.js +54 -0
  22. package/dist/directory-classify.js.map +1 -0
  23. package/dist/discovery.d.ts +304 -0
  24. package/dist/discovery.d.ts.map +1 -0
  25. package/dist/discovery.js +926 -0
  26. package/dist/discovery.js.map +1 -0
  27. package/dist/evidence-ref.d.ts +96 -0
  28. package/dist/evidence-ref.d.ts.map +1 -0
  29. package/dist/evidence-ref.js +179 -0
  30. package/dist/evidence-ref.js.map +1 -0
  31. package/dist/facilitator-config.d.ts +83 -0
  32. package/dist/facilitator-config.d.ts.map +1 -0
  33. package/dist/facilitator-config.js +94 -0
  34. package/dist/facilitator-config.js.map +1 -0
  35. package/dist/index.d.ts +15 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +18 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/manifest-sig.d.ts +131 -0
  40. package/dist/manifest-sig.d.ts.map +1 -0
  41. package/dist/manifest-sig.js +262 -0
  42. package/dist/manifest-sig.js.map +1 -0
  43. package/dist/merkle-transcript.d.ts +34 -0
  44. package/dist/merkle-transcript.d.ts.map +1 -0
  45. package/dist/merkle-transcript.js +162 -0
  46. package/dist/merkle-transcript.js.map +1 -0
  47. package/dist/open-catalog.d.ts +111 -0
  48. package/dist/open-catalog.d.ts.map +1 -0
  49. package/dist/open-catalog.js +307 -0
  50. package/dist/open-catalog.js.map +1 -0
  51. package/dist/reconcile.d.ts +150 -0
  52. package/dist/reconcile.d.ts.map +1 -0
  53. package/dist/reconcile.js +136 -0
  54. package/dist/reconcile.js.map +1 -0
  55. package/dist/trust-sdk.d.ts +127 -0
  56. package/dist/trust-sdk.d.ts.map +1 -0
  57. package/dist/trust-sdk.js +147 -0
  58. package/dist/trust-sdk.js.map +1 -0
  59. package/dist/types.d.ts +213 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +68 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +52 -0
@@ -0,0 +1,162 @@
1
+ // GENERATED from src/x402/census-reconcile.ts by scripts/sdk/sync-sdk.ts — do not edit here.
2
+ // The canonical module is the one FlareClaw runs in production; this is a copy so
3
+ // that what we publish and what we run cannot diverge. Edit the canonical file.
4
+ /**
5
+ * census-reconcile — a checkable statement of where two observers disagree.
6
+ *
7
+ * THE GAP THIS FILLS. Every party in this ecosystem publishes its own index
8
+ * and asks readers to trust it. When two indexes disagree — and they always
9
+ * do — there is no artifact for the disagreement itself, so a reader must pick
10
+ * an observer to believe. Tonight alone: an uptime observatory reporting 36.7%
11
+ * alive while a cross-probe of the same targets reported 92.5%; our own
12
+ * second-observer sweep differing from a peer's on 17 of 1,521 hosts; and a
13
+ * first draft of our own census mislabelling 1,418 hosts.
14
+ *
15
+ * Melchiorre Oliva's phrase for it is the right one: disagreement is the
16
+ * signal. This makes the signal into an object.
17
+ *
18
+ * THE VOCABULARY IS BORROWED ON PURPOSE. Iman Schrock proposed a five-category
19
+ * reconciliation vocabulary in the SCITT disclosure-evidence thread
20
+ * (2026-08-16) and Emek adopted it in -03. It was built for reconciling two
21
+ * populations of settlement evidence, and it fits two populations of discovery
22
+ * observations without modification. Inventing a sixth vocabulary for the same
23
+ * problem would be worse than useless — the value of a category name is that
24
+ * two parties already agree what it means.
25
+ *
26
+ * Iman also corrected his own runner's strongest category DOWNWARD, because a
27
+ * missing record only shows absence from a supplied population. That
28
+ * correction is load-bearing here: this module NEVER concludes which observer
29
+ * is right. It cannot. Two honest observers with different vantages,
30
+ * different clocks and different resolvers will disagree without either being
31
+ * wrong, and an artifact that adjudicates would be asserting more than its
32
+ * inputs support.
33
+ *
34
+ * WHAT A READER GETS. The reconciliation is Merkle-committed, so a third party
35
+ * who trusts NEITHER observer can verify one host's row — "these two disagreed
36
+ * about this domain, this way" — from a handful of hashes, without downloading
37
+ * either census.
38
+ */
39
+ import { createHash } from 'node:crypto';
40
+ import { jcsCanonical, assertJcsSafe } from './manifest-sig.js';
41
+ import { transcriptRoot, inclusionProof, verifyInclusion } from './merkle-transcript.js';
42
+ import { recomputeRoots } from './attested-directory.js';
43
+ export const RECONCILE_SCHEMA = 'x402-census-reconciliation/1';
44
+ const EMPTY_TALLY = () => ({
45
+ matched: 0, 'observed-by-a-only': 0, 'observed-by-b-only': 0,
46
+ divergent: 0, indeterminate: 0,
47
+ });
48
+ /**
49
+ * Row leaves carry the domain, both verdicts AND the note. The note was
50
+ * previously outside the commitment, so a relayer could rewrite a verified
51
+ * row's note to adjudicating language ("observer B is known-bad") and the
52
+ * proof would still verify — defeating the no-adjudication doctrine at exactly
53
+ * the layer a third party checks. It is committed now. (Review R2.)
54
+ */
55
+ const rowLeaf = (r) => {
56
+ const core = { domain: r.domain, category: r.category, a: r.a ?? null, b: r.b ?? null, note: r.note };
57
+ assertJcsSafe(core);
58
+ return {
59
+ drawId: r.domain,
60
+ nonce: r.category,
61
+ seed: '0x' + createHash('sha256').update(jcsCanonical(core), 'utf8').digest('hex'),
62
+ };
63
+ };
64
+ const VALID_VERDICTS = new Set([
65
+ 'discoverable-verified', 'discoverable', 'one-edit-away', 'not-discoverable', 'unreachable',
66
+ ]);
67
+ /** A verdict that admits the observer could not tell. */
68
+ const isIndeterminate = (v) => v === 'unreachable';
69
+ export function reconcile(aCensus, aEntries, bCensus, bEntries, reconciledAt, digestOf) {
70
+ // R1 — the entries must actually be the ones each census committed to.
71
+ // Without this, a reconciler embeds a census's genuine digest as the
72
+ // provenance of verdicts it never produced — a Merkle-committed, retail-
73
+ // provable FABRICATED attribution, in the one artifact whose whole pitch
74
+ // is "checkable by someone who trusts neither observer". Recompute both
75
+ // root pairs and refuse on any mismatch.
76
+ for (const [label, census, entries] of [['a', aCensus, aEntries], ['b', bCensus, bEntries]]) {
77
+ if (entries.length !== census.size) {
78
+ throw new Error(`[reconcile] ${label}: ${entries.length} entries but census.size ${census.size}`);
79
+ }
80
+ const r = recomputeRoots(entries);
81
+ if (r.inputRoot !== census.inputRoot || r.outputRoot !== census.outputRoot) {
82
+ throw new Error(`[reconcile] ${label}: entries do not recompute to the census's committed roots — refusing to attribute them`);
83
+ }
84
+ }
85
+ // A present-but-invalid verdict must never reach the category ladder: an
86
+ // entry with a dropped/blank verdict would otherwise slip through every
87
+ // guard and land in 'matched' (undefined === undefined), publishing a
88
+ // committed agreement out of a corrupt record. (Silent-failure 2a.)
89
+ for (const [label, entries] of [['a', aEntries], ['b', bEntries]]) {
90
+ for (const e of entries) {
91
+ if (!VALID_VERDICTS.has(e.verdict)) {
92
+ throw new Error(`[reconcile] ${label}: entry ${e.domain} has an out-of-vocabulary verdict ${JSON.stringify(e.verdict)}`);
93
+ }
94
+ }
95
+ }
96
+ const aBy = new Map(aEntries.map((e) => [e.domain, e]));
97
+ const bBy = new Map(bEntries.map((e) => [e.domain, e]));
98
+ // The denominator is the UNION. Reconciling only the intersection hides
99
+ // exactly the population difference most worth seeing.
100
+ const domains = [...new Set([...aBy.keys(), ...bBy.keys()])].sort();
101
+ const rows = domains.map((domain) => {
102
+ const av = aBy.get(domain)?.verdict, bv = bBy.get(domain)?.verdict;
103
+ if (av && !bv) {
104
+ return { domain, category: 'observed-by-a-only', a: av,
105
+ note: `in ${aCensus.observer}'s population, absent from ${bCensus.observer}'s — not a claim either missed it` };
106
+ }
107
+ if (bv && !av) {
108
+ return { domain, category: 'observed-by-b-only', b: bv,
109
+ note: `in ${bCensus.observer}'s population, absent from ${aCensus.observer}'s — not a claim either missed it` };
110
+ }
111
+ if (isIndeterminate(av) || isIndeterminate(bv)) {
112
+ return { domain, category: 'indeterminate', ...(av ? { a: av } : {}), ...(bv ? { b: bv } : {}),
113
+ note: 'at least one observer reported a vantage-limited verdict; no comparison is safe' };
114
+ }
115
+ if (av === bv) {
116
+ return { domain, category: 'matched', ...(av ? { a: av } : {}), ...(bv ? { b: bv } : {}), note: 'both observers, same verdict' };
117
+ }
118
+ return { domain, category: 'divergent', ...(av ? { a: av } : {}), ...(bv ? { b: bv } : {}),
119
+ note: 'both observed it and disagree; this artifact does not decide which is correct' };
120
+ });
121
+ const tally = EMPTY_TALLY();
122
+ for (const r of rows)
123
+ tally[r.category]++;
124
+ // Skew is part of the honesty story ("timing explains many divergences"),
125
+ // so an unparseable observedAt is a refusal, not a silent -1 sentinel that
126
+ // still publishes. (Review R3.)
127
+ const aT = Date.parse(aCensus.observedAt), bT = Date.parse(bCensus.observedAt);
128
+ if (!Number.isFinite(aT) || !Number.isFinite(bT)) {
129
+ throw new Error('[reconcile] a census has an unparseable observedAt — cannot compute skew, refusing');
130
+ }
131
+ const skew = Math.abs(aT - bT) / 1000;
132
+ return {
133
+ rows,
134
+ reconciliation: {
135
+ schema: RECONCILE_SCHEMA,
136
+ reconciledAt,
137
+ a: { observer: aCensus.observer, digest: digestOf(aCensus), size: aCensus.size, observedAt: aCensus.observedAt },
138
+ b: { observer: bCensus.observer, digest: digestOf(bCensus), size: bCensus.size, observedAt: bCensus.observedAt },
139
+ size: rows.length,
140
+ rowsRoot: transcriptRoot(rows.map(rowLeaf)),
141
+ tally,
142
+ adjudication: 'none — two honest vantages may differ without either being wrong',
143
+ observationSkewSeconds: Math.round(skew),
144
+ },
145
+ };
146
+ }
147
+ /** Prove ONE host's reconciliation row without disclosing either census. */
148
+ export function proveRow(rows, index) {
149
+ const row = rows[index];
150
+ if (!row)
151
+ throw new Error(`index ${index} outside reconciliation of ${rows.length}`);
152
+ return { row, index, size: rows.length, proof: inclusionProof(rows.map(rowLeaf), index) };
153
+ }
154
+ export function verifyRow(row, index, size, proof, rowsRoot) {
155
+ return verifyInclusion(rowLeaf(row), index, size, proof, rowsRoot);
156
+ }
157
+ /** The digest a reconciliation is anchored under. */
158
+ export function reconciliationDigest(r) {
159
+ assertJcsSafe(r);
160
+ return 'sha256:' + createHash('sha256').update(jcsCanonical(r), 'utf8').digest('hex');
161
+ }
162
+ //# sourceMappingURL=census-reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"census-reconcile.js","sourceRoot":"","sources":["../src/census-reconcile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * directory-classify — the ONE place a raw DNS sweep row becomes a directory
3
+ * verdict.
4
+ *
5
+ * WHY THIS IS ITS OWN MODULE. The classification was duplicated across
6
+ * build-directory-census.ts and directory-lane.ts and had already DIVERGED:
7
+ * the two produced different `detail` strings for the same host. Because
8
+ * `detail` is committed inside the census Merkle leaf (attested-directory.ts
9
+ * outputLeaf), that divergence meant the two scripts computed DIFFERENT census
10
+ * digests over identical observations — a reproducibility defect in an
11
+ * artifact whose entire value is that a third party can reproduce it. One
12
+ * source of truth removes the class of bug, not just this instance.
13
+ *
14
+ * THE LOAD-BEARING RULE. A DNS-only method has NO evidence about host
15
+ * reachability. NXDOMAIN on `_x402.<host>` means "no such record", not "host
16
+ * down". So this classifier NEVER emits `unreachable`: that verdict belongs to
17
+ * a method that actually probed the host (the fc-liveness lane), and emitting
18
+ * it here would assert a host was offline on the strength of a missing TXT
19
+ * record. A verdict vocabulary must be narrowed to the claims the method can
20
+ * support; the `detail` strings say exactly which claim is being made.
21
+ */
22
+ import type { DirectoryEntry } from './attested-directory.js';
23
+ /** One row of the DNS discovery sweep (scripts/x402/dns-discovery-sweep.mjs). */
24
+ export interface SweepRow {
25
+ host: string;
26
+ /** TXT strings at `_x402.<host>`, or null when the query returned nothing. */
27
+ disc: string[] | null;
28
+ /** TXT strings at `s1._x402key.<host>`, or null. */
29
+ key: string[] | null;
30
+ }
31
+ /** A TXT answer with at least one non-blank string. `[]` and `[""]` are empty. */
32
+ export declare function hasContent(a: string[] | null): a is string[];
33
+ /**
34
+ * Classify one sweep row into a committed directory verdict.
35
+ *
36
+ * The returned `detail` is part of the census leaf, so it is treated as a
37
+ * stable, canonical string — changing its wording changes every census digest
38
+ * and is a deliberate, breaking act, not an editorial one.
39
+ */
40
+ export declare function classifySweepRow(r: SweepRow): DirectoryEntry;
41
+ /**
42
+ * Classify a whole sweep into a DETERMINISTIC, sorted entry list. Row order in
43
+ * a census must not depend on how the sweep happened to finish, or two
44
+ * observers who saw identical facts would still commit to different roots.
45
+ */
46
+ export declare function classifySweep(rows: SweepRow[]): DirectoryEntry[];
47
+ //# sourceMappingURL=directory-classify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-classify.d.ts","sourceRoot":"","sources":["../src/directory-classify.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtB,oDAAoD;IACpD,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACxB;AAED,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,CAAC,IAAI,MAAM,EAAE,CAE5D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,GAAG,cAAc,CA8B5D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,cAAc,EAAE,CAEhE"}
@@ -0,0 +1,54 @@
1
+ // GENERATED from src/x402/directory-classify.ts by scripts/sdk/sync-sdk.ts — do not edit here.
2
+ // The canonical module is the one FlareClaw runs in production; this is a copy so
3
+ // that what we publish and what we run cannot diverge. Edit the canonical file.
4
+ /** A TXT answer with at least one non-blank string. `[]` and `[""]` are empty. */
5
+ export function hasContent(a) {
6
+ return Array.isArray(a) && a.some((t) => typeof t === 'string' && t.trim().length > 0);
7
+ }
8
+ /**
9
+ * Classify one sweep row into a committed directory verdict.
10
+ *
11
+ * The returned `detail` is part of the census leaf, so it is treated as a
12
+ * stable, canonical string — changing its wording changes every census digest
13
+ * and is a deliberate, breaking act, not an editorial one.
14
+ */
15
+ export function classifySweepRow(r) {
16
+ // No usable discovery TXT: not discoverable. We distinguish "no record at
17
+ // all" from "a record with no content" in the detail, because they are
18
+ // different facts even though the verdict is the same.
19
+ if (!hasContent(r.disc)) {
20
+ return {
21
+ domain: r.host,
22
+ verdict: 'not-discoverable',
23
+ detail: r.disc === null ? 'no _x402 TXT record (NXDOMAIN or no answer)' : 'empty _x402 TXT content',
24
+ };
25
+ }
26
+ const rec = r.disc.find((t) => /v=x402/i.test(t));
27
+ if (!rec) {
28
+ return {
29
+ domain: r.host,
30
+ verdict: 'not-discoverable',
31
+ detail: '_x402 TXT present but no v=x402 record among its strings',
32
+ };
33
+ }
34
+ // A signing key published under the host is what upgrades "resolves" to
35
+ // "checkable". Recorded as a distinct verdict rather than flattened.
36
+ const signed = hasContent(r.key) && r.key.some((t) => /v=x402key1/i.test(t));
37
+ return {
38
+ domain: r.host,
39
+ verdict: signed ? 'discoverable-verified' : 'discoverable',
40
+ detail: signed
41
+ ? 'record resolves; signing key published under the host'
42
+ : 'record resolves; no signing key in DNS',
43
+ record: rec,
44
+ };
45
+ }
46
+ /**
47
+ * Classify a whole sweep into a DETERMINISTIC, sorted entry list. Row order in
48
+ * a census must not depend on how the sweep happened to finish, or two
49
+ * observers who saw identical facts would still commit to different roots.
50
+ */
51
+ export function classifySweep(rows) {
52
+ return rows.map(classifySweepRow).sort((a, b) => (a.domain < b.domain ? -1 : 1));
53
+ }
54
+ //# sourceMappingURL=directory-classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-classify.js","sourceRoot":"","sources":["../src/directory-classify.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,kFAAkF;AAClF,gFAAgF;AAkChF,kFAAkF;AAClF,MAAM,UAAU,UAAU,CAAC,CAAkB;IACzC,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAW;IACxC,0EAA0E;IAC1E,uEAAuE;IACvE,uDAAuD;IACvD,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO;YACH,MAAM,EAAE,CAAC,CAAC,IAAI;YACd,OAAO,EAAE,kBAAkB;YAC3B,MAAM,EAAE,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,6CAA6C,CAAC,CAAC,CAAC,yBAAyB;SACtG,CAAC;IACN,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,IAAI,CAAC,GAAG,EAAE,CAAC;QACP,OAAO;YACH,MAAM,EAAE,CAAC,CAAC,IAAI;YACd,OAAO,EAAE,kBAAkB;YAC3B,MAAM,EAAE,0DAA0D;SACrE,CAAC;IACN,CAAC;IACD,wEAAwE;IACxE,qEAAqE;IACrE,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7E,OAAO;QACH,MAAM,EAAE,CAAC,CAAC,IAAI;QACd,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,cAAc;QAC1D,MAAM,EAAE,MAAM;YACV,CAAC,CAAC,uDAAuD;YACzD,CAAC,CAAC,wCAAwC;QAC9C,MAAM,EAAE,GAAG;KACd,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAgB;IAC1C,OAAO,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF,CAAC"}
@@ -0,0 +1,304 @@
1
+ /**
2
+ * x402 `discovery` extension — reference implementation.
3
+ *
4
+ * Spec: docs/plans/x402/x402-discovery-extension-spec.md (draft for
5
+ * x402-foundation). Two mechanisms:
6
+ * 1. `/.well-known/x402` HTTPS manifest (authoritative capability record)
7
+ * 2. `_x402.<domain>` DNS TXT pointer (`v=x402-1; wk=<manifest url>; …`)
8
+ *
9
+ * This module is pure protocol logic (parse/validate/build) plus a resolver
10
+ * that composes DNS + fetch per the spec's resolution algorithm. The public
11
+ * facilitator serves the manifest; anyone can resolve it with resolveX402().
12
+ */
13
+ export type DiscoveryKind = 'facilitator' | 'resource-server' | 'both';
14
+ export interface SupportedKind {
15
+ x402Version: number;
16
+ scheme: string;
17
+ network: string;
18
+ }
19
+ export interface DiscoveryAsset {
20
+ network: string;
21
+ address: string;
22
+ symbol?: string;
23
+ decimals?: number;
24
+ standard?: string;
25
+ }
26
+ export interface DiscoveryManifest {
27
+ x402Version: number;
28
+ kind: DiscoveryKind;
29
+ name?: string;
30
+ description?: string;
31
+ facilitator?: {
32
+ baseUrl: string;
33
+ endpoints: {
34
+ supported: string;
35
+ verify: string;
36
+ settle: string;
37
+ };
38
+ kinds: SupportedKind[];
39
+ assets?: DiscoveryAsset[];
40
+ };
41
+ resources?: {
42
+ url: string;
43
+ method?: string;
44
+ description?: string;
45
+ }[];
46
+ /** Peer hints: bare domain names of other discovery publishers. Hints only — see sanitizePeers. */
47
+ peers?: string[];
48
+ attestation?: {
49
+ type: string;
50
+ [k: string]: unknown;
51
+ };
52
+ docs?: string;
53
+ contact?: string;
54
+ updated?: string;
55
+ }
56
+ export interface X402TxtRecord {
57
+ v: string;
58
+ wk: string;
59
+ k?: DiscoveryKind;
60
+ net?: string[];
61
+ scheme?: string[];
62
+ }
63
+ /**
64
+ * Parse a `_x402.<domain>` TXT record ("v=x402-1; wk=https://…; k=…").
65
+ * Returns null for records that aren't x402-1 (callers skip them).
66
+ * Throws on records that claim x402-1 but are malformed.
67
+ */
68
+ /**
69
+ * A HARD discovery violation — a record or redirect that is present but
70
+ * malformed or spoofing (missing/HTTP wk, invalid k, duplicate key/record,
71
+ * out-of-domain or downgraded redirect). Distinguished by TYPE, not by message
72
+ * text, so classification cannot be fooled by attacker-controlled strings (a
73
+ * wk URL containing the word "conflicting") and cannot silently swallow a
74
+ * malformed record as a DNS miss. These cache as `invalid`; DNS misses do not.
75
+ */
76
+ export declare class DiscoveryViolation extends Error {
77
+ }
78
+ export declare function parseX402TxtRecord(txt: string): X402TxtRecord | null;
79
+ /**
80
+ * What a `_x402` TXT record IS, when it isn't ours.
81
+ *
82
+ * WHY THIS EXISTS. A census of the Bazaar catalog on 2026-08-22 found five
83
+ * hosts publishing a TXT record at `_x402` under a grammar that is not this
84
+ * one, in three mutually incompatible syntaxes:
85
+ *
86
+ * v=x4021;descriptor=api;url=https://api.telemost.io/.well-known/x402
87
+ * v=x4021;url=https://tablint.dev/.well-known/x402
88
+ * x402-manifest=https://vibesprings.net/.well-known/x402.json
89
+ * https://api.auor.io/.well-known/x402 (bare URL, no key)
90
+ *
91
+ * Every one of them points at its own domain, so this is not an attack — it is
92
+ * independent convergence on the same design. Two of them differ from a
93
+ * conformant record by exactly two tokens.
94
+ *
95
+ * `parseX402TxtRecord` returns null for all of them, which is correct as a GATE
96
+ * and useless as a DIAGNOSIS: a resolver that silently skips a near-miss makes
97
+ * the publisher invisible and tells nobody they were one character away. The
98
+ * fragmentation risk to `_x402` is not that someone attacks the name, it is
99
+ * that several people spell it differently and no implementation ever says so.
100
+ *
101
+ * So: strict enforcement, generous diagnostics — the same doctrine
102
+ * `diagnoseManifest` applies one layer up. THE GATE IS UNCHANGED.
103
+ */
104
+ export type TxtDiagnosis = {
105
+ kind: 'conformant';
106
+ record: X402TxtRecord;
107
+ }
108
+ /** Claims x402-1 and breaks the grammar. */
109
+ | {
110
+ kind: 'malformed';
111
+ message: string;
112
+ }
113
+ /** Not conformant, but recognizably an attempt at this record. */
114
+ | {
115
+ kind: 'near-miss';
116
+ hints: string[];
117
+ manifestUrl?: string;
118
+ }
119
+ /** A TXT record at this name that is not about x402 at all. */
120
+ | {
121
+ kind: 'foreign';
122
+ };
123
+ export declare function diagnoseX402TxtRecord(txt: string): TxtDiagnosis;
124
+ /** Render the TXT record value for a manifest URL (for operators with DNS). */
125
+ export declare function buildX402TxtRecord(p: {
126
+ manifestUrl: string;
127
+ kind?: DiscoveryKind;
128
+ networks?: string[];
129
+ schemes?: string[];
130
+ }): string;
131
+ /**
132
+ * Spec security rule: the wk URL must live on the queried domain or a
133
+ * subdomain of it — a TXT record may not claim another operator's manifest.
134
+ */
135
+ /**
136
+ * A facilitator endpoint value that is safe to concatenate onto baseUrl and
137
+ * fetch: a rooted relative path only. A leading single "/" makes authority
138
+ * injection impossible (nothing after "/" reaches the authority component), and
139
+ * "@", "://", "//" and whitespace/backslash are refused outright. This is the
140
+ * SYNTACTIC guard; isWkInDomain on the assembled URL is the semantic backstop.
141
+ */
142
+ export declare function isSafeRelativePath(p: unknown): boolean;
143
+ export declare function isWkInDomain(wkUrl: string, domain: string): boolean;
144
+ /** How far up a consumer may walk. Two ancestors, never below two labels. */
145
+ export declare const MAX_ANCESTOR_STEPS = 2;
146
+ /**
147
+ * The `_x402` owner names to query for a host, nearest first.
148
+ *
149
+ * The FIRST entry is the host itself and is always authoritative for it. Later
150
+ * entries are ancestors, and a record found at one is only usable if
151
+ * `manifestCoversHost` accepts it.
152
+ */
153
+ export declare function discoveryNamesFor(hostOrUrl: string): string[];
154
+ /**
155
+ * Does a manifest published at `ownerName` speak for `host`?
156
+ *
157
+ * Always true when the manifest was found at the host itself. For an ancestor,
158
+ * the manifest MUST name the host — otherwise a platform's record would vouch
159
+ * for every tenant under it, which is how a discovery layer manufactures
160
+ * adopters it does not have.
161
+ *
162
+ * Referenced URLs are only counted when they are themselves in-domain of the
163
+ * owner, so a manifest cannot vouch for a host it does not control either.
164
+ */
165
+ export declare function manifestCoversHost(m: unknown, ownerName: string, host: string): boolean;
166
+ /**
167
+ * One structural violation, classified by WHO introduced the requirement.
168
+ *
169
+ * The distinction is the point. A host that fails only on `introducedBy:
170
+ * 'x402-discovery'` is one mechanical edit from compliant; a host failing a
171
+ * 'core' rule has a different problem. Reporting them identically is what
172
+ * makes adoption look like a wall instead of an afternoon.
173
+ */
174
+ export interface ManifestViolation {
175
+ field: string;
176
+ message: string;
177
+ introducedBy: 'x402-discovery' | 'core';
178
+ }
179
+ /**
180
+ * Collect EVERY structural violation instead of stopping at the first.
181
+ *
182
+ * Why this exists (Melchiorre Oliva on PR #2979, 2026-08-18): our resolver
183
+ * refused his manifest with `invalid kind: undefined` and read nothing else.
184
+ * Everything else in the record was valid, but a first-throw validator cannot
185
+ * say so — the operator learns one field per debugging round-trip and has no
186
+ * way to tell "one edit away" from "fundamentally wrong". He measured 383
187
+ * hosts (39.5% of those serving a manifest) violating nothing except fields
188
+ * this extension introduces. That population is invisible unless a resolver
189
+ * reports the whole diagnosis and says which requirements are new.
190
+ *
191
+ * The GATE ITSELF IS UNCHANGED: resolveX402 still refuses a manifest that
192
+ * fails any rule. Strict enforcement, generous diagnostics.
193
+ */
194
+ export declare function diagnoseManifest(m: unknown): {
195
+ ok: boolean;
196
+ violations: ManifestViolation[];
197
+ };
198
+ /**
199
+ * True when a manifest's ONLY problems are requirements this extension
200
+ * introduced — i.e. it was a valid x402 manifest before this spec and is one
201
+ * mechanical edit from valid after it. This is the population worth counting
202
+ * when anyone asks what the extension costs deployed hosts.
203
+ */
204
+ export declare function isOneEditAway(m: unknown): boolean;
205
+ /** Structural validation per the spec. Throws with a reason on failure. */
206
+ export declare function validateManifest(m: unknown): DiscoveryManifest;
207
+ /** Spec cap: entries beyond this are ignored (and worth flagging). */
208
+ export declare const MAX_PEER_HINTS = 32;
209
+ /**
210
+ * Filter a manifest's `peers` array down to entries a consumer may act on:
211
+ * bare multi-label DNS names — no scheme, path, port, or userinfo, no IP
212
+ * literals, not the manifest's own domain — capped at MAX_PEER_HINTS.
213
+ * Everything dropped is returned as a signal, mirroring droppedResources.
214
+ */
215
+ export declare function sanitizePeers(raw: unknown, ownDomain: string): {
216
+ peers: string[];
217
+ dropped: string[];
218
+ };
219
+ /** True for addresses no discovery fetch should ever connect to:
220
+ * loopback, link-local, RFC1918/ULA private ranges, unspecified. */
221
+ export declare function isPrivateAddress(ip: string): boolean;
222
+ export type LookupImpl = (hostname: string) => Promise<string[]>;
223
+ export interface ResolveResult {
224
+ manifest: DiscoveryManifest;
225
+ manifestUrl: string;
226
+ via: 'dns-txt' | 'well-known';
227
+ txtRecord?: X402TxtRecord;
228
+ /** Live GET {baseUrl}{endpoints.supported} result (authoritative over manifest kinds). */
229
+ liveKinds?: SupportedKind[];
230
+ /** True when live kinds and manifest kinds agree (spec: divergence = misconfiguration). */
231
+ kindsMatchLive?: boolean;
232
+ /**
233
+ * Outcome of the live cross-check, EXPLICIT so "not checked" can never be
234
+ * read as "checked and matched":
235
+ * ok — fetched, kinds[] present, kindsMatchLive is meaningful
236
+ * skipped — skipLiveCheck, or no facilitator block
237
+ * unreachable — non-2xx, redirect, timeout, size breach, or bad JSON
238
+ * malformed — 2xx but no kinds[] array to compare
239
+ * refused — the endpoint was not a safe same-origin path
240
+ */
241
+ liveCheck?: 'ok' | 'skipped' | 'unreachable' | 'malformed' | 'refused';
242
+ /**
243
+ * Resource URLs removed because they pointed off-domain or weren't
244
+ * HTTPS. NON-EMPTY IS A SIGNAL, not noise: the spec invites indexers
245
+ * to probe `resources[]`, so an off-domain entry is an attempt to
246
+ * aim a stranger's crawler somewhere. Surface it; don't just drop it.
247
+ */
248
+ droppedResources?: string[];
249
+ /**
250
+ * Peer hints that survived sanitizePeers — names only, each still to
251
+ * be resolved from scratch. Never act on a peer without re-resolving.
252
+ */
253
+ peers?: string[];
254
+ /** Peer entries dropped by sanitizePeers. Non-empty is a manifest-quality signal. */
255
+ droppedPeers?: string[];
256
+ /**
257
+ * False whenever an `attestation` block cannot be independently checked
258
+ * (absent, `type: none`, or no dereferenceable HTTPS verifier URL).
259
+ * Consumers MUST treat a false value as "not attested" — the spec's
260
+ * "absent verification, treat as `none`" rule, made machine-readable so
261
+ * a caller can't accidentally believe an unverifiable claim.
262
+ */
263
+ attestationUsable?: boolean;
264
+ }
265
+ export type NegativeReason = 'no-record' | 'unreachable' | 'invalid';
266
+ export declare const NEGATIVE_TTL_MS: Readonly<Record<NegativeReason, number>>;
267
+ export declare class X402NegativeCacheError extends Error {
268
+ readonly reason: NegativeReason;
269
+ readonly cachedMessage: string;
270
+ constructor(reason: NegativeReason, cachedMessage: string, domain: string);
271
+ }
272
+ /** Drop cached negatives — tests, and operator triage after a fix. */
273
+ export declare function clearDiscoveryCache(domain?: string): void;
274
+ /** Inspect the cache without mutating it (observability, tests). */
275
+ export declare function discoveryCacheEntry(domain: string, now?: number): {
276
+ reason: NegativeReason;
277
+ message: string;
278
+ expiresInMs: number;
279
+ } | null;
280
+ /**
281
+ * Resolve a domain's x402 capability per the spec:
282
+ * TXT `_x402.<domain>` → manifest URL, else `https://<domain>/.well-known/x402`;
283
+ * then cross-check the facilitator's live `supported` endpoint.
284
+ */
285
+ export declare function resolveX402(domain: string, opts?: {
286
+ fetchImpl?: typeof fetch;
287
+ resolveTxt?: (name: string) => Promise<string[][]>;
288
+ skipLiveCheck?: boolean;
289
+ /**
290
+ * Address resolution for the private-destination guard. Defaults to
291
+ * real DNS when using the real `fetch`; when a fetchImpl is injected
292
+ * (tests, transports that resolve internally) the guard runs only if
293
+ * a lookupImpl is supplied — a fake fetch makes real DNS both
294
+ * meaningless and a hidden network dependency.
295
+ */
296
+ lookupImpl?: LookupImpl;
297
+ /** Skip the negative cache for this call (forced refresh). */
298
+ noCache?: boolean;
299
+ /** Override negative TTLs (tests, aggressive crawlers). */
300
+ negativeTtlMs?: Partial<Record<NegativeReason, number>>;
301
+ /** Injectable clock so TTL expiry is testable without waiting. */
302
+ now?: () => number;
303
+ }): Promise<ResolveResult>;
304
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}