@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.
@@ -233,12 +233,17 @@ async function authorize(context: APIContext): Promise<Response> {
233
233
  const requirements = await readCheckoutRequirements(tenant.tenant_id, contentEnv);
234
234
  if (!requirements) return deny("checkout-requirements-unreadable", 503);
235
235
  const policy = testStoreEvidencePolicy(requirements);
236
+ // Stock is tracked only for a tenant that declares the `mirror` provider. An undeclared tenant
237
+ // has no balance to read, so its stock evidence is honestly `not-required`, not a refusal.
238
+ const stockTracked = tenant.inventory?.provider === "mirror";
236
239
  const authorities = [];
237
- for (const sku of new Set(snapshot.lines.map(line => line.sku))) {
238
- authorities.push(await readMirrorStock(queryable, { tenantId: snapshot.tenantId, stage: snapshot.stage, sku, locationId: "unspecified", now, maxAgeMs: 15 * 60_000 }));
240
+ if (stockTracked) {
241
+ for (const sku of new Set(snapshot.lines.map(line => line.sku))) {
242
+ authorities.push(await readMirrorStock(queryable, { tenantId: snapshot.tenantId, stage: snapshot.stage, sku, locationId: "unspecified", now, maxAgeMs: 15 * 60_000 }));
243
+ }
239
244
  }
240
245
  const stockExpiry = Math.min(...authorities.flatMap(authority => authority ? [Date.parse(authority.asOf) + 15 * 60_000] : []));
241
- const evidence: CommerceEvidence = { verification: "absent", tax: "absent", risk: "absent", stock: "fresh" };
246
+ const evidence: CommerceEvidence = { verification: "absent", tax: "absent", risk: "absent", stock: stockTracked ? "fresh" : "not-required" };
242
247
  const expiresAt = existing?.expiresAt ?? new Date(Math.min(Date.now() + 5 * 60_000, stockExpiry)).toISOString();
243
248
  const provenance = {} as CommerceEvidenceProvenance;
244
249
  for (const field of ["verification", "tax", "risk"] as const) {
@@ -247,7 +252,9 @@ async function authorize(context: APIContext): Promise<Response> {
247
252
  provenance[field] = { requirement: "not-required", state: "not-required", source: SHARED_STORE_POLICY_SOURCE, policyRevision: SHARED_STORE_POLICY_REVISION, expiresAt };
248
253
  }
249
254
  }
250
- provenance.stock = { requirement: "required", state: "satisfied", source: "tenant-inventory-movements", policyRevision: "mirror-v1", expiresAt };
255
+ provenance.stock = stockTracked
256
+ ? { requirement: "required", state: "satisfied", source: "tenant-inventory-movements", policyRevision: "mirror-v1", expiresAt }
257
+ : { requirement: "not-required", state: "not-required", source: "tenant-inventory-undeclared", policyRevision: "untracked-v1", expiresAt };
251
258
  const presented: FinalCartBinding = { tenantId: snapshot.tenantId, stage: snapshot.stage, currency: snapshot.currency, lines: snapshot.lines,
252
259
  destinationDigest: snapshot.destinationDigest, verificationExpiresAt: expiresAt, taxExpiresAt: expiresAt };
253
260
  const seamView = await seam(authorityContext, snapshot, selected, policy);
@@ -257,7 +264,7 @@ async function authorize(context: APIContext): Promise<Response> {
257
264
  }
258
265
  const decision = await evaluateFinalCart({ now, routedTenantId: commerceTenantId(tenant), quoted: presented, presented, evidence,
259
266
  seam: seamView, catalogAvailable: true, browserConfirmed: false, token,
260
- trustedIssuers: issuers, expectedAudience: audience, activeKids, authorities });
267
+ trustedIssuers: issuers, expectedAudience: audience, activeKids, stockTracked, authorities });
261
268
  if (!decision.ok) {
262
269
  if (decision.finding === "verification-refused:flavored_prohibited" && seamView.flavors) {
263
270
  return json({ ok: false, finding: decision.finding, flavors: seamView.flavors }, 409);
@@ -269,7 +276,7 @@ async function authorize(context: APIContext): Promise<Response> {
269
276
  const cartDigest = existing?.cartDigest ?? await digest(`commerce-provider-cart-v1\n${snapshot.providerCartId}\n${snapshot.providerCartDigest}`);
270
277
  const reserved = await reserveAllocation(d1CommerceRunner(database), { tenantId: decision.tenantId, stage: decision.stage, orderId: authorizationId,
271
278
  cartDigest, expiresAt: decision.expiresAt, now, currency: snapshot.currency, amountMinor: snapshot.totals.totalMinor,
272
- issuer: decision.issuer, jti: decision.jti, holds: decision.holds,
279
+ issuer: decision.issuer, jti: decision.jti, holds: decision.holds, stockUntracked: !stockTracked,
273
280
  providerCart: { provider: snapshot.provider, id: snapshot.providerCartId, digest: snapshot.providerCartDigest, expectedAuthorizationId: snapshot.expectedAuthorizationId },
274
281
  admissionStatement: admissionSnapshotStatement({ ...snapshot, authorizationId, cartDigest, evidence, provenance, expiresAt: decision.expiresAt, now }),
275
282
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "3.10.2",
3
+ "version": "3.10.4",
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",
@@ -401,7 +401,7 @@ export type CallerTokenRefusal =
401
401
  | "stage";
402
402
 
403
403
  export function evidencePermitsRelease(evidence: CommerceEvidence): boolean {
404
- return evidence.stock === "fresh" && (["verification", "tax", "risk"] as const)
404
+ return (["verification", "tax", "risk", "stock"] as const)
405
405
  .every(field => evidence[field] === "fresh" || evidence[field] === "not-required");
406
406
  }
407
407