Telemetry: slices 4 + 2 — LastKnownPosition store + eviction, gRPC ReportLocations ingest - #45
Merged
Merged
Conversation
…est) Session prompt for PR B of the W006 Telemetry chain: slice 4 (LastKnownPosition store + eviction) and slice 2 (gRPC ReportLocations client-streaming ingest). Includes the 2026-07-21 Verify-before-wiring source-verification pass and the 2026-07-24 consistency alignment: - fork 3 and gate 8a now name the NTS Coordinate-degrees H3 recipe; the H3Index.FromLatLng tuple path is marked as the radians trap it is - gate 8b closed: package id is pocketken.H3 4.5.0.1, ships a native lib/net10.0, pulls NetTopologySuite 2.6.0 transitively - deliverables 3 and 6 carry gate 5's BackgroundService + EvictStalePositions handler split (Wolverine has no first-class recurring primitive) and gate 6's HardDeleteWhere refinement - deliverable 1 pins the bump to 6.21.0 (the source-verified V6.21.0-12 baseline, not the newer 6.22.0) and makes it lockstep across all 11 WolverineFx entries The previous subject on this commit claimed to implement slices 4 and 2; it only ever contained this prompt document.
…unk A)
First codegen-behind-a-proto in the repo. Chunk A of prompt 007 — the
version bump and codegen wiring that slices 4 and 2 build on.
Directory.Packages.props
- all 11 WolverineFx* entries 6.19.0 -> 6.21.0 in lockstep. Only WolverineFx
and WolverineFx.Grpc strictly need it (6.21.0 is the release that added
client-streaming auto-codegen), but bumping the pair alone would leave the
other nine as a version island.
- 6.21.0 rather than the newer 6.22.0: 6.21.0 is what prompt 007's
Verify-before-wiring citations were source-verified against
(local checkout V6.21.0-12), so the gate line numbers keep matching the
binaries we build on. 6.22.0 is a later tidy: packages bump.
- new Grpc.AspNetCore / Grpc.Tools / Google.Protobuf entries, pinned to
exactly what WolverineFx.Grpc 6.21.0 declares (2.76.0 / 2.76.0 / 3.31.1)
so the codegen toolchain matches Wolverine's own tested pairing.
CritterCab.Telemetry.csproj
- adds WolverineFx.Grpc plus the three gRPC packages.
- adds the <Protobuf> item compiling report_locations.proto in from the
repo-root protos/ tree. The proto stays there because the contract is the
artifact (ADR-009) and is shared across services; ProtoRoot points at that
tree so the proto's package path resolves.
Verified: dotnet build emits TelemetryService.TelemetryServiceBase with
Task<LocationIngestAck> ReportLocations(
IAsyncStreamReader<LocationPing>, ServerCallContext)
and MethodType.ClientStreaming — the shape gate 1 predicted. Full solution
builds with 0 warnings; 15/15 tests pass locally against the new dependency
line (Marten 9.16.1, JasperFx 2.30.1), resolving gate 7.
…tion (chunk B) W006 §6.4 and §3.3. CritterCab's first non-event-sourced document write path and first recurring/scheduled work in the repo. LastKnownPosition — a plain Marten document, overwrite-in-place, defending no invariant: LWW on ServerReceivedAt is the whole concurrency story. Ships the minimal single-timestamp shape: ServerReceivedAt serves BOTH §6.2's lastPublishedAt trigger baseline AND §6.4's eviction key, because §6.4 locks upsert-on-publish-only, which makes them the same instant by construction. A separate LastPublishedAt would have held a duplicate value and forced eviction and the trigger to read different names for one moment. §3.3's DeviceTimestamp / Speed / Heading are omitted deliberately — slice 3 passes speed and heading to Kafka straight from the ping, so the document never reads them. Field is Lon, not §3.3's Lng: the proto is the contract (ADR-009). Eviction is split, per the prompt's gate 5 — Wolverine has no first-class recurring-message primitive, so the timer is a plain BackgroundService and only the work is Wolverine's: - LastKnownPositionEvictionService holds no logic and stays untested: it loops on Task.Delay, opens a scope, and calls InvokeAsync (not PublishAsync, so a slow sweep back-pressures its own timer rather than overlapping the next tick). It takes IServiceScopeFactory rather than IMessageBus because Wolverine registers IMessageBus scoped (HostBuilderExtensions.cs:232) and a BackgroundService is a singleton — injecting the bus directly fails DI scope validation at host build. - EvictStalePositionsHandler holds everything testable: reads HeartbeatIntervalSeconds from the TelemetryPolicy view, derives the 3-missed-heartbeats threshold (a documented constant, not a v1 policy param), and HardDeleteWhere's the stale rows. HardDeleteWhere rather than DeleteWhere so "the row is gone" survives a future soft-delete config — DeleteWhere would silently degrade to a flag and break the Return GWT. No staleness event is published (v1, R3/R8). Registered inside Program.cs's Marten guard: without a store there is nothing to sweep and the timer would only log failures. Tests (6 new, 21/21 green): Upsert, Evict, threshold-respects-fresh-documents, Return, and a proof the threshold is read from policy rather than hardcoded (the same 10-second-old document survives under the seeded 30s heartbeat and is swept under a reconfigured 1s one). The §6.4 "No-write" GWT is not here — deciding not to write is slice 2's trigger, which lands in chunk C. Also covers the production host wiring, which nothing else reaches: the fixture host strips the timer for determinism and the smoke test runs without a connection string, so the registration would otherwise be untested. Verified by mutation that the guard actually fails when the bus is injected directly.
…unk C) W006 §6.2. CritterCab's first gRPC surface that serves traffic, and the client-streaming shape that was blocked for three months until WolverineFx.Grpc 6.21.0 shipped auto-codegen for it. Nothing here is hand-wired. TelemetryGrpcService is an empty abstract [WolverineGrpcService] deriving the generated TelemetryServiceBase. Two codegen layers stack: protoc emits the virtual ReportLocations over a raw IAsyncStreamReader, then Wolverine emits the override that adapts it to IAsyncEnumerable and forwards to IMessageBus.StreamAsync. ReportLocationsHandler therefore never sees a gRPC type — the whole stream is simply the message. The per-ping pipeline lives inside the handler, which is forced rather than stylistic: Wolverine cannot weave Before/Validate frames for client-streaming because a before-frame needs a concrete request at method entry and a stream cannot supply one. So unlike slice 1, which validates at the HTTP boundary with FluentValidation, validation and the publish trigger are handler concerns here. Trigger, per §6.2: shouldPublish = heartbeatDue OR (cellChanged AND throttleFloorElapsed), evaluated against slice 4's document. Policy and baseline are read once at window open and the baseline kept current in memory — safe because within a window the driver's own pings are the sole writer of their own document. An absent baseline publishes immediately, which is what closes §6.4's Return case. On publish it goes publish-first, then store: that ordering fails benignly (a duplicate the consumer's dedup absorbs) where the reverse fails as a missed cell change. Invalid pings are silently dropped, never errored. Seams, both ready-to-swap rather than final: - IDriverPrincipalAccessor resolves driverId from the principal, never the payload (R5) — which is why the proto has no driver_id field. The dev stub reads x-driver-id; gRPC metadata travels as HTTP/2 headers so it arrives there. Documented swap site for a real Entra claim. - IDriverLocationPublisher carries the generated DriverLocationUpdated — the real published language, so PR C swaps only the implementation, never the contract. driver_location_updated.proto joins codegen for this (GrpcServices="None", message-only). No Kafka is wired. H3CellIndexer wraps one line because that line has a footgun on both axes: H3's LatLng is (lat, lon) in radians while NTS Coordinate is (lon, lat) in degrees. The degrees-native Coordinate path removes the unit conversion entirely. The pinning test computes the same point down both paths — opposite on order AND units, so they can only agree if production has both right — and separately proves a swapped Coordinate yields a different cell, since "is a valid cell" passes happily for the wrong place on Earth. apphost.cs did not compile, and had not since PR #42 added the Telemetry service block without its #:project directive; CI never builds the apphost so nothing caught it. Fixed here because deliverable 3 cannot land on a file that does not build. gRPC rides the existing HTTPS endpoint over HTTP/2, matching the convention the Dispatch block already documents, so it needs no port of its own. Tests: 31/31 green. Five drive a real gRPC client stream against the Alba host — gate 11 resolves cleanly, since Alba wraps WebApplicationFactory over TestServer, so GetTestServer().CreateHandler() reaches the same in-memory transport and CritterCab keeps its Alba-first default with no parallel host recipe. Covers Happy publish, Throttled, Heartbeat, Window close and unauthenticated, plus the §6.4 No-write GWT that slice 4 could not reach alone because deciding not to write is this slice's trigger.
Closes the session's documentation deliverables and retires the client-streaming forward-constraint everywhere it was recorded. report_locations.proto — the stale comment directing a hand-wire against IMessageBus is corrected. This one mattered beyond tidiness: the proto's leading comments become doc-comments in the generated ReportLocationsGrpc.cs, so the obsolete instruction was being compiled into the build output. The replacement also records the middleware caveat at contract level, since it is a property of the RPC shape rather than of our implementation. wolverine-grpc-handlers — corrected under the session-runner-blocking exception: a session cannot follow a skill that tells it to hand-wire a shape the library now generates. Seven stale claims fixed (frontmatter, intro, why-not-code-first, the abstract-stub note, two pitfalls, see-also), and a new Client-streaming handlers section added mirroring the server-streaming one. It documents the empty stub, the two stacked codegen layers, the IAsyncEnumerable handler shape, and three things the session had to discover from source rather than docs: middleware does NOT weave and fails silently rather than loudly; identity must come from an IHttpContextAccessor seam because there is no [WolverineBefore] to read ServerCallContext from; and the handler slot is keyed on the request type, so two client-streaming RPCs sharing a request type collide. wolverine-grpc-bidirectional-handlers — banner plus mental-model corrections only, as scoped. The ~145-line hand-written-workaround body is left intact under an explicit obsolete banner rather than half-rewritten; its removal is a DEBT row. The mental-model table and the prose around it were corrected too, because leaving "Rejected at startup" in the most-read section would have kept the file contradicting shipped code regardless of the banner. DEBT.md — six rows: the bidirectional rewrite; test-class naming across all three Telemetry suites (a decision, not a cleanup, and explicitly not to be fixed slice-locally); no skill for plain-document write paths; no skill for recurring work; identity-acl's gRPC auth pattern not applying to streaming shapes; and the feature-folder/type-name collision now on its third occurrence. W006 Document History — slices 2 and 4 designed -> realized, the forward constraint closed, and the §11 windowed-client-streaming candidate recorded as firing into a skill rather than an ADR. It also records that MaxAccuracyMeters = 100 was invented at implementation time, since §6.2 names the threshold but fixes no value. Retro — records what the session learned rather than what it did: that the verification pass earned its cost by disconfirming its own prompt twice, that both load-bearing tests were built to fail a naive implementation, that the DI guard was mutation-verified, and that the apphost break survived two weeks because CI's existing completeness guard cannot see a file-based app. 31/31 still green.
erikshafer
marked this pull request as ready for review
July 24, 2026 23:39
erikshafer
added a commit
that referenced
this pull request
Jul 25, 2026
…t-agnostic topic naming (#46) * docs: add prompt 008 — Telemetry slice 3 (DriverLocationUpdated -> Kafka) Session prompt for PR C. The IDriverLocationPublisher seam shipped in PR #45 already carries the generated DriverLocationUpdated, so this session swaps only the implementation and must not touch the contract. A jasperfx-source-verifier pass ran during authoring and closed all six verify-before-wiring gates against local wolverine @ V6.21.0-12. It surfaced the session's one load-bearing fork: W006 §6.3 argues publish-first from failure-mode asymmetry, but a Kafka publishing endpoint defaults to BufferedInMemory, where PublishAsync returns before the broker acks — making that ordering nominal. Resolved to SendInline + UseSyncRetryBlock + UseIdempotentProducer. W006 §11 candidate #1 fires here (first Kafka topic lands) and lands as ADR-019 inside this PR, which also supplies the ADR-004 design-return interleave. * Telemetry slice 3: publish DriverLocationUpdated to Kafka Swaps LoggingDriverLocationPublisher for a real WolverineFx.Kafka producer behind the unchanged IDriverLocationPublisher seam (W006 §6.3). CritterCab's first Kafka topic and second live transport; ReportLocationsHandler is untouched, which is what the seam existed for. Wiring: telemetry.driver-location-updated, partition key driverId, endpoint-scoped binary protobuf, AutoProvision, and the broker read by name so Aspire, the Testcontainer, and Event Hubs all work without branching. SendInline + UseSyncRetryBlock rather than the BufferedInMemory default. Buffered returns before the broker acks and Wolverine's default async retry block swallows the failure, which would have made §6.3's publish-before-store ordering true only in statement order — the upsert would land on a lost publish, the branch §6.3 argued against. Inline plus the sync block makes a rejection throw, so the baseline stays stale and the next ping republishes. Tests: a broker-backed round trip asserting the partition key and that the value is binary protobuf the generated parser accepts, on its own fixture so the other suites do not wait on Kafka; plus an ordering test pinning that a failed publish leaves no baseline. 33/33 green. * docs: ADR-019 transport-agnostic topic naming, W006 slice 3 realized, retro Fires W006 §11 candidate #1, whose trigger was literally "first Kafka topic lands". ADR-019 generalizes ADR-014's <source-bc>.<event-name-kebab> across transports while explicitly withholding its two ASB-specific operational clauses — session keying and outbox coordination — behind a per-transport table. ADR-014 stays Accepted and authoritative for ASB, with a scope note pointing at 019. It also resolves a contradiction that predated this session: the wolverine-kafka skill had independently proposed a stream-descriptive naming rule for Kafka, so the repo held two conventions before either had a real topic to name. That skill's topic-naming and serialization sections are corrected here under the session-runner-blocking exception — a session cannot follow a skill that contradicts the ADR it is authoring. The listener-side examples still name a LocationPing/telemetry.location-pings pairing that never existed; deferred to PR D as DEBT rather than swapped for differently speculative names. W006's Document History records the publish-first qualifier as an amendment rather than a correction: §6.3's failure-mode argument is sound but never states its own precondition, that the publish outcome is known when the store runs. Three DEBT rows: wolverine-kafka listener examples, transport-selection's missing built-vs-modeled status axis, and the aspire skill's AddKafka example, which does not compile on 13.4.6. * docs: fix skill drift and stale csproj comment found by the Phase 2 audit The topic-naming correction left section 'Convention-based routing' two sections below still justifying itself with the rejected descriptive-name rule, inside a file whose new banner claimed that section was reconciled. Rewritten to the real reason Cab declines convention-based routing: it would bind a wire-visible topic to a C# type name, so a refactor rename would repoint the producer silently. Fourth DEBT row from the same audit — service-bootstrap does not sanction the optional connection-string guard Telemetry has now used twice. * refactor: address two-axis code review findings Standards axis, both live rather than theoretical: - Testcontainers were unnamed. Latent while the project had one container; slice 3 added a second Postgres and xUnit runs the two collections in parallel, so a fixed name now collides. Both fixtures get unique names. Following the skill's own fix required correcting it twice over -- WithPullPolicy is really WithImagePullPolicy, and PullPolicy lives in DotNet.Testcontainers.Images. - A guard clause inside the UseWolverine lambda would have silently swallowed any configuration appended below it on the broker-less path, and the connection string was branched on twice in opposite polarity. Read once into kafkaEnabled, extracted ConfigureKafkaPublishing. - StreamAsync/PingAt had been copied into three test classes. Extracted ReportLocationsClient; updated the pre-existing slice-2 copy too, since leaving one caller off a helper this session introduced is worse than the duplication it replaces. Spec axis: closed deliverable 7 (the proto comment now records shipped state), and dropped two speculative topic names the skill correction had minted in the same file whose new banner warns against speculative names. Two DEBT rows added: testing-integration's wrong API names and its collection convention that no shipped fixture follows, and the CI-cannot-build-apphost gap. 33/33 green.
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.
All four chunks complete — implementation (A–C) and docs/ledger (D), including the retro, which this repo's cadence ships in the session's own PR.
PR B of the W006 Telemetry transport chain, following #42 (skeleton + slice 1). Realizes slice 4 (
LastKnownPositionstore + heartbeat-absence eviction) and slice 2 (gRPCReportLocationsclient-streaming ingest) together, because they are mutually dependent: slice 2's publish trigger evaluates against slice 4's document, and slice 4's document is written only when slice 2 decides to publish. Building either alone means evaluating against a document that does not exist, or writing one nothing reads.This is the PR where CritterCab's showcase premise starts running. gRPC has been richly modeled across five workshops and sixteen ADRs and wired in zero lines of code. It now serves traffic.
What landed
Chunk A — version bump + proto codegen (
aa31558)All eleven
WolverineFx*entries 6.19.0 → 6.21.0 in lockstep (bumping onlyWolverineFx+WolverineFx.Grpcwould have left the other nine as a version island). 6.21.0 deliberately rather than the newer 6.22.0: it is the version the prompt's Verify-before-wiring citations were source-verified against, so the gate line numbers keep matching the binaries.Grpc.AspNetCore/Grpc.Tools/Google.Protobufpinned to exactly whatWolverineFx.Grpc 6.21.0declares (2.76.0 / 2.76.0 / 3.31.1), so the codegen toolchain matches Wolverine's own tested pairing. First<Protobuf>item in the repo.Chunk B — slice 4 (
3222549,LastKnownPosition/)LastKnownPositionis a plain Marten document defending no invariant — LWW onServerReceivedAtis the entire concurrency story. It ships with a single timestamp:ServerReceivedAtserves both §6.2'slastPublishedAttrigger baseline and §6.4's eviction key, because §6.4 locks upsert-on-publish-only, which makes them the same instant by construction.Eviction is split, because Wolverine has no first-class recurring-message primitive (
ScheduleAsyncis one-shot delayed delivery;ToLocalQueue()is routing config). The timer is a plainBackgroundServiceholding no logic;EvictStalePositionsHandlerholds everything testable. It usesHardDeleteWhererather thanDeleteWhere— the latter silently degrades to a soft-delete flag if the document is ever configured for one, which would break the Return GWT.Chunk C — slice 2 (
ce34000,ReportLocations/)TelemetryGrpcServiceis an empty abstract[WolverineGrpcService]. Two codegen layers stack: protoc emits the virtualReportLocationsover a rawIAsyncStreamReader, then Wolverine emits the override adapting it toIAsyncEnumerableand forwarding toIMessageBus.StreamAsync. The handler consequently never references a gRPC type — the whole stream is the message.The per-ping pipeline lives inside the handler, which is forced rather than stylistic: Wolverine cannot weave
Before/Validateframes for client-streaming, because a before-frame needs a concrete request at method entry and a stream cannot supply one. This is a deliberate, documented departure from slice 1's FluentValidation-at-the-boundary pattern — seeGrpcServiceChain.cs:270-272.Both fan-out targets are ready-to-swap seams:
IDriverPrincipalAccessor(driverId from the principal, never the payload — R5, which is why the proto has nodriver_idfield) andIDriverLocationPublisher, carrying the generatedDriverLocationUpdatedso PR C swaps only the implementation, never the contract.Firsts in code
protos/files have been authored contracts with nothing consuming them since PR Telemetry v1 protobuf contracts (protos/crittercab/telemetry/v1) #39.BackgroundService, and the first Wolverine message invocation from a test.pocketken.H3).The client-streaming forward-constraint is retired
Every prior handoff recorded that WolverineFx.Grpc could not auto-generate
stream in → unary outand thatReportLocationswould have to be hand-wired againstIMessageBus. 6.21.0 shipped the emit path, and this PR consumes it. Nothing here is hand-wired. The stale comment inreport_locations.protoand the two gRPC skills that still describe the workaround are corrected in chunk D.Verification
pocketken.H34.5.0.1 ships a nativelib/net10.0and pulls NetTopologySuite transitively, confirming the degrees-nativeCoordinatepath costs no added dependency. Gate 11 dissolved: Alba wrapsWebApplicationFactoryoverTestServer, soGetTestServer().CreateHandler()feeds aGrpcChanneldirectly — CritterCab keeps its Alba-first default with no parallel host recipe and no new test packages.Coordinate(lon, lat)in degrees andLatLng(lat, lon)in radians, mirror opposites on both axes, so they can only agree if production has both right — then separately asserts a swappedCoordinateyields a different cell. A swapped-axis bug produces a perfectly valid cell in the Indian Ocean, so "is a valid cell" would have shipped it.IMessageBusis scoped and aBackgroundServiceis a singleton, so the eviction shell takes anIServiceScopeFactory. Temporarily injecting the bus directly was confirmed to fail host construction viaCallSiteValidator— the regression test has real teeth.Fixed in passing:
apphost.csdid not compileIt has been broken since #42, which added the Telemetry service block without its
#:projectdirective, soProjects.CritterCab_Telemetrynever existed. CI only buildsCritterCab.slnxand never the file-based apphost, so nothing caught it. Fixed here because the gRPC-endpoint deliverable cannot land on a file that does not build. gRPC needs no port of its own — it rides the existing HTTPS endpoint over HTTP/2, matching the convention the Dispatch block already documents. The CI gap is the more durable finding and is flagged for the retro.Decisions taken during the session
LastKnownPositionshapeLastPublishedAtcollapsed intoServerReceivedAt; §3.3'sDeviceTimestamp/Speed/Headingomitted (slice 3 passes them to Kafka straight from the ping)lat/lonvslngLon— the proto is the contract (ADR-009); §3.3's prose sayslngDriverLocationUpdated, so PR C swaps only the implementationaccuracyMetersthresholdChunk D — docs and ledger (
cf4ec73)The stale proto comment mattered beyond tidiness. A
.proto's leading comments become doc-comments in the generated C#, so the instruction to hand-wire againstIMessageBuswas being compiled into the build output. The replacement also records the middleware caveat at contract level, since it is a property of the RPC shape rather than of our implementation.wolverine-grpc-handlers— corrected under the session-runner-blocking exception (a session cannot follow a skill telling it to hand-wire a shape the library now generates). Seven stale claims fixed, plus a new Client-streaming handlers section mirroring the server-streaming one. It documents three things this session had to learn from source rather than docs: middleware does not weave and fails silently rather than loudly; identity must come from anIHttpContextAccessorseam because there is no[WolverineBefore]to readServerCallContextfrom; and the handler slot is keyed on the request type, so two client-streaming RPCs sharing one would collide.wolverine-grpc-bidirectional-handlers— banner plus mental-model corrections only, as scoped. The ~145-line workaround body is left intact under an explicit obsolete banner rather than half-rewritten; its removal is a DEBT row. The mental-model table was corrected, because leaving "Rejected at startup" in the most-read section would have kept the file contradicting shipped code regardless of the banner.Six DEBT rows, W006's Document History (including that
MaxAccuracyMeters = 100was invented at implementation time), both index entries, and the retro.Resolved during chunk D
testing-fundamentalsis unambiguous and that all three Telemetry suites violate it, includingSlice1TelemetryPolicyTestsfrom Telemetry: service skeleton + slice 1 (TelemetryPolicyConfigured config-as-events) #42. That makes it rename-all-three or amend-the-skill; fixing only the two new ones would deepen the exact inconsistency the skill's own pitfall warns about.identity-acl's gRPC auth pattern depends on the same single-request binding client-streaming lacks, so it silently does not apply to streaming shapes and says nothing about it.Known follow-up: CI cannot see the apphost
The
apphost.csbreak survived two weeks because CI buildsCritterCab.slnxand never the file-based app. CI does have a "Verify solution completeness" step for exactly this class of mistake — but the apphost has no.csproj, so it falls through a guard already written to catch it. The fix is to extend that step, not to add a new one. Left for its own session; CI changes are their own scope.Follow-on PRs
DriverLocationUpdated→ Kafka topictelemetry.driver-location-updated, partitioned bydriverId). SwapsLoggingDriverLocationPublisherfor the real producer, wires Kafka intoapphost.cs, fires the topic-naming ADR candidate.NearbyAvailableDriversStubwith the Kafka-fed view).