@tokenoftrust/storefront-runner 2.4.55 → 2.4.57
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,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET/POST /api/admin/commerce-readiness
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: the client fulfills in their own carrier account. This route reports
|
|
5
|
+
* whether order and payment reconcile has been exercised. It does not read an
|
|
6
|
+
* order row, write a payment, or call a carrier. Postage is not a result.
|
|
7
|
+
*
|
|
8
|
+
* The exercised comparison is scripts/commerce/scrappy-reconcile.mjs. This
|
|
9
|
+
* surface has no ledger, so configured, absent, and unreadable stay unhealthy.
|
|
10
|
+
*/
|
|
11
|
+
import type { APIContext } from "astro";
|
|
12
|
+
import { requireAdminApiEntry } from "@/lib/auth/adminApiGuard";
|
|
13
|
+
import { workerCommit } from "@/lib/activity/workerCommit";
|
|
14
|
+
import { readEnv } from "@/lib/env";
|
|
15
|
+
import {
|
|
16
|
+
COMMERCE_SCHEDULER,
|
|
17
|
+
actorMayCommand,
|
|
18
|
+
decideCommerceCommand,
|
|
19
|
+
type CommerceEvidence,
|
|
20
|
+
type CommerceStage,
|
|
21
|
+
} from "@tot/public-runtime";
|
|
22
|
+
|
|
23
|
+
export const prerender = false;
|
|
24
|
+
|
|
25
|
+
const JSON_HEADERS = {
|
|
26
|
+
"content-type": "application/json; charset=utf-8",
|
|
27
|
+
"cache-control": "no-store",
|
|
28
|
+
"x-robots-tag": "noindex, nofollow",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const READINESS = ["configured-only", "exercised", "stale", "failed", "absent", "unreadable"] as const;
|
|
32
|
+
type Readiness = (typeof READINESS)[number];
|
|
33
|
+
|
|
34
|
+
export interface CommerceReadinessReport {
|
|
35
|
+
schema: "scrappy-reconcile-v1";
|
|
36
|
+
readiness: Readiness;
|
|
37
|
+
healthy: boolean;
|
|
38
|
+
sourceSha: string | null;
|
|
39
|
+
deployedSha: string | null;
|
|
40
|
+
watermark: { asOf: string; source: { cursor: string | null }; tot: { cursor: string | null } } | null;
|
|
41
|
+
orderCount: number | null;
|
|
42
|
+
capturedMinor: number | null;
|
|
43
|
+
refundedMinor: number | null;
|
|
44
|
+
labelAndPostageCompared: false;
|
|
45
|
+
discrepancies: { code: string; id: string; owner: string }[] | null;
|
|
46
|
+
owner: "owner";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const EVIDENCE: CommerceEvidence = { verification: "UNKNOWN", tax: "UNKNOWN", risk: "UNKNOWN", stock: "UNKNOWN" };
|
|
50
|
+
const REPLAY_KEYS = new Set(["kind", "orderId", "sourceEventId", "stage"]);
|
|
51
|
+
const OPAQUE = /^[A-Za-z0-9:_-]{1,80}$/;
|
|
52
|
+
|
|
53
|
+
function json(body: unknown, status = 200): Response {
|
|
54
|
+
return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function projectAdminReadiness(input: {
|
|
58
|
+
scheduler: string | undefined;
|
|
59
|
+
deployedSha: string;
|
|
60
|
+
}): CommerceReadinessReport {
|
|
61
|
+
const configured = input.scheduler === COMMERCE_SCHEDULER;
|
|
62
|
+
const deployedSha = input.deployedSha.length > 0 ? input.deployedSha : null;
|
|
63
|
+
const readiness: Readiness = deployedSha === null ? "unreadable" : configured ? "configured-only" : "absent";
|
|
64
|
+
return {
|
|
65
|
+
schema: "scrappy-reconcile-v1",
|
|
66
|
+
readiness,
|
|
67
|
+
healthy: false,
|
|
68
|
+
sourceSha: null,
|
|
69
|
+
deployedSha,
|
|
70
|
+
watermark: null,
|
|
71
|
+
orderCount: null,
|
|
72
|
+
capturedMinor: null,
|
|
73
|
+
refundedMinor: null,
|
|
74
|
+
labelAndPostageCompared: false,
|
|
75
|
+
discrepancies: readiness === "unreadable" ? null : [],
|
|
76
|
+
owner: "owner",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function replayStage(value: unknown): CommerceStage | null {
|
|
81
|
+
return value === "preview" || value === "publication" ? value : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function admitAdminReplay(input: {
|
|
85
|
+
tenantId: string;
|
|
86
|
+
stage: CommerceStage;
|
|
87
|
+
kind: "order" | "refund";
|
|
88
|
+
}): { admitted: boolean; reason: "admitted" | "denied" | "refused" } {
|
|
89
|
+
const actor = { role: "owner" as const, tenantId: input.tenantId, stage: input.stage };
|
|
90
|
+
const resource = { tenantId: input.tenantId, stage: input.stage };
|
|
91
|
+
if (!actorMayCommand(actor, resource)) return { admitted: false, reason: "denied" };
|
|
92
|
+
const decision = decideCommerceCommand({
|
|
93
|
+
command: "reconcile",
|
|
94
|
+
payment: input.kind === "refund" ? "refund_unknown" : "captured",
|
|
95
|
+
stock: "UNKNOWN",
|
|
96
|
+
fulfillment: "UNKNOWN",
|
|
97
|
+
label: "none",
|
|
98
|
+
evidence: EVIDENCE,
|
|
99
|
+
providerOutcome: null,
|
|
100
|
+
});
|
|
101
|
+
if (!decision.admit || decision.restock !== false || decision.blindRepurchase !== false) {
|
|
102
|
+
return { admitted: false, reason: "refused" };
|
|
103
|
+
}
|
|
104
|
+
return { admitted: true, reason: "admitted" };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function GET(context: APIContext): Promise<Response> {
|
|
108
|
+
const access = await requireAdminApiEntry(context, { requireOwner: true });
|
|
109
|
+
if (!access.ok) return json({ ok: false, reason: access.reason }, access.status);
|
|
110
|
+
const scheduler = await readEnv("SCRAPPY_COMMERCE_SCHEDULER");
|
|
111
|
+
return json({ ok: true, report: projectAdminReadiness({ scheduler, deployedSha: workerCommit() }) });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function POST(context: APIContext): Promise<Response> {
|
|
115
|
+
const access = await requireAdminApiEntry(context, { requireOwner: true });
|
|
116
|
+
if (!access.ok) return json({ ok: false, reason: access.reason }, access.status);
|
|
117
|
+
const text = await context.request.text();
|
|
118
|
+
if (text.length > 2000) return json({ ok: false, reason: "refused" }, 400);
|
|
119
|
+
let body: unknown;
|
|
120
|
+
try {
|
|
121
|
+
body = JSON.parse(text);
|
|
122
|
+
} catch {
|
|
123
|
+
return json({ ok: false, reason: "refused" }, 400);
|
|
124
|
+
}
|
|
125
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) return json({ ok: false, reason: "refused" }, 400);
|
|
126
|
+
const record = body as Record<string, unknown>;
|
|
127
|
+
if (Object.keys(record).some((key) => !REPLAY_KEYS.has(key))) return json({ ok: false, reason: "refused" }, 400);
|
|
128
|
+
const stage = replayStage(record.stage);
|
|
129
|
+
const kind = record.kind === "order" || record.kind === "refund" ? record.kind : null;
|
|
130
|
+
if (!stage || !kind || typeof record.orderId !== "string" || typeof record.sourceEventId !== "string") {
|
|
131
|
+
return json({ ok: false, reason: "refused" }, 400);
|
|
132
|
+
}
|
|
133
|
+
if (!OPAQUE.test(record.orderId) || !OPAQUE.test(record.sourceEventId)) return json({ ok: false, reason: "refused" }, 400);
|
|
134
|
+
const decision = admitAdminReplay({ tenantId: context.locals.tenant.tenant_id, stage, kind });
|
|
135
|
+
if (!decision.admitted) return json({ ok: false, reason: decision.reason }, 403);
|
|
136
|
+
const scheduler = await readEnv("SCRAPPY_COMMERCE_SCHEDULER");
|
|
137
|
+
return json({
|
|
138
|
+
ok: true,
|
|
139
|
+
admitted: true,
|
|
140
|
+
durableWrite: false,
|
|
141
|
+
command: "reconcile",
|
|
142
|
+
reason: decision.reason,
|
|
143
|
+
report: projectAdminReadiness({ scheduler, deployedSha: workerCommit() }),
|
|
144
|
+
});
|
|
145
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.57",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
package/pnpm-lock.runner.yaml
CHANGED
|
@@ -87,7 +87,7 @@ importers:
|
|
|
87
87
|
version: 4.12.1(playwright-core@1.61.1)
|
|
88
88
|
'@cloudflare/vitest-pool-workers':
|
|
89
89
|
specifier: 0.12.21
|
|
90
|
-
version: 0.12.21(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.
|
|
90
|
+
version: 0.12.21(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0))
|
|
91
91
|
'@playwright/test':
|
|
92
92
|
specifier: ^1.61.1
|
|
93
93
|
version: 1.61.1
|
|
@@ -106,6 +106,9 @@ importers:
|
|
|
106
106
|
esbuild:
|
|
107
107
|
specifier: 0.28.1
|
|
108
108
|
version: 0.28.1
|
|
109
|
+
happy-dom:
|
|
110
|
+
specifier: 20.11.2
|
|
111
|
+
version: 20.11.2
|
|
109
112
|
tsx:
|
|
110
113
|
specifier: ^4.22.4
|
|
111
114
|
version: 4.22.4
|
|
@@ -114,7 +117,7 @@ importers:
|
|
|
114
117
|
version: 5.9.3
|
|
115
118
|
vitest:
|
|
116
119
|
specifier: 2.1.8
|
|
117
|
-
version: 2.1.8(@types/node@24.13.3)(happy-dom@20.
|
|
120
|
+
version: 2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0)
|
|
118
121
|
wrangler:
|
|
119
122
|
specifier: ^4.105.0
|
|
120
123
|
version: 4.105.0
|
|
@@ -139,7 +142,7 @@ importers:
|
|
|
139
142
|
version: 5.9.3
|
|
140
143
|
vitest:
|
|
141
144
|
specifier: ^2.1.8
|
|
142
|
-
version: 2.1.8(@types/node@24.13.3)(happy-dom@20.
|
|
145
|
+
version: 2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0)
|
|
143
146
|
|
|
144
147
|
packages:
|
|
145
148
|
|
|
@@ -2789,8 +2792,8 @@ packages:
|
|
|
2789
2792
|
h3@1.15.11:
|
|
2790
2793
|
resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==}
|
|
2791
2794
|
|
|
2792
|
-
happy-dom@20.
|
|
2793
|
-
resolution: {integrity: sha512-
|
|
2795
|
+
happy-dom@20.11.2:
|
|
2796
|
+
resolution: {integrity: sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw==}
|
|
2794
2797
|
engines: {node: '>=20.0.0'}
|
|
2795
2798
|
|
|
2796
2799
|
has-flag@4.0.0:
|
|
@@ -4437,14 +4440,14 @@ snapshots:
|
|
|
4437
4440
|
- utf-8-validate
|
|
4438
4441
|
- workerd
|
|
4439
4442
|
|
|
4440
|
-
'@cloudflare/vitest-pool-workers@0.12.21(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.
|
|
4443
|
+
'@cloudflare/vitest-pool-workers@0.12.21(@vitest/runner@2.1.9)(@vitest/snapshot@2.1.9)(vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0))':
|
|
4441
4444
|
dependencies:
|
|
4442
4445
|
'@vitest/runner': 2.1.9
|
|
4443
4446
|
'@vitest/snapshot': 2.1.9
|
|
4444
4447
|
cjs-module-lexer: 1.4.3
|
|
4445
4448
|
esbuild: 0.27.3
|
|
4446
4449
|
miniflare: 4.20260310.0
|
|
4447
|
-
vitest: 2.1.8(@types/node@24.13.3)(happy-dom@20.
|
|
4450
|
+
vitest: 2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0)
|
|
4448
4451
|
wrangler: 4.72.0
|
|
4449
4452
|
transitivePeerDependencies:
|
|
4450
4453
|
- '@cloudflare/workers-types'
|
|
@@ -5372,11 +5375,11 @@ snapshots:
|
|
|
5372
5375
|
'@tiptap/extension-text': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
|
|
5373
5376
|
'@tiptap/extension-text-style': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
|
|
5374
5377
|
'@tiptap/extension-underline': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))
|
|
5375
|
-
'@tiptap/html': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.
|
|
5378
|
+
'@tiptap/html': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.11.2)
|
|
5376
5379
|
'@tiptap/pm': 3.27.1
|
|
5377
5380
|
'@tiptap/suggestion': 3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)
|
|
5378
5381
|
emojibase: 17.0.0
|
|
5379
|
-
happy-dom: 20.
|
|
5382
|
+
happy-dom: 20.11.2
|
|
5380
5383
|
markdown-it: 14.2.0
|
|
5381
5384
|
transitivePeerDependencies:
|
|
5382
5385
|
- '@floating-ui/dom'
|
|
@@ -5558,11 +5561,11 @@ snapshots:
|
|
|
5558
5561
|
dependencies:
|
|
5559
5562
|
'@tiptap/core': 3.27.1(@tiptap/pm@3.27.1)
|
|
5560
5563
|
|
|
5561
|
-
'@tiptap/html@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.
|
|
5564
|
+
'@tiptap/html@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.11.2)':
|
|
5562
5565
|
dependencies:
|
|
5563
5566
|
'@tiptap/core': 3.27.1(@tiptap/pm@3.27.1)
|
|
5564
5567
|
'@tiptap/pm': 3.27.1
|
|
5565
|
-
happy-dom: 20.
|
|
5568
|
+
happy-dom: 20.11.2
|
|
5566
5569
|
|
|
5567
5570
|
'@tiptap/pm@3.27.1':
|
|
5568
5571
|
dependencies:
|
|
@@ -6349,7 +6352,7 @@ snapshots:
|
|
|
6349
6352
|
ufo: 1.6.4
|
|
6350
6353
|
uncrypto: 0.1.3
|
|
6351
6354
|
|
|
6352
|
-
happy-dom@20.
|
|
6355
|
+
happy-dom@20.11.2:
|
|
6353
6356
|
dependencies:
|
|
6354
6357
|
'@types/node': 24.13.3
|
|
6355
6358
|
'@types/whatwg-mimetype': 3.0.2
|
|
@@ -7330,7 +7333,7 @@ snapshots:
|
|
|
7330
7333
|
optionalDependencies:
|
|
7331
7334
|
vite: 8.1.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
|
|
7332
7335
|
|
|
7333
|
-
vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.
|
|
7336
|
+
vitest@2.1.8(@types/node@24.13.3)(happy-dom@20.11.2)(lightningcss@1.32.0):
|
|
7334
7337
|
dependencies:
|
|
7335
7338
|
'@vitest/expect': 2.1.8
|
|
7336
7339
|
'@vitest/mocker': 2.1.8(vite@5.4.21(@types/node@24.13.3)(lightningcss@1.32.0))
|
|
@@ -7354,7 +7357,7 @@ snapshots:
|
|
|
7354
7357
|
why-is-node-running: 2.3.0
|
|
7355
7358
|
optionalDependencies:
|
|
7356
7359
|
'@types/node': 24.13.3
|
|
7357
|
-
happy-dom: 20.
|
|
7360
|
+
happy-dom: 20.11.2
|
|
7358
7361
|
transitivePeerDependencies:
|
|
7359
7362
|
- less
|
|
7360
7363
|
- lightningcss
|