v2.7.0 — Geospatial map: trajectories, playback & entity grouping #662
thomas-stegemann
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
The map widget stops being a scatter of dots and becomes something an operator can read: paths per entity, entities told apart by their own identity, and a cursor that rewinds. Alongside it, the gRPC plugin can finally call a server with Server Reflection switched off — the state Bowire's own scanner recommends — and the scanner learns to see three failures that survive every "is it public?" check.
The map
A path, not a scatter of dots
A stream of positions rendered as independent pins. Good for "where has it been", useless for "where is it going": the operator connected the dots by eye. There is now an optional LineString layer under the pins, off by default, toggled from the map's own overlay and remembered per workspace (#238).
The geometry is derived from the pin collection rather than accumulated beside it. Two things fall out of that: the existing 5000-pin FIFO cap already bounds it, so a vertex cannot outlive the pin it came from; and a pin and its segment cannot disagree about position, selection or colour, because there is one copy of that state.
Entities told apart by their own id
The map grouped and coloured by
discriminator— the protocol's word for message type. On a wire carrying many entities that is one bucket for all of them, while the operator's actual question is "show me every ping of UnitID 42 as one track". A configurable track-id path now answers it (#240), with a collapsible legend: one row per track, its colour, how many pins it has on the map, and a switch to hide it.Two stream shapes had to work and they pull in opposite directions. TacticalAPI puts N entities in one frame; DIS sends one entity per PDU across many. So a path resolves absolutely first, then relatively — walking up from the coordinate's own parent, because the identity lives above the node that holds the position: one level for a flat
position, five forsymbol.location.content.point.geoPoint. Only the data knows how deep.Hiding a track is a render concern and never a state one. The master collection keeps every pin, so a hidden track still counts, keeps its selection, and comes back unchanged — rather than being deleted and having to be re-streamed, which for a finished stream means never.
The pin cap stays global. A per-track quota of
5000/Nshrinks every existing track's allowance the moment an N+1th track appears, retroactively deleting history from tracks that did nothing, and the quiet ones lose the most. Taking each pin from whichever track is currently longest gives the same protection, because a chatty entity is the longest track.Rewinding a stream
Once a stream stopped there was no way back to minute 7, and "where was it when the alert fired" needed a tool outside Bowire. A cursor now decides which frames count as already arrived, and the pin layer, the trajectory and the legend all read that one answer (#239).
The axis is frames, not pins — a frame is one update, and one frame may carry thirteen pins. Playback follows the data's own rate rather than a fixed tick, clamped at both ends so two frames stamped the same millisecond cannot spin and a five-minute gap cannot look frozen. The transport stays locked while the stream is live, because a cursor holding a position while the tail moves flickers between the two; the stream ending unlocks it, from
finallyrather than the happy path, since a stream that ends by throwing is exactly when someone wants to rewind.Scrubbing never touches the selection. A frame selected while the cursor sits before it stays selected, is simply not drawn, and returns when the cursor passes it.
gRPC without reflection
Calling a server that has reflection switched off
Disabling Server Reflection is the recommended production state — Bowire's own scanner says so — and until now that made the gRPC plugin unable to call such a server at all, because descriptors came from exactly one place (#653).
A caller can now supply a compiled descriptor set, the artefact of
protoc --descriptor_set_out=api.protoset --include_importsand the same inputgrpcurl -protosettakes. It travels in request metadata under a marker key, the way mTLS configuration already does, so no plugin signature changes and the marker is stripped before anything reaches the wire.Reachable from every surface
The capability stopped at the plugin boundary, so walking it end to end against the sample carried it out:
--grpc-descriptor-setonbowire list,describe,discover,call,scan,exportanddiff;?grpcDescriptorSet=on/api/services;grpcDescriptorSetonbowire.discoverandbowire.lint. All of them write the same metadata marker the plugin already reads, so there is no second channel to keep in step.Security
The map widget never mounted at all
Not "rendered wrong" — did not render, in every build carrying the current Content-Security-Policy (#655).
MapLibre parses tiles in a web worker it constructs from a
blob:URL.worker-srchas no default of its own: absent, the browser falls back toscript-src, which we set to'self'plus a nonce. Neither covers a blob, so the worker was refused, MapLibre never reached itsloadevent, and the widget's mount awaits exactly that. Nothing on the server said so — the page was 200, the header strict, and Bowire's own scanner read the policy as healthy, because it grades the header and the header was good.worker-src 'self' blob:is now spelled out rather than left to inherit, so a laterscript-srcedit cannot move the worker rule with it unnoticed.Three failures that survive "is it public?"
GrpcAuthorizationProbereaches a verdict from three observations — an anonymous call refused, identity A reaching the handler, identity B reaching it too — because only that combination shows a server gating on having a credential rather than on which one.A reflection marker that read as reassurance
When reflection returned nothing, the probe reported "reflection is disabled (the desired production state)" and stopped. Two things were true and only one was being said: reflection is off, and the transport-authentication check never ran, because it needs a method to call. Read together, that told an operator their server was in the recommended state — about a server whose authentication had not been examined at all. And the better a deployment follows that recommendation, the more often it lands in that branch.
A plugin's documentation page is checked before it is published
fetch-plugin-docs.mjspullsdocs/protocol.mdout of every repository carrying the plugin topic and writes it where DocFX renders it onto bowire.io — and DocFX passes raw HTML straight through markdown, so a page's bytes reach a visitor's browser on our own origin. The repositories are the org's own, which makes this a supply-chain guard: it is what stops one compromised plugin repository from putting script on the documentation site. Pages are now bounded in size, required to carry front matter, and refused outright if they carry markup that would execute.Also in this release
MCP resources address a workspace
They read the workspace-less files, so they answered with the wrong data (#642). Resources are now workspace-addressable, with an index to address them by.
The correlation suggester weighs evidence
It gated on the field name before weighing any evidence, so a shared identifier under an unexpected name was never suggested and a coincidental name always was (#650).
The VS Code extension is 1.0
kuestenlogik.bowire-vscode1.0.0 is on the Marketplace (#619). 1.0 is a promise about the surface: thebowire.*settings keys and the command ids stay where they are, and moving one is a major version. The extension's version stays independent of Bowire's — it drives an installed CLI rather than bundling one, and hosts anything from 2.5 upwards.A SCIM provisioning round-trip leaves evidence
scim/events.jsonlrecords mutations and their outcome, so a connector's reads — the paging walk, the existence filter — left no trace, and the reads are most of what a live round-trip is for.Bowire:Scim:TraceProvisioning(off by default) now writes one line per SCIM request: method, path and query, status, duration, and for PATCH the dialect read off the wire.docs/setup/scim.mdgrows the matrix to walk and a table naming which provider version was exercised and when.Off by default, and meant to be turned off again: the lines carry what the connector sent — user names, e-mail addresses, the filters a directory walk used.
SOAP ships in the workbench bundle
It was the only in-repo protocol left out of it without a reason. NATS and Pulsar sit outside because each carries a third-party client library, which the optional-package rule keeps out of a bundle an embedded host has to pay for; SOAP has no dependency at all, its sample's header printed
bowire --url soap@…with no install step, and the bundle's own comment already listed SOAP among the universal protocols it contains. Only theProjectReferencewas missing.soap@now answers on a fresh install.The protocol guide gained a Bundled column at the same time, because the table's split was "ships from this repo" and a reader took it for "works out of the box" — which for NATS and Pulsar it is not.
Under the hood
The release cascade resolves
dotnet newtemplate defaultsBowire.Templates holds its Bowire version twice: as a placeholder in the plugin template's
Directory.Packages.props, and as thedefaultValueof thetemplate.jsonsymbol that replaces it. The bump step walks past the placeholder on purpose — it is not a version number — and nothing resolved the other half. The default had read1.6.0since May while the same template's non-CPM file tracked every release, sodotnet new bowire-plugingenerated a plugin against a Bowire five minors old. Four cascade runs merged green, because the postcondition only ever looked at project files.One marker for "was I explicitly asked for", not one per plugin
A plugin that discovers from a bundled schema, or has an ad-hoc separate-target fallback, needs to know whether the caller pinned it with
hint@url— andBowireServerUrl.Parseconsumes that prefix before any plugin is reached. SSE and SignalR had each grown a private URL marker for it, kept aligned with a comment. TacticalAPI gated on the prefix itself, which can never arrive, so its discovery returned an empty list on every path while its own unit test passed. There is now one shared marker, and both private ones are gone.The release gate agrees with the publish step
Two steps in
release.ymlresolved the same source chain and disagreed about what counts as curated. A template body cleared the first test and failed the second, so the gate would report "found curated block", the publish step would discard it, and the release would go out carrying the auto-generated change list — the exact outcome the gate exists to prevent, with nothing failing.Acknowledgements
The gRPC reflection work started from a walk against the sample rather than from a unit test, and the three defects it found were the kind only that walk produces. The same is true of the map: every fixture passed while the browser showed thirteen entities as eight tracks, a map that never framed its data, and a checkbox reading unchecked over a visible layer.
The full commit list, contributors, and compare-URL diff are auto-generated below.
Added
6135fd6)4b943de)4bd7ba4)d6ac9df)03fa0b8)91a6d6a)af72554)b1c8772)d94f7a5)019d8d0)4d8f8bb)Fixed
6d10904)1e7309e)d701f9e)826ff64)2d6250e)7c3d3b2)0b43cca)Documentation
36f6377)325e7af)0c526ee)c33f058)038303e)Other
5b9a861)Maintenance — 20 maintenance commits · 5 dependency updates
19740e8)b1b2c4f)a0da970)e4861f0)dc98e55)8e8de09)ab67125)ea616c1)82d9cb1)ce6b361)d5afdbd)c2e5a9d)e88e529)db84e78)7625d07)95eae24)c12d4a4)33b9d29)00345dd)2be37aa)Dependency updates
8706d89)36ea6b9)fda00a0)78ae294)17c9f62)Contributors: Thomas Stegemann
Full diff: v2.6.2...v2.7.0
This discussion was created from the release v2.7.0 — Geospatial map: trajectories, playback & entity grouping.
All reactions