Demo runbook · governed data platform

SAPIENT: reading the standard the sensors speak, and writing it back

Twenty minutes, three files. A capture in the UK MOD's open sensor standard; the enriched record an analyst made from it; and the same governed corpus projected back into that standard, once per recipient, so a fusion node consumes what it is entitled to with no translation step in front of it. Every figure here was captured from the deployed container on 2026-08-20.

counter_uas_sapient.bin · SAPIENT capture · 556 KB counter_uas_detections.csv · the enriched record two .bin files you make on stage · the point of the whole thing

The story in one paragraph

BSI Flex 335 — SAPIENT is Dstl's open standard for networks of autonomous sensors: edge nodes register with a fusion node and then report what they detect. It is freely published, its .proto files and reference middleware are Apache-2.0, and NATO is adopting it as the counter-UAS interoperability standard. So the interesting question is not whether this platform can read a drone detection — it is where a governance platform sits in a sensor network. The answer this demo gives: between the sensors and the fusion node, speaking the standard in both directions, deciding what each recipient receives rather than handing over a file and trusting them to redact it.

Before you start. Open http://127.0.0.1:8092 and log in. The two counter-UAS profiles ship unapproved on purpose — approving one live is a good thirty seconds, not a fault. Do not pick a local model from the analyst dropdown while presenting over the public tunnel: it takes about 394 seconds and Cloudflare cuts the response at 100.
01

What the sensors actually speak

The capture · counter_uas_sapient.bin

01

On the Run tab, under 1 · Dataset, choose counter_uas_sapient.bin.

What appears under the file picker
21 columns · 2,000 records · sapient_bsi_flex_335_v2_0
 · 2,012 messages · 2,000 detection reports
 · 12 registration (not governed)

Three things in one line. The format was decided from the bytes — rename the file notes.csv and it still reads as SAPIENT, because on the input path of a governance platform a filename is a claim by whoever typed it. The capture is a conversation, and 12 of its messages are nodes registering with the fusion node: those describe the network rather than an observation of the world, so they are counted and not turned into governed records. And 2,000 detection reports went in without anybody converting anything to a spreadsheet first.

02

Say where the field names came from, because this is the part that is different from every other format the platform reads.

One detection report, as decoded
timestamp                    2026-03-02T06:11:51Z
node_id                      018f3a2c-1f00-7a10-8000-000000000032
object_id                    CEJZ05XHT5GMEJ146S6JR410DD
location_x                   -97.23268    ← longitude
location_y                    26.9091     ← latitude
location_z                   158.496
location_coordinate_system   LOCATION_COORDINATE_SYSTEM_LAT_LNG_DEG_M
location_datum               LOCATION_DATUM_WGS84_E
detection_confidence         0.96
object_info_sensor_modality  RF
object_info_cooperative      true      ← it identified itself
object_info_id_type          remote_id_serial
classification               Air:0.81
behaviour                    Approaching:0.83
signal_0_centre_frequency    2436999936
id                           1596F6090228

Four of those columns are not fields in the standard at all. object_info is a repeated {type, value} pair — SAPIENT's declared extension point, whose vocabulary each node registers up front — and the reader turns the type into the column name. So a sensor saying type: "cooperative", value: "true" arrives as a governable column with nothing written in Python. That is how a use case adds a field to this platform.

What to say

“The other two binary formats we read needed somebody to sit down with a specification and type out a JSON table of what each tag means. That table is a place errors live silently. SAPIENT ships its own schema — so we vendored the .proto files verbatim, Crown copyright notice and all, and the decoder runs off a descriptor compiled from them. There is no hand-written field table here to drift out of date, and no generated code in the tree either. Upgrading to the next BSI Flex version is dropping in new .proto files.”

“location_x is longitude and location_y is latitude per the standard's own comment, and the enum underneath says which convention is in force. We did not rename them to something friendlier on the way in. Renaming is interpretation dressed as decoding — and interpretation is the profile author's job, which is the next thing you will see.”

03

Under 2 · Profile, read the ranked picker before selecting.

The picker
✓ Counter-UAS SAPIENT capture — sensor network (unapproved)
                                — describes 100.0% of columns
✓ DCSA applicant vetting          — describes 0.0% of columns
✓ Counter-UAS Remote ID capture   — describes 0.0% of columns
✓ Telecom CDR                     — describes 0.0% of columns

✗ 5 profile(s) cannot govern this file

A vetting profile can run on a drone capture — it would apply lineage and a detection pass and describe none of it. Ranking on coverage is how “able” is kept from reading as “appropriate”. Select the SAPIENT profile, which shows unapproved, type your name, and press Approve as named person. Its fingerprint is 3df3cd15a681cd36 before approval and identical after: approval records a decision, it does not modify the thing decided on.

04

Press 4 · Run. Then stop on the second line for a moment.

Result
PROMOTED · 2,000 records · 0 quarantined · composite 100.0 · 0.42 s

  lineage      2,000/2,000   stamped whether anyone asked or not
  pii_detect       0/2,000   ran on every record, tagged nothing
What to say

“Personal-data detection ran across two thousand detection reports and found nothing. Not because it was switched off — it ran on every record. There is nothing personal in this format to find. A SAPIENT DetectionReport carries position, confidence, classification, behaviour, RF signal. It carries no operator name, no home address, no classification marking, no U.S. Persons determination and no retention basis.”

“That is now three sensing standards in a row — MISB ST 0601 for full-motion video metadata, ASTM Remote ID off the air, and SAPIENT. Sensing standards carry observation, not entitlement. Which tells you where a governance platform's obligations actually begin, and it is not at the sensor.”

One thread does run back to a person: id — the field BSI Flex 335 documents as “ID of the object being reported (e.g. the tail number of aircraft)” — carrying the Remote ID serial an RF node heard on 591 of the 2,000 reports. object_info_cooperative splits the corpus on exactly that line: 591 aircraft identified themselves and 1,409 were painted by radar and said nothing. What the sensor does not report is who the serial belongs to; that needs a registry, and that join is enrichment. It is pseudonymous — it becomes identifying only through a civil aviation registry this platform does not hold — so the profile leaves it undeclared rather than asserting an answer that is jurisdiction-dependent. Silence means “nobody has said”, so the detector keeps looking at it. Remember this field; it carries the whole of Act Three.

02

What it will not govern, and why that is the finding

Same file, one click

05

Open the line that reads 5 profile(s) cannot govern this file.

Counter-UAS Detections — ground sensor network
✗ 'Classification markings' cannot run on this dataset:
    missing element: security_classification
✗ 'U.S. Persons controls' cannot run on this dataset:
    missing element: us_person
✗ 'Declassification review' cannot run on this dataset:
    missing element: security_classification or creation_date

remedy: remove it, or supply the missing element

The profile that governs the enriched record is refused on this one, by name, with the element each refusal turns on — and the answer arrives before a single record is touched, because whether a control can run is a property of the columns, not of any row. It does not quietly skip §2.3 and hand you a clean-looking output.

What to say

“A container that cannot carry the flag must not be allowed to quietly retire the control. If this platform ran the U.S. Persons control here it would report success on two thousand records and protect nobody, because there is no field in the standard saying who is a U.S. person. Governance obligations attach at enrichment, not at the sensor — and that generalises a long way past drones.”

03

Govern the enriched record, then write the standard back out

counter_uas_detections.csv · and two captures you make on stage

06

Choose counter_uas_detections.csv, select Counter-UAS Detections — ground sensor network, approve it.

What appears
27 columns · 2,000 records · CSV

Same drones, same afternoon. An analyst has since joined the registry, made a classification call, and recorded whether a lawful basis exists for holding the identity. Nine of the twenty-seven columns did not come off any antenna — they are judgements somebody made afterwards. That is the whole difference between Act One and Act Three.

07

Leave 3 · Recipient authority unticked and run. This is the airport security desk: cleared for the air picture, not for U.S. Persons information.

Result · unauthorised recipient
PROMOTED · 2,000 records · 0 withheld · composite 100.0 · 0.43 s

  lineage          2,000/2,000
  classification   2,000/2,000
  pii_detect       2,000/2,000
  usper            2,000/2,000   changed 5 columns:
       remote_id_serial · operator_latitude · operator_longitude
       operator_name    · operator_address
  declass_review       0/2,000

The drone's own position is untouched — a machine is not a protected person, and minimising it would destroy the track the system exists to produce. So is reporting_officer: §2.3 protects the subject of collection, not the government employee who filed the report.

08

Click the Emission node and press SAPIENT DetectionReport (BSI Flex 335 v2.0). Save the file.

What you get, and what the panel says
emission_sapient_detections_….bin   513,596 bytes

Specification not held — the records are emitted and
labelled non-conformant, not withheld.

Read that disclaimer out. The platform does not hold the BSI Flex 335 ICD, so it does not claim conformance to it — but it does validate every record's mandatory fields against the is_mandatory annotations carried in the standard's own .proto files, rather than against a list maintained here that could drift. That check earned itself the day it was written: it caught that report_id was unset on every record.

09

Tick Recipient is USPER-authorised, run again, and emit again.

Result · authorised recipient
PROMOTED · 2,000 records · 89 withheld
  usper   2,000/2,000   changed 0 columns

emission_sapient_detections_….bin   489,589 bytes

Nothing was masked for this recipient — and the file got smaller, because 89 detections left it entirely. Under E.O. 12333 §2.3 you need authority and a lawful basis for retaining the information. Those 89 involve U.S. persons and nobody recorded a reason for holding their identity, so the authorised recipient receives less than the unauthorised one did. Not a masked name — no record.

10

The round trip. Feed each downloaded .bin straight back into 1 · Dataset.

The unauthorised recipient's capture, read back by the platform
9 columns · 2,000 records · sapient_bsi_flex_335_v2_0
 · 2,000 messages · 2,000 detection reports
The authorised recipient's capture, read back
9 columns · 1,911 records · sapient_bsi_flex_335_v2_0
 · 1,911 messages · 1,911 detection reports

Then put the same detection side by side. It is the first record in both files.

DET-00000000 · one detection, two recipients
report_id   EPB3A4T7YKAYVW4KEDR3PRN2Z2   ← identical in both
object_id   DET-00000000                 ← identical in both
location_x  -97.23268                    ← identical in both
location_y   26.9091                     ← identical in both

id          [U.S. PERSON]    airport security desk
id          1596F6090228     federal partner, authorised
What to say

“That is our own reader, running the standard's own schema, accepting a file we produced. It is not a SAPIENT-flavoured export — a fusion node would take it.”

“The serial is the field carrying the entitlement. It is pseudonymous, but it resolves to a named person through the registry, and it is the field that reaches a SAPIENT emission — so the profile puts it in the minimisation set, and an unauthorised fusion node does not get a handle on the operator.”

“And the message id is the same in both files. Message ids derive from the profile fingerprint and the row's position, not from a clock, so re-emitting one governed corpus produces identical bytes. Two runs of a release set cannot differ for reasons that are not governed.”

Across the two captures: 456 serials minimised and 135 in the clear for the security desk; none minimised and 502 in the clear for the federal partner. 1,409 detections carry no serial at all in either — those are drones nobody heard a Remote ID broadcast from.

04

The slide to leave up

One corpus, one standard, two entitlements

Recipient Detections Serial in the clear What the fusion node receives
Airport security desk — not authorised 2,000 135 Mask 456 serials as [U.S. PERSON]; full air picture
Federal partner — authorised, basis stated 1,911 502 Release every serial, reason recorded
Federal partner — authorised, no basis 89 — Withhold the detection does not leave

Both files are valid BSI Flex 335 v2.0 captures, and the platform reads either of them straight back. The aircraft's position is never touched in either.


05

One upload, every release

Optional · a terminal beat, because the button is not wired for this yet

Steps 07–09 produce each recipient's capture with two runs. The platform can also produce every release from one upload as a single archive with a manifest — but the UI's Produce the release set button is currently hardcoded to the governed CSV, so a SAPIENT release set is an API call today. Say so if you show it.

POST /api/release
releases = [
  {"name": "Airport security desk", "target": "sapient_detections"},
  {"name": "Federal partner",       "target": "sapient_detections",
   "usper_authorised": true}
]

→ releases_0.1_….zip · 183,761 bytes
   manifest.json
   airport_security_desk_emission_sapient_detections_….bin   2,000 records
   federal_partner_emission_sapient_detections_….bin         1,911 records

The manifest reports each release's authority, the controls applied, and that mandatory-field validation passed — with conformant: false stated plainly. It also says what it cannot tell you: the two releases hold different record counts, so no per-record comparison is possible. An honest “no answer” rather than a misleading diff. The pipeline runs once per distinct authority, not once per release, so two releases cannot disagree about what was governed.


06

Questions you will get

And short answers that are true

“Is this really SAPIENT, or your idea of it?”

The standard's .proto files are in the repository verbatim with their Crown copyright and Apache-2.0 notice, and decoding runs through a descriptor compiled from them — no hand-typed field table, no generated code in the tree. A test holds the descriptor to its sources, so a .proto change without a rebuild fails the suite rather than the deployment.

“You built a drone corpus last week and only then found the standard.”

Yes — and it is written down. Tuesday's counter-UAS CSV is a hand-rolled version of a SAPIENT DetectionReport and nobody looked first. It is recorded in docs/alternatives-considered.md beside the same mistake made with ODCS. The rule that came out of it is: research the standard before designing a format. Conceding those two is what makes the rest of that document worth reading.

“Can it subscribe to our fusion node?”

Not today. The platform reads uploads, and a capture of length-delimited messages is the same shape as any other file. Live SAPIENT is TCP to the middleware and is separate work — it would not need Kafka, which is ruled out here by name for the laptop requirement, because SAPIENT already ships a broker.

“Why does it say non-conformant?”

Because we do not hold the ICD, and claiming conformance to a document you do not hold is how this goes wrong quietly. What is claimed is what was checked: structurally valid, every mandatory field verified against the standard's own annotations. A missing specification is not a reason to withhold governed data — it is a reason to label it accurately.

“How much of this was code?”

The profile governing the capture is a JSON file. The emission target — which column maps to which field of the standard, and the coordinate-system constants — is a JSON file. What stays code is the walker that turns bytes into values, and what a control does to a record. A target naming a format nothing implements is refused by name at load, with the refusal saying where the behaviour would go.

“What stops the AI getting it wrong?”

Nothing rests on it being right. A machine reading only ever proposes meanings — it cannot mark a field required, write a personal-data declaration, raise a profile's authority, approve anything, or choose which profile governs a run. Each is enforced in code with a test behind it.


07

If something goes wrong

The four things most likely to

SymptomCauseFix
The .bin upload is refused, or the rail shows nothing The container was built without the sapient extra, so there is no protobuf runtime to decode with docker compose up -d --build gdp — rebuild only the gdp service. Rebuilding the tunnel changes the public hostname.
“no detection reports in this capture” The file holds only the registration handshake, not the traffic The refusal names what it did find. Use counter_uas_sapient.bin.
Profile greyed out in the picker Refused for this file, or not yet approved Read the refusal panel — it names every missing element. If it says unapproved, select it and use the approve box.
Demo URL is dead The tunnel container restarted and the hostname changed docker compose logs tunnel | grep trycloudflare

Every figure on this page was captured from the deployed container on 2026-08-20, not estimated. A profile named telco sits in the picker from an earlier upload and is unrelated to this demo. If a number on screen differs from a number here, trust the screen and tell me.