Operational ↔ tactical information flow¶
Status: Design (proposed) — 2026-07-03. Covers the operational-content distribution model (artifact store + manifest) and the flow of plans, reports, and ORBAT between the operational layer (web — PJHQ/TJHQ) and the tactical edge (Android devices).
See also: Comms-plan registry (the pattern this generalises), Authorization model (roles and capabilities), Security model (envelope verification and classification), and the UX flows (agreed screen-level journeys for this design).
Goal¶
- Down + out: an operational HQ builds an operation (ORBAT, comms matrix, plans) on web and pushes it to every device it tasks. A team leader at the edge builds a plan on Android and one publish serves both his troop's bubble and the HQ above him.
- Up: devices submit structured reports (sitreps) that roll up per unit on web; commanders can task reports and see compliance.
- Rhythm: both layers plan and report multiple times a day — records are cheap, versioned, and offline-first.
The two-plane rule¶
Content splits by weight, not by direction:
| Plane | Carries | Transport | Properties |
|---|---|---|---|
| Record plane | Plans, reports, report requirements, ORBAT entries, Directory-authorized channel defs — small structured signed records | Zenoh gossip (AuthEnvelope-signed) |
Instant and bubble-native; offline-first only for actions covered by cached valid authority; web ingests as store of record |
| Blob plane | Map packs, KMZ overlays, imagery, order documents — large static artifacts | HTTPS pull from web's artifact store | Hash-addressed, deduped, resolved via per-principal manifest |
Records reference blobs by sha256; devices resolve the bytes over HTTPS when
they have reach. Web SQL is the store of record for all structured content;
Android holds gossip-fed projections (no SQL). Field-origin records flow up
the same plane and are ingested by web as provisional — the
comms-plan registry reverse flow, generalised to every
record kind. A new channel created while Directory is unreachable is only a local,
visibly unactivated draft and is not a field-origin record until connected activation.
flowchart TB
subgraph Web["Web — operational layer (PJHQ/TJHQ)"]
SQL[(store of record<br/>operations, ORBAT, plans, reports)]
ART[(artifact store<br/>sha256-deduped blobs)]
MAN[per-principal manifest]
SQL --- MAN
ART --- MAN
end
subgraph Edge["Tactical edge (Android)"]
TL[team leader device]
TR[troop devices]
TL <-->|peer bubble| TR
end
SQL -->|"publish signed records<br/>(plans, ORBAT, requirements)"| TL
SQL -->|records| TR
TL -->|"field records up<br/>(plans, reports) — ingested provisional"| SQL
MAN -->|"HTTPS pull: blobs by sha256<br/>(IdentityToken bearer)"| TL
Blob plane: artifact store + manifest¶
- Flat artifact store on web: content-addressed by
sha256, deduped — the same KMZ uploaded twice is one artifact. - Attachments bind artifacts to a scope:
operation | plan | phase(phase is an optional label for now). One artifact can attach to several plans. - Manifest: web serves one per-principal operational content manifest —
entries
{kind: overlay | map-pack | plan | comms-matrix, scope, name, artifact URL, sha256, classification label}. It resolves everything visible to that principal: their operation, their plans (per the visibility rule below), the active phase. - Delivery is pull-only over HTTPS. Blobs never ride Zenoh — no broadcast, gossip, or republish of artifact bytes.
- Auth: the device presents its Directory-issued
IdentityTokenas a bearer; web verifies the Directory signature and expiry. Devices can also upload artifacts (e.g. a report photo) over the same authenticated channel; the record referencing the blob rides the record plane. - Directory owns identity plus the connected authority ledger for canonical operation and unit identifiers. It commits complete current ORBAT unit records but is not the application history/archive or blob store.
ORBAT is the spine¶
The operation's ORBAT — operation → units (squadron/troop/section) → callsigns (principals) — is the single grouping structure everything else keys off:
| ORBAT drives | How |
|---|---|
| Publish scopes | Each unit gets a key-space subtree; plans and reports address unit scopes |
| Comms matrix | Auto-suggested nets per unit + a command net; a signaller edits from there |
| Plan targeting | A plan's tasked units define its distribution set |
| Report rollup | Reports aggregate up the unit tree |
- Commander = Directory-authorized appointment, not a token role. Token roles (authorization model) stay global capability grants; the ORBAT records position. Team-leader powers — author a plan for the unit, task subordinates, submit the unit report — derive from the appointment. Directory must authorize and commit the exact complete ORBAT unit mutation while connected. Relay + Storage still receives only the opaque key and sealed record; it never receives the reverse-addressing salt or interprets the unit structure.
- Edge can request provisional teams while connected. A field callsign requests an
ad-hoc team (an ORBAT record,
origin=field, provisional); after Directory commits and authorizes that exact mutation, it is rendered in the bubble, ingested by web, and blessed or retired by an admin exactly like field channels in the comms-plan registry. While disconnected, the team exists only as a visibly unactivated local draft: it notifies nobody, grants no appointment or team scope, and must be explicitly retried after reconnecting.
Receivers derive appointment powers only from an ORBAT envelope whose exact
COMMITTED_AUTHORITY_MUTATION publication grant and current Directory/revocation state
verify. A locally drafted, reusable-content, missing-grant, or stale-ledger ORBAT record
is only an inert proposal/projection and cannot authorize planning, tasking, reporting,
membership, or channel scope.
Each unit_id is deployment-global and belongs to one operation. Its one opaque retained
key carries one complete OrbatRecord: definition/parent, membership, commander,
origin/provisional state, classification, owner, and retired state. Changing any field
republishes the complete record at seq + 1; the Directory capability ledger version is
that same sequence. Receivers never merge independent ORBAT subfield streams. Latest
complete sequence wins, an equal conflicting sequence fails closed, and a higher
retired=true record prevents delayed stale resurrection. A multi-unit edit commits all
affected records atomically, while each unit increments independently.
Callsign — assigned, not self-declared¶
Every IdentityToken carries a canonical, non-blank callsign covered by the
Directory signature. Directory derives the token value at its single issuance
seam:
- Human (
kind:operator) fallback: the first eight characters of the stableprincipal_id, uppercased. Directory derives and signs this value; clients do not implement their own short-ID fallback. - Thing or service (
kind:device): the Directory device record'scallsign, trimmed and uppercased. The device-administration form accepts 1–32 characters from[A-Z0-9 ._/-]after canonicalisation and rejects a blank or oversized result.
Human tactical appointments remain an ORBAT concern. When web adds a human to
an ORBAT unit, it may assign an operation-scoped callsign in
orbat_unit_members.callsign through the
ORBAT builder.
The value rides OrbatRecord.member_callsigns; there is no separate callsign
message or self-service entry point.
Consumers resolve a display callsign only after verifying the relevant signed objects:
- For a human, use the current verified ORBAT appointment's canonical callsign when present; otherwise use the verified token callsign.
- For a thing or service, always use the verified token callsign. An ORBAT record cannot override it.
Directory display_name remains separate from the tactical label for OPSEC.
It may appear in an explicitly named human-facing suffix such as
"CALLSIGN · Operator Name", but never replaces either verified callsign
source. Component configuration, login/session request fields, payload
self-claims, and local UI state are not callsign sources.
Snapshot vs live. Most surfaces — COP labels, chat, voice speaker badges, the command-and-control issuer panel, device tasking dialogs — resolve live off the current ORBAT projection, so a re-assignment or a callsign edit is visible immediately everywhere. Two surfaces snapshot instead, because they are records of a moment rather than a live view:
- SitReps stamp the author's resolved callsign at compose time
(
ReportRecord.callsign) — durable, kept even if the ORBAT changes later. - Web replay rows snapshot at ingest time (a resolver backed by a 30 s TTL cache), so a replay reads as it did at the time it was recorded.
Provisional-team merge. If, on bless/merge, a field-formed provisional
unit's member callsign collides with an existing planned callsign, the
colliding callsign drops to null on merge (first-merged-wins) rather than
failing the merge or silently overwriting the planned assignment.
Wire boundary. PETRA 1.0 places the mandatory token callsign at
IdentityToken field 18; retired field 3 stays reserved.
TacNetMessage.device_callsign, VoiceDatagram.sender_callsign, and retired
session device_callsign fields remain unavailable for attribution. The
wire contract
defines verification, boot failure, and the coordinated credential reset.
Plans — down and out¶
Web authoring flow (operational): create operation → build ORBAT + appoint commanders → accept/adjust the auto-suggested comms matrix → author a plan → publish. Publishing writes the plan record to the record plane (tasked units' scopes) and registers its attachments so tasked principals' manifests list the blobs.
Android authoring flow (tactical) — the 7 Questions estimate. A guided flow, one card per question, whose answers are mostly references to things already on the device:
| Question | Captured as |
|---|---|
| Q1–2 — situation, mission | Text + reference to the received parent plan |
| Q3–5 — effects, actions, resources | Tasks assigned to callsigns / sub-units picked from the ORBAT |
| Q6 — synchronisation | Ordered timeline of those tasks |
| Q7 — control measures | Drawings on the map — boundaries, report lines, FUPs are ordinary draw-plane content; the plan record binds their ids |
Plan record shape:
| Field | Meaning |
|---|---|
id |
Stable plan id — the merge key |
parent |
Parent plan id (empty for a top-level operational plan) |
scope |
The authoring unit — defines the distribution subtree |
mission / situation |
Text |
tasks[] |
{unit or callsign, task text, timing} |
controlMeasures[] |
Draw-plane drawing ids |
attachments[] |
{name, sha256} blob references |
classification |
Label; operation ceiling applies |
seq |
Monotonic per id — latest-wins versioning |
- One publish, two audiences. The team leader's publish lands in his troop's bubble immediately (peer mode — no HQ reach needed) and, whenever any path to web exists, web ingests it as a field-origin plan under the operation. The author never chooses "send up" vs "send out".
- Plans form a tree mirroring the ORBAT. A commander receives a parent
plan, extracts his unit's task, runs his own estimate, publishes a child plan
with
parentset. HQ can walk the tree: intent at the top, execution detail at the leaves. - Versioned, not append-only. Re-planning during the day republishes the
same
idwithseq+1; latest wins on every device. - Visibility: own subtree + chain of command. A plan flows down to the units it tasks and up the command chain (superiors and web always see it). Flanking units do not receive it by default; a per-plan additive "also share to unit X" is deferred (see Not in scope).
Reports — up¶
A report is a small append-only record:
| Field | Meaning |
|---|---|
unit / callsign |
Reporting unit and author |
dtg |
Date-time group (auto-stamped) |
location |
Auto-stamped from the device position |
kind |
sitrep at v1; new kinds (LOCSTAT, CASEVAC 9-liner) are additive later on the same shape |
status |
Green / amber / red |
narrative |
Free text |
attachments[] |
Blob references (e.g. a photo, uploaded via the blob plane) |
inResponseTo |
Report-requirement id, when answering a tasked report |
- Published to the unit scope + the echelon above: bubble peers get free situational awareness; web ingests into the operation log and renders a per-unit feed plus a rollup (which units are green, which silent).
- Append-only — a correction is a new report, never an edit.
- Offline, the existing outbox holds it and forwards on reconnect.
Tasked reports. A commander publishes a report requirement record down
his subtree: {kind, one-shot due DTG or cadence, scope}. The expected
reporters are the commander-appointed units of that subtree, root included —
a unit with nobody appointed is never asked, and is surfaced as an explicit
ORBAT gap beside the matrix rather than dropped from view. The commander's device prompts
when a report is due; web and the commander's device render a compliance
matrix (reported / late / silent) per battle-rhythm period.
Security and enforcement¶
- Every record is an
AuthEnvelope-signed publish, verified per the security model — envelope crypto proves who. - The ORBAT projection proves may: receivers (devices and web ingest) check
the verified author's appointment against the current ORBAT before treating a
plan or report requirement as authoritative for a scope — the same
receive-side pattern as the
channel-definition integrity gate.
A report needs the command appointment on the reporting unit itself —
membership alone does not qualify, and a member-authored report is dropped.
Authority is judged at ingest against the CURRENT ORBAT, not at the
report's
dtg_ms: a return authored by the then-commander but delivered after a re-appointment is refused ("the latest ORBAT always takes precedence"). A refused field return is an accountability event — someone in contact believed they had reported — so it lands on the operation's activity timeline (and therefore in replay and the export), never only in a server log. - The appointment rule itself is pinned across platforms by common's
testdata/report_appointment_vectors.json: web and Android keep their own implementations, and the shared corpus is what stops the two ingest gates drifting apart. - Every record and artifact carries a classification label; the operation ceiling and the author's clearance bound it, as for field channels.
- Web ingests field-origin records as provisional and never partially trusts a failed verification.
Replay and catch-up¶
"Replay" means two things here; the design serves both.
Device catch-up (record plane). A late joiner or reconnecting device queries catch-up per scope subtree — the same machinery channel defs use today:
- Plans, atomic ORBAT unit records, report requirements are
authority-bearing durable records (the
DURABLE_GRANTverify pattern in the security model — they must verify after the author's token expires). Catch-up returns the latestseqversion only — a device joining mid-operation receives the current orders and the current ORBAT, never the day's edit history. - Reports are append-only content (the
DURABLE_CONTENTpattern). Catch-up is bounded to the current battle-rhythm window; deeper report history lives on web and is pulled over HTTPS, never gossip.
Operation replay (AAR / export). Web keeps every plan seq version and
the full report log, and exposes them as new NDJSON layers in the
replay export alongside the existing ones
(sync_task is the precedent): plan, report, orbat_change,
report_requirement. ORBAT state at any cursor T reconstructs by folding
orbat_change rows — the same pattern as target_transition. Attachments
export by reference, as voice audio does. An AAR consumer can then answer:
which orders existed at 1400, who commanded a callsign then, which units had
reported that period. The plan layer replaces web's sync_task layer
outright — the synchronisation-matrix task store retires into plan records
(see the UX flows, Journey 7) with no
dual-export compatibility window.
Degraded operations¶
The bubble is fully functional with no HQ reach: the 7 Questions flow, plan publication to the troop, drawings, and report authoring all work peer-to-peer. Records queue in the outbox toward web; on reconnect they flow up, web ingests provisional field content, and manifests refresh for any new blob references. There is no separate offline mode — the same publish serves both states.
Not in scope (deferred)¶
- Per-plan additive share ("also share to unit X") for flanking coordination — visibility stays subtree + chain of command until a real need appears.
- Report template kinds beyond
sitrep— the record shape admits newkinds without schema change. - Per-operation role-definition overrides — tracked in the authorization model.
- Phase as a first-class object —
phasestays an optional attachment label until phase transitions need machinery.
Sequencing (multi-repo)¶
Each behind its own PR set; the record plane reuses the comms-plan-registry machinery (signed defs, seq-monotonic merge, web ingest of field-origin records):
common— record schemas: plan, report, report requirement, ORBAT entry/appointment; scope-key layout per unit.- web — operation setup flow (ORBAT builder, appointments, matrix auto-suggest), artifact store + manifest endpoint, record-plane ingesters (plans, reports, ORBAT), report-compliance and plan-tree views.
- Android — manifest client + blob cache, 7 Questions flow, plan projection + ORBAT projection, report form + due prompts, appointment check on the receive side.
Appendix — prior art¶
How the established systems handle the same problem, and what this design takes from each:
| System | Mechanism | Takeaway |
|---|---|---|
| TAK / ATAK | CoT events are the ephemeral live plane; the Mission API / Data Sync keeps a server-side mission store clients subscribe to, with late joiners syncing missed changes on reconnect; Data Packages are blobs downloaded on demand | Validates the two-plane split (mission feed ≈ record plane, data package ≈ blob plane, mission DB ≈ web store of record). Gaps this design closes: no ORBAT spine (flat server groups), plans are unstructured shared content, and mission sync requires the server — a pure peer mesh loses it, where the bubble here keeps working |
| SitaWare (Systematic) | Headquarters does staff planning and plans/orders dissemination down to Frontline/Edge; SitaWare Tactical Communication replicates typed data over DDIL links, resynchronising latest-first on reconnect and skipping outdated data; plans share as MIP4 ZIP files when disconnected | Closest echelon model. Latest-first resync is this design's latest-seq-only catch-up; MIP4 files ≈ the blob plane. Worth stealing later: per-information-type bandwidth priority (tracks > reports > plans > blobs). Its planning is staff-driven top-down — edge-authored plans flowing up are not its strength |
| Delta (Ukraine) | Cloud-native backend, thin clients on any device, deployed across all echelons; situational-awareness centres fuse reports flowing up from units, drones, and vetted civilians | Validates the web-native operational layer and the reports-up-then-vet flow (their hub vetting ≈ web ingest-provisional-then-bless). Weakness to avoid: cloud dependence — the offline tactical bubble is not its story, which is exactly what the record plane's peer mode covers |
The combination this design targets — structured planning authored at the edge, one publish serving both the local bubble and HQ, fully functional disconnected — none of the three provides end-to-end.