WIP: feat: Fleet Engine (ODRD) REST client, config and JWT auth#1381
WIP: feat: Fleet Engine (ODRD) REST client, config and JWT auth#1381prakharritik wants to merge 2 commits into
Conversation
|
Warning Review limit reached
Next review available in: 26 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
WalkthroughAdds Fleet Engine support in ChangesFleet Engine Integration
Estimated code review effort: 3 (Moderate) | ~25 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/mobility-core/src/Kernel/External/FleetEngine/Client.hs`:
- Around line 77-118: Both Fleet Engine write helpers currently hide failures by
returning m () after logging, so callers cannot react to remote write errors.
Update createTrip and updateTrip in Client.hs to propagate non-success responses
from callAPI by throwing/returning an error in the MonadFlow flow instead of
just logging; keep the ALREADY_EXISTS branch in createTrip as the only
idempotent success path, and ensure the change is applied where result is
pattern-matched in these functions.
- Around line 92-97: The idempotency check in FleetEngine createTrip handling is
too brittle because it relies on matching "ALREADY_EXISTS" in show err. Update
the Left err branch in Client.hs to inspect the structured
FailureResponse/response body from the FleetEngine error instead of stringifying
the exception, and use that parsed status/body to decide whether to log the
idempotent no-op or fall through to logError. Keep the existing logInfo/logError
paths, but key the duplicate-create detection off the response payload rather
than the rendered exception text.
In `@lib/mobility-core/src/Kernel/External/FleetEngine/Config.hs`:
- Around line 30-37: The FleetEngine Config currently uses a single
serviceAccountJson for both SDK token minting and server-to-server
authentication, which mixes two different trust domains. Split the signer
configuration in the FleetEngine config and related token-minting code so
consumer/driver JWTs continue to use the shared SDK signer while
server-to-server tokens use a separate backend service account or ADC path.
Update the relevant Config type and any token creation helpers that reference
serviceAccountJson to distinguish these two identities cleanly.
In `@lib/mobility-core/src/Kernel/External/FleetEngine/Types.hs`:
- Around line 86-96: Make mkCreateTripBody require a pickup point instead of
accepting it as optional, since Fleet Engine create-trip requests need
pickup_point and trip_type. Update the mkCreateTripBody signature and its use of
pickupPoint so it is always set from a provided LatLng, and remove tripStatus
from the create body because it is ignored on create. Keep the rest of the Trip
construction in emptyTrip unchanged.
In `@lib/mobility-core/src/Kernel/Utils/JWT.hs`:
- Around line 123-150: The `createSignedJWTWithClaims` function should validate
inputs before encoding the token: reject any audience that cannot be converted
by `stringOrURI` and return an error instead of proceeding with missing `aud`,
`iss`, or `sub`, and also fail fast when `ttlSeconds` is zero or negative so the
JWT is never created already expired. Update the logic around `stringOrURI`,
`audience'`, and the `exp` calculation in `createSignedJWTWithClaims` to produce
a `Left` for invalid audience or non-positive TTL, while keeping the existing
signing flow unchanged for valid inputs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d3c48b98-a10e-4a27-be58-36336b3a8445
📒 Files selected for processing (6)
lib/mobility-core/mobility-core.caballib/mobility-core/src/Kernel/External/FleetEngine/Auth.hslib/mobility-core/src/Kernel/External/FleetEngine/Client.hslib/mobility-core/src/Kernel/External/FleetEngine/Config.hslib/mobility-core/src/Kernel/External/FleetEngine/Types.hslib/mobility-core/src/Kernel/Utils/JWT.hs
| createTrip :: | ||
| (CoreMetrics m, MonadFlow m, MonadReader r m, HasRequestId r) => | ||
| BaseUrl -> | ||
| Text -> -- providerId | ||
| Text -> -- token (server JWT) | ||
| Text -> -- tripId | ||
| Trip -> | ||
| m () | ||
| createTrip baseUrl providerId token tripId trip = do | ||
| result <- | ||
| callAPI | ||
| baseUrl | ||
| (createTripClient providerId tripId (bearer token) trip) | ||
| "fleetEngineCreateTrip" | ||
| (Proxy :: Proxy CreateTripAPI) | ||
| case result of | ||
| Right _ -> logInfo $ "FleetEngine: created trip " <> tripId | ||
| Left err | ||
| | "ALREADY_EXISTS" `T.isInfixOf` show err -> | ||
| logInfo $ "FleetEngine: trip already exists (idempotent no-op) " <> tripId | ||
| | otherwise -> logError $ "FleetEngine: createTrip failed for " <> tripId <> ": " <> show err | ||
|
|
||
| -- | PATCH a trip with the given field mask and body. | ||
| updateTrip :: | ||
| (CoreMetrics m, MonadFlow m, MonadReader r m, HasRequestId r) => | ||
| BaseUrl -> | ||
| Text -> -- providerId | ||
| Text -> -- token (server JWT) | ||
| Text -> -- tripId | ||
| Text -> -- updateMask (comma-separated field paths) | ||
| Trip -> | ||
| m () | ||
| updateTrip baseUrl providerId token tripId updateMask trip = do | ||
| result <- | ||
| callAPI | ||
| baseUrl | ||
| (updateTripClient providerId tripId updateMask (bearer token) trip) | ||
| "fleetEngineUpdateTrip" | ||
| (Proxy :: Proxy UpdateTripAPI) | ||
| case result of | ||
| Right _ -> logInfo $ "FleetEngine: updated trip " <> tripId <> " [" <> updateMask <> "]" | ||
| Left err -> logError $ "FleetEngine: updateTrip failed for " <> tripId <> ": " <> show err |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Propagate Fleet Engine write failures instead of only logging them.
Both write helpers return m () and swallow every non-success response after logging. That makes upstream code proceed as if the remote trip state was updated, which can permanently desync local ride state from Fleet Engine. Fix this at the root by returning/throwing an error from createTrip and updateTrip, and keep the ALREADY_EXISTS branch as the only idempotent success case.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/mobility-core/src/Kernel/External/FleetEngine/Client.hs` around lines 77
- 118, Both Fleet Engine write helpers currently hide failures by returning m ()
after logging, so callers cannot react to remote write errors. Update createTrip
and updateTrip in Client.hs to propagate non-success responses from callAPI by
throwing/returning an error in the MonadFlow flow instead of just logging; keep
the ALREADY_EXISTS branch in createTrip as the only idempotent success path, and
ensure the change is applied where result is pattern-matched in these functions.
| case result of | ||
| Right _ -> logInfo $ "FleetEngine: created trip " <> tripId | ||
| Left err | ||
| | "ALREADY_EXISTS" `T.isInfixOf` show err -> | ||
| logInfo $ "FleetEngine: trip already exists (idempotent no-op) " <> tripId | ||
| | otherwise -> logError $ "FleetEngine: createTrip failed for " <> tripId <> ": " <> show err |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect the actual error type returned by callAPI and existing structured handling patterns.
fd 'Common.hs' lib/mobility-core/src | xargs -r rg -n -C3 '\bcallAPI\b|FailureResponse|ClientError|ALREADY_EXISTS'
fd 'Client.hs' lib/mobility-core/src/Kernel/External | xargs -r rg -n -C3 'show err|ALREADY_EXISTS|FailureResponse'Repository: nammayatri/shared-kernel
Length of output: 3618
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate callAPI definition and error type handling.
fd -a 'Common.hs' lib/mobility-core/src
fd -a 'Client.hs' lib/mobility-core/src/Kernel/External/FleetEngine
fd -a 'Common.hs' lib/mobility-core/src | xargs -r sed -n '1,260p'
printf '\n==== FleetEngine Client slice ====\n'
sed -n '1,180p' lib/mobility-core/src/Kernel/External/FleetEngine/Client.hs
printf '\n==== Search for error type definitions/usages ====\n'
rg -n -C3 'data .*ClientError|type .*ClientError|FailureResponse|ServantError|HttpException|callAPI\s*::|callAPI\s+\w+' lib/mobility-core/srcRepository: nammayatri/shared-kernel
Length of output: 50380
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate callAPI definition and error type handling.
fd -a 'Common.hs' lib/mobility-core/src
fd -a 'Client.hs' lib/mobility-core/src/Kernel/External/FleetEngine
printf '\n==== Common.hs ====\n'
sed -n '1,260p' lib/mobility-core/src/Common.hs
printf '\n==== FleetEngine Client slice ====\n'
sed -n '1,180p' lib/mobility-core/src/Kernel/External/FleetEngine/Client.hs
printf '\n==== Search for error type definitions/usages ====\n'
rg -n -C3 'data .*ClientError|type .*ClientError|FailureResponse|ServantError|HttpException|callAPI\s*::|callAPI\s+\w+' lib/mobility-core/srcRepository: nammayatri/shared-kernel
Length of output: 728
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '==== callAPI implementation ====\n'
sed -n '1,280p' lib/mobility-core/src/Kernel/Utils/Servant/Client.hs
printf '\n==== CallAPIError helper ====\n'
sed -n '1,220p' lib/mobility-core/src/Kernel/Types/Error/BaseError/HTTPError/CallAPIError.hs
printf '\n==== ALREADY_EXISTS search ====\n'
rg -n -C4 'ALREADY_EXISTS|already exists|FailureResponse _ \(Response' lib/mobility-core/src/Kernel/External lib/mobility-core/src/Kernel/Types lib/mobility-core/src/Kernel/UtilsRepository: nammayatri/shared-kernel
Length of output: 25771
Match Fleet Engine duplicate errors structurally
Use FailureResponse/response body for the idempotency check instead of show err; the current substring match is brittle and can flip duplicate creates into failures if the rendered exception text changes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/mobility-core/src/Kernel/External/FleetEngine/Client.hs` around lines 92
- 97, The idempotency check in FleetEngine createTrip handling is too brittle
because it relies on matching "ALREADY_EXISTS" in show err. Update the Left err
branch in Client.hs to inspect the structured FailureResponse/response body from
the FleetEngine error instead of stringifying the exception, and use that parsed
status/body to decide whether to log the idempotent no-op or fall through to
logError. Keep the existing logInfo/logError paths, but key the duplicate-create
detection off the response payload rather than the rendered exception text.
| -- | Build the CreateTrip body. Fleet Engine requires @tripType@; pickup/dropoff | ||
| -- are optional but improve ETA quality. | ||
| mkCreateTripBody :: TripType -> Maybe LatLng -> Maybe LatLng -> Maybe Int -> Trip | ||
| mkCreateTripBody tType mbPickup mbDropoff mbPassengers = | ||
| emptyTrip | ||
| { tripType = Just tType, | ||
| tripStatus = Just NEW, | ||
| pickupPoint = TerminalLocation <$> mbPickup, | ||
| dropoffPoint = TerminalLocation <$> mbDropoff, | ||
| numberOfPassengers = mbPassengers | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File outline ==\n'
ast-grep outline lib/mobility-core/src/Kernel/External/FleetEngine/Types.hs --view expanded || true
printf '\n== Relevant snippet ==\n'
sed -n '1,180p' lib/mobility-core/src/Kernel/External/FleetEngine/Types.hs | cat -n
printf '\n== mkCreateTripBody usages ==\n'
rg -n "mkCreateTripBody" -S .
printf '\n== Trip / pickupPoint definitions ==\n'
rg -n "data Trip|pickupPoint|tripStatus|tripType|newtype Trip|type Trip" lib/mobility-core/src -SRepository: nammayatri/shared-kernel
Length of output: 6240
🌐 Web query:
Fleet Engine CreateTrip trip_type pickup_point required tripStatus create request docs
💡 Result:
To create a trip in Fleet Engine using the CreateTrip request, you must provide a parent string (the project ID in the format providers/{provider}) and a unique trip_id [1][2]. Within the Trip object included in the request, the following fields are strictly required: 1. trip_type: Specifies the type of trip (e.g., EXCLUSIVE or SHARED) [1][3][4]. 2. pickup_point: Defines the point of origin for the trip [1][3][4]. Regarding the tripStatus field: it is not required in the creation request because all new trips are automatically assigned a trip_status of NEW by the Fleet Engine system [3][4]. Any value you provide for trip_status during the creation request is ignored [3][4]. Other optional fields that can be provided include dropoff_point, number_of_passengers, vehicle_id, and intermediate_destinations [3][4].
Citations:
- 1: https://developers.google.com/maps/documentation/mobility/fleet-engine/journeys/trips/create-trip
- 2: https://developers.google.cn/maps/documentation/mobility/fleet-engine/reference/trips/rest/v1/providers.trips/create
- 3: https://developers.google.com/maps/documentation/mobility/fleet-engine/reference/trips/rpc/maps.fleetengine.v1
- 4: https://googleapis.dev/python/fleetengine/latest/fleetengine_v1/types_.html
Make pickupPoint required in mkCreateTripBody. Fleet Engine create trips require both trip_type and pickup_point; tripStatus is ignored on create and can be omitted.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/mobility-core/src/Kernel/External/FleetEngine/Types.hs` around lines 86 -
96, Make mkCreateTripBody require a pickup point instead of accepting it as
optional, since Fleet Engine create-trip requests need pickup_point and
trip_type. Update the mkCreateTripBody signature and its use of pickupPoint so
it is always set from a provided LatLng, and remove tripStatus from the create
body because it is ignored on create. Keep the rest of the Trip construction in
emptyTrip unchanged.
| createSignedJWTWithClaims sa audience ttlSeconds additionalClaims = do | ||
| let issuer = stringOrURI . saClientEmail $ sa | ||
| let subject = stringOrURI . saClientEmail $ sa | ||
| let audience' = Left <$> stringOrURI audience | ||
| let unregisteredClaims = ClaimsMap $ Map.fromList additionalClaims | ||
| let jwtHeader = | ||
| JOSEHeader | ||
| { typ = Just "JWT", | ||
| cty = Nothing, | ||
| alg = Just RS256, | ||
| kid = Just $ saPrivateKeyId sa | ||
| } | ||
| case readRsaSecret . C8.pack $ saPrivateKey sa of | ||
| Nothing -> pure $ Left "Bad RSA key!" | ||
| Just pkey -> do | ||
| let key = EncodeRSAPrivateKey pkey | ||
| iat <- numericDate <$> getPOSIXTime | ||
| exp <- numericDate . (+ fromInteger ttlSeconds) <$> getPOSIXTime | ||
| let claims = | ||
| mempty | ||
| { exp = exp, | ||
| iat = iat, | ||
| iss = issuer, | ||
| sub = subject, | ||
| aud = audience', | ||
| unregisteredClaims = unregisteredClaims | ||
| } | ||
| pure $ Right (encodeSigned key jwtHeader claims) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Reject invalid audiences and non-positive TTLs before signing.
stringOrURI can fail silently into missing iss/sub/aud, and ttlSeconds <= 0 returns a Right token that is already expired. Fleet Engine JWT guidance expects an expiry-bearing bearer token, so fail fast here instead. (developers.google.com)
Suggested fix
createSignedJWTWithClaims sa audience ttlSeconds additionalClaims = do
- let issuer = stringOrURI . saClientEmail $ sa
- let subject = stringOrURI . saClientEmail $ sa
- let audience' = Left <$> stringOrURI audience
- let unregisteredClaims = ClaimsMap $ Map.fromList additionalClaims
- let jwtHeader =
- JOSEHeader
- { typ = Just "JWT",
- cty = Nothing,
- alg = Just RS256,
- kid = Just $ saPrivateKeyId sa
- }
- case readRsaSecret . C8.pack $ saPrivateKey sa of
- Nothing -> pure $ Left "Bad RSA key!"
- Just pkey -> do
+ case (stringOrURI $ saClientEmail sa, stringOrURI audience) of
+ (Nothing, _) -> pure $ Left "Invalid service account client email"
+ (_, Nothing) -> pure $ Left "Invalid JWT audience"
+ (Just issuer, Just audienceUri)
+ | ttlSeconds <= 0 -> pure $ Left "JWT TTL must be positive"
+ | otherwise -> case readRsaSecret . C8.pack $ saPrivateKey sa of
+ Nothing -> pure $ Left "Bad RSA key!"
+ Just pkey -> do
+ let subject = issuer
+ let audience' = Just $ Left audienceUri
+ let unregisteredClaims = ClaimsMap $ Map.fromList additionalClaims
+ let jwtHeader =
+ JOSEHeader
+ { typ = Just "JWT",
+ cty = Nothing,
+ alg = Just RS256,
+ kid = Just $ saPrivateKeyId sa
+ }
let key = EncodeRSAPrivateKey pkey
iat <- numericDate <$> getPOSIXTime
exp <- numericDate . (+ fromInteger ttlSeconds) <$> getPOSIXTime
let claims =
mempty
{ exp = exp,
iat = iat,
- iss = issuer,
- sub = subject,
+ iss = Just issuer,
+ sub = Just subject,
aud = audience',
unregisteredClaims = unregisteredClaims
}
pure $ Right (encodeSigned key jwtHeader claims)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| createSignedJWTWithClaims sa audience ttlSeconds additionalClaims = do | |
| let issuer = stringOrURI . saClientEmail $ sa | |
| let subject = stringOrURI . saClientEmail $ sa | |
| let audience' = Left <$> stringOrURI audience | |
| let unregisteredClaims = ClaimsMap $ Map.fromList additionalClaims | |
| let jwtHeader = | |
| JOSEHeader | |
| { typ = Just "JWT", | |
| cty = Nothing, | |
| alg = Just RS256, | |
| kid = Just $ saPrivateKeyId sa | |
| } | |
| case readRsaSecret . C8.pack $ saPrivateKey sa of | |
| Nothing -> pure $ Left "Bad RSA key!" | |
| Just pkey -> do | |
| let key = EncodeRSAPrivateKey pkey | |
| iat <- numericDate <$> getPOSIXTime | |
| exp <- numericDate . (+ fromInteger ttlSeconds) <$> getPOSIXTime | |
| let claims = | |
| mempty | |
| { exp = exp, | |
| iat = iat, | |
| iss = issuer, | |
| sub = subject, | |
| aud = audience', | |
| unregisteredClaims = unregisteredClaims | |
| } | |
| pure $ Right (encodeSigned key jwtHeader claims) | |
| createSignedJWTWithClaims sa audience ttlSeconds additionalClaims = do | |
| case (stringOrURI $ saClientEmail sa, stringOrURI audience) of | |
| (Nothing, _) -> pure $ Left "Invalid service account client email" | |
| (_, Nothing) -> pure $ Left "Invalid JWT audience" | |
| (Just issuer, Just audienceUri) | |
| | ttlSeconds <= 0 -> pure $ Left "JWT TTL must be positive" | |
| | otherwise -> | |
| case readRsaSecret . C8.pack $ saPrivateKey sa of | |
| Nothing -> pure $ Left "Bad RSA key!" | |
| Just pkey -> do | |
| let subject = issuer | |
| let audience' = Just $ Left audienceUri | |
| let unregisteredClaims = ClaimsMap $ Map.fromList additionalClaims | |
| let jwtHeader = | |
| JOSEHeader | |
| { typ = Just "JWT", | |
| cty = Nothing, | |
| alg = Just RS256, | |
| kid = Just $ saPrivateKeyId sa | |
| } | |
| let key = EncodeRSAPrivateKey pkey | |
| iat <- numericDate <$> getPOSIXTime | |
| exp <- numericDate . (+ fromInteger ttlSeconds) <$> getPOSIXTime | |
| let claims = | |
| mempty | |
| { exp = exp, | |
| iat = iat, | |
| iss = Just issuer, | |
| sub = Just subject, | |
| aud = audience', | |
| unregisteredClaims = unregisteredClaims | |
| } | |
| pure $ Right (encodeSigned key jwtHeader claims) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/mobility-core/src/Kernel/Utils/JWT.hs` around lines 123 - 150, The
`createSignedJWTWithClaims` function should validate inputs before encoding the
token: reject any audience that cannot be converted by `stringOrURI` and return
an error instead of proceeding with missing `aud`, `iss`, or `sub`, and also
fail fast when `ttlSeconds` is zero or negative so the JWT is never created
already expired. Update the logic around `stringOrURI`, `audience'`, and the
`exp` calculation in `createSignedJWTWithClaims` to produce a `Left` for invalid
audience or non-positive TTL, while keeping the existing signing flow unchanged
for valid inputs.
45b76d4 to
b15d415
Compare
Add Kernel.External.FleetEngine.{Types,Config,Client,Auth} providing a
Servant client for the Google Fleet Engine (On-demand Rides & Deliveries)
trip API (createTrip / updateTrip / updateTripStatus / assignVehicleAndStart),
a per-city FleetEngineCfg (encrypted service-account JSON), and self-signed
scoped-JWT minting (consumer/driver/server) reusing Kernel.Utils.JWT.
createTrip is keyed on the BPP ride id so re-issues are idempotent
(ALREADY_EXISTS treated as success). Foundation for rider/driver journey
sharing; no call sites yet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4VptpJt2kd4GQSDXcWL1c
Satisfy the pre-commit CI hook: sort the FleetEngine modules alphabetically in the cabal, convert TokenScope trailing haddocks to leading, and fix the createTrip guard-body indentation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R4VptpJt2kd4GQSDXcWL1c
b15d415 to
800c68c
Compare
Type of Change
Description
Adds a new
Kernel.External.FleetEngine.*module tree — the shared foundation for Google Fleet Engine (On-demand Rides & Deliveries / journey sharing), consumed by the driver-app (BPP) trip mirroring and the rider/driver auth-token endpoints.Types.hs—TripStatus/TripTypeenums (JSON spelled exactly as the Fleet Engine REST enums),LatLng,TerminalLocation, a partialTripresource (Nothing fields omitted so a PATCH never clobbers server state), andmkCreateTripBody.Config.hs—FleetEngineCfg(provider/project id, optional base URL, encrypted service-account JSON, per-token-type TTLs), modelled onKernel.External.Maps.Google.Config.GoogleCfg.Auth.hs— mints short-lived self-signed Fleet Engine JWTs scoped to a trip (consumer), a vehicle (driver), or unrestricted (server), reusing the existing Google service-account signing inKernel.Utils.JWT.Client.hs— Servant client (createTrip,updateTrip,updateTripStatus,assignVehicleAndStart) modelled onKernel.External.Maps.Google.MobilityBilling.createTripis keyed on the BPP ride id, so a re-issue returnsALREADY_EXISTSand is treated as an idempotent success.Kernel.Utils.JWT— addscreateSignedJWTWithClaims(a self-signed JWT with caller-supplied audience/TTL/claims), used byAuth.hs.Reuses the existing Google SA primitives (
ServiceAccount,Web.JWTsigning) and thecallAPIServant pattern — no new heavy dependencies. There are no call sites in this repo; the driver-app PR wires it in.Additional Changes
Motivation and Context
Rider-side journey-sharing ETAs (driver→pickup and vehicle→destination) require the ride to exist as a Fleet Engine trip. Fleet Engine has no "bring-your-own-data" mode, so the backend must mirror trips and mint scoped SDK tokens. This is the shared client/auth layer those services build on.
How did you test it?
Kernel.External.FleetEngineis a self-contained library addition that mirrors the existing Google Maps client/auth patterns. It builds withcabal buildin the Nix dev shell (not runnable in the authoring sandbox, which has nonix/cabal). Suggested follow-up: HUnit tests for JWT claim/expiry shape andTripStatusJSON, plus a staging smoke test ofcreateTrip/updateTripStatus(asserting idempotentALREADY_EXISTS).Checklist
./dev/format-all-files.sh🤖 Generated with Claude Code
Generated by Claude Code
Summary by CodeRabbit