@tokenoftrust/storefront-runner 4.11.10 → 4.11.12

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.
@@ -58,7 +58,9 @@
58
58
  for (const g of grants) {
59
59
  const tr = document.createElement("tr");
60
60
  const dev = document.createElement("td");
61
- dev.textContent = (g.principal && g.principal.email) || "";
61
+ // tot20 names the grantee by an opaque id in this list, never the email.
62
+ dev.textContent = (g.principal && g.principal.email) || "Not shown (one grant per person)";
63
+ if (!(g.principal && g.principal.email)) dev.className = "muted";
62
64
  const cap = document.createElement("td");
63
65
  cap.textContent = LABELS[g.capability] || g.capability || "";
64
66
  const exp = document.createElement("td");
@@ -31,26 +31,31 @@ export const UNPAID_DELEGATION_NOTE =
31
31
  "unpaid — no delegation needed. Ship-on-behalf delegation becomes available once the live " +
32
32
  "site is activated.";
33
33
 
34
- /** The delegate section: an unpaid store's note, or the paid store's {@link DelegateGate}. */
35
- export type DelegateSection = { mode: "unpaid"; role: null; explanation: string } | DelegateGate;
34
+ /** A capability the owner's grant form offers. */
35
+ export type OwnerGrantCapability = "ship-on-behalf" | "customer-contact-read";
36
36
 
37
37
  /**
38
- * Resolve the whole delegate section. Before a store is paid, an invited developer can
39
- * already add changes AND publish them (both `merge-to-preview` and `publish-live` admit the
40
- * "unpaid-store-developer" basis, lib/publication/pipeline/admission.ts), so delegation has nothing to
41
- * grant: every viewer, the owner included, gets {@link UNPAID_DELEGATION_NOTE}. A paid store
42
- * resolves {@link resolveDelegateGate} unchanged.
38
+ * What the owner's "Grant access" form offers this viewer. Customer contact access is a support
39
+ * grant unrelated to payment, so a real owner always gets it. Ship-on-behalf is offered only once
40
+ * the store can go live: before that an invited developer can already add changes AND publish
41
+ * them (both `merge-to-preview` and `publish-live` admit the "unpaid-store-developer" basis,
42
+ * lib/publication/pipeline/admission.ts), so the owner sees {@link UNPAID_DELEGATION_NOTE} for it
43
+ * instead. A non-owner gets no form and the gate's explanation. PURE.
43
44
  */
44
- export function resolveDelegateSection(
45
+ export function resolveOwnerGrantForm(
45
46
  storefrontMode: string | null | undefined,
46
47
  tenants: readonly TenantMembership[] | undefined,
47
48
  resource: string,
48
49
  staff?: StaffAdmission,
49
- ): DelegateSection {
50
- if (!storefrontModeAllowsGoLive(storefrontMode)) {
51
- return { mode: "unpaid", role: null, explanation: UNPAID_DELEGATION_NOTE };
52
- }
53
- return resolveDelegateGate(tenants, resource, staff);
50
+ ): { offers: OwnerGrantCapability[]; note: string | null; gate: DelegateGate } {
51
+ const gate = resolveDelegateGate(tenants, resource, staff);
52
+ if (gate.mode !== "owner") return { offers: [], note: gate.explanation, gate };
53
+ const paid = storefrontModeAllowsGoLive(storefrontMode);
54
+ return {
55
+ offers: paid ? ["ship-on-behalf", "customer-contact-read"] : ["customer-contact-read"],
56
+ note: paid ? null : UNPAID_DELEGATION_NOTE,
57
+ gate,
58
+ };
54
59
  }
55
60
 
56
61
  /**
@@ -82,6 +82,25 @@
82
82
  */
83
83
  import { readEnv } from "@/lib/publication/shared/env";
84
84
  import { BROKER_ASSERTION_HEADER, BROKER_PRIVATE_KEY_ENV } from "@/lib/access/auth/brokerAssertion";
85
+ import { resolveContactReadGrant } from "@/lib/access/auth/contactReaderAuthority";
86
+ import { resolveViewerShipCapability } from "@/lib/access/shared/viewerGrant";
87
+
88
+ /** The live principal grant a person holds on a store, as far as the grant reads can tell. */
89
+ export type HeldPrincipalGrant = "customer-contact-read" | "ship-on-behalf" | null;
90
+
91
+ /**
92
+ * The live principal grant `email` already holds on `tenant`, read through the per-person
93
+ * subject-capability seam (keyed by email). tot20 keeps ONE principal grant per person per store
94
+ * (`addPrincipalGrant` writes `principalGrants[principalKey]`, capability included), so a new grant
95
+ * to someone who holds one REPLACES it; callers refuse instead. tot20's grant LIST cannot answer
96
+ * this: its rows name the principal by an opaque id, never the email. Both reads fail closed to
97
+ * "none" on a seam fault, so a fault lets the grant through rather than blocking it.
98
+ */
99
+ export async function heldPrincipalGrant(email: string, tenant: string): Promise<HeldPrincipalGrant> {
100
+ if (await resolveContactReadGrant(email, tenant)) return "customer-contact-read";
101
+ if ((await resolveViewerShipCapability(email, tenant)) === "ship-on-behalf") return "ship-on-behalf";
102
+ return null;
103
+ }
85
104
 
86
105
  /** A human grantee — the only principal type this HTTP surface accepts. */
87
106
  export interface UserPrincipal {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "4.11.10",
3
+ "version": "4.11.12",
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",