fix(zr-express): production hardening from live API audit + label CORS fix - #111
Merged
Conversation
All findings verified against the live ZR Express API (see
.agents/skills/zr-express/CONFORMANCE.md for the full audit):
- Tracking: resolve the parcel UUID via GET /parcels/{trackingNumber}
before reading state-history — /state-history 404s on tracking
numbers, so ZR orders silently showed no events.
- Cancel: switch deletion to the documented DELETE
/parcels/bulk/by-tracking-number (POST answered 405); enable
canDeleteBeforeValidation + dashboard cancel button; surface carrier
refusals instead of silently resetting the order.
- Address updates: send the full DeliveryAddressInputDto
(cityTerritoryId + districtTerritoryId) — a street-only body 400s;
pickup-point updates additionally require hubId.
- Stop desk: pickup points are HUBS (POST /hubs/search, isPickupPoint),
not territory UUIDs — passing a territory as hubId 404s HubNotFound.
getStopDesks now lists hubs with their wilaya resolved from
address.cityTerritoryId.
- Geo: accent-stripped territory searches (ZR stores accent-free
names), strict commune matching (no silent wrong-commune fallback),
and a clear error for the 4 wilayas ZR does not serve (33, 37, 50, 56).
- Webhooks: subscribe to all three event types the receiver
understands, map the real default-workflow state slugs (case- and
accent-insensitive, description fallback), and accept all order
statuses in the custom-mapping UI.
- Bulk create: keep all failure messages per parcel index.
- Add zr-status-mapper.test.ts and the zr-express skill docs.
Handlers that return a raw new Response(...) (label PDF proxy, R2
image streaming) bypass Hono's prepared-header application, so the
Access-Control-Allow-* headers set before next() never reached them.
The browser blocked the 200 response with a CORS error — most visibly
on GET /api/orders/{id}/label (print label).
The middleware now re-applies the headers on the final response after
next(). Covered by regression tests for both raw and context-built
responses.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A live audit of the ZR Express integration (every adapter call exercised against the production API, see
.agents/skills/zr-express/CONFORMANCE.md) found that only the happy-path single home-delivery flow worked. Several flows were silently broken:GET /parcels/{trackingNumber}/state-history404s; the endpoint only accepts the parcel UUID, and the error was swallowed, so every ZR order showed no tracking events.readywhile the parcel kept moving at the carrier.CityTerritoryId is required), and the failure was swallowed into a false success.hubId; ZR requires a real hub id (404HubNotFound).commande_recue,en_livraison,livre, …), so terminal events recorded asunmapped. Registration also subscribed to only one of the three event types the receiver understands.Response, which bypassed Hono's prepared-header application, so the 200 response reached the browser withoutAccess-Control-Allow-Originand was blocked.Solution
ZR adapter (
cod-server,cod-shareduntouched):GET /parcels/{trackingNumber}first, then read state-history.DELETE /parcels/bulk/by-tracking-number; enablecanDeleteBeforeValidation+ dashboard cancel; carrier refusals now surface as errors instead of resetting the order.DeliveryAddressInputDto(cityTerritoryId+districtTerritoryId, plushubIdfor pickup-point parcels); failures throw instead of being swallowed.getStopDeskslists hubs (isPickupPoint) with wilayas resolved fromaddress.cityTerritoryId; dispatch and address updates resolve the real hub for the station.CORS middleware: re-apply the CORS headers to the final response after the handler runs, so raw streamed responses (label PDF proxy, R2 image streaming) carry them.
Dashboard (
cod-client-astro): enable the cancel button for ZR orders (non-terminal statuses).Docs: add the
zr-expressskill (endpoint references, schemas, conformance audit) under.agents/skills/.Testing
cod-server: typecheck clean, 1919/1919 tests pass (incl. newzr-status-mapper.test.tswith a drift guard over the live-verified slug vocabulary, and CORS regression tests for raw + context-built responses).cod-client-astro: typecheck clean, 147/147 tests pass.No schema changes, no new dependencies, no other carriers touched.