Data first, drawing second
Subsystems publish typed facts. C lays them out and Glint draws them, and neither decides what a number means.
- Subsystem typed debug facts
- Debug model shared shape
- C layout view preparation
- Glint render-only overlay
2027 · MerTek technology
Engineering reference / System overview
See inside a running program without touching it
A running tri program publishes copies of its state to a separate Wheelhouse process over an authenticated debug association. What the debugger may do and what data it may see are checked as two separate questions. Scene overlays and service panels draw the same typed facts.
Worked example
The Harlan County water utility runs its pump scheduler as a headless tri service. At 02:10 the schedule for Station 7 stops advancing while the other five stations keep running. The on-call engineer works from home, and the service has to keep serving. The customer billing records it holds are off limits to on-call staff.
The operator declared debugging before the incident. The service's debug document opens one endpoint and pins each on-call engineer's key at observe. That level reads and captures but can never stop a call. Debug memory comes from its own budget, maxDebugBytes, so building a snapshot can never fail a call that would have succeeded.
# _host/s/debug.draft: who may look inside the scheduler, and how far.
u16 schemaVersion = 1
u64 maxDebugBytes = 67108864
chest[] members = [
{
text constructorId = "debugEndpoint"
text key = "oncall"
chest config = {
text position = "listener"
text carrier = "plaintext"
ip address = 10.20.0.15
u16 port = 7400
chest[] principals = [
{
u8[32] principal = [
41, 7, 188, 92, 13, 250, 66, 141, 9, 202, 37, 118, 75, 3, 164, 29,
210, 58, 97, 133, 6, 241, 84, 19, 172, 45, 230, 101, 62, 155, 8, 217]
text[] levels = ["observe"]
}
]
text pausePolicy = "suspendBudgets"
}
}
]Ask the timer subject which timers are pending. Every subject answers the same request shape: a subject name, a key and a window. The host takes one reading between two calls, so the answer names the last committed call and never a half-run one.
# debugger -> target
text kind = "observeRequest"
u64 request = 8
text subject = "timer"
text key = "session"
chest window = { u64 start = 0, u64 count = 20 }
# target -> debugger: five session timers, where six stations run
text kind = "observation"
u64 request = 8
text subject = "timer"
text status = "ok"
u64 generation = 41
u64 total = 5
guid trace = 3f2c9a10-7b4e-4c21-9d8a-0e5f6a7b8c9d
u64 span = 418233
u64 parentSpan = 1
u64 runtime = 1
bool after = true
chest? callFirst = null
u64 content = 4
u64 size = 1912Capture the hot path without stopping it. A capture point publishes a snapshot and lets the call continue, under a count and spacing the debugger declares. Snapshots at the line that stores each gauge reading showed Station 7's level arriving null.
# debugger -> target: at most 5 captures, at least 1 s apart
text kind = "capturePoints"
u64 request = 9
text sourceUnit = "a.gauges"
u64[] lines = [58]
u32 limit = 5
u64 spacing = 1000000000
# target -> debugger: the call kept running; its snapshot follows as content
text kind = "snapshot"
u64 request = 0
text reason = "capturePoint"
u64 calls = 418236
text cause = "published"
u64 dropped = 0
u64 refused = 0
u64 stateGeneration = 96114
u64 sceneGeneration = 0
u64 content = 5
u64 size = 22816A pause asked at observe is refused. Stopping a call stalls every request the runtime serves, so control is a separate level the operator grants by name. The receipt says level, nothing changed, and the service kept serving.
# debugger -> target
text kind = "pause"
u64 request = 10
# target -> debugger
text kind = "receipt"
u64 request = 10
text status = "level"
u64 calls = 418240
text[] levels = ["observe"]Reproduce the faulted call offline. The service keeps a rolling flight-recorder tape, so the engineer replays the call that faulted in a separate child with every effect denied. The replay shows each frame as it stood and reaches no live store, sensor or billing record.
# How the service runs: headless, keeping the most recent calls on tape.
tri --headless --flight-recorder 268435456 /opt/pumpsched
# On the engineer's machine: replay the tape, pinned to its digest.
tri --replay /home/oncall/pumpsched-0210.tape --tape-digest <tape-digest>
# A read the tape did not capture answers captureIncomplete;
# the replay refuses rather than guess or reach the live world.Station 7's level gauge went offline at 02:09, its reading came back null, and the timer handler faulted before it re-armed the station's timer. The engineer found that at observe, never paused the service and never saw a billing row. The fix holds the last schedule when a reading is missing, and the replayed tape became its regression test.
System diagram
The overlay and service panel read the same typed facts. Neither view changes application behavior.
Inside the system
Follow the data, decisions, and access boundaries. Each diagram explains a concept used by applications.
Subsystems publish typed facts. C lays them out and Glint draws them, and neither decides what a number means.
A normal product view and a debug overlay use one visibility rule.
Each overlay stage owns one input, one visual result, and one declared setting.
A view reports the setting it asked for, the setting that is active, and why any part of it is unavailable.
Key parts
These are the main boundaries, inputs, outputs, and failure rules. The examples show a specific use of each part.
The debugger is Wheelhouse, in its own process and window. The target is an ordinary tri session. Every debug snapshot crosses as a copied Chest document that the debugger validates as hostile input, so neither side can write the other's memory.
A breakpoint blocks the target's VM thread at a poll point, while Wheelhouse keeps drawing and taking input in its own process.
The debug document grants levels per principal: observe, control, mutate and reveal. Seeing protected data is a separate check against the viewer's own grants. A pause stops the call's wall budget and the session clock, never security time, so revocations and grant changes are checked again on resume.
A snapshot or replay supplies observations and never live authority. An on-call key at observe sees a stored password verifier only as its kind, length and digest.
Systems publish typed facts: state, events, timings and boundaries. A rendered app draws them as a scene overlay, and a headless service shows them in a service panel. Both read the same documents through one request shape.
A frame overlay and the job panel name the same call span. A reviewer can line up a rendered frame with the job that fed it.
Code
Each example is short and complete enough to read on its own. Hover a dotted word for its definition.
A development launch can hold the session until Wheelhouse attaches. A remote engineer reaches a declared endpoint. A client behind NAT dials a support debugger that listens.
# Development: Wheelhouse attaches before the first call runs.
tri --debug --stop-at-entry pumpsched
# Support behind NAT: the support debugger listens ...
wheelhouse --debugger --listen 203.0.113.40:7411
# ... and the customer launches the client with support's host file,
# whose debug document declares a connector endpoint at that address.
On the author's own machine, script mode grants a paired debugger all four levels. A release launch carries an empty set unless its own debug document declares one.
text constructorId = "debugPaired"
text key = "author"
chest config = {
text[] levels = ["observe", "control", "mutate", "reveal"]
u32? storeAudience = null
text pausePolicy = "suspendBudgets"
}
In connector position the target dials support once and admits only the principal it pins. A hijacked address reaches nobody, and launching with the host file is the customer's consent.
text constructorId = "debugEndpoint"
text key = "support"
chest config = {
text position = "connector"
text carrier = "plaintext"
ip address = 203.0.113.40
u16 port = 7411
chest[] principals = [
{
u8[32] principal = [
88, 3, 140, 27, 201, 66, 9, 115, 230, 41, 172, 18, 95, 250, 7, 133,
64, 199, 12, 81, 157, 36, 222, 103, 49, 178, 20, 245, 90, 11, 167, 58]
text[] levels = ["observe", "control"]
}
]
text pausePolicy = "suspendBudgets"
}
Subsystems publish facts and hold no view. Chest carries them, scene policy decides how they look, and the renderer draws. One schema serves live views and replay.
C subsystem hooks -> Chest telemetry -> scene policy -> Reef and Glint
# subsystem snapshots, counters, events, stable ids, source spans
# Chest the same document live and on tape
# scene policy grouping, filtering, layout, color, interaction
# Reef, Glint retained blocks and drawing; no debug logic
The job subject answers the same request the timers do. A subject that cannot answer says unavailable and never sends an empty document in its place.
text kind = "observeRequest"
u64 request = 12
text subject = "job"
text key = "job"
chest window = { u64 start = 0, u64 count = 50 }
Uses
Review questions
Which principals need observe, and which ever need control
Which protected data each debugging role may see
How long flight-recorder tapes are kept on each node