@fora-protocol/sdk 1.0.3
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/LICENSE +201 -0
- package/README.md +37 -0
- package/gen/ts/vocab/functiontokens.js +78 -0
- package/gen/ts/vocab/functiontokens.ts +84 -0
- package/gen/ts/vocab/geographytokens.js +30 -0
- package/gen/ts/vocab/geographytokens.ts +37 -0
- package/gen/ts/vocab/pricingunits.js +56 -0
- package/gen/ts/vocab/pricingunits.ts +63 -0
- package/gen/ts/vocab/quotametrics.js +40 -0
- package/gen/ts/vocab/quotametrics.ts +47 -0
- package/gen/ts/vocab/usertypes.js +40 -0
- package/gen/ts/vocab/usertypes.ts +46 -0
- package/gen/ts/wire/base.js +245 -0
- package/gen/ts/wire/base.ts +255 -0
- package/gen/ts/wire/names.js +41 -0
- package/gen/ts/wire/names.ts +42 -0
- package/gen/ts/wire/schemas.js +111 -0
- package/gen/ts/wire/schemas.ts +216 -0
- package/package.json +164 -0
- package/sdk/ts/client/content.d.ts +74 -0
- package/sdk/ts/client/content.js +466 -0
- package/sdk/ts/client/errors.d.ts +79 -0
- package/sdk/ts/client/errors.js +148 -0
- package/sdk/ts/client/index.d.ts +241 -0
- package/sdk/ts/client/index.js +740 -0
- package/sdk/ts/client/route.d.ts +41 -0
- package/sdk/ts/client/route.js +107 -0
- package/sdk/ts/client/send.d.ts +64 -0
- package/sdk/ts/client/send.js +142 -0
- package/sdk/ts/client/transport.d.ts +187 -0
- package/sdk/ts/client/transport.js +381 -0
- package/sdk/ts/core/multisig-parse.d.ts +47 -0
- package/sdk/ts/core/multisig-parse.js +234 -0
- package/sdk/ts/core/sign-request.d.ts +83 -0
- package/sdk/ts/core/sign-request.js +146 -0
- package/sdk/ts/core/sign.d.ts +31 -0
- package/sdk/ts/core/sign.js +82 -0
- package/sdk/ts/core/signing-transport.d.ts +75 -0
- package/sdk/ts/core/signing-transport.js +172 -0
- package/sdk/ts/core/verifier.d.ts +177 -0
- package/sdk/ts/core/verifier.js +269 -0
- package/sdk/ts/core/verify-multisig-request.d.ts +72 -0
- package/sdk/ts/core/verify-multisig-request.js +112 -0
- package/sdk/ts/core/verify-request.d.ts +181 -0
- package/sdk/ts/core/verify-request.js +284 -0
- package/sdk/ts/core/window.d.ts +22 -0
- package/sdk/ts/core/window.js +43 -0
- package/sdk/ts/core/wire-canon.d.ts +20 -0
- package/sdk/ts/core/wire-canon.js +174 -0
- package/sdk/ts/hono/middleware.d.ts +38 -0
- package/sdk/ts/hono/middleware.js +64 -0
- package/sdk/ts/resolvers/errors.d.ts +130 -0
- package/sdk/ts/resolvers/errors.js +150 -0
- package/sdk/ts/resolvers/http.d.ts +98 -0
- package/sdk/ts/resolvers/http.js +274 -0
- package/sdk/ts/resolvers/index.d.ts +9 -0
- package/sdk/ts/resolvers/index.js +15 -0
- package/sdk/ts/resolvers/jwks.d.ts +2 -0
- package/sdk/ts/resolvers/jwks.js +43 -0
- package/sdk/ts/resolvers/offer-key-cache.d.ts +68 -0
- package/sdk/ts/resolvers/offer-key-cache.js +152 -0
- package/sdk/ts/resolvers/registration-requirements.d.ts +47 -0
- package/sdk/ts/resolvers/registration-requirements.js +220 -0
- package/sdk/ts/resolvers/ssrf.d.ts +28 -0
- package/sdk/ts/resolvers/ssrf.js +235 -0
- package/sdk/ts/resolvers/static.d.ts +7 -0
- package/sdk/ts/resolvers/static.js +16 -0
- package/sdk/ts/resolvers/wba.d.ts +134 -0
- package/sdk/ts/resolvers/wba.js +506 -0
- package/sdk/ts/resolvers/wellknown.d.ts +28 -0
- package/sdk/ts/resolvers/wellknown.js +169 -0
- package/sdk/ts/src/acceptance.d.ts +46 -0
- package/sdk/ts/src/acceptance.js +162 -0
- package/sdk/ts/src/base64url.d.ts +19 -0
- package/sdk/ts/src/base64url.js +55 -0
- package/sdk/ts/src/crossfield.d.ts +28 -0
- package/sdk/ts/src/crossfield.js +215 -0
- package/sdk/ts/src/endpoint-rule.d.ts +30 -0
- package/sdk/ts/src/endpoint-rule.js +63 -0
- package/sdk/ts/src/errordetail.d.ts +115 -0
- package/sdk/ts/src/errordetail.js +256 -0
- package/sdk/ts/src/hashurl.d.ts +5 -0
- package/sdk/ts/src/hashurl.js +15 -0
- package/sdk/ts/src/host-ref.d.ts +70 -0
- package/sdk/ts/src/host-ref.js +320 -0
- package/sdk/ts/src/hosts.d.ts +138 -0
- package/sdk/ts/src/hosts.js +214 -0
- package/sdk/ts/src/idempotency.d.ts +11 -0
- package/sdk/ts/src/idempotency.js +27 -0
- package/sdk/ts/src/jsondepth.d.ts +27 -0
- package/sdk/ts/src/jsondepth.js +77 -0
- package/sdk/ts/src/licenseterm.d.ts +113 -0
- package/sdk/ts/src/licenseterm.js +433 -0
- package/sdk/ts/src/money.d.ts +21 -0
- package/sdk/ts/src/money.js +60 -0
- package/sdk/ts/src/offer-sign.d.ts +11 -0
- package/sdk/ts/src/offer-sign.js +31 -0
- package/sdk/ts/src/opaque-url.d.ts +3 -0
- package/sdk/ts/src/opaque-url.js +20 -0
- package/sdk/ts/src/pop.d.ts +51 -0
- package/sdk/ts/src/pop.js +166 -0
- package/sdk/ts/src/regschema.d.ts +229 -0
- package/sdk/ts/src/regschema.js +1315 -0
- package/sdk/ts/src/scopes.d.ts +20 -0
- package/sdk/ts/src/scopes.js +45 -0
- package/sdk/ts/src/signurl.d.ts +23 -0
- package/sdk/ts/src/signurl.js +107 -0
- package/sdk/ts/src/thumbprint.d.ts +14 -0
- package/sdk/ts/src/thumbprint.js +24 -0
- package/sdk/ts/src/verify.d.ts +49 -0
- package/sdk/ts/src/verify.js +109 -0
- package/sdk/ts/src/wire-names.d.ts +1 -0
- package/sdk/ts/src/wire-names.js +16 -0
- package/sdk/ts/src/wire.d.ts +68 -0
- package/sdk/ts/src/wire.js +114 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { thumbprint } from "./thumbprint.js";
|
|
2
|
+
import { AgentKeyHeader } from "./wire.js";
|
|
3
|
+
import { decodeBase64Url, utf8Bytes } from "./base64url.js";
|
|
4
|
+
import { opaqueUrl } from "./opaque-url.js";
|
|
5
|
+
// Proof-of-possession verification for delivery-URL identity binding (ADR-013),
|
|
6
|
+
// relocated from the app edge (src/edge/src/pop.ts) as a pure L1 helper.
|
|
7
|
+
//
|
|
8
|
+
// When a signed URL carries an `agent_id` (the agent's RFC 7638 thumbprint), a
|
|
9
|
+
// code-capable edge requires the fetcher to prove possession of the bound key —
|
|
10
|
+
// fully offline:
|
|
11
|
+
//
|
|
12
|
+
// 1. present its raw Ed25519 public key in `X-FORA-Agent-Key`, and
|
|
13
|
+
// 2. sign the GET with RFC 9421 over `@method` + `@target-uri`,
|
|
14
|
+
//
|
|
15
|
+
// and the edge enforces the 3-way identity:
|
|
16
|
+
//
|
|
17
|
+
// agent_id (URL) == keyid (Signature-Input) == thumbprint(presented key)
|
|
18
|
+
//
|
|
19
|
+
// The Ed25519 verify primitive is INJECTABLE (input.verifyEd25519) so a runtime
|
|
20
|
+
// without WebCrypto Ed25519 (Fastly Compute) can supply its own — the byte
|
|
21
|
+
// contract is the signature base, not the primitive. The default primitive is
|
|
22
|
+
// WebCrypto crypto.subtle, which observes byte-identical output on the default
|
|
23
|
+
// path. Byte-parity guard: the pop vectors are produced by the sdk/go signer
|
|
24
|
+
// over the same @method/@target-uri base (sigbase.go buildSignatureBase).
|
|
25
|
+
export const AGENT_KEY_HEADER = AgentKeyHeader.toLowerCase();
|
|
26
|
+
const ED25519_PUBLIC_KEY_BYTES = 32;
|
|
27
|
+
const okResult = { ok: true };
|
|
28
|
+
const fail = (reason) => ({ ok: false, reason });
|
|
29
|
+
/**
|
|
30
|
+
* Verify the agent's proof of possession of the key bound to `agentId`.
|
|
31
|
+
* Returns `{ ok: true }` only when the presented key, the RFC 9421 signature,
|
|
32
|
+
* and the 3-way identity all check out.
|
|
33
|
+
*/
|
|
34
|
+
export async function verifyAgentBinding(input) {
|
|
35
|
+
const presented = readPresentedKey(input.headers);
|
|
36
|
+
if (!presented.ok)
|
|
37
|
+
return presented.result;
|
|
38
|
+
const parsed = parseSignatureInput(input.headers.get("signature-input"));
|
|
39
|
+
if (!parsed)
|
|
40
|
+
return fail("malformed_sig_input");
|
|
41
|
+
// alg is an early reject only; the verify primitive is hard-pinned to Ed25519,
|
|
42
|
+
// so alg is never a key/algorithm SELECTION input (no downgrade possible).
|
|
43
|
+
if (parsed.alg.toLowerCase() !== "ed25519")
|
|
44
|
+
return fail("unsupported_alg");
|
|
45
|
+
if (!coversExactly(parsed.covered))
|
|
46
|
+
return fail("bad_covered_components");
|
|
47
|
+
const sigBytes = parseSignature(input.headers.get("signature"));
|
|
48
|
+
if (!sigBytes)
|
|
49
|
+
return fail("missing_sig");
|
|
50
|
+
// 3-way identity. keyid and the presented-key thumbprint must both equal the
|
|
51
|
+
// URL-bound agent_id before any signature work is trusted.
|
|
52
|
+
if (parsed.keyid !== input.agentId)
|
|
53
|
+
return fail("keyid_mismatch");
|
|
54
|
+
const presentedThumb = await thumbprint(presented.key);
|
|
55
|
+
if (presentedThumb !== input.agentId)
|
|
56
|
+
return fail("thumbprint_mismatch");
|
|
57
|
+
const stale = freshnessFailure(parsed, input.now);
|
|
58
|
+
if (stale)
|
|
59
|
+
return fail(stale);
|
|
60
|
+
// Coerce a URL-like input (Fastly hands a URL object, not a string) to its
|
|
61
|
+
// opaque string form once at the boundary. The @target-uri line must carry the
|
|
62
|
+
// verbatim bytes the agent signed, never a WHATWG-normalized toString(). This
|
|
63
|
+
// is behavior-neutral for the template literal on a string caller
|
|
64
|
+
// (String(s) === s); it makes the opaque-string contract explicit and matches
|
|
65
|
+
// the signed-URL verify boundary.
|
|
66
|
+
const url = opaqueUrl(input.url);
|
|
67
|
+
const base = signatureBase(input.method, url, parsed.rawParams);
|
|
68
|
+
const verify = input.verifyEd25519 ?? defaultVerifyEd25519;
|
|
69
|
+
const valid = await verify(presented.key, sigBytes, utf8Bytes(base));
|
|
70
|
+
return valid ? okResult : fail("pop_sig_invalid");
|
|
71
|
+
}
|
|
72
|
+
function readPresentedKey(headers) {
|
|
73
|
+
const raw = headers.get(AGENT_KEY_HEADER);
|
|
74
|
+
if (!raw)
|
|
75
|
+
return { ok: false, result: fail("missing_agent_key") };
|
|
76
|
+
const bytes = decodeBase64Url(raw);
|
|
77
|
+
if (!bytes || bytes.length !== ED25519_PUBLIC_KEY_BYTES) {
|
|
78
|
+
return { ok: false, result: fail("bad_agent_key") };
|
|
79
|
+
}
|
|
80
|
+
return { ok: true, key: bytes };
|
|
81
|
+
}
|
|
82
|
+
function coversExactly(covered) {
|
|
83
|
+
return covered.length === 2 && covered.includes("@method") && covered.includes("@target-uri");
|
|
84
|
+
}
|
|
85
|
+
// MAX_FUTURE_SKEW_SEC: a proof's created timestamp may not lead the verifier
|
|
86
|
+
// clock by more than this (mirrors the Go service-to-service verifier).
|
|
87
|
+
const MAX_FUTURE_SKEW_SEC = 300;
|
|
88
|
+
function freshnessFailure(parsed, now) {
|
|
89
|
+
const nowSec = Math.floor((now?.() ?? Date.now()) / 1000);
|
|
90
|
+
if (parsed.created === undefined)
|
|
91
|
+
return "pop_missing_created";
|
|
92
|
+
if (parsed.created > nowSec + MAX_FUTURE_SKEW_SEC)
|
|
93
|
+
return "pop_future_created";
|
|
94
|
+
if (parsed.expires === undefined)
|
|
95
|
+
return "pop_missing_exp";
|
|
96
|
+
if (nowSec >= parsed.expires)
|
|
97
|
+
return "pop_expired";
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Rebuild the RFC 9421 signature base for the covered GET components. Order
|
|
102
|
+
* mirrors the inner list (`@method`, `@target-uri`), then `@signature-params`.
|
|
103
|
+
* MUST stay byte-identical to the sdk/go signer (sigbase.go buildSignatureBase).
|
|
104
|
+
*/
|
|
105
|
+
export function signatureBase(method, url, rawParams) {
|
|
106
|
+
return [
|
|
107
|
+
`"@method": ${method.toUpperCase()}`,
|
|
108
|
+
`"@target-uri": ${url}`,
|
|
109
|
+
`"@signature-params": ${rawParams}`,
|
|
110
|
+
].join("\n");
|
|
111
|
+
}
|
|
112
|
+
/** Parse `label=("@method" "@target-uri");keyid="..";alg="..";created=..;expires=..`. */
|
|
113
|
+
export function parseSignatureInput(raw) {
|
|
114
|
+
if (!raw)
|
|
115
|
+
return undefined;
|
|
116
|
+
const eq = raw.indexOf("=");
|
|
117
|
+
if (eq < 0)
|
|
118
|
+
return undefined;
|
|
119
|
+
const rawParams = raw.slice(eq + 1).trim();
|
|
120
|
+
const listMatch = rawParams.match(/^\(([^)]*)\)(.*)$/);
|
|
121
|
+
if (!listMatch)
|
|
122
|
+
return undefined;
|
|
123
|
+
const covered = [...(listMatch[1] ?? "").matchAll(/"([^"]+)"/g)].map((m) => m[1]);
|
|
124
|
+
const tail = listMatch[2] ?? "";
|
|
125
|
+
const keyid = matchParam(tail, /;keyid="([^"]*)"/);
|
|
126
|
+
const alg = matchParam(tail, /;alg="([^"]*)"/);
|
|
127
|
+
if (keyid === undefined || alg === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
const created = matchInt(tail, /;created=(\d+)/);
|
|
130
|
+
const expires = matchInt(tail, /;expires=(\d+)/);
|
|
131
|
+
return {
|
|
132
|
+
covered,
|
|
133
|
+
rawParams,
|
|
134
|
+
keyid,
|
|
135
|
+
alg,
|
|
136
|
+
...(created !== undefined ? { created } : {}),
|
|
137
|
+
...(expires !== undefined ? { expires } : {}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function matchParam(s, re) {
|
|
141
|
+
return s.match(re)?.[1];
|
|
142
|
+
}
|
|
143
|
+
function matchInt(s, re) {
|
|
144
|
+
const m = s.match(re);
|
|
145
|
+
return m ? Number(m[1]) : undefined;
|
|
146
|
+
}
|
|
147
|
+
/** Parse the RFC 9421 `Signature` header value `label=:<base64>:`. */
|
|
148
|
+
export function parseSignature(raw) {
|
|
149
|
+
if (!raw)
|
|
150
|
+
return undefined;
|
|
151
|
+
const m = raw.match(/:([^:]+):/);
|
|
152
|
+
if (!m || !m[1])
|
|
153
|
+
return undefined;
|
|
154
|
+
// The Signature byte string is standard base64; decodeBase64Url's url->std
|
|
155
|
+
// alphabet remap is a no-op on it, so the shared decoder handles it.
|
|
156
|
+
return decodeBase64Url(m[1]);
|
|
157
|
+
}
|
|
158
|
+
const defaultVerifyEd25519 = async (pubkey, sig, message) => {
|
|
159
|
+
try {
|
|
160
|
+
const key = await crypto.subtle.importKey("raw", pubkey, { name: "Ed25519" }, false, ["verify"]);
|
|
161
|
+
return await crypto.subtle.verify("Ed25519", key, sig, message);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { type ValidateFunction } from "ajv/dist/2020.js";
|
|
2
|
+
import type { RegistrationFieldError as RegistrationFieldErrorShape } from "./errordetail.ts";
|
|
3
|
+
/**
|
|
4
|
+
* maxRegistrationSchemaBytes is the published schema's size cap, measured as the
|
|
5
|
+
* UTF-8 bytes of the data_schema member AS SERVED in fora.json — which is why the
|
|
6
|
+
* compile face takes raw bytes rather than a decoded document. A re-encoding is a
|
|
7
|
+
* different length than what the origin sent, and the cap is defined over what the
|
|
8
|
+
* origin sent.
|
|
9
|
+
*/
|
|
10
|
+
export declare const maxRegistrationSchemaBytes = 16384;
|
|
11
|
+
/**
|
|
12
|
+
* maxRegistrationSchemaDepth bounds how deeply the schema document may nest. It
|
|
13
|
+
* counts JSON containers, so a bare `{}` is depth 1. Deep allOf/$ref chains are
|
|
14
|
+
* the cheapest way to make a compile expensive, and a real registration schema is
|
|
15
|
+
* three to five levels deep.
|
|
16
|
+
*/
|
|
17
|
+
export declare const maxRegistrationSchemaDepth = 32;
|
|
18
|
+
/**
|
|
19
|
+
* maxRegistrationFieldErrors is the number of member failures a refusal may carry
|
|
20
|
+
* — the wire's own bound (RegistrationFailure.field_errors declares
|
|
21
|
+
* repeated.max_items = 64), restated so the validator never builds a list the
|
|
22
|
+
* contract would reject.
|
|
23
|
+
*/
|
|
24
|
+
export declare const maxRegistrationFieldErrors = 64;
|
|
25
|
+
/** The wire bounds on RegistrationFieldError.path and .error. */
|
|
26
|
+
export declare const maxRegistrationFieldErrorPathLen = 255;
|
|
27
|
+
export declare const maxRegistrationFieldErrorTextLen = 255;
|
|
28
|
+
/**
|
|
29
|
+
* registrationSchemaDialect is the only $schema value a published data_schema may
|
|
30
|
+
* name. A document that names none is read as this dialect; one that names another
|
|
31
|
+
* is refused rather than validated under semantics its author did not intend.
|
|
32
|
+
*/
|
|
33
|
+
export declare const registrationSchemaDialect = "https://json-schema.org/draft/2020-12/schema";
|
|
34
|
+
/**
|
|
35
|
+
* maxRegistrationSchemaEvaluations bounds the WORK of checking a payload, which the
|
|
36
|
+
* size and depth caps do not: `anyOf` branches multiply along a reference chain, so a
|
|
37
|
+
* schema can be small and shallow and still cost an unbounded amount to evaluate.
|
|
38
|
+
* Cost is linear in this count, so the bound is really a time bound expressed as a
|
|
39
|
+
* number a static walk can compute and a shared corpus can pin, which a stopwatch
|
|
40
|
+
* cannot.
|
|
41
|
+
*/
|
|
42
|
+
export declare const maxRegistrationSchemaEvaluations = 10000;
|
|
43
|
+
/**
|
|
44
|
+
* maxRegistrationSchemaRefHops bounds how long a `$ref` chain may be, measured as the
|
|
45
|
+
* longest path of reference hops rather than as the number of references a document
|
|
46
|
+
* contains.
|
|
47
|
+
*
|
|
48
|
+
* A SEPARATE axis from maxRegistrationSchemaDepth, and the shape that forced it shows
|
|
49
|
+
* why: a chain of five hundred definitions, each referring to the next, is three JSON
|
|
50
|
+
* containers deep however long it is, so the depth cap never sees it. The evaluation cap
|
|
51
|
+
* does not see it either — a flat chain costs one evaluation per link.
|
|
52
|
+
*
|
|
53
|
+
* What it bounds is the RECURSION a validator does while resolving that chain. The cost
|
|
54
|
+
* walk here does not care, but the libraries the three SDKs hand an accepted schema to
|
|
55
|
+
* do, and one of them exhausted its interpreter stack at 495 links — raising out of a
|
|
56
|
+
* face documented as returning a verdict, on a document every SDK had just called valid.
|
|
57
|
+
*/
|
|
58
|
+
export declare const maxRegistrationSchemaRefHops = 100;
|
|
59
|
+
/**
|
|
60
|
+
* SchemaVerdict is the outcome of compiling a published data_schema. The tokens
|
|
61
|
+
* are the Go `SchemaVerdict.String()` vocabulary verbatim, which is what the
|
|
62
|
+
* shared vectors record — a union of string literals rather than a numeric enum,
|
|
63
|
+
* matching the sibling AudienceVerdict.
|
|
64
|
+
*
|
|
65
|
+
* "no_verdict" is Go's zero value and is never returned here; it is in the
|
|
66
|
+
* vocabulary because the corpus carries the whole vocabulary.
|
|
67
|
+
*/
|
|
68
|
+
export declare const schemaVerdicts: readonly ["no_verdict", "accepted", "malformed", "wrong_dialect", "remote_ref", "too_large", "too_deep", "unsafe_pattern", "too_complex", "ref_cycle", "ref_chain_too_long", "compile_timeout", "uncompilable", "not_published"];
|
|
69
|
+
export type SchemaVerdict = (typeof schemaVerdicts)[number];
|
|
70
|
+
export type { RegistrationFieldError } from "./errordetail.ts";
|
|
71
|
+
/** One failure before it is narrowed to the wire's two-field shape. */
|
|
72
|
+
export interface SchemaViolation extends RegistrationFieldErrorShape {
|
|
73
|
+
/**
|
|
74
|
+
* The failed keyword. The shared corpus pins THIS rather than the prose:
|
|
75
|
+
* `error` wording is validator-defined by contract, while the keyword is the
|
|
76
|
+
* same word in every JSON Schema library.
|
|
77
|
+
*/
|
|
78
|
+
keyword: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The largest {n,m} bound admitted. RE2 refuses a repeat count over 1000 outright
|
|
82
|
+
* while the other two engines expand it, so a larger bound is a pattern one SDK
|
|
83
|
+
* compiles and another does not.
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* The largest {n,m} bound admitted. RE2 refuses a repeat count over 1000 outright while
|
|
87
|
+
* the other two engines expand it, so a larger bound is a pattern one SDK compiles and
|
|
88
|
+
* another does not. Exported so the parity suite can hold it against the corpus header:
|
|
89
|
+
* it and the escape alphabet are the two values that decide which patterns this port
|
|
90
|
+
* admits, and they were the two the suite did not check.
|
|
91
|
+
*/
|
|
92
|
+
export declare const maxPortableRepeat = 1000;
|
|
93
|
+
/**
|
|
94
|
+
* isSafeSchemaPattern reports whether a `pattern` uses only constructs all three SDK
|
|
95
|
+
* languages express identically, and none that make a backtracking engine explode.
|
|
96
|
+
*
|
|
97
|
+
* Draft 2020-12 patterns are ECMA-262, and the three SDKs run three different engines
|
|
98
|
+
* over them. Two distinct failures follow, and this function answers only the first.
|
|
99
|
+
*
|
|
100
|
+
* Some constructs one engine cannot express at all — RE2 has no lookaround, this
|
|
101
|
+
* runtime has no inline flags — so no care at the call site reconciles them and they
|
|
102
|
+
* are refused here. Others every engine compiles and then reads DIFFERENTLY: `\d` is
|
|
103
|
+
* Unicode-aware in Python and ASCII here and in RE2, and Python's `$` also matches
|
|
104
|
+
* before a trailing newline. Those are not refused — they appear in almost every real
|
|
105
|
+
* pattern — they are corrected in the port that diverges, which is Python.
|
|
106
|
+
*
|
|
107
|
+
* The escape rule is an ALLOWLIST rather than a list of the divergent escapes, because
|
|
108
|
+
* the divergent set is open-ended: two successive reviews found new counterexamples by
|
|
109
|
+
* trying, which is the signature of a rule stated from the wrong side. The portable set
|
|
110
|
+
* is small, closed and checkable, and the corpus carries it as data.
|
|
111
|
+
*
|
|
112
|
+
* The last rule is about availability rather than agreement: a quantified group whose
|
|
113
|
+
* body can repeat or branch is refused, because that is what makes backtracking
|
|
114
|
+
* catastrophic and no timer here could stop one.
|
|
115
|
+
*/
|
|
116
|
+
export declare function isSafeSchemaPattern(pattern: string): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* A compiled, accepted data_schema. Immutable and safe to share, so a server
|
|
119
|
+
* compiles the operator's schema once at start-up and a client caches one per
|
|
120
|
+
* Exchange.
|
|
121
|
+
*/
|
|
122
|
+
export declare class RegistrationSchema {
|
|
123
|
+
#private;
|
|
124
|
+
constructor(validate: ValidateFunction);
|
|
125
|
+
/**
|
|
126
|
+
* Check a registration_data payload and name what failed. An empty array means
|
|
127
|
+
* the payload conforms.
|
|
128
|
+
*
|
|
129
|
+
* The result is ready for the refusal builder: `path` an RFC 6901 pointer
|
|
130
|
+
* relative to registration_data ("" addresses the whole object), `error` the
|
|
131
|
+
* violated CONSTRAINT and never the submitted value.
|
|
132
|
+
*
|
|
133
|
+
* The order is deterministic — entries are deduplicated by pointer and keyword,
|
|
134
|
+
* then sorted by both, before the list is capped. Three validators walk a
|
|
135
|
+
* failing document in three different orders, so an unsorted list is one no
|
|
136
|
+
* shared corpus could pin.
|
|
137
|
+
*/
|
|
138
|
+
validate(data: unknown): RegistrationFieldErrorShape[];
|
|
139
|
+
/**
|
|
140
|
+
* The whole answer, keyword included. `validate` narrows it to the wire's
|
|
141
|
+
* two-field shape; the parity suite reads it whole, because the corpus pins the
|
|
142
|
+
* keyword while `error` wording is validator-defined by contract.
|
|
143
|
+
*/
|
|
144
|
+
violations(data: unknown): SchemaViolation[];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* compileRegistrationSchema checks a published data_schema against every rule and
|
|
148
|
+
* compiles it.
|
|
149
|
+
*
|
|
150
|
+
* `raw` is the schema AS SERVED — the exact UTF-8 bytes of the data_schema member
|
|
151
|
+
* in fora.json — because maxRegistrationSchemaBytes is defined over those bytes.
|
|
152
|
+
*
|
|
153
|
+
* The schema is null unless the verdict is "accepted". Nothing throws: every way
|
|
154
|
+
* this can fail is a property of the schema, and both callers need to know WHICH.
|
|
155
|
+
* They read the same refusal differently, and that difference is the contract. A
|
|
156
|
+
* CLIENT pre-checking a payload treats any non-accepted verdict as "do not
|
|
157
|
+
* pre-check" and sends anyway — the Exchange's enforcement is the deciding one, and
|
|
158
|
+
* a client that refused here would block a payload the Exchange would have taken.
|
|
159
|
+
* An EXCHANGE compiling its OWN configured schema treats the same verdict as an
|
|
160
|
+
* operator misconfiguration, and must not advertise a schema it cannot enforce.
|
|
161
|
+
*/
|
|
162
|
+
export declare function compileRegistrationSchema(raw: Uint8Array | string): {
|
|
163
|
+
schema: RegistrationSchema | null;
|
|
164
|
+
verdict: SchemaVerdict;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* maxRegistrationDataBytes bounds a submitted registration_data payload, measured as
|
|
168
|
+
* its RFC 8785 canonical JSON encoding.
|
|
169
|
+
*
|
|
170
|
+
* The UNIT has to be named, and that is the whole point of this constant. Every other
|
|
171
|
+
* cap in this module is over bytes a party actually served; registration_data is not
|
|
172
|
+
* served as bytes at all — it arrives as a decoded google.protobuf.Struct — so "16KB"
|
|
173
|
+
* means nothing until an encoding is chosen, and two implementations choosing privately
|
|
174
|
+
* is the disagreement this module exists to remove. JCS is the choice because all three
|
|
175
|
+
* SDKs already compute it for the signing primitive, and because it pins number
|
|
176
|
+
* formatting: a payload carrying 1e300 is seven bytes to one renderer and three hundred
|
|
177
|
+
* to another.
|
|
178
|
+
*
|
|
179
|
+
* It bounds WORK, not storage. The schema's own caps bound the schema; nothing bounded
|
|
180
|
+
* the payload the schema is applied to, and validation cost is roughly the schema's cost
|
|
181
|
+
* multiplied by the elements in the payload — a subschema under `items` is counted once
|
|
182
|
+
* by maxRegistrationSchemaEvaluations and evaluated once per element.
|
|
183
|
+
*/
|
|
184
|
+
export declare const maxRegistrationDataBytes = 16384;
|
|
185
|
+
/**
|
|
186
|
+
* maxRegistrationDataMembers bounds the number of members at the TOP LEVEL of a
|
|
187
|
+
* payload. Top level rather than recursive, deliberately: nested bulk is already bounded
|
|
188
|
+
* by the byte cap, and a recursive count would refuse a small document that merely
|
|
189
|
+
* nests, which a business entity legitimately does (an address is an object).
|
|
190
|
+
*/
|
|
191
|
+
export declare const maxRegistrationDataMembers = 64;
|
|
192
|
+
/**
|
|
193
|
+
* maxRegistrationDataDepth bounds how deeply a submitted registration_data payload may
|
|
194
|
+
* nest, counting JSON containers so a bare `{}` is depth 1. Same number and same counting
|
|
195
|
+
* rule as maxRegistrationSchemaDepth, because it is the same question asked of the other
|
|
196
|
+
* document.
|
|
197
|
+
*
|
|
198
|
+
* It exists because without it the ANSWER depended on the reader's runtime rather than on
|
|
199
|
+
* the payload. Canonicalising walks the payload recursively, and where that walk runs out
|
|
200
|
+
* of stack differs by language and even by interpreter version — one port refused a
|
|
201
|
+
* payload past about five hundred containers on one Python and accepted nine hundred on
|
|
202
|
+
* the next, while this one and Go accepted every depth tried. A static bound checked
|
|
203
|
+
* first turns that into one verdict every implementation reaches.
|
|
204
|
+
*/
|
|
205
|
+
export declare const maxRegistrationDataDepth = 32;
|
|
206
|
+
/**
|
|
207
|
+
* RegistrationDataVerdict is the outcome of checking a submitted registration_data
|
|
208
|
+
* payload. The tokens are the Go oracle's vocabulary verbatim.
|
|
209
|
+
*
|
|
210
|
+
* "no_verdict" is Go's zero value and is never returned here; it is in the vocabulary
|
|
211
|
+
* because the corpus carries the whole vocabulary.
|
|
212
|
+
*/
|
|
213
|
+
export declare const registrationDataVerdicts: readonly ["no_verdict", "accepted", "too_large", "too_many_members", "too_deep", "uncanonicalizable"];
|
|
214
|
+
export type RegistrationDataVerdict = (typeof registrationDataVerdicts)[number];
|
|
215
|
+
/**
|
|
216
|
+
* checkRegistrationData bounds a submitted registration_data payload.
|
|
217
|
+
*
|
|
218
|
+
* `data` is the decoded object. A null/undefined or empty payload is accepted: sending
|
|
219
|
+
* no business data is a matter for the published schema's `required` list, not for a
|
|
220
|
+
* size bound.
|
|
221
|
+
*
|
|
222
|
+
* This runs BEFORE RegistrationSchema.validate, for the same reason the schema's own
|
|
223
|
+
* size cap runs before the schema is parsed: the bound exists to stop work, so it has to
|
|
224
|
+
* precede the work. An Exchange refuses an over-bound payload outright — a malformed
|
|
225
|
+
* request rather than a schema failure, so NOT
|
|
226
|
+
* REGISTRATION_FAILURE_REASON_INVALID_REGISTRATION_DATA, which names non-conformance to
|
|
227
|
+
* a published schema and applies only when one is published.
|
|
228
|
+
*/
|
|
229
|
+
export declare function checkRegistrationData(data: Record<string, unknown> | null | undefined): RegistrationDataVerdict;
|