feat(stream): read the connection ack instead of assuming it - #63
Merged
Merged
Conversation
The platform now opens every stream connection with a `ready` frame (protean 237bcd0, #80) stating what it is, what it is recording, how often it pushes and how much it retains. Four things the console had been asserting on its own are now read from it, and where the platform says nothing the console says it does not know rather than falling back to the guess. - the LIVE badge printed "200 buffer" on every platform — a ring size this console had never been told and could not see. It prints the stated `capacity`, or nothing at all. - the silence watchdog was 6s because the platform was assumed to push at 1Hz. It is six times the stated `tickMs`, clamped to 2-60s: a few milliseconds would trip on scheduler jitter and an hour would never trip, and this is the only signal that a platform is gone. - the module table read an empty `metrics` array as "switch this on", which is equally true of a platform that is on and idle — and it told operators to enable a setting that was already enabled. It reads `metricsEnabled`. Its "and restart" was wrong too: the key is live. - the empty trace table promised "requests appear here as the platform serves them", which nothing keeps while `trace.enabled` is false. It reads `tracesEnabled`. `tracesEnabled` is also read before `metricsEnabled` in the module panel. Per-module metrics are aggregated from recorded requests, and the contract states `metricsEnabled` as the effective value — so a platform with recording off reports metrics off as well, and that value read on its own would produce "turn it on and rows appear": an instruction that changes nothing beside a promise nothing can keep. The ack is display and diagnostics only — no behaviour branches on which implementation answered. Its values are connection-scoped and dropped on reconnect, because they are live settings on the platform and the last connection's answers may no longer be true. A platform that sends no ack is unchanged: Go still sends the older `hello`, Rust sends nothing, and both captures stay in the fixtures as that axis. The protean fixture is the real connect bytes, captured 2026-09-21 against examples/quickstart. Tags: #stream #honesty Co-Authored-By: htjulia <htjulia1@gmail.com>
An idle platform is what a console shows the moment someone opens it, and two panels handled that state badly. The latency chart removed itself when there were no samples, which also took half the row it shares with the status mix and left a layout that read as broken rather than quiet. It draws its frame instead — on a default scale, labelled as one, with nothing over the grid: a line, an area or a point would each be data the platform never sent. The status mix printed "1 req" beside four zeroes. The header count and the bar widths were one number, and the `|| 1` that keeps a width from dividing by zero was also what the header showed. The guard belongs to the geometry, not to the count. Tags: #honesty Co-Authored-By: htjulia <htjulia1@gmail.com>
The dev server and the header answered "which platform is this console talking to" separately, and disagreed twice. The proxy read `process.env`, which does not carry `.env` files — Vite loads those itself — so setting VITE_PROTEAN_TARGET in `.env` as the README instructs moved the name in the header while every request still went to the default. The header named a host nothing was sent to. With the variable unset they then fell back differently: the proxy to localhost:8080, the header to the dev server's own origin. The config resolves it once, hands it to the proxy, and injects that exact string for the header to read. Injection is dev-only on purpose: a production build carries no proxy and must keep naming its own origin, which is where its requests actually go. Tags: #dev Co-Authored-By: htjulia <htjulia1@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The platform opens every stream connection with a
readyframe now(protean #80,
237bcd0), stating what it is, what it isrecording, how often it pushes and how much it retains. The console had
been asserting four of those on its own. It reads them instead — and
where the platform says nothing, it says it does not know rather than
falling back to the guess.
Two further fixes came out of staring at an idle platform while
verifying this, and are in their own commits.
What the console stopped asserting
LIVEbadgeLIVE · 200 bufferon every platform — a ring size this console had never been told and could not seecapacity, or no claim at alltickMs, clamped to 2–60sprotean.trace.metrics.enabled=trueand restart" — told operators to switch on a setting that was already on, and the key is live, so "restart" was wrong toometricsEnabled: off / on-and-idle / not statedtrace.enabled=falsetracesEnabledand says recording is offprotean 0.1.0-SNAPSHOT(display only)tracesEnabledis read beforemetricsEnabledin the module panel,and that ordering is required rather than defensive. Per-module metrics
are aggregated from recorded requests, and the contract states
metricsEnabledas the effective value (trace.enabledANDtrace.metrics.enabled) — so a platform with recording off reportsmetrics off as well, and that value read on its own produces "turn it on
and rows appear": an instruction that changes nothing beside a promise
nothing can keep.
The ack is display and diagnostics only — nothing branches on which
implementation answered. Its values are connection-scoped and dropped
when the stream is rebuilt, because they are live settings on the
platform and the last connection's answers may no longer be true.
A platform that sends no ack is unchanged. Go still sends the older
hello, Rust sends nothing, and both captures stay in the fixtures asthat axis: the console stays live, claims nothing, and keeps the 6s
window it has always used.
Verified against a running platform, not only against tests
examples/quickstarton :8080, protean237bcd0. The protean fixture instream-fixtures.tsis the real connect bytes from that session(correlation ids synthetic — public repo), not a constructed example.
readyfirst, once per connection;metrics/modules/summarythen repeat at 1Hz.
buffered(5) equalled the rows of thetraceframe behind it (5).
platform into that state, because they cannot be told apart from the
data: the
metricsframe is[]in all of them.metricsEnabled: true→ No module traffic yetmetricsEnabled: false→ Module metrics are offtracesEnabled: false→ Trace recording is off, in both panelsdropped the stream, the hook rebuilt it, and the panels changed on the
new ack without the page being touched.
The contract dependency above was found during that verification and
fixed on the platform side as well: the ack was sending the raw metrics
switch while the MCP tool answered with the effective value, so the same
name meant two things. Reported, and protean has pinned the contract to
the effective value.
Also in this PR
fix(ui): keep an idle screen readable and truthful— the latencychart removed itself when there were no samples, taking half the row it
shares with the status mix and leaving a layout that read as broken
rather than quiet. It draws its frame now, on a scale labelled as a
default, with nothing over the grid: a line, an area or a point would
each be data the platform never sent. And the status mix printed
1 reqbeside four zeroes — the header count and the bar widths were onenumber, and the
|| 1that keeps a width from dividing by zero was alsowhat the header showed.
fix(dev): resolve the proxy target once, for both uses— the devproxy read
process.env, which does not carry.envfiles (Vite loadsthose itself), so setting
VITE_PROTEAN_TARGETin.envas the READMEinstructs moved the name in the header while every request still went to
the default: the header named a host nothing was sent to. Unset, the two
then fell back differently (proxy to
localhost:8080, header to the devserver's own origin). The config resolves it once and hands it to both.
Dev only — a production build carries no proxy and must keep naming its
own origin, which is where its requests actually go.
Tests
199 passing, 29 of them new: the ack's watchdog sizing and clamps,
partial and unreadable acks, per-connection scoping, the three empty
states in both panels, the empty chart, and the status count. The
ack-contradicting pair (
tracesEnabled: falsewithmetricsEnabled: true) is kept as a guard — the console talks to three implementationsand cannot verify an ack, so it declines to promise rows on the strength
of one.