Fleet API: serve the zone vocabulary from the site bundle - #87
Merged
Conversation
The API served the roster, the basemaps and dispatch, but not the one thing a dispatcher most needs -- what places can I name? Zone names lived only in the site bundle on the robot, so a caller had to configure them out of band or scrape the list a robot prints when it refuses an unknown zone, which is an accident of an error message rather than a contract. GET /v1/zones and /v1/zones/<site>/<floor> answer it from the same site bundles the server already reads for maps, shaped as zone/v0. Names travel; coordinates do not. A zone's pose is a coordinate in one robot's map frame, whose origin is an accident of where its SLAM session started, so it is a different physical point on the robot beside it and no fleet-level transform fixes that. The name is true for both. So the vocabulary (name, kind, display_name, aliases, navigable, parent, tags) is published and the binding is not, and the split is expressed by the route rather than by a rule someone has to remember: everything under /v1/maps is bound to a basemap and served to a client that already has one, everything under /v1/zones is bound to nothing. zones.json keeps its shape and its coordinates; the dashboard is unaffected. Four things are load-bearing: - The payload is *built* from the fields a vocabulary may carry, never stripped of the ones it may not. Stripping holds only until someone adds a geometry key to zones.yaml and never reads this code, and the leak would be a plausible-looking coordinate rather than a crash. - The vocabulary is not gated on a published map where the binding rightly is. Names are a fact about the building, so a floor someone has named but no robot has mapped still answers -- that is the portability the split buys, and gating it would have quietly given it back. - problems is reported, not enforced. An ambiguous alias or a name a dispatcher cannot type (Café) leaves the map perfectly good, and refusing to serve a floor's basemap over one would be the wrong price. The name is served verbatim rather than slugified, because inventing one is a rename nobody asked for. The robot does refuse an ambiguous vocabulary at load_zones, since it could not honour goto unambiguously. - Contradictions with no reading at all -- an unknown kind, a keepout marked navigable: true -- are refused at the parse by the shared ROS-free bundle.py, so save-map catches them locally and the server on upload. Every vocabulary field is optional, so no zones.yaml needed rewriting: an untouched zone is a navigable area. save-zone --kind teaches the one field it can know, segment-map emits kind: room because what it segments are rooms, and re-teaching a pose carries the vocabulary through and bumps vocabulary_revision -- a better coordinate is not a rename. goto and fetch now resolve aliases and display names, and refuse a non-navigable zone rather than driving to it -- fetch explicitly, because falling through to its label branch would send the detector hunting for an object called "keepout". Verified: 304 tests pass (the 6 skips are the pre-existing broker-dependent e2e tests; no MQTT path is touched), pre-commit clean, and the new routes driven over a real socket -- the vocabulary walked for geometry-shaped keys at any depth, the binding asserted to still carry them, and a named-but- unmapped floor serving a vocabulary while its binding 404s. Related: mote #249 (the rest of spec v0), AugereAI #192/#193.
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.
Fleet API: serve the zone vocabulary from the site bundle
Branch
fleet-zone-vocabulary(commit b5e8fcb), committed not pushed.What changed
GET /v1/zonesandGET /v1/zones/<site>/<floor>serve the zone vocabularyfrom the same site bundles the server already reads for maps, shaped as
zone/v0: names, kinds, display names, aliases, navigable, parent, tags — and
no coordinates.
The split is expressed by the route, not by a rule someone has to
remember: everything under
/v1/mapsis bound to a basemap and served to aclient that already has one; everything under
/v1/zonesis bound tonothing.
/v1/maps/<site>/<floor>/zones.jsonkeeps its shape and itscoordinates, so the dashboard is unaffected — a dispatcher that must never
be handed a map gets
/v1/zonesand only that.Supporting work, since a vocabulary needs something to carry:
zones.yamlgains optional vocabulary fields. All optional, so no existingfile needed rewriting — an untouched zone is a navigable
area.mote_bringup/bundle.py(zone_term,ambiguities,check_vocabulary,vocabulary), shared by the robot,save-mapand the fleet server, as M4 established.save-zone --kindteaches the one field it can know;segment-mapemitskind: roombecause what it segments are rooms; re-teaching a posecarries the vocabulary through and bumps
vocabulary_revision.goto/fetchresolve aliases and display names, and refuse anon-navigable zone rather than driving to it —
fetchexplicitly, becausefalling through to its label branch would send the detector hunting for an
object called "keepout".
fleet-api.md(new contract section),README.md§12 (operatorflow),
CLAUDE.md(new "Fleet: the zone vocabulary" section).Four decisions worth reviewing
Built, not stripped. The payload is constructed from the fields a
vocabulary may carry, never filtered of the ones it may not. Stripping
holds only until someone adds a geometry key to
zones.yamland neverreads this code, and the leak would be a plausible-looking coordinate
rather than a crash. The test walks the whole payload for geometry-shaped
keys at any depth rather than checking the ones it thought of.
The vocabulary is not gated on a published map, where the binding
rightly is. Names are a fact about the building, so a floor someone has
named but no robot has mapped still answers. That is the portability the
split buys, and gating it would have quietly given it back.
problemsis reported, not enforced. An ambiguous alias, or a name adispatcher cannot type (
Café), leaves the map perfectly good — refusingto serve a floor's basemap over one would be the wrong price. The name is
served verbatim rather than slugified to
cafe, because inventing one isa rename nobody asked for. The robot does refuse an ambiguous vocabulary
at
load_zones, since it could not honourgotounambiguously.Contradictions are refused at the parse — an unknown
kind, akeepoutmarkednavigable: true,aliasesthat are not a list. Notambiguities to report; honouring the last one written would make the flag
mean whatever was typed most recently.
Verification
tests (no MQTT path is touched). Confirmed the 8 failures I first saw when
running all of
mote_bringup/testin one pytest process reproduceidentically on the unmodified base commit — shared rclpy context, not this
change.
mote_fleet/test/test_zone_vocabulary.py(17 tests) over a realsocket; new vocabulary tests in
test_zones.py,test_goto_command.py,test_parse_command.py,test_segmented_zones.py.mapped), the binding for contrast, and the binding 404ing on the unmapped
floor.
gen_hospital.pyregenerated; SDF output byte-identical, only zones.yamlchanged.
Not pushed, per the standing rule that remote git operations need your
approval —
voro pr 260when you are ready.Related: mote #249 (rest of spec v0), AugereAI #192/#193.