diff --git a/docs/prompts/README.md b/docs/prompts/README.md index d944400..11b65d9 100644 --- a/docs/prompts/README.md +++ b/docs/prompts/README.md @@ -158,6 +158,7 @@ Subsequent sections are prompt-specific. Existing prompts in this directory serv ### Decisions - [`decisions/001-protobuf-ride-assigned.md`](./decisions/001-protobuf-ride-assigned.md) — Author the three Dispatch business-event protobuf contracts (`RideAssigned`, `RideRequestCancelled`, `RideRequestAbandoned`) and establish the `/protos/` directory. First exercise of ADR-009. Status: complete (2026-05-07). Produced proto files in `/protos/crittercab/dispatch/v1/` and `/protos/crittercab/common/v1/`. +- [`decisions/003-protobuf-telemetry-v1.md`](./decisions/003-protobuf-telemetry-v1.md) — Author the Telemetry `v1` protobuf surface (W006 §10) under `/protos/crittercab/telemetry/v1/`: the `TelemetryService.ReportLocations` gRPC **client-streaming** ingest with `LocationPing` / `LocationIngestAck`, and the `DriverLocationUpdated` **Kafka** event message (ADR-018 supplier half). First gRPC client-streaming surface and first Kafka-event proto in CritterCab; realizes the design locked by W006 (R1–R8) + ADR-018. Four durable wire/config forks put to the user and resolved: keep ubiquitous-language message names via the repo's **first scoped `buf.yaml` `ignore_only` exception** (the two `RPC_*_STANDARD_NAME` rules, telemetry ingest file only) rather than renaming to `...Request`/`...Response`; flat `lat`/`lon` scalars (workshop's flat shape, repo-consistent `lon` spelling); `int64` `throttle_policy_version` (verified numeric Marten stream version); buf codegen deferred to the implementation session (no service `.csproj` yet — PR #4 precedent). `jasperfx-source-verifier` surfaced a WolverineFx.Grpc **client-streaming auto-codegen gap** (startup `NotSupportedException` on the verified 5.x line) — recorded as an implementation forward-constraint, not a change to the locked contract. Status: complete (2026-07-04). Produced retro at [`retrospectives/decisions/003-protobuf-telemetry-v1.md`](../retrospectives/decisions/003-protobuf-telemetry-v1.md). ### Implementations diff --git a/docs/prompts/decisions/003-protobuf-telemetry-v1.md b/docs/prompts/decisions/003-protobuf-telemetry-v1.md new file mode 100644 index 0000000..c5316cc --- /dev/null +++ b/docs/prompts/decisions/003-protobuf-telemetry-v1.md @@ -0,0 +1,90 @@ +# Prompt 003 — Author Telemetry v1 Protobuf Contracts + +| Field | Value | +|---|---| +| **Status** | Complete (2026-07-04). Produced proto files in `/protos/crittercab/telemetry/v1/`; added the repo's first scoped `buf.yaml` lint exception. | +| **Authored** | 2026-07-04 | +| **Target artifacts** | `/protos/crittercab/telemetry/v1/report_locations.proto`, `/protos/crittercab/telemetry/v1/driver_location_updated.proto` | +| **Companion artifacts** | `/protos/buf.yaml` (scoped `ignore_only` lint exception); `docs/retrospectives/decisions/003-protobuf-telemetry-v1.md` | +| **Source-of-truth dependencies** | [`docs/workshops/006-telemetry-event-model.md`](../../workshops/006-telemetry-event-model.md) §10 (proto surface), §6.2 (ingest), §6.3 (Kafka publish), §4 (UL); [`docs/decisions/018-candidate-projection-ownership-and-telemetry-geospatial-supply.md`](../../decisions/018-candidate-projection-ownership-and-telemetry-geospatial-supply.md); [`docs/decisions/009-protobuf-contracts-as-first-class-artifacts.md`](../../decisions/009-protobuf-contracts-as-first-class-artifacts.md); [`docs/skills/protobuf-contracts/SKILL.md`](../../skills/protobuf-contracts/SKILL.md); [`docs/skills/cli-grpc-tooling/SKILL.md`](../../skills/cli-grpc-tooling/SKILL.md) | +| **Workflow position** | Third proto-authoring session (after PR #4 dispatch/common, and the pricing bundle in slice 5.2). First Telemetry contract; first gRPC **client-streaming** surface and first **Kafka event** message proto in CritterCab. Item 2 of the [post-tidy-38 handoff](../../planning/2026-07-02-post-tidy38-telemetry-protos-handoff.md). | + +--- + +## Framing — why this session exists + +Workshop 006 §10 named the Telemetry `v1` protobuf surface as an owed follow-up (PR #4 precedent): the `TelemetryService` client-streaming ingest RPC plus its message pair, and the `DriverLocationUpdated` Kafka event that supplies Dispatch's `NearbyAvailableDrivers` projection per ADR-018. The design is fully locked — R1–R8, the five W006 slices, and ADR-018 stand without re-litigation. This session is **transcription of a locked design into wire contracts**, not new design work. It is the smallest code-adjacent move that unblocks the later implementation chain (the `CritterCab.Telemetry` service, gRPC client-streaming ingest, the first Kafka topic, and the Dispatch consumer side). + +--- + +## Goal + +Author the two Telemetry `v1` protobuf files under `/protos/crittercab/telemetry/v1/` — the `TelemetryService.ReportLocations` client-streaming ingest (with `LocationPing` / `LocationIngestAck`) and the `DriverLocationUpdated` Kafka event message — and add a file-scoped `buf.yaml` lint exception that preserves the ubiquitous-language message names. + +## Spec delta + +- **W006 §10 realized:** the candidate protobuf surface (`TelemetryService.ReportLocations(stream LocationPing) → LocationIngestAck`; `DriverLocationUpdated`) lands as authored `.proto` files under `protos/crittercab/telemetry/v1/`. W006's `## Document History` records the realization. +- **New forward-constraint recorded on the Telemetry implementation path:** WolverineFx.Grpc has no client-streaming auto-codegen adapter on the verified 5.x line (fails fast at startup); the implementation session must hand-wire `ReportLocations` against `IMessageBus` or re-verify 6.8 support first. Captured here and in the retro, not folded into the locked design. +- **buf governance first:** the repo's first scoped `ignore_only` lint exception (the two `RPC_*_STANDARD_NAME` rules, telemetry ingest file only), preserving `LocationPing` / `LocationIngestAck` as first-class UL wire names. + +--- + +## Decisions locked by this session + +| Decision | Resolution | Source | +|---|---|---| +| Proto file location | `/protos/crittercab/telemetry/v1/` | ADR-009, `protobuf-contracts` skill | +| File organization | One file per contract (`report_locations.proto`, `driver_location_updated.proto`) — the shipped-repo precedent, not the skill's single-`telemetry.proto` diagram | `dispatch/v1/*.proto`, `pricing/v1/get_fare_quote.proto` precedent | +| Package / namespace | `crittercab.telemetry.v1` / `option csharp_namespace = "CritterCab.Telemetry.V1"` | `protobuf-contracts` skill | +| Service name | `TelemetryService` (not `Telemetry`) — `SERVICE_SUFFIX` conformance, mechanical, semantics preserved | buf `STANDARD`; retro-001 enum-prefix precedent | +| RPC request/response naming | **Keep UL names** `LocationPing` / `LocationIngestAck`; except the two `RPC_*_STANDARD_NAME` rules for `report_locations.proto` via scoped `buf.yaml` `ignore_only` | User sign-off 2026-07-04; W006 §4 UL; verified buf `ignore_only` mechanism | +| Coordinate representation | **Flat `lat` / `lon` scalars** on both messages (workshop's flat shape; `lon` spelling matches `common.v1.Location` for repo-wide consistency; no vestigial `street_address` on a ping) | User sign-off 2026-07-04 | +| `throttle_policy_version` type | **`int64`** — the numeric Marten singleton stream version (verified `long`), per W006 §6.1 | User sign-off 2026-07-04; jasperfx-source-verifier | +| `throttle_policy_version` on ack | Non-optional (§10 / §6.2 authority; always present after ADR-011 bootstrap seed — resolves the §4 `?` ambiguity toward the proto-surface sections) | W006 §10, §6.2 | +| `h3_cell` type | `string` (opaque published-language key; id-stringification convention); `h3_resolution` carried separately as `int32` | `protobuf-contracts` id convention; W006 §6.3 | +| `driver_id` on `LocationPing` | **Absent** — from the authenticated principal, never the payload (R5) | W006 §2.2, §4 | +| `driver_id` on `DriverLocationUpdated` | **Present** — the Kafka partition key (R7) | W006 §6.3 | +| Money/enum types | None needed — no monetary or categorical fields in these three messages | — | +| buf codegen | **Deferred** to the implementation session — no `CritterCab.Telemetry.csproj` exists yet; `buf.gen.yaml` already configured | User sign-off 2026-07-04; PR #4 precedent | +| `.gitignore` | No change — proto-gen exclusions already present (added by PR #4) | — | + +--- + +## Design decisions flagged during authoring + +1. **RPC standard-name vs. ubiquitous language (the load-bearing call).** buf `STANDARD` enforces `RPC_REQUEST_STANDARD_NAME` / `RPC_RESPONSE_STANDARD_NAME`, which reject `LocationPing` / `LocationIngestAck` as the RPC input/output types. These are first-class W006 §4 UL terms — a single GPS reading and the window ack. Resolved (user sign-off) by keeping the domain names and adding a **file-scoped** `ignore_only` exception in `buf.yaml`, leaving every other proto fully `STANDARD`. This matches the `protobuf-contracts` skill's own streaming example (`rpc PushTelemetry(stream LocationPing) …`) and buf's documented per-file exception mechanism. A latent inconsistency surfaced: the skill's example itself wouldn't pass the repo's own `STANDARD` gate — registered as a retro follow-up, not fixed here (no opportunistic edits). +2. **`lat`/`lon` vs `lat`/`lng`.** The shipped `common.v1.Location` uses `lon`; W006 prose uses `lng`. Resolved (user sign-off) toward `lon` for repo-wide coordinate consistency, treating field spelling as a mechanical convention (parallel to retro-001's enum-prefix / `PACKAGE_DIRECTORY_MATCH` calls) while keeping the workshop's *flat* message shape. +3. **Reuse `common.v1.Location` vs. flat scalars.** Reuse is more DRY and matches `ride_assigned.proto`'s pickup/dropoff, but drags a meaningless `street_address` onto a high-volume GPS ping. Resolved (user sign-off) toward flat scalars. +4. **WolverineFx.Grpc client-streaming gap.** Verified: no auto-codegen adapter for client-streaming on the 5.x line (startup `NotSupportedException`). Does **not** change the locked contract (R5 fixes v1 as client-streaming; bidirectional is a v2 deferral) — recorded as an implementation forward-constraint. + +--- + +## Orientation files (read in order) + +1. **[`docs/workshops/006-telemetry-event-model.md`](../../workshops/006-telemetry-event-model.md)** — §10 for the proto surface; §6.2 for the ingest RPC + `LocationPing`/`LocationIngestAck` fields; §6.3 for `DriverLocationUpdated`; §4 for the UL definitions. +2. **[`docs/decisions/018-candidate-projection-ownership-and-telemetry-geospatial-supply.md`](../../decisions/018-candidate-projection-ownership-and-telemetry-geospatial-supply.md)** — the parent decision the Kafka supplier half realizes. +3. **[`docs/decisions/009-protobuf-contracts-as-first-class-artifacts.md`](../../decisions/009-protobuf-contracts-as-first-class-artifacts.md)** — the governing ADR. +4. **[`docs/skills/protobuf-contracts/SKILL.md`](../../skills/protobuf-contracts/SKILL.md)** and **[`docs/skills/cli-grpc-tooling/SKILL.md`](../../skills/cli-grpc-tooling/SKILL.md)** — file layout, naming, versioning, the buf lint/breaking gates. +5. **[`docs/prompts/decisions/001-protobuf-ride-assigned.md`](./001-protobuf-ride-assigned.md)** and its retro — the proto-authorship precedent (`/protos/` layout, buf config, workshop-as-source-of-truth discipline). + +--- + +## Deliverable plan + +1. `/protos/crittercab/telemetry/v1/report_locations.proto` — `TelemetryService.ReportLocations` (client-streaming) + `LocationPing` + `LocationIngestAck`. +2. `/protos/crittercab/telemetry/v1/driver_location_updated.proto` — `DriverLocationUpdated` message-only Kafka event (mirrors `ride_assigned.proto`'s header-comment shape). +3. `/protos/buf.yaml` — add `lint.ignore_only` scoping `RPC_REQUEST_STANDARD_NAME` + `RPC_RESPONSE_STANDARD_NAME` to `report_locations.proto`, with a rationale comment. +4. `docs/prompts/README.md` — add this prompt's entry to the Decisions index. +5. `docs/retrospectives/decisions/003-protobuf-telemetry-v1.md` + `docs/retrospectives/README.md` index entry. +6. `docs/workshops/006-telemetry-event-model.md` — `## Document History` line recording the §10 realization (spec-delta closure loop). + +--- + +## Out of scope + +- **gRPC handler / service implementation, the `CritterCab.Telemetry` project, Kafka wiring, the Dispatch consumer** — all later implementation sessions. No C# this session. +- **buf code generation** — deferred; no consumer project exists (see decisions table). +- **The ASB / Driver-Profile availability half** of the ADR-018 join — a forward-constraint to an un-workshopped BC; modeling or building it here would be cross-BC over-reach (handoff constraint #2). +- **`TelemetryPolicyConfigured` / `ConfigureTelemetryPolicy` contracts** — that stream is HTTP + Marten event-sourced (config-as-events, ADR-011), not a gRPC or Kafka wire surface; no proto owed for it. +- **Bidirectional streaming ingest** — v2 (R5); the locked v1 contract is client-streaming. +- **Fixing the `protobuf-contracts` skill's stale single-file directory diagram and its `STANDARD`-incompatible streaming example** — surfaced, registered for a future `tidy: skills` session, not edited here (no opportunistic edits). diff --git a/docs/retrospectives/README.md b/docs/retrospectives/README.md index 6f360ae..19bcb51 100644 --- a/docs/retrospectives/README.md +++ b/docs/retrospectives/README.md @@ -97,6 +97,7 @@ Phase 1–3 retrospectives were not authored at the time those phases ran (the r - [`001-protobuf-ride-assigned.md`](./decisions/001-protobuf-ride-assigned.md) — First proto-authoring session. Established `/protos/` directory layout and buf configuration. Four proto files authored per ADR-009. Triggered by [`prompts/decisions/001-protobuf-ride-assigned.md`](../prompts/decisions/001-protobuf-ride-assigned.md). Status: complete (2026-05-07). - [`002-bundled-pattern-adrs.md`](./decisions/002-bundled-pattern-adrs.md) — Bundled ADR authorship session lifting four Workshop 001 §11 candidates plus one new Workshop 002 §12 candidate to authored ADRs (011–015). Four pattern ADRs with two-BC evidence (config-as-events bootstrap, aggregate-per-invariant, shared cross-BC identifier, ASB topic naming) plus one cross-cutting NFR (driver-app projection timing budget). Triggered by [`prompts/decisions/002-bundled-pattern-adrs.md`](../prompts/decisions/002-bundled-pattern-adrs.md). Status: complete (2026-05-10). +- [`003-protobuf-telemetry-v1.md`](./decisions/003-protobuf-telemetry-v1.md) — Third proto-authoring session; realized W006 §10's Telemetry surface under `/protos/crittercab/telemetry/v1/` (`TelemetryService.ReportLocations` client-streaming + `LocationPing`/`LocationIngestAck`; `DriverLocationUpdated` Kafka event — ADR-018 supplier half). **First gRPC client-streaming and first Kafka-event proto in CritterCab.** Both mandated agents ran before any proto was touched: `critter-skill-auditor` Phase 1 (skill surface) and `jasperfx-source-verifier` (Marten version type `long` → `int64`; buf `STANDARD` rule set; **WolverineFx.Grpc client-streaming auto-codegen gap** — recorded as an implementation forward-constraint, not a change to the locked R5 contract). Four durable forks resolved by user sign-off; the load-bearing one keeps the ubiquitous-language message names via the repo's **first scoped `buf.yaml` `ignore_only`** rather than renaming to `...Request`/`...Response`. Surfaced two `protobuf-contracts` skill gaps (stale single-file diagram; `STANDARD`-incompatible streaming example) — registered for a future `tidy: skills` session, not fixed here. Spec-delta closure loop exercised: W006 `## Document History` records the §10 realization in the same PR. Triggered by [`prompts/decisions/003-protobuf-telemetry-v1.md`](../prompts/decisions/003-protobuf-telemetry-v1.md). Status: complete (2026-07-04). ### Per-implementation retros (`implementations/`) diff --git a/docs/retrospectives/decisions/003-protobuf-telemetry-v1.md b/docs/retrospectives/decisions/003-protobuf-telemetry-v1.md new file mode 100644 index 0000000..79685b4 --- /dev/null +++ b/docs/retrospectives/decisions/003-protobuf-telemetry-v1.md @@ -0,0 +1,70 @@ +# Retrospective — Author Telemetry v1 Protobuf Contracts + +## Metadata + +- **Triggering prompt:** [`docs/prompts/decisions/003-protobuf-telemetry-v1.md`](../../prompts/decisions/003-protobuf-telemetry-v1.md) +- **Status:** Complete +- **Date authored:** 2026-07-04 +- **Output artifacts:** + - `/protos/crittercab/telemetry/v1/report_locations.proto` — `TelemetryService.ReportLocations` client-streaming ingest + `LocationPing` + `LocationIngestAck` + - `/protos/crittercab/telemetry/v1/driver_location_updated.proto` — `DriverLocationUpdated` Kafka event (message-only) + - `/protos/buf.yaml` — first scoped `ignore_only` lint exception in the repo + - `docs/workshops/006-telemetry-event-model.md` — `## Document History` line (spec-delta closure) + - `docs/prompts/README.md`, `docs/retrospectives/README.md` — index entries +- **Outcome:** Third proto-authoring session complete. W006 §10's owed Telemetry surface realized; first gRPC client-streaming and first Kafka-event proto in CritterCab; the ubiquitous-language message names preserved via a file-scoped buf lint exception. + +--- + +## Framing + +Item 2 of the post-tidy-38 handoff: transcribe W006 §10's fully-specified Telemetry proto surface into `/protos/crittercab/telemetry/v1/`, realizing the ADR-018 Kafka supplier half. Design-locked (R1–R8, ADR-018) — transcription and buf reconciliation, not new design. + +--- + +## Outcome summary + +Two proto files delivered — the client-streaming ingest surface and the Kafka event — plus a scoped `buf.yaml` exception. Four durable wire/config forks were put to the user and resolved: keep UL message names + scoped lint exception; flat `lat`/`lon` scalars; defer buf codegen; `int64` throttle-policy version. Both mandated agents (`critter-skill-auditor` Phase 1, `jasperfx-source-verifier`) ran before any proto was touched, per the handoff. + +--- + +## What worked + +- **Verify-before-wiring caught a real forward-constraint.** `jasperfx-source-verifier` surfaced that WolverineFx.Grpc has no client-streaming auto-codegen adapter on the verified 5.x line (startup `NotSupportedException`; only unary/server/bidi are wired). This does not touch the `.proto` (Wolverine imposes nothing on the contract), but it is exactly the kind of surprise that would blindside the implementation session. Recorded as a forward-constraint, not acted on — the locked design (R5: client-streaming v1) stands. +- **buf `STANDARD` rule set confirmed by docs, not memory.** `ctx7` + the verifier independently confirmed `STANDARD` includes `SERVICE_SUFFIX`, `RPC_REQUEST_STANDARD_NAME`, `RPC_RESPONSE_STANDARD_NAME` — so the UL-vs-lint conflict was real, and `ignore_only` (per-file) is the sanctioned resolution. `buf` is not installed locally, so getting this right by construction mattered; CI runs the gates. +- **The mechanical-vs-domain precedent from retro-001 held.** Where the collision was mechanical and semantics-preserving (`SERVICE_SUFFIX` → `TelemetryService`, field-name `lon`), the buf/repo convention won automatically. Where it would have destroyed UL (`LocationPing`/`LocationIngestAck`), it became a user decision — the right altitude for the split. +- **Shipped-repo precedent overrode the skill diagram cleanly.** One-file-per-contract (from `dispatch/v1` + `get_fare_quote.proto`) rather than the skill's single-`telemetry.proto` diagram; `driver_location_updated.proto` mirrors `ride_assigned.proto` exactly (message-only, Kafka/ASB header comment). + +--- + +## What was harder than expected + +- **The RPC-naming conflict is a genuine design-lock-vs-tooling collision, not an oversight.** W006 §10 locks names that the repo's own `buf.yaml` would reject. Neither "silently rename" nor "silently except" was acceptable; it needed to be surfaced as a user decision. The resolution (scoped exception) is defensible and self-documenting, but it means the repo now carries its first lint exception — a small governance precedent worth noting. +- **The `protobuf-contracts` skill is internally inconsistent with the repo's `STANDARD` gate.** Its canonical streaming example (`rpc PushTelemetry(stream LocationPing) …`) would itself fail `RPC_REQUEST_STANDARD_NAME`. Surfaced but not fixed (no opportunistic edits) — see below. + +--- + +## Methodology refinements that emerged + +- **When a design-lock names a wire shape that fails the repo's own lint gate, that is a user decision, surfaced explicitly** — do not silently conform (loses domain language) or silently except (hides a governance choice). The mechanical-vs-domain test from retro-001 is the discriminator: conform silently only when the change is mechanical *and* semantics-preserving. +- **A locally-uninstalled CLI gate (buf) shifts the burden to construction-time correctness + doc verification.** With no `buf lint` to run, confirming the `STANDARD` rule set against buf docs (`ctx7`) before authoring is not optional. + +--- + +## Skill-file gaps surfaced (for a future `tidy: skills` session) + +- **`protobuf-contracts` directory diagram is stale** — shows a single monolithic `telemetry.proto` per package; the shipped repo (and this session) use one file per contract. (Same class of gap retro-001 already noted about the `/protos/dispatch/v1/` nesting.) +- **`protobuf-contracts` streaming example is `STANDARD`-incompatible** — `rpc PushTelemetry(stream LocationPing) …` would fail `RPC_REQUEST_STANDARD_NAME` under the repo's own `buf.yaml`. The skill should either show the `ignore_only` pattern or use standard-named request messages in the example, and cross-reference the exception mechanism. + +These are registered here, not fixed in this PR (no opportunistic edits). Candidates for `docs/skills/DEBT.md` if the next skill-tidy session doesn't absorb them directly. + +--- + +## Outstanding items / next-session inputs + +- **Narrative-layer decision** (next in the Telemetry chain): does a thin driver-device journey narrative apply to Telemetry, or is the narrative step explicitly skipped with recorded rationale? +- **Telemetry implementation prompt(s):** the `CritterCab.Telemetry` service skeleton, gRPC client-streaming ingest, the first Kafka topic, and the Dispatch consumer side. **Carry the WolverineFx.Grpc client-streaming forward-constraint into that session** — hand-wire `ReportLocations` against `IMessageBus`, or re-verify 6.8's adapter support first (the local checkout was pre-6.0 and could not confirm 6.8). +- **`TelemetryPolicyConfigured` config-as-events surface** (HTTP + Marten, not a proto) — its own implementation slice. + +## Spec delta — landed? + +Landed as planned. W006 §10's candidate protobuf surface is realized as `report_locations.proto` + `driver_location_updated.proto` under `protos/crittercab/telemetry/v1/`; W006's `## Document History` records the realization in this PR. The named forward-constraint (WolverineFx.Grpc client-streaming gap) and the first scoped `buf.yaml` `ignore_only` both landed as described. No divergence. diff --git a/docs/workshops/006-telemetry-event-model.md b/docs/workshops/006-telemetry-event-model.md index 23d6c57..6fd7d8b 100644 --- a/docs/workshops/006-telemetry-event-model.md +++ b/docs/workshops/006-telemetry-event-model.md @@ -637,3 +637,4 @@ Resume the paused W006 design (grill R1–R8, 2026-06-25) after signing off ADR- ## Document History - **v0.1** (2026-06-30): Full workshop authored in one session, resuming the 2026-06-25 grill-with-docs pause (R1–R8) after ADR-018 sign-off. Scope, stream-processing shape sidebar (§3, CritterCab's fourth modeling shape), UL, event list, five slices walked with per-slice sign-off, cross-reference tables, three ADR candidates, parking lot, retrospective. Cross-workshop amendments (W001 §5.3, context-map edges #5/#6, vision-doc override) land in the same PR. +- **2026-07-04** — §10 candidate protobuf surface **realized**. The Telemetry `v1` contracts were authored under `protos/crittercab/telemetry/v1/`: `TelemetryService.ReportLocations(stream LocationPing) → LocationIngestAck` (client-streaming ingest, §6.2) and `DriverLocationUpdated` (Kafka event, §6.3). The ubiquitous-language message names `LocationPing`/`LocationIngestAck` (§4) were preserved via a file-scoped `buf.yaml` lint exception rather than renamed to buf-`STANDARD` `...Request`/`...Response`. Implementation forward-constraint recorded (not a design change): WolverineFx.Grpc has no client-streaming auto-codegen adapter on the verified 5.x line, so §6.2's "first gRPC client-streaming surface" may need hand-wiring against `IMessageBus` (re-verify against 6.8 when implementation begins). Session: [`prompts/decisions/003-protobuf-telemetry-v1.md`](../prompts/decisions/003-protobuf-telemetry-v1.md). diff --git a/protos/buf.yaml b/protos/buf.yaml index 0a714b4..b7146db 100644 --- a/protos/buf.yaml +++ b/protos/buf.yaml @@ -2,6 +2,17 @@ version: v2 lint: use: - STANDARD + ignore_only: + # The Telemetry ingest RPC keeps its ubiquitous-language message names + # (LocationPing, LocationIngestAck) instead of the buf-STANDARD + # ReportLocationsRequest/ReportLocationsResponse. Both are first-class domain + # terms in Workshop 006 §4 — a single GPS reading and the window-close ack. + # Scoped to this one file so every other proto stays fully STANDARD-compliant. + # Rationale: docs/prompts/decisions/003-protobuf-telemetry-v1.md. + RPC_REQUEST_STANDARD_NAME: + - crittercab/telemetry/v1/report_locations.proto + RPC_RESPONSE_STANDARD_NAME: + - crittercab/telemetry/v1/report_locations.proto breaking: use: - FILE diff --git a/protos/crittercab/telemetry/v1/driver_location_updated.proto b/protos/crittercab/telemetry/v1/driver_location_updated.proto new file mode 100644 index 0000000..3c0de65 --- /dev/null +++ b/protos/crittercab/telemetry/v1/driver_location_updated.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package crittercab.telemetry.v1; + +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "CritterCab.Telemetry.V1"; + +// Published to Kafka on a throttled / cell-changed grain (cell-change OR heartbeat) — +// never per raw ping. Published, not stored: the topic IS the breadcrumb history +// (W006 §3.2). Dispatch's NearbyAvailableDrivers local projection is the first +// consumer (ADR-018); the supplier half is locked here. First Kafka topic in CritterCab. +// Topic: telemetry.driver-location-updated +// | Partition key: driver_id — per-driver ordering becomes a transport property (R7) +// | Dedup / LWW key: server_received_at — server-stamped, monotonic; consumer dedups +// on (driver_id, server_received_at) against at-least-once redelivery (R7) +// Slice reference: docs/workshops/006-telemetry-event-model.md §6.3. +message DriverLocationUpdated { + string driver_id = 1; + double lat = 2; + double lon = 3; + // H3 cell id at h3_resolution — published-language: Telemetry and Dispatch agree on + // the cell system + resolution (contract vocabulary, like ADR-013's canonical id). + // Carried as an opaque string key. h3_resolution is carried explicitly for consumer + // clarity rather than bit-decoded from the index (H3 self-encodes it; clarity wins). + string h3_cell = 4; + int32 h3_resolution = 5; + google.protobuf.Timestamp server_received_at = 6; + int64 throttle_policy_version = 7; + optional double speed = 8; + optional double heading = 9; +} diff --git a/protos/crittercab/telemetry/v1/report_locations.proto b/protos/crittercab/telemetry/v1/report_locations.proto new file mode 100644 index 0000000..3b4fdf8 --- /dev/null +++ b/protos/crittercab/telemetry/v1/report_locations.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package crittercab.telemetry.v1; + +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "CritterCab.Telemetry.V1"; + +// Telemetry's GPS ingest surface. gRPC client-streaming: a mobile client opens a +// windowed ReportLocations stream, sends many LocationPings, then half-closes to +// receive one LocationIngestAck. Pings are processed in flight and never +// event-sourced (W006 §3.2 — stream-processing shape). First gRPC client-streaming +// surface in CritterCab. +// Slice reference: docs/workshops/006-telemetry-event-model.md §6.2 (R5). +// +// The RPC request/response keep their ubiquitous-language names (LocationPing, +// LocationIngestAck) rather than the buf-STANDARD ReportLocationsRequest/Response; +// the two RPC_*_STANDARD_NAME lint rules are excepted for this file only in +// buf.yaml. See docs/prompts/decisions/003-protobuf-telemetry-v1.md. +// +// Implementation forward-constraint: WolverineFx.Grpc has no client-streaming +// auto-codegen adapter on the verified 5.x line — a [WolverineGrpcService]-marked +// stub declaring this RPC fails fast at startup. If 6.8 still lacks it, hand-wire +// ReportLocations against IMessageBus (the documented workaround). Re-verify against +// 6.8 source when implementation begins. The .proto itself is plain proto3 — Wolverine +// imposes no proto-side shape. +service TelemetryService { + rpc ReportLocations(stream LocationPing) returns (LocationIngestAck); +} + +// A single GPS reading streamed from a driver's device. driver_id is deliberately +// NOT in the payload — it comes from the authenticated principal server-side, never +// the wire (R5); enforcement lives in the handler, not this contract. The server +// stamps its own receipt time (the LWW/dedup key); device_timestamp is the client +// clock, is informational only, skews, and is never trusted for ordering (W006 §4). +message LocationPing { + double lat = 1; + double lon = 2; + google.protobuf.Timestamp device_timestamp = 3; + double accuracy_meters = 4; + optional double speed = 5; + optional double heading = 6; +} + +// Returned once per window-close. accepted_count reflects only the pings that passed +// validation — invalid pings are silently dropped, not errored, in v1 (W006 §6.2). +// server_time lets the client correct for clock skew. throttle_policy_version is the +// Marten singleton stream version of the active TelemetryPolicy (§6.1), so a client +// can detect a policy change across windows; it is always present after the ADR-011 +// bootstrap seed, so it is non-optional here. +message LocationIngestAck { + int32 accepted_count = 1; + google.protobuf.Timestamp server_time = 2; + int64 throttle_policy_version = 3; +}