diff --git a/src/app/orders/[entityKey]/page.tsx b/src/app/orders/[entityKey]/page.tsx
index 1aa4f13..dd9849e 100644
--- a/src/app/orders/[entityKey]/page.tsx
+++ b/src/app/orders/[entityKey]/page.tsx
@@ -63,10 +63,22 @@ export default function OrderDetailPage({ params }: { params: Promise<{ entityKe
if (isLoading) return
Loading order…
;
if (error || !o) {
+ // A missing order and a broken query are different problems and must not
+ // share a sentence. Only a 404 means "no such order"; anything else is this
+ // page failing, and saying so is what sends the next person to the server
+ // log instead of to the spine.
+ const status = (error as (Error & { status?: number }) | null)?.status;
return (
← All orders
-
That order could not be found.
+ {status === 404 ? (
+
That order could not be found.
+ ) : (
+
+ This order could not be loaded{status ? ` (server error ${status})` : ""}. The order exists —
+ something in this page failed. Check the finance-app log.
+
+ )}
);
}
diff --git a/src/lib/hooks.ts b/src/lib/hooks.ts
index 79100a5..1a1e765 100644
--- a/src/lib/hooks.ts
+++ b/src/lib/hooks.ts
@@ -1243,7 +1243,16 @@ export function useOrderDetail(entityKey: string | null) {
staleTime: 60_000,
queryFn: async () => {
const res = await fetch(`/api/orders/${encodeURIComponent(entityKey!)}`);
- if (!res.ok) throw new Error("Failed to load order");
+ if (!res.ok) {
+ // Carry the status. Collapsing every failure into one Error is how a
+ // 500 on this route rendered as "that order could not be found" on
+ // every single order for weeks — a server fault wearing the costume of
+ // a data condition, which reads as "the spine is empty" and gets
+ // investigated nowhere near the actual bug.
+ const err = new Error(res.status === 404 ? "Order not found" : "Failed to load order");
+ (err as Error & { status?: number }).status = res.status;
+ throw err;
+ }
return res.json();
},
});
diff --git a/src/lib/order-feed.ts b/src/lib/order-feed.ts
index ed379e2..6b4cc1f 100644
--- a/src/lib/order-feed.ts
+++ b/src/lib/order-feed.ts
@@ -73,6 +73,12 @@ export interface OrderRow {
/** Merchant orders on a regular cadence: the subscription signal, derived
* not extracted. NULL unless the gaps are tight relative to their mean. */
cadence_days: number | null;
+ /** Worst sender-authentication verdict across the order's source documents.
+ * 93% are 'pass' — the badge exists for the 7% that are not. Do NOT badge
+ * source_trust instead: it is 'untrusted_external' on 100% of rows, because
+ * every order here came from email. */
+ auth_verdict: string | null;
+ injection_flagged: boolean | null;
txn_count: number;
first_txn_id: number | null;
}
@@ -220,6 +226,7 @@ export async function getOrderFeed(filters: OrderFilters) {
f.ordered_at, f.eta_date, f.delivered_at,
f.currency, f.order_total, f.line_item_count,
f.content_class, f.display_name, f.cadence_days,
+ f.auth_verdict, f.injection_flagged,
m.canonical_name AS merchant_name,
link.txn_count, link.first_txn_id,
(SELECT sp.refunded_amount FROM order_spend sp
@@ -399,7 +406,16 @@ export async function getOrderDetail(entityKey: string): Promise