@burdenoff/microfe-movethewheels 2026.1010.2 → 2026.1011.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.
Files changed (38) hide show
  1. package/dist/MoveTheWheelsRoot.js +12 -1
  2. package/dist/MoveTheWheelsRoutes.js +57 -49
  3. package/dist/generated/shipment-status-authority-operations.d.ts +4473 -0
  4. package/dist/generated/tracking-quality-operations.d.ts +4302 -0
  5. package/dist/hooks/useEpod.js +85 -77
  6. package/dist/hooks/useShipmentStatusAuthority.js +127 -0
  7. package/dist/hooks/useTracking.js +15 -14
  8. package/dist/hooks/useTrackingEventQuality.js +60 -0
  9. package/dist/movethewheels/hooks/useEpod.d.ts +23 -0
  10. package/dist/movethewheels/hooks/useShipmentStatusAuthority.d.ts +23 -0
  11. package/dist/movethewheels/hooks/useTrackingEventQuality.d.ts +18 -0
  12. package/dist/movethewheels/pages/ShipmentStatusAuthorityPage.d.ts +1 -0
  13. package/dist/movethewheels/pages/TrackingEventQualityPage.d.ts +1 -0
  14. package/dist/movethewheels/utils/shipmentStatusDraft.d.ts +45 -0
  15. package/dist/node_modules/lucide-react/dist/esm/icons/radio-tower.js +36 -0
  16. package/dist/pages/DeliveryCapturePage.js +181 -116
  17. package/dist/pages/ShipmentStatusAuthorityPage.js +563 -0
  18. package/dist/pages/TrackingEventQualityPage.js +224 -0
  19. package/dist/src/generated/shipment-status-authority-operations.js +86 -0
  20. package/dist/src/generated/tracking-quality-operations.js +44 -0
  21. package/dist/utils/shipmentStatusDraft.js +41 -0
  22. package/package.json +16 -7
  23. package/patches/README.md +25 -0
  24. package/patches/braces@3.0.3.patch +87 -0
  25. package/src/generated/shipment-status-authority-operations.ts +5503 -0
  26. package/src/generated/tracking-quality-operations.ts +5323 -0
  27. package/src/movethewheels/MoveTheWheelsRoot.tsx +14 -0
  28. package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
  29. package/src/movethewheels/hooks/useEpod.ts +47 -12
  30. package/src/movethewheels/hooks/useShipmentStatusAuthority.ts +179 -0
  31. package/src/movethewheels/hooks/useTracking.ts +27 -12
  32. package/src/movethewheels/hooks/useTrackingEventQuality.ts +110 -0
  33. package/src/movethewheels/pages/DeliveryCapturePage.tsx +113 -6
  34. package/src/movethewheels/pages/ShipmentStatusAuthorityPage.tsx +639 -0
  35. package/src/movethewheels/pages/TrackingEventQualityPage.tsx +250 -0
  36. package/src/movethewheels/utils/shipmentStatusDraft.ts +118 -0
  37. package/src/operations/shipment-status-authority/status.graphql +80 -0
  38. package/src/operations/tracking-quality/quality.graphql +15 -0
@@ -92,6 +92,20 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
92
92
  path: joinPath(basePath, 'tracking'),
93
93
  icon: <Map size={16} />,
94
94
  order: 3,
95
+ children: [
96
+ {
97
+ id: 'mtw-tracking-quality',
98
+ label: tr('movethewheels.root.nav.trackingQuality', 'Tracking event quality'),
99
+ path: joinPath(basePath, 'tracking/event-quality'),
100
+ icon: <Map size={16} />,
101
+ },
102
+ {
103
+ id: 'mtw-status-authority',
104
+ label: tr('movethewheels.root.nav.statusAuthority', 'Shipment status authority'),
105
+ path: joinPath(basePath, 'shipments/status-authority'),
106
+ icon: <ClipboardList size={16} />,
107
+ },
108
+ ],
95
109
  },
96
110
  {
97
111
  id: 'mtw-consolidation',
@@ -55,6 +55,8 @@ const OrdersListPage = lazy(() => import('./pages/OrdersListPage'));
55
55
  const OrderDetailsPage = lazy(() => import('./pages/OrderDetailsPage'));
56
56
  const CreateOrderPage = lazy(() => import('./pages/CreateOrderPage'));
57
57
  const TrackingPage = lazy(() => import('./pages/TrackingPage'));
58
+ const TrackingEventQualityPage = lazy(() => import('./pages/TrackingEventQualityPage'));
59
+ const ShipmentStatusAuthorityPage = lazy(() => import('./pages/ShipmentStatusAuthorityPage'));
58
60
  const LiveTrackingPage = lazy(() => import('./pages/LiveTrackingPage'));
59
61
  const FleetPage = lazy(() => import('./pages/FleetPage'));
60
62
  const VehiclesPage = lazy(() => import('./pages/VehiclesPage'));
@@ -108,6 +110,8 @@ export function MoveTheWheelsRoutes() {
108
110
  {/* Tracking */}
109
111
  <Route path="tracking" element={<TrackingPage />} />
110
112
  <Route path="tracking/live" element={<LiveTrackingPage />} />
113
+ <Route path="tracking/event-quality" element={<TrackingEventQualityPage />} />
114
+ <Route path="shipments/status-authority" element={<ShipmentStatusAuthorityPage />} />
111
115
 
112
116
  {/* Driver ePOD capture (BOFF-4399) */}
113
117
  <Route path="deliveries/mine" element={<DeliveryCapturePage />} />
@@ -12,11 +12,13 @@
12
12
  * multi-language delivery instructions.
13
13
  */
14
14
  import { useCallback, useEffect, useMemo, useState } from 'react';
15
+ import { parse } from 'graphql';
16
+ import { useShipmentStatusCommand } from './useShipmentStatusAuthority';
15
17
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
16
18
  import type { Order, ProofOfDelivery, SignatureType } from '../types';
17
19
  import { executeGraphQL } from '../utils/graphqlClient';
18
20
 
19
- interface ApiProofOfDelivery {
21
+ export interface ApiProofOfDelivery {
20
22
  id: string;
21
23
  deliveryJobId: string;
22
24
  shipmentId?: string | null;
@@ -81,6 +83,7 @@ const CAPTURE_EPOD_MUTATION = `
81
83
  }
82
84
  }
83
85
  `;
86
+ const CAPTURE_EPOD_DOCUMENT = parse(CAPTURE_EPOD_MUTATION);
84
87
 
85
88
  const INITIATE_UPLOAD_MUTATION = `
86
89
  mutation EpodInitiateUpload($input: InitiateUploadInput!) {
@@ -257,6 +260,9 @@ export function useMyPendingEpodDeliveryJobs(options: { limit?: number; offset?:
257
260
  }
258
261
 
259
262
  export interface CaptureEpodArgs {
263
+ expectedShipmentId: string | null;
264
+ expectedShipmentStatusRevision?: number;
265
+ completionAttestation: boolean;
260
266
  deliveryJobId: string;
261
267
  recipientName: string;
262
268
  signatureType: SignatureType;
@@ -268,22 +274,48 @@ export interface CaptureEpodArgs {
268
274
  notes?: string;
269
275
  }
270
276
 
277
+ export function epodAcknowledged(
278
+ actual: ApiProofOfDelivery | null | undefined,
279
+ expected: CaptureEpodArgs
280
+ ): boolean {
281
+ return (
282
+ !!actual &&
283
+ typeof actual.id === 'string' &&
284
+ actual.id.trim().length > 0 &&
285
+ actual.deliveryJobId === expected.deliveryJobId &&
286
+ actual.shipmentId === expected.expectedShipmentId &&
287
+ actual.recipientName === expected.recipientName &&
288
+ actual.signatureType === toApiSignatureType(expected.signatureType) &&
289
+ actual.signatureData === expected.signatureData &&
290
+ typeof actual.capturedByDriverId === 'string' &&
291
+ actual.capturedByDriverId.trim().length > 0 &&
292
+ typeof actual.deliveredAt === 'string' &&
293
+ Number.isFinite(Date.parse(actual.deliveredAt)) &&
294
+ Array.isArray(actual.photoFileIds) &&
295
+ actual.photoFileIds.length === (expected.photoFileIds ?? []).length &&
296
+ actual.photoFileIds.every((id, index) => id === (expected.photoFileIds ?? [])[index]) &&
297
+ actual.latitude === (expected.latitude ?? null) &&
298
+ actual.longitude === (expected.longitude ?? null) &&
299
+ actual.locationAccuracy === (expected.locationAccuracy ?? null) &&
300
+ actual.notes === (expected.notes ?? null)
301
+ );
302
+ }
303
+
271
304
  /** Driver-facing capture mutation. Identity/ownership is enforced server-side — this never sends a driver id. */
272
305
  export function useCaptureEpod() {
273
- const graphQLOptions = useGraphQLOptions();
274
- const [isSubmitting, setIsSubmitting] = useState(false);
306
+ const command = useShipmentStatusCommand();
275
307
  const [error, setError] = useState<Error | null>(null);
276
308
 
277
309
  const captureEpod = useCallback(
278
310
  async (args: CaptureEpodArgs): Promise<ProofOfDelivery> => {
279
- setIsSubmitting(true);
280
311
  setError(null);
281
312
  try {
282
- const data = await executeGraphQL<{ captureEpod: ApiProofOfDelivery }>(
283
- graphQLOptions,
284
- CAPTURE_EPOD_MUTATION,
313
+ const data = await command.run<{ captureEpod: ApiProofOfDelivery }>(
314
+ CAPTURE_EPOD_DOCUMENT,
285
315
  {
286
316
  input: {
317
+ expectedShipmentStatusRevision: args.expectedShipmentStatusRevision,
318
+ completionAttestation: args.completionAttestation,
287
319
  deliveryJobId: args.deliveryJobId,
288
320
  recipientName: args.recipientName,
289
321
  signatureType: toApiSignatureType(args.signatureType),
@@ -294,8 +326,13 @@ export function useCaptureEpod() {
294
326
  locationAccuracy: args.locationAccuracy,
295
327
  notes: args.notes,
296
328
  },
297
- }
329
+ },
330
+ (data) => epodAcknowledged(data.captureEpod, args)
298
331
  );
332
+ if (!data)
333
+ throw new Error(
334
+ 'Capture was not acknowledged. Review the recorded status and the capture message before taking further action.'
335
+ );
299
336
  return mapEpod(data.captureEpod);
300
337
  } catch (captureError) {
301
338
  const err =
@@ -304,14 +341,12 @@ export function useCaptureEpod() {
304
341
  : new Error('Failed to capture proof of delivery');
305
342
  setError(err);
306
343
  throw err;
307
- } finally {
308
- setIsSubmitting(false);
309
344
  }
310
345
  },
311
- [graphQLOptions]
346
+ [command]
312
347
  );
313
348
 
314
- return { captureEpod, isSubmitting, error };
349
+ return { captureEpod, isSubmitting: command.busy, error, captureFeedback: command.feedback };
315
350
  }
316
351
 
317
352
  export interface UploadedEpodPhoto {
@@ -0,0 +1,179 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { print, type DocumentNode } from 'graphql';
3
+ import { graphqlFetch, clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';
4
+ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
5
+
6
+ const KNOWN_REJECTIONS = new Set([
7
+ 'STATUS_REVISION_CONFLICT',
8
+ 'BAD_USER_INPUT',
9
+ 'FORBIDDEN',
10
+ 'UNAUTHENTICATED',
11
+ 'NOT_FOUND',
12
+ ]);
13
+
14
+ export class ShipmentStatusRequestError extends Error {
15
+ constructor(
16
+ message: string,
17
+ readonly code: string | null
18
+ ) {
19
+ super(message);
20
+ this.name = 'ShipmentStatusRequestError';
21
+ }
22
+ }
23
+
24
+ /** Normal shared gateway transport; mutation acknowledgements are never automatically retried. */
25
+ export function useShipmentStatusApi() {
26
+ const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId } = useMoveTheWheels();
27
+ const context = useMemo(
28
+ () => ({ apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId }),
29
+ [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId]
30
+ );
31
+ return useCallback(
32
+ async <T>(
33
+ document: DocumentNode,
34
+ variables: Record<string, unknown>,
35
+ mutation = false,
36
+ signal?: AbortSignal
37
+ ): Promise<T> => {
38
+ const response = await graphqlFetch<T>({
39
+ gateway: 'workspace',
40
+ query: print(document),
41
+ variables,
42
+ baseUrl: context.apiGatewayUrl,
43
+ authToken: context.authToken,
44
+ workspaceToken: context.workspaceToken || true,
45
+ workspaceId: context.workspaceId || true,
46
+ tenantId: context.tenantId || true,
47
+ organizationId: true,
48
+ signal,
49
+ skipAuthRetry: mutation,
50
+ });
51
+ if (response.errors?.length) {
52
+ // A single verified rejection is distinct from a transport failure or partial response.
53
+ const first = response.errors[0];
54
+ const code =
55
+ response.data === null &&
56
+ response.errors.length === 1 &&
57
+ typeof first?.extensions?.code === 'string'
58
+ ? first.extensions.code
59
+ : null;
60
+ throw new ShipmentStatusRequestError(
61
+ first?.message ?? 'Unable to load the recorded status.',
62
+ code
63
+ );
64
+ }
65
+ if (response.data == null)
66
+ throw new ShipmentStatusRequestError('No acknowledgement was received.', null);
67
+ return response.data;
68
+ },
69
+ [context]
70
+ );
71
+ }
72
+
73
+ export function useShipmentStatusQuery<T>(
74
+ document: DocumentNode,
75
+ variables: Record<string, unknown> | null,
76
+ revision = 0
77
+ ) {
78
+ const request = useShipmentStatusApi();
79
+ const key = JSON.stringify(variables);
80
+ const [state, setState] = useState<{
81
+ request: typeof request;
82
+ key: string;
83
+ revision: number;
84
+ data: T | null;
85
+ error: string | null;
86
+ loading: boolean;
87
+ } | null>(null);
88
+ useEffect(() => {
89
+ const controller = new AbortController();
90
+ const scope = { request, key, revision };
91
+ setState({ ...scope, data: null, error: null, loading: key !== 'null' });
92
+ if (key === 'null') return;
93
+ // An explicit review refresh must read the gateway rather than its short-lived response cache.
94
+ if (revision > 0) clearGraphqlFetchResponseCache();
95
+ const input: Record<string, unknown> = JSON.parse(key);
96
+ void request<T>(document, input, false, controller.signal)
97
+ .then((data) => {
98
+ if (!controller.signal.aborted) setState({ ...scope, data, error: null, loading: false });
99
+ })
100
+ .catch((failure: unknown) => {
101
+ if (!controller.signal.aborted)
102
+ setState({
103
+ ...scope,
104
+ data: null,
105
+ error: failure instanceof Error ? failure.message : 'Unable to load recorded status.',
106
+ loading: false,
107
+ });
108
+ });
109
+ return () => controller.abort();
110
+ }, [document, request, key, revision]);
111
+ const current = state?.request === request && state.key === key && state.revision === revision;
112
+ return {
113
+ data: current ? state.data : null,
114
+ error: current ? state.error : null,
115
+ loading: current ? state.loading : key !== 'null',
116
+ };
117
+ }
118
+
119
+ export function useShipmentStatusCommand() {
120
+ const request = useShipmentStatusApi();
121
+ const lock = useRef(false);
122
+ const uncertain = useRef(false);
123
+ const [busy, setBusy] = useState(false);
124
+ const [feedback, setFeedback] = useState<{
125
+ kind: 'APPLIED' | 'REJECTED' | 'UNKNOWN';
126
+ message: string;
127
+ } | null>(null);
128
+ const [revision, setRevision] = useState(0);
129
+ const run = async <T>(
130
+ document: DocumentNode,
131
+ variables: Record<string, unknown>,
132
+ acknowledged: (result: T) => boolean
133
+ ): Promise<T | null> => {
134
+ if (lock.current || uncertain.current) return null;
135
+ lock.current = true;
136
+ setBusy(true);
137
+ setFeedback(null);
138
+ try {
139
+ const result = await request<T>(document, variables, true);
140
+ if (!acknowledged(result))
141
+ throw new ShipmentStatusRequestError(
142
+ 'The returned acknowledgement does not identify this decision.',
143
+ null
144
+ );
145
+ setFeedback({
146
+ kind: 'APPLIED',
147
+ message: 'Decision recorded. Review the refreshed status and history.',
148
+ });
149
+ setRevision((value) => value + 1);
150
+ return result;
151
+ } catch (failure) {
152
+ const known =
153
+ failure instanceof ShipmentStatusRequestError &&
154
+ failure.code !== null &&
155
+ KNOWN_REJECTIONS.has(failure.code);
156
+ uncertain.current = !known;
157
+ setFeedback({
158
+ kind: known ? 'REJECTED' : 'UNKNOWN',
159
+ message: known
160
+ ? `${failure.message} Your input is preserved; refresh and review the recorded status before another action.`
161
+ : 'Acknowledgement is uncertain. Do not resend this action. Refresh the recorded status and review its decisions before beginning a separate new action.',
162
+ });
163
+ return null;
164
+ } finally {
165
+ lock.current = false;
166
+ setBusy(false);
167
+ }
168
+ };
169
+ return {
170
+ run,
171
+ busy,
172
+ feedback,
173
+ revision,
174
+ beginSeparateAction: () => {
175
+ uncertain.current = false;
176
+ setFeedback(null);
177
+ },
178
+ };
179
+ }
@@ -108,6 +108,10 @@ const LOOKUP_DELIVERY_JOBS_QUERY = `
108
108
 
109
109
  const DELIVERY_JOB_TRACKING_QUERY = `
110
110
  query GetDeliveryJobTracking($deliveryJobId: ID!, $pagination: PaginationInput) {
111
+ shipments(filter: { deliveryJobId: $deliveryJobId }, pagination: { limit: 2, offset: 0 }) {
112
+ id
113
+ status
114
+ }
111
115
  deliveryJobTracking(deliveryJobId: $deliveryJobId, pagination: $pagination) {
112
116
  id
113
117
  shipmentId
@@ -232,7 +236,8 @@ function mapTrackingEventToLocation(event: ApiTrackingEvent): Location | null {
232
236
 
233
237
  function mapTrackingEventsToTracking(
234
238
  events: ApiTrackingEvent[],
235
- entityId: string
239
+ entityId: string,
240
+ recordedShipmentStatus?: string
236
241
  ): Tracking | null {
237
242
  const latest = events[0];
238
243
  if (!latest) return null;
@@ -248,7 +253,8 @@ function mapTrackingEventsToTracking(
248
253
  entityType: 'order',
249
254
  currentLocation,
250
255
  locationHistory,
251
- status: latest.eventType.toLowerCase(),
256
+ // Observation history is not shipment-status authority. Ambiguous/unlinked shipments stay unknown.
257
+ status: recordedShipmentStatus?.toLowerCase() ?? 'unknown',
252
258
  estimatedArrival: undefined,
253
259
  nextCheckpoint: latest.details ?? undefined,
254
260
  alerts: [],
@@ -472,18 +478,23 @@ export function useOrderTracking(orderLookup: string | undefined) {
472
478
  setTrackingLoading(true);
473
479
  setTrackingError(null);
474
480
  try {
475
- const data = await executeGraphQL<{ deliveryJobTracking: ApiTrackingEvent[] }>(
476
- graphQLOptions,
477
- DELIVERY_JOB_TRACKING_QUERY,
478
- {
479
- deliveryJobId: resolvedOrderId,
480
- pagination: { limit: 50, offset: 0 },
481
- }
482
- );
481
+ const data = await executeGraphQL<{
482
+ deliveryJobTracking: ApiTrackingEvent[];
483
+ shipments: Array<{ id: string; status: string }>;
484
+ }>(graphQLOptions, DELIVERY_JOB_TRACKING_QUERY, {
485
+ deliveryJobId: resolvedOrderId,
486
+ pagination: { limit: 50, offset: 0 },
487
+ });
483
488
  const events = [...data.deliveryJobTracking].sort(
484
489
  (first, second) => Date.parse(second.timestamp) - Date.parse(first.timestamp)
485
490
  );
486
- setTracking(mapTrackingEventsToTracking(events, resolvedOrderId));
491
+ setTracking(
492
+ mapTrackingEventsToTracking(
493
+ events,
494
+ resolvedOrderId,
495
+ data.shipments.length === 1 ? data.shipments[0]?.status : undefined
496
+ )
497
+ );
487
498
  } catch (fetchError) {
488
499
  setTrackingError(
489
500
  fetchError instanceof Error ? fetchError : new Error('Failed to load order tracking')
@@ -580,7 +591,11 @@ export function useLiveTracking() {
580
591
  order: mapOrder(deliveryJob),
581
592
  driver: deliveryJob.driver ? mapDriver(deliveryJob.driver) : null,
582
593
  vehicle: deliveryJob.vehicle ? mapVehicle(deliveryJob.vehicle) : null,
583
- tracking: mapTrackingEventsToTracking(shipment.trackingEvents ?? [], deliveryJob.id),
594
+ tracking: mapTrackingEventsToTracking(
595
+ shipment.trackingEvents ?? [],
596
+ deliveryJob.id,
597
+ shipment.status
598
+ ),
584
599
  estimatedArrival: shipment.estimatedDelivery
585
600
  ? new Date(shipment.estimatedDelivery)
586
601
  : undefined,
@@ -0,0 +1,110 @@
1
+ import {
2
+ TrackingQualityIntegrationsDocument,
3
+ TrackingQualityDeliveriesDocument,
4
+ TrackingQualityShipmentDocument,
5
+ } from '../../generated/tracking-quality-operations';
6
+ export {
7
+ TrackingQualityIntegrationsDocument as QualityIntegrationsDocument,
8
+ TrackingQualityDeliveriesDocument as QualityDeliveriesDocument,
9
+ TrackingQualityShipmentDocument as QualityShipmentDocument,
10
+ };
11
+
12
+ export interface TrackingUpdateOutcome {
13
+ index: number;
14
+ externalEventId: string | null;
15
+ shipmentReference: string;
16
+ shipmentId: string | null;
17
+ status: 'ACCEPTED' | 'DUPLICATE' | 'CONFLICT' | 'REJECTED';
18
+ errorCode: string | null;
19
+ projection: {
20
+ status: 'APPLIED' | 'HELD' | 'LEGACY_NO_DECISION';
21
+ decisionId: string | null;
22
+ heldReason: string | null;
23
+ } | null;
24
+ }
25
+
26
+ const statuses = new Set(['ACCEPTED', 'DUPLICATE', 'CONFLICT', 'REJECTED']);
27
+ const heldReasons = new Set([
28
+ 'LEGACY_AUTHORITY',
29
+ 'MANUAL_AUTHORITY',
30
+ 'NON_DESIGNATED_SOURCE',
31
+ 'UNKNOWN_SOURCE_TIME',
32
+ 'OLDER_SOURCE_TIME',
33
+ 'EQUAL_SOURCE_TIME',
34
+ 'SOURCE_TIME_TOO_OLD',
35
+ 'SOURCE_TIME_IN_FUTURE',
36
+ 'UNMAPPED_EVENT_TYPE',
37
+ 'UNRECEIPTED_EVIDENCE',
38
+ ]);
39
+ const codes = new Set([
40
+ 'KEY_CONFLICT',
41
+ 'BUDGET_EXHAUSTED',
42
+ 'INTEGRATION_UNAVAILABLE',
43
+ 'SHIPMENT_UNAVAILABLE',
44
+ 'UPDATE_REJECTED',
45
+ ]);
46
+ const bounded = (value: unknown, max: number): value is string =>
47
+ typeof value === 'string' && value.length <= max;
48
+ const nullable = (value: unknown, max: number): value is string | null =>
49
+ value === null || bounded(value, max);
50
+
51
+ /** JSON scalar decoding fails closed; legacy delivery history has no outcomes. */
52
+ export function trackingOutcomes(value: unknown): TrackingUpdateOutcome[] | null {
53
+ if (!Array.isArray(value) || value.length > 5) return null;
54
+ const result: TrackingUpdateOutcome[] = [];
55
+ for (const item of value) {
56
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) return null;
57
+ const row: Record<string, unknown> = item;
58
+ if (
59
+ !Number.isInteger(row.index) ||
60
+ typeof row.index !== 'number' ||
61
+ row.index !== result.length ||
62
+ row.index < 0 ||
63
+ row.index > 4 ||
64
+ !nullable(row.externalEventId, 255) ||
65
+ !bounded(row.shipmentReference, 255) ||
66
+ !nullable(row.shipmentId, 255) ||
67
+ typeof row.status !== 'string' ||
68
+ !statuses.has(row.status) ||
69
+ (row.status === 'CONFLICT' && row.shipmentId !== null) ||
70
+ !(row.errorCode === null || (typeof row.errorCode === 'string' && codes.has(row.errorCode)))
71
+ ) {
72
+ return null;
73
+ }
74
+ let projection: TrackingUpdateOutcome['projection'] = null;
75
+ const projectionStatus = row.projectionStatus;
76
+ const decisionId = row.projectionDecisionId ?? null;
77
+ const heldReason = row.projectionHeldReason ?? null;
78
+ if (projectionStatus !== undefined && projectionStatus !== null) {
79
+ if (row.status !== 'ACCEPTED' && row.status !== 'DUPLICATE') return null;
80
+ if (projectionStatus === 'LEGACY_NO_DECISION') {
81
+ if (row.status !== 'DUPLICATE' || decisionId !== null || heldReason !== null) return null;
82
+ } else if (projectionStatus === 'APPLIED' || projectionStatus === 'HELD') {
83
+ if (!bounded(decisionId, 255) || !decisionId) return null;
84
+ if (
85
+ projectionStatus === 'APPLIED'
86
+ ? heldReason !== null
87
+ : typeof heldReason !== 'string' || !heldReasons.has(heldReason)
88
+ )
89
+ return null;
90
+ } else return null;
91
+ // The guarded finite status and bounded identifiers justify the JSON narrowing.
92
+ projection = {
93
+ status: projectionStatus,
94
+ decisionId: decisionId as string | null,
95
+ heldReason: heldReason as string | null,
96
+ };
97
+ } else if (decisionId !== null || heldReason !== null) return null;
98
+ // The finite status/code guards justify narrowing the untyped JSON scalar.
99
+ result.push({
100
+ index: row.index,
101
+ externalEventId: row.externalEventId,
102
+ shipmentReference: row.shipmentReference,
103
+ shipmentId: row.shipmentId,
104
+ status: row.status as TrackingUpdateOutcome['status'],
105
+ errorCode: row.errorCode,
106
+ projection,
107
+ });
108
+ }
109
+ return result;
110
+ }