Repository navigation
Release v0.6.72 - #372
Merged
Merged
Release v0.6.72#372
Conversation
…resource The public Track Order page's GET int/v1/fleet-ops/lookup had no session, no rate limit, and answered with the full OrderResource: notes, meta, internal ids, files (possible POD photos and signatures), purchase rate, and live driver location, to anyone holding a tracking number. - Rate limit it per address on its own limiter (throttle:30,1,tracking-lookup), as the public inspection routes do. - Answer with PublicOrderTracking, which carries only what order-tracking-lookup renders: tracking number, status, timeline, ETA and progress, stop coordinates for the route map, and the item list. Records are keyed by public id, so no internal uuid reaches the page. The driver position is included only while the order is started and unfinished. It extends JsonResource so registered resource transformers cannot add fields. - Missing, malformed and unknown tracking numbers get one identical error, and malformed input never reaches the query.
… avatar URLs - ProofController::storeSignature no longer passes 'public': the media bucket enforces bucket-owner ownership, rejects ACL'd PUTs, and put() silently returned false (no signature objects written since 2024-11-25). - Vehicle/Driver/Place avatar_url accessors re-sign legacy absolute bucket URLs via core-api File::signStoredUrl when available.
The customer portal resolves a company's access slug only under
/customer-access/{slug}; the credentials email linked /{slug}, which the
console does not route. Without a configured slug the link still points
at the portal's own /customer-portal sign-in.
…page Add the settings backend for the customer tracking page refactor (PR 2): - TrackingPageConfig: the company config's shape, defaults and sanitizing (pages, branding, access, what verified visitors see), instance defaults for the generic page, slug rules with a reserved list, and accent contrast helpers (ink colour and WCAG ratio). - SettingController: get/save tracking-page-settings, a live slug check, and admin defaults. Saved slugs are indexed in fleet-ops.tracking-page-slugs.* so the public page resolves a company in one read; an enabled organization page refuses an invalid, reserved or taken slug. The read-back includes the slug status, the accent's ink and contrast, whether SMS is configured (Twilio counts only with credentials), and whether the customer portal is installed. - Routes under settings/, and a tracking-page-settings permission resource.
QR codes now carry a versioned tracking url naming the tracking number and the owner's public id (https://<console>/track-order?order=<tn>&r=<public_id>&v=1) instead of the owner's bare uuid, so a phone camera opens the tracking page while scanners read the parameters. The barcode is now Code 128 with the bare tracking number, matching the text printed beneath it. capture-qr and tracking-numbers/from-qr resolve scans through one parser that accepts the new url, a bare tracking number or public id, and the bare owner uuid printed on labels already in circulation. from-qr is now scoped to the session's company and resolves waypoint and place owners too. qr_code_content is published in every environment: it no longer exposes a uuid. Labels print the QR as a fixed square instead of cropping it with object-fit.
…can resolver - TrackingCode: generation, parsing of every format in circulation (legacy uuid, tracking url from any host, bare tracking number or public id, hostile urls), and subject matching for capture-qr - TrackingNumber::findOwnerByCode against SQLite: company scoping, disagreeing url identifiers, unknown numbers and unmapped prefixes - insert path, observer and resource expectations moved off the owner uuid
capture-qr reads `code` straight from the request. The navigator v3 branch sends the scanner's whole result object; the old strict comparison answered 400, and the typed matcher would have thrown. Non-strings now match nothing.
PR 4 of the tracking pages refactor: FleetOps -> Settings -> Tracking Page. - Pages: own page on/off, its address under /t/ with a live availability check, copy and open links, shared-page opt-out, and which page emails and labels link to. - Branding: display name, accent with a live contrast check (the text colour on it is derived), an optional dark-mode accent, support contacts, Powered by Fleetbase, light/dark behaviour, default and enabled languages. - Access: status without verification, SMS and email codes (SMS disabled with a reason when the instance has no SMS provider), session length, and customer sign-in and account upsell when the customer portal is installed. - What verified customers see, with the privacy rules that always apply. - A live light and dark preview themed from the same tokens the page will use (utils/tracking-page-theme). - Admin panel 'Tracking Page' under Fleet-Ops Config for the generic page's instance defaults; sidebar item, settings hub card, and translation keys in all 10 locales (English text until translated). Settings load in the route's model hook, not a controller constructor.
filter_var(null, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) returns false, not null, so every unset toggle (generic page allowed, powered by, the visibility defaults) came out false instead of its default.
…per-customer tracking channel - Register who may subscribe to every FleetOps channel prefix with the core SocketChannelRegistry: company match for console users and API credentials; narrow rules for drivers (own record, current vehicle and its devices and positions, assigned orders incl. via payload entities) and customers (own orders, driver and vehicle of their active orders). tracking.* is denied by the registry (reachable only through a scoped tracking token). - Claim Sanctum users who drive for the current company as driver socket principals on v1/socket/token. - positions/replay validates channel_id with the ChannelAuthorizer when socket authentication is on. - Public tracking channel: TrackingScope (one customer of one order), TrackingChannel (name/opaque id/token via core-api), TrackingUpdate (sanitized per-customer payload) and TrackingPublisher (gated, throttled, queued) publishing on order, stop and position changes. - Require fleetbase/core-api ^1.6.69.
…s only its own
The scheduler subscribed to company.{id}.orders, which nothing publishes to,
and its teardown closed every socket channel, chat included. It now opens
the per-driver channels only and on teardown closes just those (sweeping
once more for subscriptions still pending).
…rinting
Production console builds fingerprint .png files, so boxes/small.png ships
as boxes/small-<hash>.png. broccoli-asset-rev rewrites literal asset URLs to
the hashed names, but it cannot rewrite the interpolated
/engines-dist/images/boxes/{{parcelFee.size}}.png, so the request misses and
the server answers with index.html.
Add a parcel-box-image helper that maps each size to a literal URL the
fingerprinter can rewrite, and use it in the service rate form and details.
…d vehicles The Google live map fell back to /engines-dist/images/driver-marker.png and vehicle-marker.png, neither of which exists in assets/images, so a driver or vehicle without an avatar rendered a broken marker. Point both at the existing map-marker.png, as the leaflet live-fleet widget already does. The URLs stay literal strings so broccoli-asset-rev can fingerprint them in production.
…ource fix(tracking): throttle the public order lookup and return a minimal resource
fix(files): store signatures without a public ACL; re-sign stored avatar URLs
…l-link
fix(mail): link customer credentials to /customer-access/{slug}
feat(tracking-page): organization settings for the customer tracking page
feat(tracking): structured tracking-url QR codes, Code 128 barcodes, backward-compatible scan resolver
feat(tracking-page): settings screen for the customer tracking page
fix(service-rate): resolve parcel box images after production fingerprinting
…lback fix(live-map): use the shipped map-marker.png as the driver/vehicle fallback icon
feat(socket-auth): fleetops channel resolvers, driver socket tokens, per-customer tracking channel
Tracking links, the TrackingNumber url field and label QR codes pointed at /track-order?order=..., which falls into the console's own routes. A signed-out visitor was sent on to the public /~/track-order page after login, but a signed-in user (staff or admin) landed on an empty console page. - TrackingCode::PATH is now ~/track-order, the console's public route prefix, which renders whether or not the visitor is signed in. The notifications' Track Order buttons build their link from the same constants. - The tracking page is also registered in the console registry, so links already emailed or printed without the prefix show it to signed-in staff. - The scan parser never reads the tracking page path itself as a tracking number; old /track-order codes still parse.
The public tracking page the settings configure, at /~/track (shared) and
/~/track?org={slug} (a company's own), with /~/track-order kept for links
already sent. Works signed in or out; signed-in staff see what the customer
sees.
Server (public/track/*, FleetOps):
- TrackingResolver: a tracking number (order, stop, item or service-stop
place) opens its order for one customer within it (TrackingScope).
Unknown, malformed, opted-out and other-company numbers all resolve to
nothing and get one identical 404; lookups throttle inside the controller
so a throttled lookup looks the same.
- TrackingAccess: one-time codes hashed via VerificationCode::issue(), sent
only to contact details on the order (masked), with a 30 s resend
cooldown, 3 sends per scope and 5 per address per 15 min, and a 5-minute
pause after 3 wrong codes. A correct code gives the device an httpOnly
session cookie (fb_track, stored as SHA-256) with a grant per customer.
- TrackingView: level 0 (stage, last update, how to verify) and levels 1/2
built on TrackingUpdate, filtered to the customer's own stops, items,
timeline and proof of delivery. Never sends uuids, notes, meta, rates,
the sender's address or other customers' stops; the route line only when
every stop is the customer's; the driver and position only while en
route to them.
- Endpoints for codes, sign-out, delivery instructions, proof images,
reporting a problem, the socket token, and a signed-in customer's orders.
- Links (TrackingNumber url, notification buttons) follow the company's
'links in emails and labels' setting.
Page (Ember): lookup, limited view, verify (every code state), the full
view with live map, ETA, driver, updates, items, proof lightbox, delivery
instructions and reports, the order list, toasts, light/dark from the
company's tokens, RTL, and polling live updates behind a swappable source.
Tests for the resolver, recipient, access, view, controller, links and routes.
- Wrap the code input's normalizing chain the way prettier wants. - Rename the locale loop's block param so it no longer shadows <option>. - Describe the map with screen-reader text instead of role=img, which can't contain the map's interactive controls.
…umber}
The company page was /~/track?org={slug}. Links now carry the company and
tracking number in the path; the page still opens ?org= and ?order= links.
QR codes keep /~/track-order?order=, which printed labels carry.
Needs the console's /~/:slug/*path route (fleetbase/fleetbase).
Assigning a driver now starts an offer on the order, kept in meta.offer as
{ driver, offered_at, expires_at, accepted_at, declined_at, status } with
status offered, accepted, declined or expired. The offer is written in the
same save as the assignment (order observer "saving"), and by the bulk
assign path. The window is the company setting
fleetops.offer_timeout_minutes (default 5; 0 disables expiry). Adhoc
drivers who take an order through `start` with `assign` start accepted,
and starting an order accepts an open offer.
New public endpoints for the authenticated driver (or a `driver` id):
- POST v1/orders/{id}/accept: accepts the offer (assigns an unassigned
adhoc order to the driver) and broadcasts `order.offer_accepted`.
- POST v1/orders/{id}/decline { reason? }: records the decline, unassigns
the driver, broadcasts `order.offer_declined` and notifies dispatch
(OrderOfferDeclined).
The scheduled fleetops:expire-offers command (every minute) expires offers
past expires_at, raises the `driver_not_accepted` operational alert once per
offer (DriverNotAccepted, meta.operational_alerts.driver_not_accepted) and
broadcasts `order.offer_expired` on order.{public_id}, order.{uuid} and
company.{uuid}. The driver stays assigned. The order resources expose `offer`.
DELETE v1/orders/{id}/cancel and PATCH int/v1/orders/cancel accept an
optional `reason` (string, up to 1000 characters). Order::cancelWithReason()
records it as the details of the canceled tracking status (the config's
activity is cloned, not changed) and carries that activity on the
OrderCanceled event, so NotifyOrderEvent and the driver's canceled
notification include it. The canceled notification data gains `reason` when
one was given. Order::cancel() keeps its signature and behaviour.
…reason rule and observer-sent assignment
fix(tenancy): scope inspection, maintenance, telematics, warranty, proof and route data to the session company
…e verification Phase 1 of the SMS guard on the FleetOps side (needs core-api with the hashed-code APIs, fleetbase/core-api#307): - The customer API no longer persists a User for an identity that merely asked for a creation code; the code's subject is the existing user or an unsaved one. A users row per unverified identity was a way to fill the table from a leaked credential. - Every SMS code the driver and customer APIs send is issued hashed (HMAC on the row, 10 minute expiry, plain code only in the message). - One verify-and-consume path: create, verify-code, and the password resets check the submitted code against the live codes for the identity or user, newest first, lock a code after five wrong guesses, and mark the matching one used. Plaintext rows written before this verify the same way. - The public tracking page consumes a verified code instead of deleting it, so the row stays for the ledger.
fix(routes): throttle the public SMS-sending routes per address
feat(auth): hashed single-use verification codes, no stub users before verification
The customer and driver login, creation-code and forgot-password endpoints wrap their SMS send in a catch-all, so a refusal from the guard (a RuntimeException) was flattened into the generic 400 send error, or into the email fallback, and an app could neither explain it nor back off. The refusal now reaches the caller with the status it carries: 422 for a number that can never be texted, 429 with Retry-After for a cooldown or budget, 423 while texting is paused. Login still falls back to email when the account has an address; the refusal is the answer only when nothing else can carry the code.
feat(orders): status-guarded edits, order.changed events, driver offers
fix(api): answer an SMS guard refusal with its status
…rivers
Assigning a driver started an expiring "offer" in meta.offer that the driver
had to accept, and raised "driver not accepted" when they did not. That is
not how dispatch works: an order assigned to a driver (by dispatch or
auto-assign) is simply theirs. Only adhoc orders are offered, and the
existing adhoc flow already does it: dispatching pings drivers within
adhoc_distance (OrderPing), fleetops:dispatch-adhoc pings again every minute
while nobody has taken it, and a driver takes it with `start` + `assign`.
This reverts "feat(orders): driver offers with accept, decline and expiry":
- meta.offer and its sync on assignment (observer, bulk assign)
- POST v1/orders/{id}/accept and /decline
- fleetops:expire-offers, the driver_not_accepted alert (DriverNotAccepted),
OrderOfferDeclined, the order.offer_* broadcasts
- `offer` on the order resources and fleetops.offer_timeout_minutes
The cancel reason keeps its request helper, which it shared with decline.
The fixture customer has an email, so the refusal fell back to email and answered 200 where the test expects the guard's 429. Never ran green: PHP CI could not resolve core-api ^1.6.69 until it was tagged.
chore(deps): ember-ui 0.4.6, ember-core 0.3.26 and fleetops-data 0.2.3
test(customers): the over-budget login case has no email on file
… its own row Against core-api 1.6.69 the suite hit the real guard with its counters in the shared cache: a second text to the same number inside a file tripped the 60-second cooldown, so driver login tests got a 429 refusal where they expect the transport failure or the refusal they staged themselves. The bootstrap now seeds sms.guard.enabled = false, as core-api's and storefront's do; the tests that exercise refusals raise them from the provider fake. The helper-seams test reused one code for two lookups and omitted the purpose; a code is used up by the lookup that accepts it and the live-code query is per purpose, so each check gets its own row and carries 'for'.
The gate never ran before: the suite failed first. With the tests passing it reports 57 statements short across the order edit, its event, notifications, policy, tracking publisher, payload sync and the controllers that begin and refuse edits. This covers them: an edit's policy, marked changes, schedule, cash, contact and item detection with the kitchen flag, orders with no or an unsaved payload, the summary's fallbacks; order.changed's customer channel, session editor, missing driver and null place; push builders reaching for the FCM and APN clients; a config that cannot answer, cannot load, and times that do not parse; route changes queued for tracking; caching switched off; the controllers' begin, locked-edit and switched-payload cash paths; replaced drivers told on bulk assignment; and the entity sync's non-array inputs. The older-core branch in ApiCacheThrottle::telemetryWrite() cannot run against the core this package requires and is marked for coverage with the reason.
test: run the suite against core-api 1.6.69 and cover what its coverage gate flags
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #372 +/- ##
=============================================
Coverage 100.00% 100.00%
- Complexity 12379 13585 +1206
=============================================
Files 600 634 +34
Lines 46496 49308 +2812
=============================================
+ Hits 46496 49308 +2812
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The contract caller pinned fleetbase/fleetbase at a commit from before the run-order fixes that the Fleetbase API collection now needs (tracking deletes ahead of the order delete, the self-terminating activity cycle). Storefront's caller is deliberately unpinned for the same reason; this matches it. Each run still records the image digest it resolved.
This was referenced Oct 14, 2026
ci: track the shared API contract workflow on main
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.
Release branch for v0.6.72, cut from
main. It bumpspackage.json,composer.jsonandextension.jsonto 0.6.72 and rewritesRELEASE.md.What this release collects
These PRs now target this branch:
/customer-access/{slug}Merge order
File::signStoredUrl()). It is safe on older core-api releases: the stored URLs pass through unchanged.Upgrade note
Run
php artisan fleetbase:create-permissionsto register the newtracking-page-settingspermission.