@tokenoftrust/storefront-runner 6.1.1 → 6.1.2
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,22 @@
|
|
|
1
|
+
-- Erasure group bits: identity=1, email=2, phone=4, ship=8, bill=16.
|
|
2
|
+
ALTER TABLE `order_contact_markers`
|
|
3
|
+
ADD COLUMN `erased_groups` integer NOT NULL DEFAULT 0 CHECK (`erased_groups` BETWEEN 0 AND 31);
|
|
4
|
+
--> statement-breakpoint
|
|
5
|
+
UPDATE `order_contact_markers` AS m SET `erased_groups` = COALESCE((
|
|
6
|
+
SELECT MAX(CASE WHEN instr(',' || a.groups || ',', ',identity,') > 0 THEN 1 ELSE 0 END)
|
|
7
|
+
+ MAX(CASE WHEN instr(',' || a.groups || ',', ',email,') > 0 THEN 2 ELSE 0 END)
|
|
8
|
+
+ MAX(CASE WHEN instr(',' || a.groups || ',', ',phone,') > 0 THEN 4 ELSE 0 END)
|
|
9
|
+
+ MAX(CASE WHEN instr(',' || a.groups || ',', ',ship,') > 0 THEN 8 ELSE 0 END)
|
|
10
|
+
+ MAX(CASE WHEN instr(',' || a.groups || ',', ',bill,') > 0 THEN 16 ELSE 0 END)
|
|
11
|
+
FROM `contact_access_audit` AS a
|
|
12
|
+
WHERE a.tenant_id = m.tenant_id AND a.stage = m.stage AND a.record_id = m.order_id
|
|
13
|
+
AND a.record_kind = 'order-contact' AND a.action = 'erase' AND a.outcome = 'admitted'
|
|
14
|
+
), 0);
|
|
15
|
+
--> statement-breakpoint
|
|
16
|
+
CREATE TRIGGER `order_contact_markers_erasure_monotonic`
|
|
17
|
+
BEFORE UPDATE ON `order_contact_markers`
|
|
18
|
+
WHEN NEW.tenant_id != OLD.tenant_id OR NEW.stage != OLD.stage OR NEW.order_id != OLD.order_id
|
|
19
|
+
OR (NEW.erased_groups | OLD.erased_groups) != NEW.erased_groups
|
|
20
|
+
BEGIN
|
|
21
|
+
SELECT RAISE(ABORT, 'erasure-monotonic');
|
|
22
|
+
END;
|
|
@@ -801,7 +801,11 @@ export class FulfillmentService {
|
|
|
801
801
|
if (state.shipment.labelPurchaseState === mapped.shipment && state.labelOutcome?.adultSignature === input.adultSignature) {
|
|
802
802
|
return { type: "done", view: state.view };
|
|
803
803
|
}
|
|
804
|
-
if(input.outcome==="purchased"
|
|
804
|
+
if (input.outcome === "purchased") {
|
|
805
|
+
if (state.sourceHolds.length) return { type: "refuse", code: state.sourceHolds[0]!, holds: state.sourceHolds };
|
|
806
|
+
const contactHolds = releaseHolds(this.readiness(state)).filter((hold) => hold.startsWith("ship-to-"));
|
|
807
|
+
if (contactHolds.length) return { type: "refuse", code: contactHolds[0]!, holds: contactHolds };
|
|
808
|
+
}
|
|
805
809
|
const version = state.order.eventVersion + 1;
|
|
806
810
|
return {
|
|
807
811
|
type: "write",
|
|
@@ -71,6 +71,8 @@ const LAYOUT: Readonly<Record<Group, GroupLayout>> = {
|
|
|
71
71
|
bill: { sealed: ["bill_name_ct", "bill_company_ct", "bill_line1_ct", "bill_line2_ct"], bidx: null, plain: ["bill_city", "bill_postal_code"] },
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
+
const ERASURE_BITS: Readonly<Record<Group, number>> = { identity: 1, email: 2, phone: 4, ship: 8, bill: 16 };
|
|
75
|
+
|
|
74
76
|
function groupColumns(group: Group): string[] {
|
|
75
77
|
const layout = LAYOUT[group];
|
|
76
78
|
return [...layout.sealed, ...(layout.bidx ? [layout.bidx] : []), ...layout.plain];
|
|
@@ -624,7 +626,9 @@ function completeGroupSql(group: typeof AVAILABILITY_GROUPS[number]): string {
|
|
|
624
626
|
|
|
625
627
|
const AVAILABILITY_SELECT = `SELECT o.order_id, o.placed_at AS order_placed_at,
|
|
626
628
|
o.fulfillment_state AS order_fulfillment_state, (${correlatedTerminalSql("o")}) AS order_terminal_at,
|
|
627
|
-
v.order_id IS NOT NULL AS vault_present,
|
|
629
|
+
v.order_id IS NOT NULL AS vault_present,
|
|
630
|
+
COALESCE((SELECT m.erased_groups FROM order_contact_markers m
|
|
631
|
+
WHERE m.tenant_id = o.tenant_id AND m.stage = o.stage AND m.order_id = o.order_id), 0) AS erased_groups,
|
|
628
632
|
EXISTS (SELECT 1 FROM order_contact_markers m
|
|
629
633
|
WHERE m.tenant_id = o.tenant_id AND m.stage = o.stage AND m.order_id = o.order_id) AS marked,
|
|
630
634
|
EXISTS (SELECT 1 FROM contact_vault_data_keys k
|
|
@@ -648,13 +652,17 @@ function uniformAvailability(state: OrderContactState): OrderContactAvailability
|
|
|
648
652
|
function contactAvailability(row: VaultRow, input: {
|
|
649
653
|
tenantId: string; stage: VaultStage; now: Date; policy: RetentionPolicy;
|
|
650
654
|
}): OrderContactAvailability {
|
|
651
|
-
|
|
655
|
+
const erased = (group: Group): boolean => (Number(row.erased_groups) & ERASURE_BITS[group]) !== 0;
|
|
656
|
+
if (Number(row.vault_present) !== 1) {
|
|
657
|
+
const missing = (group: Group): OrderContactState => erased(group) ? "erased" : Number(row.marked) === 1 ? "expired" : "absent";
|
|
658
|
+
return { identity: missing("identity"), email: missing("email"), shipTo: missing("ship") };
|
|
659
|
+
}
|
|
652
660
|
const times = orderTimes(row.order_placed_at, row.order_fulfillment_state, row.order_terminal_at);
|
|
653
661
|
if (times === "order-time-unreadable" || !Number.isFinite(input.now.getTime())) return uniformAvailability("unreadable");
|
|
654
662
|
const states = groupStates(row, input.tenantId, input.stage, times, input.now, input.policy);
|
|
655
663
|
const state = (group: typeof AVAILABILITY_GROUPS[number]): OrderContactState => {
|
|
656
664
|
const current = states[group];
|
|
657
|
-
if (!current.sealed &&
|
|
665
|
+
if (!current.sealed && erased(group)) return "erased";
|
|
658
666
|
if (!current.expiresAt) return "unreadable";
|
|
659
667
|
if (!isGroupReadable(input.now, current.expiresAt, text(row, `${group}_ceiling`)) ||
|
|
660
668
|
(input.stage === "preview" && input.now.getTime() >= Date.parse(previewDeleteAt(times.placedAt, input.policy)))) return "expired";
|
|
@@ -949,6 +957,10 @@ export async function eraseOrderContact(
|
|
|
949
957
|
});
|
|
950
958
|
const statements: QueryStatement[] = [
|
|
951
959
|
batchAuditStatement(audit),
|
|
960
|
+
{
|
|
961
|
+
sql: `UPDATE order_contact_markers SET erased_groups = erased_groups | ? WHERE ${scope}`,
|
|
962
|
+
params: [targets.reduce((mask, group) => mask | ERASURE_BITS[group], 0), ...scopeParams],
|
|
963
|
+
},
|
|
952
964
|
{ sql: `UPDATE order_contact_vault SET ${clearSql(targets)}, erased_at = ? WHERE ${scope}`, params: [now.toISOString(), ...scopeParams] },
|
|
953
965
|
...normalizeStatements(scope, scopeParams),
|
|
954
966
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.2",
|
|
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",
|