platform-types 1.10.714 → 1.10.715

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.
@@ -1,2 +1,50 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DELIVERY_ERROR_CODES = void 0;
4
+ /**
5
+ * Every refusal code the delivery handlers (`POST /deliveries`,
6
+ * `POST /deliveries/{id}/events`, `GET /deliveries`, `GET /deliveries/{id}`)
7
+ * put on the wire, beyond the platform-wide set (`VALIDATION_FAILED`,
8
+ * `ROLE_FORBIDDEN`, `tryCatch`'s generic `UNHANDLED_ERROR`). Pinned against
9
+ * the handler sources by `tests/unit/deliveryErrorCodesPublished.test.ts`.
10
+ *
11
+ * - `INVALID_DELIVERY_ID` · 400 — no delivery id in the path.
12
+ * - `DELIVERY_NOT_FOUND` · 404 — unknown id, or another tenant's.
13
+ * - `DELIVERY_NOT_ASSIGNED` · 403 — the delivery names a platform courier
14
+ * (`courier.userId`) and the caller is neither that user nor ADMIN-tier.
15
+ * - `DELIVERY_TERMINAL` · 409 — the delivery is already `'delivered'` or
16
+ * `'failed'`; a failed delivery is re-dispatched through `POST /deliveries`.
17
+ * - `INVALID_IDEMPOTENCY_KEY` · 400 — the key is not ≤200 chars of
18
+ * `[A-Za-z0-9._:-]`.
19
+ * - `IDEMPOTENCY_KEY_REUSE` · 422 — the key was used with a different body.
20
+ * - `IDEMPOTENCY_KEY_FAILED` · 409 — the attempt under the key failed after it
21
+ * may have written proof, or the attempt lookup fenced the key; ask the
22
+ * lookup, then use a new key.
23
+ * - `IDEMPOTENCY_IN_PROGRESS` · 409 — the same key is still running.
24
+ * - `IDEMPOTENCY_LOCK_CONTENTION` · 409 — the key's marker could not be
25
+ * claimed; retry under the SAME key.
26
+ * - `INVALID_CURSOR` · 400 — a `?mine=true` `startKey` that is malformed or
27
+ * names another store's partition.
28
+ * - `IMPERSONATION_READONLY` · 403 — the attempt lookup, under a support
29
+ * session, could answer only by fencing the key (`op: 'delivery.attemptFence'`).
30
+ * - `INVALID_BASE64` · 400, `CONTENT_TOO_LARGE` · 413,
31
+ * `UNSUPPORTED_MEDIA_TYPE` · 415 — the inline proof payload was refused
32
+ * before anything was stored.
33
+ */
34
+ exports.DELIVERY_ERROR_CODES = [
35
+ 'DELIVERY_NOT_FOUND',
36
+ 'DELIVERY_NOT_ASSIGNED',
37
+ 'DELIVERY_TERMINAL',
38
+ 'INVALID_DELIVERY_ID',
39
+ 'INVALID_IDEMPOTENCY_KEY',
40
+ 'IDEMPOTENCY_KEY_REUSE',
41
+ 'IDEMPOTENCY_KEY_FAILED',
42
+ 'IDEMPOTENCY_IN_PROGRESS',
43
+ 'IDEMPOTENCY_LOCK_CONTENTION',
44
+ 'INVALID_CURSOR',
45
+ 'IMPERSONATION_READONLY',
46
+ // The proof payload gate (`deliveryAttachmentValidation.ts`).
47
+ 'INVALID_BASE64',
48
+ 'CONTENT_TOO_LARGE',
49
+ 'UNSUPPORTED_MEDIA_TYPE',
50
+ ];
@@ -84,8 +84,9 @@ declare global {
84
84
  }
85
85
  /**
86
86
  * One append-only entry in a delivery's timeline. Never overwritten and never
87
- * deleted: a correction is a NEW event, so a `'delivered'` later contradicted
88
- * by a `'failed'` leaves both on the record.
87
+ * deleted. `'delivered'` and `'failed'` are TERMINAL: nothing is appended
88
+ * after either (`409 DELIVERY_TERMINAL`), and a failed delivery is
89
+ * re-dispatched as a new delivery through `POST /deliveries`.
89
90
  *
90
91
  * PK `DELIVERY#{storeId}#{deliveryId}`, SK `EVENT#{createdAt}#{eventId}` —
91
92
  * the storeId segment is load-bearing, exactly as it is for `CashEvent`:
@@ -108,6 +109,18 @@ declare global {
108
109
  proof?: DeliveryProofAsset[];
109
110
  /** Set only on a `'failed'` event. */
110
111
  failureReason?: DeliveryFailureReason;
112
+ /**
113
+ * The `Idempotency-Key` the actor appended this event under — what lets a
114
+ * client match its own attempt exactly instead of guessing from type, time
115
+ * or photo count.
116
+ *
117
+ * ⚠️ Actor-scoped on the wire: `GET /deliveries/{id}` returns it only when
118
+ * the reader is this event's `actorUserId` or an ADMIN-tier role, and omits
119
+ * it otherwise. Absent on an event appended WITHOUT a key (still accepted —
120
+ * see `DeliveryEventAppendRequest`), on one written before keys existed, and
121
+ * on the opening `'assigned'` event `POST /deliveries` writes.
122
+ */
123
+ idempotencyKey?: string;
111
124
  }
112
125
  /**
113
126
  * A `DeliveryProofAsset` carrying the read-time-only `downloadUrl` minted by
@@ -126,5 +139,125 @@ declare global {
126
139
  type DeliveryEventWithProofUrls = Omit<DeliveryEvent, 'proof'> & {
127
140
  proof?: DeliveryProofAssetWithUrl[];
128
141
  };
142
+ /**
143
+ * Body of `POST /deliveries/{id}/events`. The `Idempotency-Key` header is
144
+ * OPTIONAL for now and strongly recommended — the key, not the body, is what
145
+ * makes a retry safe. It becomes REQUIRED in a later change, once the courier
146
+ * app sends it.
147
+ *
148
+ * ⚠️ WITHOUT a key the append is NOT idempotent: every call writes a new
149
+ * event, so a resend after a lost response records the event twice. Same
150
+ * validation, assignee scoping and terminal refusal; no replay protection and
151
+ * no attempt lookup.
152
+ *
153
+ * WITH a key, resend the SAME key with the byte-identical body after a lost
154
+ * response: a committed attempt answers its original event
155
+ * (`Idempotent-Replayed: true`), a changed body under that key is
156
+ * `422 IDEMPOTENCY_KEY_REUSE`.
157
+ *
158
+ * A transient fault answers `tryCatch`'s generic `400 UNHANDLED_ERROR`. A
159
+ * same-key resend then replays the attempt if it committed, RE-RUNS only if
160
+ * DynamoDB cancelled its transaction (nothing applied), and otherwise answers
161
+ * `409 IDEMPOTENCY_KEY_FAILED` — the attempt may have stored proof or
162
+ * committed, so the key is sealed. On that 409, ask
163
+ * `GET /deliveries/{id}?idempotencyKey=` (`DeliveryEventAttemptLookup`) and
164
+ * mint a new key only when it answers `'not-committed'`.
165
+ *
166
+ * The server sets `actorUserId`, `createdAt`, `gps.receivedAt` and the proof
167
+ * object keys; none of them is accepted here.
168
+ *
169
+ * Held to its schema by `AssertDeliveryEventAppendRequestMatchesSchema`.
170
+ */
171
+ interface DeliveryEventAppendRequest {
172
+ type: DeliveryEventType;
173
+ /** ≤ 1000 characters. */
174
+ note?: string;
175
+ gps?: Omit<DeliveryGpsFix, 'receivedAt'>;
176
+ /**
177
+ * Inline base64 proof, at most 5 assets (`MAX_PROOF_ASSETS_PER_EVENT`). Stored
178
+ * under a name derived from the delivery, the key, the asset's position and
179
+ * its bytes, written create-only — a retry can never replace an object.
180
+ */
181
+ proof?: Array<{
182
+ kind: DeliveryProofAsset['kind'];
183
+ contentType: string;
184
+ data: string;
185
+ }>;
186
+ /** Only on a `'failed'` event — anything else is `400 VALIDATION_FAILED`. */
187
+ failureReason?: DeliveryFailureReason;
188
+ }
189
+ /**
190
+ * What `GET /deliveries/{id}?idempotencyKey=<key>` answers about the
191
+ * CALLER'S OWN attempt under that key on THAT delivery — the authoritative
192
+ * recovery read, unlike the bounded (`truncated`) timeline, which can never
193
+ * prove absence.
194
+ *
195
+ * - `'committed'` — the attempt's event exists; `event` is that exact
196
+ * original. Never append again for it.
197
+ * - `'pending'` — a request under the key is still in flight. Re-ask after
198
+ * `retryAfterMs`; nothing was fenced.
199
+ * - `'not-committed'` — nothing was appended under the key, and the key is
200
+ * FENCED: a durable tombstone (written by this read, or an earlier one, at
201
+ * `fencedAt`) makes every later commit under it refuse. Only now may the
202
+ * client mint a NEW key for the same intent; the old key answers
203
+ * `409 IDEMPOTENCY_KEY_FAILED` forever.
204
+ *
205
+ * ⚠️ This read can WRITE: when the key is neither committed nor in flight it
206
+ * writes the tombstone before answering `'not-committed'`, so asking is itself
207
+ * a decision to give the key up. A client that would rather keep it resends
208
+ * under the same key instead.
209
+ *
210
+ * Attempt evidence is durable (no TTL), so both `'committed'` and
211
+ * `'not-committed'` stay answerable for the life of the delivery.
212
+ *
213
+ * The lookup takes the append's own authority bar: on a delivery that names
214
+ * a platform courier, a caller who is neither that courier nor ADMIN-tier is
215
+ * `403 DELIVERY_NOT_ASSIGNED`, before anything is read or written. Under an
216
+ * impersonated support session it answers only from stored state, and is
217
+ * `403 IMPERSONATION_READONLY` (`op: 'delivery.attemptFence'`) exactly where
218
+ * answering would need the tombstone.
219
+ */
220
+ interface DeliveryEventAttemptLookup {
221
+ idempotencyKey: string;
222
+ verdict: 'committed' | 'pending' | 'not-committed';
223
+ /** Present only on `'committed'`. */
224
+ event?: DeliveryEventWithProofUrls;
225
+ /** Present only on `'pending'`. */
226
+ retryAfterMs?: number;
227
+ /** Present only on `'not-committed'` — ms epoch the tombstone was written. */
228
+ fencedAt?: number;
229
+ }
129
230
  }
231
+ /**
232
+ * Every refusal code the delivery handlers (`POST /deliveries`,
233
+ * `POST /deliveries/{id}/events`, `GET /deliveries`, `GET /deliveries/{id}`)
234
+ * put on the wire, beyond the platform-wide set (`VALIDATION_FAILED`,
235
+ * `ROLE_FORBIDDEN`, `tryCatch`'s generic `UNHANDLED_ERROR`). Pinned against
236
+ * the handler sources by `tests/unit/deliveryErrorCodesPublished.test.ts`.
237
+ *
238
+ * - `INVALID_DELIVERY_ID` · 400 — no delivery id in the path.
239
+ * - `DELIVERY_NOT_FOUND` · 404 — unknown id, or another tenant's.
240
+ * - `DELIVERY_NOT_ASSIGNED` · 403 — the delivery names a platform courier
241
+ * (`courier.userId`) and the caller is neither that user nor ADMIN-tier.
242
+ * - `DELIVERY_TERMINAL` · 409 — the delivery is already `'delivered'` or
243
+ * `'failed'`; a failed delivery is re-dispatched through `POST /deliveries`.
244
+ * - `INVALID_IDEMPOTENCY_KEY` · 400 — the key is not ≤200 chars of
245
+ * `[A-Za-z0-9._:-]`.
246
+ * - `IDEMPOTENCY_KEY_REUSE` · 422 — the key was used with a different body.
247
+ * - `IDEMPOTENCY_KEY_FAILED` · 409 — the attempt under the key failed after it
248
+ * may have written proof, or the attempt lookup fenced the key; ask the
249
+ * lookup, then use a new key.
250
+ * - `IDEMPOTENCY_IN_PROGRESS` · 409 — the same key is still running.
251
+ * - `IDEMPOTENCY_LOCK_CONTENTION` · 409 — the key's marker could not be
252
+ * claimed; retry under the SAME key.
253
+ * - `INVALID_CURSOR` · 400 — a `?mine=true` `startKey` that is malformed or
254
+ * names another store's partition.
255
+ * - `IMPERSONATION_READONLY` · 403 — the attempt lookup, under a support
256
+ * session, could answer only by fencing the key (`op: 'delivery.attemptFence'`).
257
+ * - `INVALID_BASE64` · 400, `CONTENT_TOO_LARGE` · 413,
258
+ * `UNSUPPORTED_MEDIA_TYPE` · 415 — the inline proof payload was refused
259
+ * before anything was stored.
260
+ */
261
+ export declare const DELIVERY_ERROR_CODES: readonly ["DELIVERY_NOT_FOUND", "DELIVERY_NOT_ASSIGNED", "DELIVERY_TERMINAL", "INVALID_DELIVERY_ID", "INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_REUSE", "IDEMPOTENCY_KEY_FAILED", "IDEMPOTENCY_IN_PROGRESS", "IDEMPOTENCY_LOCK_CONTENTION", "INVALID_CURSOR", "IMPERSONATION_READONLY", "INVALID_BASE64", "CONTENT_TOO_LARGE", "UNSUPPORTED_MEDIA_TYPE"];
262
+ export type DeliveryErrorCode = (typeof DELIVERY_ERROR_CODES)[number];
130
263
  export {};
package/dist/delivery.js CHANGED
@@ -1 +1,47 @@
1
- export {};
1
+ /**
2
+ * Every refusal code the delivery handlers (`POST /deliveries`,
3
+ * `POST /deliveries/{id}/events`, `GET /deliveries`, `GET /deliveries/{id}`)
4
+ * put on the wire, beyond the platform-wide set (`VALIDATION_FAILED`,
5
+ * `ROLE_FORBIDDEN`, `tryCatch`'s generic `UNHANDLED_ERROR`). Pinned against
6
+ * the handler sources by `tests/unit/deliveryErrorCodesPublished.test.ts`.
7
+ *
8
+ * - `INVALID_DELIVERY_ID` · 400 — no delivery id in the path.
9
+ * - `DELIVERY_NOT_FOUND` · 404 — unknown id, or another tenant's.
10
+ * - `DELIVERY_NOT_ASSIGNED` · 403 — the delivery names a platform courier
11
+ * (`courier.userId`) and the caller is neither that user nor ADMIN-tier.
12
+ * - `DELIVERY_TERMINAL` · 409 — the delivery is already `'delivered'` or
13
+ * `'failed'`; a failed delivery is re-dispatched through `POST /deliveries`.
14
+ * - `INVALID_IDEMPOTENCY_KEY` · 400 — the key is not ≤200 chars of
15
+ * `[A-Za-z0-9._:-]`.
16
+ * - `IDEMPOTENCY_KEY_REUSE` · 422 — the key was used with a different body.
17
+ * - `IDEMPOTENCY_KEY_FAILED` · 409 — the attempt under the key failed after it
18
+ * may have written proof, or the attempt lookup fenced the key; ask the
19
+ * lookup, then use a new key.
20
+ * - `IDEMPOTENCY_IN_PROGRESS` · 409 — the same key is still running.
21
+ * - `IDEMPOTENCY_LOCK_CONTENTION` · 409 — the key's marker could not be
22
+ * claimed; retry under the SAME key.
23
+ * - `INVALID_CURSOR` · 400 — a `?mine=true` `startKey` that is malformed or
24
+ * names another store's partition.
25
+ * - `IMPERSONATION_READONLY` · 403 — the attempt lookup, under a support
26
+ * session, could answer only by fencing the key (`op: 'delivery.attemptFence'`).
27
+ * - `INVALID_BASE64` · 400, `CONTENT_TOO_LARGE` · 413,
28
+ * `UNSUPPORTED_MEDIA_TYPE` · 415 — the inline proof payload was refused
29
+ * before anything was stored.
30
+ */
31
+ export const DELIVERY_ERROR_CODES = [
32
+ 'DELIVERY_NOT_FOUND',
33
+ 'DELIVERY_NOT_ASSIGNED',
34
+ 'DELIVERY_TERMINAL',
35
+ 'INVALID_DELIVERY_ID',
36
+ 'INVALID_IDEMPOTENCY_KEY',
37
+ 'IDEMPOTENCY_KEY_REUSE',
38
+ 'IDEMPOTENCY_KEY_FAILED',
39
+ 'IDEMPOTENCY_IN_PROGRESS',
40
+ 'IDEMPOTENCY_LOCK_CONTENTION',
41
+ 'INVALID_CURSOR',
42
+ 'IMPERSONATION_READONLY',
43
+ // The proof payload gate (`deliveryAttachmentValidation.ts`).
44
+ 'INVALID_BASE64',
45
+ 'CONTENT_TOO_LARGE',
46
+ 'UNSUPPORTED_MEDIA_TYPE',
47
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "platform-types",
3
- "version": "1.10.714",
3
+ "version": "1.10.715",
4
4
  "description": "Shared wire and entity contracts for the platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",