@tokenoftrust/storefront-runner 3.10.2 → 3.10.4

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.
@@ -0,0 +1,379 @@
1
+ /** Strict signed source authority controls. Business event bytes remain independent. */
2
+ import {
3
+ canonicalJson,
4
+ sha256Hex,
5
+ type CommerceStage,
6
+ } from "@tot/public-runtime";
7
+ export const SOURCE_SCHEMA = "commerce-source-authority/v1";
8
+ export const SOURCE_LIMIT = 64;
9
+ export const SOURCE_LEASE_MS = 120_000;
10
+ export const FAULT_KINDS = [
11
+ "fork",
12
+ "committed-payload-missing",
13
+ "committed-payload-corrupt",
14
+ "source-resource-invalid",
15
+ "census-incomplete",
16
+ "census-changing",
17
+ "source-unavailable",
18
+ "authority-budget",
19
+ ] as const;
20
+ export type FaultKind = (typeof FAULT_KINDS)[number];
21
+ export interface SourceKey {
22
+ tenantId: string;
23
+ stage: CommerceStage;
24
+ source: "foxycart";
25
+ providerStoreId: string;
26
+ sourceOrderId: string;
27
+ }
28
+ export interface ManifestCore {
29
+ v: 2;
30
+ store: string;
31
+ order: string;
32
+ revision: number;
33
+ parent: string | null;
34
+ parts: number;
35
+ bytes: number;
36
+ sha256: string;
37
+ }
38
+ export interface BusinessPair {
39
+ sourceEventId: string;
40
+ originalEventDigest: string;
41
+ }
42
+ export interface FaultProof {
43
+ faultKind: FaultKind;
44
+ conditionDigest: string;
45
+ completeValidatedManifestSetOrNull: ManifestCore[] | null;
46
+ divergenceParentOrNull: string | null;
47
+ conflictingValidatedGenerationsOrNull: string[] | null;
48
+ }
49
+ export interface SourceBegin {
50
+ kind: "source-check-begin";
51
+ schema: typeof SOURCE_SCHEMA;
52
+ source: "foxycart";
53
+ providerStoreId: string;
54
+ sourceOrderId: string;
55
+ requestId: string;
56
+ }
57
+ interface ResultCommon {
58
+ kind: "source-check-result";
59
+ schema: typeof SOURCE_SCHEMA;
60
+ source: "foxycart";
61
+ providerStoreId: string;
62
+ sourceOrderId: string;
63
+ checkId: string;
64
+ observationId: string;
65
+ expectedCheckRevision: number;
66
+ expectedSourceStateVersion: number;
67
+ }
68
+ export type SourceResult = ResultCommon &
69
+ (
70
+ | ({ outcome: "fault" } & FaultProof)
71
+ | {
72
+ outcome: "healthy";
73
+ actualObservedAt: string;
74
+ tipGeneration: string;
75
+ validatedManifestCores: ManifestCore[];
76
+ recognizedResourceSetDigest: string;
77
+ businessEventDigestPairs: BusinessPair[];
78
+ businessEventSetDigest: string;
79
+ resolvesHoldIds: string[];
80
+ }
81
+ );
82
+ export interface Provenance {
83
+ acceptedLedgerGeneration: string;
84
+ businessEventSetDigest: string;
85
+ exactOriginalEventDigest: string;
86
+ }
87
+ export interface SourceOrderWrapper {
88
+ kind: "source-order-event";
89
+ schema: typeof SOURCE_SCHEMA;
90
+ event: Record<string, unknown>;
91
+ provenance: Provenance;
92
+ }
93
+ export interface SourceHold {
94
+ holdId: string;
95
+ conditionDigest: string;
96
+ faultKind: FaultKind;
97
+ openingObservationId: string;
98
+ openingProof: FaultProof;
99
+ }
100
+ export interface BeginAck {
101
+ ok: true;
102
+ duplicate: boolean;
103
+ checkId: string;
104
+ checkRevision: number;
105
+ sourceStateVersion: number;
106
+ acceptedLedgerGeneration: string | null;
107
+ acceptedBusinessEventSetDigest: string | null;
108
+ unresolvedHolds: SourceHold[];
109
+ }
110
+ export interface ResultAck {
111
+ ok: true;
112
+ duplicate: boolean;
113
+ checkId: string;
114
+ observationId: string;
115
+ sourceStateVersion: number;
116
+ acceptedLedgerGeneration: string | null;
117
+ acceptedBusinessEventSetDigest: string | null;
118
+ sourceProofExpiresAt: string | null;
119
+ pendingCheck: boolean;
120
+ unresolvedHoldIdsDigest: string;
121
+ sourceSyncPending: boolean;
122
+ }
123
+ export type AuthorityCode =
124
+ | "malformed"
125
+ | "signature"
126
+ | "tenant"
127
+ | "stage"
128
+ | "source-state-changed"
129
+ | "source-authority-unavailable"
130
+ | "source-sync-pending"
131
+ | "authority-budget"
132
+ | "control-identity-conflict";
133
+ export class SourceAuthorityError extends Error {
134
+ constructor(readonly code: AuthorityCode) {
135
+ super(code);
136
+ }
137
+ }
138
+ export const isRecord = (v: unknown): v is Record<string, unknown> =>
139
+ v !== null && typeof v === "object" && !Array.isArray(v);
140
+ export const isHash = (v: unknown): v is string =>
141
+ typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
142
+ export const isSourceId = (v: unknown): v is string =>
143
+ typeof v === "string" && /^[1-9][0-9]{0,30}$/.test(v);
144
+ const opaque = (v: unknown): v is string =>
145
+ typeof v === "string" && /^[A-Za-z0-9:_-]{1,128}$/.test(v);
146
+ const integer = (v: unknown, min = 0, max = Number.MAX_SAFE_INTEGER) =>
147
+ Number.isSafeInteger(v) && Number(v) >= min && Number(v) <= max;
148
+ export function exactKeys(
149
+ v: unknown,
150
+ keys: readonly string[],
151
+ ): v is Record<string, unknown> {
152
+ return (
153
+ isRecord(v) &&
154
+ Object.keys(v).length === keys.length &&
155
+ keys.every((k) => Object.hasOwn(v, k))
156
+ );
157
+ }
158
+ export function canonicalInstant(v: unknown): v is string {
159
+ if (typeof v !== "string") return false;
160
+ const n = Date.parse(v);
161
+ return Number.isFinite(n) && new Date(n).toISOString() === v;
162
+ }
163
+ const BASE = ["kind", "schema", "source", "providerStoreId", "sourceOrderId"];
164
+ const COMMON = [
165
+ ...BASE,
166
+ "checkId",
167
+ "observationId",
168
+ "expectedCheckRevision",
169
+ "expectedSourceStateVersion",
170
+ "outcome",
171
+ ];
172
+ const FAULT = [
173
+ "faultKind",
174
+ "conditionDigest",
175
+ "completeValidatedManifestSetOrNull",
176
+ "divergenceParentOrNull",
177
+ "conflictingValidatedGenerationsOrNull",
178
+ ];
179
+ function core(v: unknown): v is ManifestCore {
180
+ return (
181
+ exactKeys(v, [
182
+ "v",
183
+ "store",
184
+ "order",
185
+ "revision",
186
+ "parent",
187
+ "parts",
188
+ "bytes",
189
+ "sha256",
190
+ ]) &&
191
+ v.v === 2 &&
192
+ typeof v.store === "string" &&
193
+ typeof v.order === "string" &&
194
+ integer(v.revision, 1) &&
195
+ (v.parent === null || isHash(v.parent)) &&
196
+ integer(v.parts, 1, 350) &&
197
+ integer(v.bytes, 1, 262144) &&
198
+ isHash(v.sha256)
199
+ );
200
+ }
201
+ function cores(v: unknown): v is ManifestCore[] {
202
+ return (
203
+ Array.isArray(v) &&
204
+ v.length > 0 &&
205
+ v.length <= SOURCE_LIMIT &&
206
+ v.every(core)
207
+ );
208
+ }
209
+ export function faultProof(v: SourceResult & { outcome: "fault" }): FaultProof {
210
+ return {
211
+ faultKind: v.faultKind,
212
+ conditionDigest: v.conditionDigest,
213
+ completeValidatedManifestSetOrNull: v.completeValidatedManifestSetOrNull,
214
+ divergenceParentOrNull: v.divergenceParentOrNull,
215
+ conflictingValidatedGenerationsOrNull:
216
+ v.conflictingValidatedGenerationsOrNull,
217
+ };
218
+ }
219
+ export function parseSourceWire(
220
+ v: unknown,
221
+ ): SourceBegin | SourceResult | SourceOrderWrapper | null {
222
+ if (!isRecord(v) || v.schema !== SOURCE_SCHEMA) return null;
223
+ if (v.kind === "source-order-event") {
224
+ if (
225
+ !exactKeys(v, ["kind", "schema", "event", "provenance"]) ||
226
+ !isRecord(v.event) ||
227
+ !exactKeys(v.provenance, [
228
+ "acceptedLedgerGeneration",
229
+ "businessEventSetDigest",
230
+ "exactOriginalEventDigest",
231
+ ]) ||
232
+ !Object.values(v.provenance).every(isHash)
233
+ )
234
+ return null;
235
+ return v as unknown as SourceOrderWrapper;
236
+ }
237
+ if (
238
+ v.source !== "foxycart" ||
239
+ !isSourceId(v.providerStoreId) ||
240
+ !isSourceId(v.sourceOrderId)
241
+ )
242
+ return null;
243
+ if (v.kind === "source-check-begin")
244
+ return exactKeys(v, [...BASE, "requestId"]) && opaque(v.requestId)
245
+ ? (v as unknown as SourceBegin)
246
+ : null;
247
+ if (
248
+ v.kind !== "source-check-result" ||
249
+ !opaque(v.checkId) ||
250
+ !isHash(v.observationId) ||
251
+ !integer(v.expectedCheckRevision) ||
252
+ !integer(v.expectedSourceStateVersion)
253
+ )
254
+ return null;
255
+ if (v.outcome === "fault") {
256
+ if (
257
+ !exactKeys(v, [...COMMON, ...FAULT]) ||
258
+ !FAULT_KINDS.includes(v.faultKind as FaultKind) ||
259
+ !isHash(v.conditionDigest)
260
+ )
261
+ return null;
262
+ if (
263
+ v.completeValidatedManifestSetOrNull !== null &&
264
+ !cores(v.completeValidatedManifestSetOrNull)
265
+ )
266
+ return null;
267
+ if (v.divergenceParentOrNull !== null && !isHash(v.divergenceParentOrNull))
268
+ return null;
269
+ const ids = v.conflictingValidatedGenerationsOrNull;
270
+ if (
271
+ ids !== null &&
272
+ (!Array.isArray(ids) ||
273
+ ids.length < 2 ||
274
+ ids.length > SOURCE_LIMIT ||
275
+ !ids.every(isHash) ||
276
+ new Set(ids).size !== ids.length)
277
+ )
278
+ return null;
279
+ return v as unknown as SourceResult;
280
+ }
281
+ if (
282
+ v.outcome !== "healthy" ||
283
+ !exactKeys(v, [
284
+ ...COMMON,
285
+ "actualObservedAt",
286
+ "tipGeneration",
287
+ "validatedManifestCores",
288
+ "recognizedResourceSetDigest",
289
+ "businessEventDigestPairs",
290
+ "businessEventSetDigest",
291
+ "resolvesHoldIds",
292
+ ])
293
+ )
294
+ return null;
295
+ if (
296
+ !canonicalInstant(v.actualObservedAt) ||
297
+ !isHash(v.tipGeneration) ||
298
+ !cores(v.validatedManifestCores) ||
299
+ !isHash(v.recognizedResourceSetDigest) ||
300
+ !isHash(v.businessEventSetDigest)
301
+ )
302
+ return null;
303
+ if (
304
+ !Array.isArray(v.resolvesHoldIds) ||
305
+ v.resolvesHoldIds.length > SOURCE_LIMIT ||
306
+ !v.resolvesHoldIds.every(opaque) ||
307
+ new Set(v.resolvesHoldIds).size !== v.resolvesHoldIds.length
308
+ )
309
+ return null;
310
+ const pairs = v.businessEventDigestPairs;
311
+ if (!Array.isArray(pairs) || pairs.length > SOURCE_LIMIT) return null;
312
+ for (let i = 0; i < pairs.length; i++) {
313
+ const p = pairs[i];
314
+ if (
315
+ !exactKeys(p, ["sourceEventId", "originalEventDigest"]) ||
316
+ typeof p.sourceEventId !== "string" ||
317
+ !/^[-A-Za-z0-9._:]{1,128}$/.test(p.sourceEventId) ||
318
+ !isHash(p.originalEventDigest) ||
319
+ (i > 0 && pairs[i - 1].sourceEventId >= p.sourceEventId)
320
+ )
321
+ return null;
322
+ }
323
+ return v as unknown as SourceResult;
324
+ }
325
+ export const digestJson = (v: unknown): Promise<string> =>
326
+ sha256Hex(canonicalJson(v));
327
+ export function conditionDigest(
328
+ key: SourceKey,
329
+ proof: Omit<FaultProof, "conditionDigest">,
330
+ ): Promise<string> {
331
+ return digestJson({ v: 1, key, ...proof });
332
+ }
333
+ export function observationId(
334
+ key: SourceKey,
335
+ result: Omit<SourceResult, "observationId"> | Record<string, unknown>,
336
+ ): Promise<string> {
337
+ const r = { ...result };
338
+ delete (r as Record<string, unknown>).observationId;
339
+ return digestJson({
340
+ domain: "commerce-source-observation/v1",
341
+ key,
342
+ result: r,
343
+ });
344
+ }
345
+ export async function validateManifestSet(
346
+ key: SourceKey,
347
+ items: readonly ManifestCore[],
348
+ ): Promise<Map<string, ManifestCore>> {
349
+ const map = new Map<string, ManifestCore>();
350
+ for (const item of items) {
351
+ if (
352
+ item.store !== `https://api.foxycart.com/stores/${key.providerStoreId}` ||
353
+ item.order !==
354
+ `https://api.foxycart.com/transactions/${key.sourceOrderId}`
355
+ )
356
+ throw new SourceAuthorityError("malformed");
357
+ const id = await digestJson(item);
358
+ if (map.has(id)) throw new SourceAuthorityError("malformed");
359
+ map.set(id, item);
360
+ }
361
+ return map;
362
+ }
363
+ export function uniqueLineage(
364
+ map: Map<string, ManifestCore>,
365
+ tip: string,
366
+ ): boolean {
367
+ let id: string | null = tip;
368
+ let child: ManifestCore | null = null;
369
+ const seen = new Set<string>();
370
+ while (id !== null) {
371
+ const c = map.get(id);
372
+ if (!c || seen.has(id) || (child && child.revision !== c.revision + 1))
373
+ return false;
374
+ seen.add(id);
375
+ child = c;
376
+ id = c.parent;
377
+ }
378
+ return child?.revision === 1 && seen.size === map.size;
379
+ }