Skip to content
ULAP ONE
Research preview
Management · actuation · tailnet only

Control plane

view-only on the public domain

Change something, and prove what changed. Every panel names its source inline; absent reads —, and a real zero reads 0.

Mode unknown PAWS unknown
Waiting Not a snapshot Updated nothing has answered yet Sources 0 of 14 sources answering · 14 waiting
Mode controller no answer yet Log tail no answer yet UPF metrics no answer yet UEs on the RAN adapter no answer yet PAWS database no answer yet Subscriber sim no answer yet
Data provenance

What this control surface reads, and what it is allowed to conclude

Waiting

Identity

Read by
Your browser, over this origin. No server-side cache sits in between.
Mode
Live poll. This is not a snapshot and it is not a recording.
Last read
not yet
Sources
14

Measurements

Mode controller
Waiting · never
GET /api/mode/status · every 2500 ms · operating mode, force lock, hysteresis thresholds and the spectrum block
Log tail
Waiting · never
GET /api/logs/logs/{container}?tail={n} · every 3 s · the last n lines from the selected container
UPF metrics
Waiting · never
GET /api/control/upf/metrics · every 4 s · user-plane byte rates, the backend/fabric pair, and the adapter/S1AP session gauge — NOT the canonical attach count
UEs on the RAN adapter
Waiting · never
GET /api/ran/metrics · every 4 s · the RAN adapter’s own ran_attached_ues gauge, with the adapter identified from the same document the number came out of
PAWS database
Waiting · never
GET /api/paws/status · every 5 s · the transmit authority — grant state and emergency waiver
Subscriber sim
Waiting · never
GET /api/subscribers/status · every 5 s · the simulated demand counter and the active scenario
Real devices
Waiting · never
GET /api/control/devices · every 6 s · the UERANSIM and srsUE attaches ran-manager reports
Service inventory
Waiting · never
GET /api/control/inventory · every 8 s · which services exist, which are running, and which are absent by choice
Access audit
Waiting · never
GET /api/control/access?limit=10 · every 8 s · the append-only access audit feed — Mongo-backed
Diagnostics
Waiting · never
GET /api/control/diagnostics · every 10 s · correlated findings across the inventory and the Prometheus targets
Open5GS subscribers
Waiting · never
GET /api/control/subscribers · every 15 s · provisioned SIM subscribers — Mongo-backed, Ki and OPc redacted server-side
Principal
Waiting · never
GET /api/control/whoami · every 15 s · who the control API thinks you are, and how far that trust reaches
Container list
Waiting · never
GET /api/logs/containers · every 15 s · the docker container inventory behind the log tail
Capability register
Waiting · never
GET /api/control/capabilities · every 30 s · the NOW / PHASE-1 / EXTENSION register parsed from docs/CAPABILITIES.md

Thresholds, and where they come from

Stale after 2.5 poll cadences (floor 4 s, cap 90 s)
Source: ui/src/lib/domain/liveness.svelte.ts — derived from wall clock, not set by the poll, so a loop that DIED is still detected
Not answering after 3 consecutive misses
Source: ui/src/lib/domain/liveness.svelte.ts — one miss is a blip, three is a fault
Static after 20 poll cadences with no change in the reported value (floor 60 s, cap 5 min)
Source: ui/src/lib/domain/liveness.svelte.ts — an ORTHOGONAL axis. A static source is still live: it answered. The two facts are separate because on 2026-08-03 a tile read 0.0 Mbps for fifteen minutes beside a chip that said "Live · just now". Only sources that report their value to feed.ok(id, value) are judged; the rest claim nothing.

What this proves

  • Each source above answered your browser at the stated time, over this origin.
  • Every actuation on this page is an explicit POST, and the panel that shows the result reads it back rather than assuming it.

What this does NOT prove

  • A byte-rate is not a radio measurement. When the fabric is sim or ZMQ the counters are real but the air interface is not, and no tile calls the result a radio number.
  • The simulated UE load is not an attachment count. Real attaches are a separate tile with a separate source.
  • An empty table is not an empty system. When a Mongo-backed endpoint answers 503 the table renders the API’s own explanation.
  • Nothing on this page is an authorisation to transmit.

Environment

Trust
The tailnet. There is no per-request authentication on the control API.
Mutations
Allowlisted profiles only, confirmation required, and never `down -v`.
Secrets
Ki and OPc are never returned by the API, so they cannot be rendered here.

Provenance

Mode controller
GET /api/mode/status · every 2500 ms
Log tail
GET /api/logs/logs/{container}?tail={n} · every 3 s
UPF metrics
GET /api/control/upf/metrics · every 4 s
UEs on the RAN adapter
GET /api/ran/metrics · every 4 s
PAWS database
GET /api/paws/status · every 5 s
Subscriber sim
GET /api/subscribers/status · every 5 s
Real devices
GET /api/control/devices · every 6 s
Service inventory
GET /api/control/inventory · every 8 s
Access audit
GET /api/control/access?limit=10 · every 8 s
Diagnostics
GET /api/control/diagnostics · every 10 s
Open5GS subscribers
GET /api/control/subscribers · every 15 s
Principal
GET /api/control/whoami · every 15 s
Container list
GET /api/logs/containers · every 15 s
Capability register
GET /api/control/capabilities · every 30 s
Caveat, verbatim

This is the only route in the console that changes system state. A view-only public origin is a deployment property, not an API guarantee; anything that can reach the tailnet address can actuate.

Raw source

  • docs/CONTROL-PLANE-API.md the endpoint contract this page is built on

01 · Throughput

canonical adapter user plane
Uplink source: /api/control/upf/metrics
Downlink source: /api/control/upf/metrics
Uplink · last 0 successful reads

Fabric unknown — the byte-rate cannot be labelled, because the adapter has not been scraped yet. Absent reads ; a real zero reads 0. The backend says what computes the physical layer; the fabric says what carries it. A sim or loopback byte-rate is never labelled a radio number. A sample is pushed to the trace only on a successful read, so use the freshness strip above to tell a flat line from a dead feed.

02 · Devices and UEs

control-api fabric: unknown
UEs on the RAN
Unknown — could not ask

The RAN adapter has not been read yet. Nothing is asserted.

No fabric was named, so what any number here would be a count of is not established.

Read nothing further into it until the fabric is known.

Source not read yet

Producer: no producer answered
Same gauge, second hop source: /api/control/upf/metrics · the control API's own read of ran_attached_ues, or the host MME S1AP count on a host-native cell. A cross-check, not a second measurement.
Sim load source: subscriber-sim · counter only, NOT a real attach

Where this number comes from, and what it is a count of. The top figure is the RAN adapter's own ran_attached_ues series, read once at /api/ran/metrics. The body is checked before a number is taken out of it: it must be a Prometheus exposition carrying ran_backend_info, so a health blob from some other service on the same port cannot become a UE count. If the check fails the panel says Unknown and prints nothing.

It counts UEs. It does not count PDU sessions. One UE can hold several; the UE on this stack holds four. Four is a different quantity, it is not shown here, and the two are never added. Sim load is a demand generator with no radio behind it, and is never added to either.

03 · Spectrum sensing

tvws ResNet18 · 6-class
Channel 21 Unknown source: /api/mode/status · spectrum block
Model confidence certainty, not accuracy

Research preview, not a spectrum-access authority. On the 28-channel UHF survey of 2026-08-04 (560 records, 10 occupied / 15 vacant, ground truth from measured physics) this checkpoint detected 2 of 10 live multiplexes and called 48 of 300 vacant records occupied, at 0.93 to 0.99 confidence. With the physics detector wired in — same unchanged checkpoint, one process, differing only in whether a calibrated control floor is passed — that becomes 10 of 10 multiplexes and 0 of 15 vacant channels called occupied. Not clean yet: UHF 28 still asserts OCCUPIED on 8 of 20 records, and that threshold was not moved. This caveat used to blame the threshold for calibration on synthetic noise; a measured receiver-noise null falsified that on 2026-08-05 — real receiver noise tops out at comb z = 2.289 against the synthetic 2.19, and 0 of 300 records reach the 3.5 threshold. The firing arrives through the antenna and its cause is now open: a genuine emission below the energy floor, which would mean the ground truth needs revising rather than the detector, or front-end intermodulation. The RX-gain sweep that separates them has not been run. The earlier “40 of 80” figure came from a 2026-07-29 set that was entirely occupied, on which answering OCCUPIED unconditionally also scores 80 of 80 — it measured no discrimination. Rollout is opt-in and this service was not restarted: with no calibrated control floor configured the engine falls back to the legacy CNN verdict and reports occupancy_source: cnn-confidence, so the occupancy shown above is the classifier’s unless a floor is set. Model confidence is not accuracy: high confidence has been measured on wrong answers, so the theta_sense = 0.85 gate does not catch them. PAWS grant state is the authority.

04 · Mode and PAWS controls

mode-controller
Mode unknown

Force locks the controller. Reset returns it to autonomous hysteresis (trace-driven). Degrade at —, recover at —, — transitions so far. Controller state: Awaiting the controller..

PAWS database PAWS unknown

Disaster scenario: down drops the regulator; the waiver pre-authorises sensed channels. The state shown is read back from the database, never assumed from the button you pressed.

05 · Scenarios and subscribers

subscriber-sim
Sim UEs source: subscriber-sim /status · counter only
Scenario source: subscriber-sim /status

Everything on this card is simulated demand. None of it registers on a radio, and none of it appears in the real-attach figure in section 02.

06 · Stack lifecycle

control-api
no job started

Allowlisted profiles only, confirmation required, and never down -v. The line above is the control API's answer, not an assumption about what happened.

07 · Diagnostics

inventory + prometheus targets
Health not measured
Healthy services source: /api/control/diagnostics

A service reported absent is not in the active profile, which is different from unhealthy. Absence is neither a pass nor a failure.

08 · Capabilities

control-api
Now 0
Phase 1 0
Extension 0

The capability register has not answered yet.

09 · Access

control-api
Principal No per-request auth yet — trust ends at the tailnet boundary.
tailnet-trusted

/api/control/{whoami,users,provision,access,auth,esim}. Ki and OPc are never returned by the API and are never rendered here. Invite codes are shown once.

10 · Service inventory

control-api The control API has not answered yet.
Every service the control API knows about, its status, and the container backing it
SERVICESTATUSCONTAINER
The control API has not answered yet.

11 · Real UEs

ran-manager
Real UERANSIM and srsUE attaches reported by ran-manager, with IMSI, kind and assigned IP
IDIMSIKINDIP
ran-manager has not answered yet.

12 · Open5GS subscribers

Ki / OPc redacted
Provisioned Open5GS subscribers. Ki and OPc are redacted by the API and never reach this browser.
IMSIKIAPN
The subscriber store has not answered yet.

13 · Audit timeline

latest 10
The ten most recent entries in the append-only access audit feed
TSACTIONACTORTARGET
The access audit feed has not answered yet.

14 · Logs

logs-proxy
Connecting to logs-proxy…

15 · Terminal

opt-in /terminal/
Terminal not connected

Operator opt-in. A web shell is remote code execution, so this panel does not dial out on page load. The host wires ttyd at /terminal/; press connect to try it. A failed probe here means the shell was never provisioned, which is absence, not a fault.

Same-origin nginx gateway. View-only on the public domain, control on the tailnet. Every panel degrades honestly: a source that is down renders as , or names the service that did not answer and repeats the reason the API gave, and never a plausible number. Ki and OPc are never returned.
Measurement axes

What the throughput and UE numbers on this page are, and are not

Fabric unknown Backend unknown

Identity

Backend / phy_tier
not reported — what COMPUTES the physical layer
Fabric
not reported — what CARRIES the physical layer
Byte counters
UPF tun0, read by the control API
UE count
GET /api/ran/metrics — the RAN adapter’s own ran_attached_ues series, taken only from a body that proves it is the adapter. A count of UEs, never of PDU sessions.
The same gauge, second hop
GET /api/control/upf/metrics attached_ues — the control API’s read of the same series, or the host MME S1AP eNB-UE count on a host-native cell. Kept as a cross-check: if the two hops disagree, one of them is stale.
Sim load
subscriber-sim — a demand generator, not an attachment

Measurements

Uplink
source: /api/control/upf/metrics. Absent reads —; a real zero reads 0.
Downlink
source: /api/control/upf/metrics. Absent reads —; a real zero reads 0.
UEs on the RAN
unknown
Nothing is asserted. The RAN adapter has not been read yet. Nothing is asserted.
The same gauge, second hop
source: /api/control/upf/metrics attached_ues — the control API’s read of the same series, or the host MME S1AP count. A cross-check, not a second measurement.
Sim load
A counter only, NOT a real attach. Scenario: —

Thresholds, and where they come from

Degrade below
Source: mode-controller /status — degrade_threshold_mbps
Recover above
Source: mode-controller /status — recover_threshold_mbps

What this proves

  • The byte counters are REAL measurements even when the fabric is simulated: ZMQ is virtual RF, but the bytes through the UPF tunnel were counted.
  • The UE count and the simulated load come from two different producers and are never added together.
  • The UE count is only read from a body that identifies itself as the RAN adapter. A different service answering 200 on the same address cannot become a number here.

What this does NOT prove

  • A sim or loopback byte-rate is NOT a radio measurement. Over ZMQ there is no air interface, no scheduler contention and no propagation, so the figure says nothing about what a radio could carry.
  • The sim load is not an attach. No UE registered and no PDU session was created for any of it.
  • A dash is not a zero. Absent means the producer did not report the field; a measured zero renders 0.
  • On a sim fabric the UE count is a presence count, not a registration. It proves a UE instance is on the RAN network; it does not prove the core accepted it.
  • No number on this card is a PDU-session count. A UE holding four sessions still counts as one UE.

Environment

Backend axis
What computes L1: none (L3 sim), a real CPU PHY, or a GPU PHY.
Fabric axis
What carries L1: sim/ZMQ, conducted SDR loopback, or an O-RU 7.2x fronthaul.
Why two axes
A GPU PHY over ZMQ and a CPU PHY over a real radio are different claims. Collapsing them into one word is how a byte-rate becomes a radio number.

Provenance

Throughput and the cross-check count
GET /api/control/upf/metrics
UE count
GET /api/ran/metrics — ran_attached_ues, shape-verified before it is read
What the count is of
the adapter has not answered in its own shape yet
Sim load
GET /api/subscribers/status
Mode and thresholds
GET /api/mode/status
Caveat, verbatim

Absent reads —; a real zero reads 0. The backend says what computes the physical layer; the fabric says what carries it. A sim or loopback byte-rate is never labelled a radio number. The UE count is a count of UEs, never of PDU sessions, and the sim load is never added to it.

Raw source

  • docs/RAN_ADAPTER.md the canonical metric contract these fields come from
Access plane

Who this console thinks you are, and where its records live

tailnet-trusted Audit feed not returning rows

Identity

Principal
Auth model
tailnet-trusted
Access store
MongoDB at mongodb:27017 — see MONGO_URL
Subscriber store
The Open5GS subscriber collection, same MongoDB instance

Measurements

Access audit
0 events
The access audit feed has not answered yet.
Provisioned subscribers
0
The subscriber store has not answered yet.
Capability register
0 now · 0 phase-1 · 0 extension
The capability register has not answered yet.

Thresholds, and where they come from

Trust boundary the tailnet
Source: There is no per-request authentication on the control API. Anything that can reach the tailnet address can actuate. View-only is enforced at the public origin, not in the API.

What this proves

  • The audit rows shown were returned by the access store and are append-only at the API.
  • Ki and OPc are never returned by the API and are therefore never rendered here — the redaction is server-side, not a UI choice.

What this does NOT prove

  • An empty audit table does not mean nothing happened. When Mongo is unreachable the API answers 503 and the table says so, in words, with the reason from the response body.
  • A named principal is not an authenticated one. The trust here ends at the network boundary.

Environment

Store
MongoDB, shared by the access plane and the Open5GS subscriber collection
Failure mode
Both endpoints answer HTTP 503 with a detail string naming mongodb:27017 and MONGO_URL

Provenance

Principal
GET /api/control/whoami
Audit feed
GET /api/control/access?limit=10
Subscribers
GET /api/control/subscribers
Capabilities
GET /api/control/capabilities
Caveat, verbatim

Until 2026-08-04 both Mongo-backed endpoints went through a fetch helper that returned null on failure and discarded the response body. The API was answering "Access store Mongo unreachable — check mongodb:27017 / MONGO_URL" and the page rendered an empty table with no cause and four console errors.

Raw source

  • services/ulap-control-api/main.py where the 503 detail strings are raised
Amini Amini Infratech for the Global South

Every link and every data fetch on this console is origin-relative. One origin fans out by path: / gateway, /video/, /grafana/, /prom/, /api/. An absolute http://localhost:NNNN URL works on exactly one machine, the one it was written on, and is blocked as mixed content the moment the page is served over https, which is how every operator actually reaches this stack.