Skip to content

Share domain types across interfaces and subjects (transport-agnostic type pool) #153

@freol35241

Description

@freol35241

Problem

Keelson partitions protobuf types by transport:

  • interfaces/*.proto — RPC request/response types (package-less, global namespace)
  • messages/payloads/*.proto — pub/sub payload types (package keelson)
  • subjects.yaml maps a subject to a type, and the SDK resolves it from a descriptor set built only from messages/payloads/ (sdks/python/keelson/__init__.py loads payloads/protobuf_file_descriptor_set.bin).

But a Coordinate, Waypoint, MissionItem, Route is a domain noun — neither inherently RPC nor pub/sub. Partitioning domain nouns by transport forces the same concept to be modelled twice, and makes a type defined for one transport unusable on the other.

This is already biting (not hypothetical)

Two Waypoint messages now exist, modelling the same thing with different coordinate types:

  • interfaces/VehicleMission.proto: Waypoint / Loiter built on Coordinate { double latitude_deg; double longitude_deg } (in VehicleCommon.proto).
  • messages/payloads/Route.proto: Waypoint / Leg built on foxglove.LocationFix.

And because the subject type pool is payloads-only, you cannot publish an interface type today: e.g. there's no way to add current_mission_item: keelson.MissionItem to subjects.yaml, even though the RPC layer already defines a clean MissionItem. The only options are duplicate the type into payloads or don't publish it.

Concrete motivating case: mission_current_seq publishes a bare TimestampedInt index that's only interpretable by re-indexing into a Mission downloaded via the VehicleMission RPC. A natural alternative — publishing the resolved MissionItem directly — is blocked by the transport split.

Proposal: factor out a third bucket, don't merge the two

The right dividing line is domain noun vs transport wrapper, not interface vs subject:

Bucket Examples Shareable?
Domain types Coordinate, Waypoint, MissionItem, Route, Weather, VesselType Yes — referenced by both RPC files and subjects.yaml
RPC wrappers MissionUploadResponse, SetCurrentWaypointRequest (carry CommandResult / request-response semantics) No — RPC-only, never published
Pub/sub primitives TimestampedFloat, … pub/sub (already in the pool)

interfaces/ keeps the request/response shapes, but their payload fields become shared domain types from the keelson pool.

Migration sketch

  1. Namespace. Put shared domain types in package keelson (interfaces are currently package-less). Decide their home: extend messages/payloads/ or add messages/domain/.
  2. Consolidate the existing duplicates first (smallest concrete win): one Coordinate (or standardise on foxglove.LocationFix), one Waypoint. Reconcile VehicleMission.WaypointRoute.Waypoint before they diverge further.
  3. Interfaces import domain types instead of redefining them (VehicleMission imports keelson.MissionItem / keelson.Coordinate).
  4. Extend the SDK subject descriptor set to include the shared domain types (today payloads-only) — this is the change that makes them subject-referenceable. Mirror in the JS SDK generator.

Cautions / open questions

  • Coupling is real but correct. A shared type means a field add touches both the RPC contract and any subject publishing it — RPC and pub/sub then version together. Acceptable, but more coordination.
  • Keep the discipline. Request/response wrappers must stay un-publishable; "domain noun yes, transport wrapper no" is easy to erode.
  • A good RPC type isn't automatically a good message. MissionItem has no identity but its list index, so publishing one in isolation loses the context (its position in a Mission) that makes it meaningful. Sharing the type is fine; the publishing design must still carry context (index, mission id, …).
  • Scope / sequencing. This is a foundational change to the messages/interfaces/ boundary. It should land after the in-flight 5.1.0V-trial split PRs settle, and start from the Coordinate/Waypoint consolidation rather than a big-bang reorg.
  • Open question: do we want a single canonical coordinate type repo-wide (foxglove.LocationFix vs a keelson Coordinate), given foxglove types carry visualization-oriented fields (covariance, frame_id) that are noise for a route waypoint?

Why now

The Route/Voyage/Weather payloads (PRs #141/#142) roughly doubled the domain-type surface and immediately collided with the vehicle RPC types (the two Waypoints). Left alone, the duplication compounds with every new domain area. Good moment to decide the direction even if the implementation is deferred.


🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions