Skip to content

Telemetry: slices 4 + 2 — LastKnownPosition store + eviction, gRPC ReportLocations ingest - #45

Merged
erikshafer merged 5 commits into
mainfrom
telemetry/slices-4-and-2-transport
Jul 24, 2026
Merged

Telemetry: slices 4 + 2 — LastKnownPosition store + eviction, gRPC ReportLocations ingest#45
erikshafer merged 5 commits into
mainfrom
telemetry/slices-4-and-2-transport

Conversation

@erikshafer

@erikshafer erikshafer commented Jul 24, 2026

Copy link
Copy Markdown
Owner

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 (LastKnownPosition store + heartbeat-absence eviction) and slice 2 (gRPC ReportLocations client-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 only WolverineFx + WolverineFx.Grpc would 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.Protobuf 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. First <Protobuf> item in the repo.

Chunk B — slice 4 (3222549, LastKnownPosition/)
LastKnownPosition is a plain Marten document defending no invariant — LWW on ServerReceivedAt is the entire concurrency story. It ships with a single timestamp: 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.

Eviction is split, because Wolverine has no first-class recurring-message primitive (ScheduleAsync is one-shot delayed delivery; ToLocalQueue() is routing config). The timer is a plain BackgroundService holding no logic; EvictStalePositionsHandler holds everything testable. It uses HardDeleteWhere rather than DeleteWhere — 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/)
TelemetryGrpcService is an empty abstract [WolverineGrpcService]. Two codegen layers stack: protoc emits the virtual ReportLocations over a raw IAsyncStreamReader, then Wolverine emits the override adapting it to IAsyncEnumerable and forwarding to IMessageBus.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/Validate frames 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 — see GrpcServiceChain.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 no driver_id field) and IDriverLocationPublisher, carrying the generated DriverLocationUpdated so PR C swaps only the implementation, never the contract.

Firsts in code

  • First gRPC surface that serves traffic, and the first client-streaming RPC — the WolverineFx.Grpc feature this project exists to showcase. Auto-generated, not hand-wired.
  • First codegen behind a proto. protos/ files have been authored contracts with nothing consuming them since PR Telemetry v1 protobuf contracts (protos/crittercab/telemetry/v1) #39.
  • First non-event-sourced document write path. Everything prior in CritterCab is event-sourced.
  • First BackgroundService, and the first Wolverine message invocation from a test.
  • First geospatial dependency (pocketken.H3).

The client-streaming forward-constraint is retired

Every prior handoff recorded that WolverineFx.Grpc could not auto-generate stream in → unary out and that ReportLocations would have to be hand-wired against IMessageBus. 6.21.0 shipped the emit path, and this PR consumes it. Nothing here is hand-wired. The stale comment in report_locations.proto and the two gRPC skills that still describe the workaround are corrected in chunk D.

Verification

  • Gates 1–11 all closed. Gate 7 resolved by the bump itself (Marten 9.14 → 9.16.1, JasperFx 2.30.1, clean). Gate 8b closed against nuget.org — pocketken.H3 4.5.0.1 ships a native lib/net10.0 and pulls NetTopologySuite transitively, confirming the degrees-native Coordinate path costs no added dependency. Gate 11 dissolved: Alba wraps WebApplicationFactory over TestServer, so GetTestServer().CreateHandler() feeds a GrpcChannel directly — CritterCab keeps its Alba-first default with no parallel host recipe and no new test packages.
  • 31/31 tests green locally, 16 of them new. Docker is working again (it was wedged throughout Telemetry: service skeleton + slice 1 (TelemetryPolicyConfigured config-as-events) #42), so Testcontainers ran locally rather than leaning on CI.
  • The H3 pinning test proves order and units. It computes the same point down both API paths — Coordinate(lon, lat) in degrees and LatLng(lat, lon) in radians, mirror opposites on both axes, so they can only agree if production has both right — then separately asserts a swapped Coordinate yields 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.
  • The DI guard was mutation-verified. IMessageBus is scoped and a BackgroundService is a singleton, so the eviction shell takes an IServiceScopeFactory. Temporarily injecting the bus directly was confirmed to fail host construction via CallSiteValidator — the regression test has real teeth.

Fixed in passing: apphost.cs did not compile

It has been broken since #42, which added the Telemetry service block without its #:project directive, so Projects.CritterCab_Telemetry never existed. CI only builds CritterCab.slnx and 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

Decision Resolution
Version target 6.21.0, not 6.22.0 — keeps the verified gate citations matching the shipped binaries
LastKnownPosition shape Minimal, one timestamp. LastPublishedAt collapsed into ServerReceivedAt; §3.3's DeviceTimestamp/Speed/Heading omitted (slice 3 passes them to Kafka straight from the ping)
lat/lon vs lng Lon — the proto is the contract (ADR-009); §3.3's prose says lng
Publisher seam payload The generated DriverLocationUpdated, so PR C swaps only the implementation
accuracyMeters threshold Documented constant at 100m. W006 §6.2 names the threshold but fixes no value — this number is invented here, following §6.4's precedent for the 3×-heartbeat constant

Chunk 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 against IMessageBus 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 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 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 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 = 100 was invented at implementation time), both index entries, and the retro.

Resolved during chunk D

  • Test class naming — registered as a DEBT row rather than fixed. The audit confirms testing-fundamentals is unambiguous and that all three Telemetry suites violate it, including Slice1TelemetryPolicyTests from 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.
  • The three skill gaps — all registered, along with a fourth the audit surfaced: 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.cs break survived two weeks because CI builds CritterCab.slnx and 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

  • C — slice 3 (DriverLocationUpdated → Kafka topic telemetry.driver-location-updated, partitioned by driverId). Swaps LoggingDriverLocationPublisher for the real producer, wires Kafka into apphost.cs, fires the topic-naming ADR candidate.
  • D — slice 5 (Dispatch consumer: replaces NearbyAvailableDriversStub with the Kafka-fed view).

…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 erikshafer self-assigned this Jul 24, 2026
@erikshafer
erikshafer marked this pull request as ready for review July 24, 2026 23:39
@erikshafer
erikshafer merged commit 144899d into main Jul 24, 2026
1 check passed
@erikshafer
erikshafer deleted the telemetry/slices-4-and-2-transport branch July 24, 2026 23:46
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant