phase: 32_gateway_resolved_target_liveness

Fixture and live stream.conf are byte-identical (modulo header comment). Let me test the parser directly:
This commit is contained in:
2026-10-26 14:03:42 -04:00
parent 25fd74b7c5
commit 51772ffef2
58 changed files with 5272 additions and 478 deletions
+603 -65
View File
@@ -4907,9 +4907,17 @@ def test_the_gateway_job_is_handed_no_rate_estimator(tmp_path: Path) -> None:
def make_gateway_client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
**overrides: Any) -> Any:
"""An app whose only host is the ingress, on clocks fast enough to poll inside a test."""
"""An app whose only host is the ingress, on clocks fast enough to poll inside a test.
The intervals are the poller's own floor, not 0.05: `Job` clamps its cadence at 0.5 s,
while the staleness threshold is three times the *configured* interval. At 0.05 s a healthy
job's gap (≥ 0.5 s) always outlived its own threshold (0.15 s), so its `:stale` row
appeared in the alert list 70 % of the time and any assertion on the list's length was
a test of when the event loop happened to tick. At 0.5 s the threshold (1.5 s) is
three times the gap a healthy job can make, and the premise jobs stay quiet whatever
the machine is doing."""
return build_client(tmp_path, monkeypatch, host_targets=(GATEWAY_TARGET,),
host_interval=0.05, disks_interval=0.05, gateway_interval=0.05,
host_interval=0.5, disks_interval=0.5, gateway_interval=0.5,
**overrides)
@@ -4975,25 +4983,56 @@ def test_the_summary_gateway_block_is_the_stub_of_a_source_that_never_answered(
assert block["href"] == GATEWAY_HREF
def endpoint_identity(out: dict[str, Any]) -> tuple[Any, ...] | None:
"""One endpoint, in the currency the kernel writes — derived here, not by the source.
An endpoint written as an address **is** that address; an endpoint written as a name is every
address the agent's resolver published for it (`out.resolved`), and `ipaddress` folds the
`::ffff:`-mapped and zero-padded spellings so one address in two tables is one key. `None`
means the join has nothing to name the far end with, which contributes nothing to a total.
This is deliberately a **third** reading of "what is one endpoint" (the others are
`gateway.endpoint_of` behind `api._endpoint_values`, and the join itself): since phase 32 two
route lines can reach one address while the configs spell it as two different names, and a
total keyed on the config's text would book it once per name.
"""
port = out.get("port")
if not isinstance(port, int):
return None
spellings = ([out["host"]] if out.get("address") is True
else list(out.get("resolved") or []))
keys: list[str] = []
for spelling in spellings:
try:
address = ipaddress.ip_address(str(spelling))
except ValueError:
continue
mapped = getattr(address, "ipv4_mapped", None)
keys.append(str(mapped if mapped is not None else address))
return (min(keys), port) if keys else None
def endpoint_sums(payload: dict[str, Any]) -> tuple[int | None, int | None]:
"""The two live totals, derived here so the block is checked against a **second** reading.
Inbound is the sum over trunks. Outbound is the sum over the **distinct** `(host, port)` the
route lines name: `sources/gateway.py` repeats an endpoint's whole count on every line that
reaches it (`shared: true`), so summing the lines would book the same sockets twice and print
a router busier than the kernel has connections for. Neither is a sum when nothing answered.
Inbound is the sum over trunks. Outbound is the sum over the **distinct endpoints** the route
lines reach: `sources/gateway.py` repeats an endpoint's whole count on every line that reaches
it (`shared: true`), so summing the lines would book the same sockets twice and print a router
busier than the kernel has connections for. Neither is a sum when nothing answered.
"""
live_in = [row["connections"] for row in payload["listeners"]
if isinstance(row["connections"], int)]
endpoints: dict[tuple[Any, Any], Any] = {}
endpoints: dict[tuple[Any, ...], Any] = {}
for row in payload["connections"]:
endpoints.setdefault((row["out"]["host"], row["out"]["port"]), row["connections"])
key = endpoint_identity(row["out"])
if key is not None:
endpoints.setdefault(key, row["connections"])
live_out = [value for value in endpoints.values() if isinstance(value, int)]
return (sum(live_in) if live_in else None,
sum(live_out) if live_out else None)
@pytest.mark.parametrize("mode", ["full", "predates", "sockets_silent"])
@pytest.mark.parametrize("mode", ["full", "resolved", "predates", "sockets_silent"])
def test_the_summary_gateway_block_is_the_payloads_sums_not_the_pages_arithmetic(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, gateway_snapshot: Any,
mode: str) -> None:
@@ -5003,12 +5042,18 @@ def test_the_summary_gateway_block_is_the_payloads_sums_not_the_pages_arithmetic
hand-written payload, so a rename in the source's shape fails here instead of printing a
plausible zero (rule 7).
The split that replaced `partial` is the point of the three arms. `listeners` / `connections`
The split that replaced `partial` is the point of the arms. `listeners` / `connections`
are **row counts** — a fact readable from a config file, so they are numbers even when the
socket table never answered — while `live_in` / `live_out` are measurements and go `null` with
it. The old block could only report one number per side and needed a `partial` flag to say it
was a subtotal; there is no residual to qualify now, because a socket outside the two files is
counted nowhere by decision.
The `resolved` arm is phase 32's, and it is here because the **block** must not change when the
join widens: twelve fields, the same names, and `live_out` still the sum over distinct
endpoints — now endpoints reached through a resolved name as well as an address, each booked
once however many lines reach it. A block that summed the lines instead would move from 15 to a
bigger, wronger number on the same capture.
"""
payload = gateway_source.parse(gateway_snapshot(mode), "gateway")
expected_in, expected_out = endpoint_sums(payload)
@@ -5054,22 +5099,40 @@ def test_the_summary_gateway_block_of_the_captured_ingress_reads_the_live_join(
"""The real capture, one figure at a time — the block an operator would actually get.
The parametrised test above proves the rule; this one pins the numbers, so a change to the join
that moves a total is visible somewhere rather than only in a browser. The outbound figure is
small **on purpose**: this capture's configs write almost every target as a domain, and a
domain is not something this stack can join to a socket, so only the two `127.0.0.1` endpoints
carry a count at all (`:4443` with 2 and `:8081` with 0).
that moves a total is visible somewhere rather than only in a browser. The three config-shaped
figures are literal, because only the operator moves them; the two live totals are derived from
the capture rather than quoted, because a re-capture of a busy router lawfully moves them.
Since phase 32 task 03 the outbound total is no longer pinned to the two `127.0.0.1` endpoints:
the recorded `gateway_resolver.json` places every name this ingress routes to, so the routes
that used to be dashes are counted through the address the gateway dialled. What is still pinned
is the **rule** the number comes from — one count per distinct endpoint, and never more than the
kernel has connections for.
"""
payload = gateway_source.parse(gateway_snapshot("resolved"), "gateway")
with make_gateway_client(tmp_path, monkeypatch) as client:
answer_as(client, "gateway", gateway_source.parse(gateway_snapshot("full"), "gateway"))
answer_as(client, "gateway", payload)
deadline = time.time() + 10
while time.time() < deadline and not client.app.state.ctx.hub.jobs["gateway"].state.ok:
time.sleep(0.05)
block = gateway_block(client)
assert block["listeners"] == 10, "6 haproxy binds + 4 nginx listen ports"
assert block["connections"] == 32, "every route the two captured configs declare"
assert block["live_in"] == 9, "9 of the 18 live sockets face a configured listen port"
assert block["live_out"] == 2, "the two loopback endpoints, counted once each"
assert block["map_targets"] == 17, "the stream map's routing entries are the fan-out"
assert block["connections"] == 33, "every route the two captured configs declare"
assert block["map_targets"] == 18, "the stream map's routing entries are the fan-out"
# The two live totals are the volatile half: a capture of a running router is a different set of
# connections every second, so they are compared with the payload the block was built from
# rather than with a number typed here (the doctrine this suite already keeps for the socket
# fixtures themselves — assert the shape and the rule, never a particular row). What stays
# literal is what only the two config files decide, above, and what stays asserted as a
# relationship is the direction of the error, below.
assert block["live_in"] == sum(row["connections"] or 0 for row in payload["listeners"])
_, expected_out = endpoint_sums(payload)
assert block["live_out"] == expected_out, "one count per distinct endpoint, never one per line"
assert sum(row["connections"] or 0 for row in payload["connections"]) >= (expected_out or 0), (
"the lines repeat a shared endpoint's count, which is exactly why the block adds the "
"endpoints up instead")
assert block["live_in"] + block["live_out"] <= payload["sockets"]["established"], (
"the reported figures may under-count the kernel's, never exceed it")
assert block["warnings"] == 0, (
"this fleet's two config files connect to themselves, and 0 is the truth rather than a "
"missing measurement — the overview line prints nothing for it either way")
@@ -5106,6 +5169,13 @@ def test_one_dangling_use_backend_is_one_warn_row_on_the_overview(
deadline = time.time() + 15
while time.time() < deadline and not client.app.state.ctx.hub.jobs["gateway"].state.ok:
time.sleep(0.05)
# The two host jobs are this test's premise, not its subject: on a slow machine the wait
# loops above outlive their 1.5 s staleness window, and the alert then gains a `:stale`
# row for the premise and the assertion below reads a three-row list. Stamping their
# `last_ok` pins the premise the docstring states ("the agent answers") and leaves the
# gateway row the only line.
for host_key in ("host:gateway", "disks:gateway"):
client.app.state.ctx.hub.jobs[host_key].state.last_ok = time.time()
rows = gateway_alerts(client)
block = client.get("/api/summary").json()["gateway"]
assert len(rows) == 1, rows
@@ -5123,6 +5193,8 @@ def test_one_dangling_use_backend_is_one_warn_row_on_the_overview(
deadline = time.time() + 15
while time.time() < deadline and client.get("/api/summary").json()["gateway"]["warnings"]:
time.sleep(0.05)
for host_key in ("host:gateway", "disks:gateway"):
client.app.state.ctx.hub.jobs[host_key].state.last_ok = time.time()
assert gateway_alerts(client) == [], "a clean ingress is an all clear, not a green line"
@@ -5164,6 +5236,13 @@ def test_a_gateway_job_that_stops_answering_keeps_one_row_not_two(
time.sleep(0.05)
else:
pytest.fail("the gateway job never went from answering to failing")
# The two host jobs are this test's premise, not its subject: on a slow machine the wait
# loops above outlive their 1.5 s staleness window, and the alert then gains a `:stale`
# row for the premise and the assertion below reads a three-row list. Stamping their
# `last_ok` pins the premise the docstring states ("the agent answers") and leaves the
# gateway row the only line.
for host_key in ("host:gateway", "disks:gateway"):
client.app.state.ctx.hub.jobs[host_key].state.last_ok = time.time()
alerts = gateway_alerts(client)
assert [row["id"] for row in alerts] == ["gateway:gateway:down"], (
"the reachability row is the gateway's only line while it is down")
@@ -5428,17 +5507,16 @@ def find_keys(node: Any, name: str) -> list[Any]:
return []
@pytest.fixture
def gateway_client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
gateway_snapshot: Any) -> Iterator[TestClient]:
"""The ingress answering with the captured configs and sockets, its gauges already stored.
def _gateway_routes_client(tmp_path: Path, mode: str,
gateway_snapshot: Any) -> Iterator[TestClient]:
"""One app per capture mode, its gauges already stored, its payload already in the job state.
Both halves come from `sources/gateway` over the **captured** tree, so every path a page reads
and every series name a chart asks for is one the source really emits (rule 7). The target
points at a dead port: no poll can overwrite the payload, dial out, or add a second sample
between the store read and the route assertion below.
"""
db = str(tmp_path / "gateway-routes.sqlite3")
db = str(tmp_path / f"gateway-routes-{mode}.sqlite3")
store = SampleStore(db)
app = create_app(Settings(db_path=db, admin_password=ADMIN_PASSWORD,
pbkdf2_iterations=FAST_PBKDF2,
@@ -5446,7 +5524,7 @@ def gateway_client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
with TestClient(app) as test_client:
sign_in(test_client)
now = time.time()
snapshot = gateway_snapshot("full")
snapshot = gateway_snapshot(mode)
snapshot["ts"] = now # "the agent answered just now", so charts are in range
result = gateway_source.parse_gateway_snapshot("gateway", snapshot, now=now)
store.add_many(result.samples)
@@ -5455,6 +5533,31 @@ def gateway_client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
store.close()
@pytest.fixture
def gateway_client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
gateway_snapshot: Any) -> Iterator[TestClient]:
"""The ingress answering as agent **1.2.0** (or 1.3.0 with `--no-resolve`) does.
No `resolver` section, so the routes whose targets the two config files write as domains carry
no count, no gauge and no stored series — phase 30 verbatim, which is what the phase-32 pins
below compare against.
"""
yield from _gateway_routes_client(tmp_path, "full", gateway_snapshot)
@pytest.fixture
def resolved_gateway_client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
gateway_snapshot: Any) -> Iterator[TestClient]:
"""The same ingress answering as agent **1.3.0**: the captured resolver section attached.
The document is the recorded `gateway_resolver.json`, so every series name this fixture's store
holds is one the deployed ingress will really write after task 05 — including the **name-keyed**
outbound gauges phase 30 stopped writing, which are writable again precisely because the
gateway published the address behind the name.
"""
yield from _gateway_routes_client(tmp_path, "resolved", gateway_snapshot)
def dial_spies(monkeypatch: pytest.MonkeyPatch, calls: list[str]) -> None:
"""Two spies that raise: a route that dials before its gate fails here, not in a timeout.
@@ -5568,11 +5671,19 @@ def test_gateway_snapshot_answers_the_pipes_the_projection_joined(
"the deleted join is back in the payload")
# the config half, verbatim from the capture the agent read (phase 28's six frontends, the
# stream map's 17 routing entries) — the echo the pipes were built from
# stream map's 18 routing entries) — the echo the pipes were built from
assert len(body["haproxy"]["frontends"]) == 6
assert len(body["nginx"]["map_targets"]) == 17
assert len(body["nginx"]["map_targets"]) == 18
# `/proc/net/tcp` is one row per live connection, so every count below is derived from the same
# capture the payload was built from instead of typed here: the rule is the pin, the number is a
# reading. The config-shaped figures above stay literal, because only the operator moves those.
established = gateway_snapshot("full")["sockets"]["established"]
stream = next(pipe for pipe in body["listeners"] if pipe["port"] == 443)
assert stream["connections"] == 6 and stream["metric"] == "gateway.in_conns@443", (
assert stream["connections"] == sum(1 for row in established
if row["local"]["port"] == 443), (
"the inbound pipe carries the sockets whose LOCAL port it is, which is the whole join")
assert stream["metric"] == "gateway.in_conns@443", (
"the pipe carries the series it is, so the page never rebuilds the name")
assert stream["clients"], "the inbound pipe names the addresses that are using it"
secure = next(pipe for pipe in body["listeners"] if pipe["port"] == 4443)
@@ -5581,14 +5692,19 @@ def test_gateway_snapshot_answers_the_pipes_the_projection_joined(
# The graph: every route the two captured configs declare, each line's inbound end a trunk this
# same payload draws, and a count on **only** the lines whose endpoint the config wrote as an
# address — this capture has exactly two (`127.0.0.1:4443` and `127.0.0.1:8081`).
assert len(body["connections"]) == 32
assert len(body["connections"]) == 33
listener_ports = {listener["port"] for listener in body["listeners"]}
assert listener_ports == {22, 2000, 5201, 8080, 25565, 25566, 80, 443, 4443, 8443}
assert all(row["in"]["port"] in listener_ports for row in body["connections"]), (
"a connection on a port no listener carries is an orphan the page cannot draw")
def sockets_to(host: str, port: int) -> int:
return sum(1 for row in established
if (row["remote"]["addr"], row["remote"]["port"]) == (host, port))
counted = {row["id"]: row["connections"] for row in body["connections"]
if row["connections"] is not None}
assert counted == {"nginx:443->127.0.0.1:4443": 2, "nginx:8443->127.0.0.1:8081": 0}, (
assert counted == {"nginx:443->127.0.0.1:4443": sockets_to("127.0.0.1", 4443),
"nginx:8443->127.0.0.1:8081": sockets_to("127.0.0.1", 8081)}, (
"a route written as a domain carries no count, and one written as an address carries the "
"endpoint's own — 0 included, because the socket table answered")
assert all(row["out"]["address"] is True for row in body["connections"]
@@ -5600,11 +5716,88 @@ def test_gateway_snapshot_answers_the_pipes_the_projection_joined(
if pipe["connections"] is not None)
endpoints = {(row["out"]["host"], row["out"]["port"]) for row in body["connections"]
if row["connections"] is not None}
assert known_in + sum(counted.values()) <= body["sockets"]["established"] == 18
assert known_in + sum(counted.values()) <= body["sockets"]["established"] == len(established)
assert len(endpoints) == 2
assert [row["key"] for row in body["states"]] == ["gateway"]
def test_the_served_snapshot_counts_a_resolved_name_and_says_which_address_it_used(
resolved_gateway_client) -> None:
"""`/api/gateway/snapshot` on the 1.3.0 document: the dash is gone, and the reason is in the row.
One route, both halves of the claim. `brain.reeseapps.com` leaves this ingress for
`reeseapps-brain-of-reese.reeselink.com:443`; the row now carries `address: false` (the config
wrote a name — verbatim, still), `resolved: ["10.1.2.64"]`, `via: "libc"` and that entry's own
`resolved_ts`, **and** a finite `connections`. A number with no provenance beside it would be
the invention this app refuses to print, which is why the two halves are one test.
"""
body = resolved_gateway_client.get("/api/gateway/snapshot").json()
assert body["resolver"]["available"] is True and body["resolver"]["via"] == "libc"
brain = next(row for row in body["connections"]
if row["out"]["host"] == "reeseapps-brain-of-reese.reeselink.com")
assert brain["out"]["selectors"] == ["brain.reeseapps.com"]
assert brain["out"]["address"] is False
assert brain["out"]["resolved"] == ["10.1.2.64"]
assert brain["out"]["via"] == "libc"
assert isinstance(brain["out"]["resolved_ts"], (int, float))
assert isinstance(brain["connections"], int) and not isinstance(brain["connections"], bool), (
"resolved and idle is a `0`, not the dash: the socket table answered and this endpoint has none")
assert brain["metric"] == "gateway.out_conns@reeseapps-brain-of-reese.reeselink.com:443"
ntfy = next(row for row in body["connections"]
if row["out"]["host"] == "reeseapps-ntfy.reeselink.com")
assert ntfy["connections"] > 0, "the capture has live ntfy sockets behind that name"
counted = [row for row in body["connections"] if row["connections"] is not None]
assert len(counted) == len(body["connections"]), (
"every route this ingress declares is placed by the recorded resolver, so no route on the "
"live page is a dash any more — the operator's complaint, in one assertion")
assert all(row["out"]["address"] is True or row["out"]["resolved"] for row in counted), (
"a count never arrives without the address it was counted through — one the config wrote, "
"or one the gateway published; `resolved` stays null on the address rows, because copying "
"the agent's `literal` echo of a string the config already wrote would claim a lookup that "
"never happened")
def test_an_injected_nxdomain_row_is_a_dash_in_the_served_document(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, gateway_snapshot: Any) -> None:
"""Both arms on one route list: placed is a number, unplaced is `null` / `null`.
The capture resolves everything, so the second arm is injected: one entry of the recorded
document emptied and marked `nxdomain`, exactly what agent 1.3.0 publishes when `getaddrinfo`
says a name has no record. Its line keeps its place in the graph with `resolved: null`,
`via: null`, `connections: null` and `metric: null` — no `0`, no series, no sample — while the
routes around it carry their counts, so a page cannot read one failed name as a dead ingress.
"""
broken = gateway_snapshot("resolved")
entries = [entry for entry in broken["resolver"]["entries"]
if entry["name"] == "reeseapps-immich.reeselink.com"]
assert entries, "the capture resolves immich, so the injection has something to un-resolve"
for entry in entries:
entry["addresses"] = []
entry["status"] = "nxdomain"
payload = gateway_source.parse(broken, "gateway")
with make_gateway_client(tmp_path, monkeypatch) as client:
answer_as(client, "gateway", payload)
deadline = time.time() + 10
while time.time() < deadline and not client.app.state.ctx.hub.jobs["gateway"].state.ok:
time.sleep(0.05)
body = client.get("/api/gateway/snapshot").json()
row = next(item for item in body["connections"]
if item["out"]["host"] == "reeseapps-immich.reeselink.com")
assert row["out"]["resolved"] is None, "never `[]`, so the page's dash arm is one `is null` test"
assert row["out"]["via"] is None and row["out"]["resolved_ts"] is None
assert row["connections"] is None and row["metric"] is None
assert row["in"] == {"port": 443}, "the route is still drawn: a name with no record is not no route"
placed = [item for item in body["connections"] if item["connections"] is not None]
assert len(placed) == len(body["connections"]) - 1, (
"one unresolved name dims one line and nothing else")
documented = {entry["name"]: entry["status"] for entry in body["resolver"]["entries"]}
assert documented["reeseapps-immich.reeselink.com"] == "nxdomain", (
"the entry the index refused to use is still the entry the page can quote")
def test_no_port_the_two_configs_do_not_name_appears_anywhere(gateway_client,
gateway_snapshot: Any) -> None:
"""The live router's own agent port is `9120`, and this payload never mentions it.
@@ -5700,10 +5893,12 @@ def test_gateway_history_serves_the_pipe_gauges_the_store_holds(
facts), and the `source` says the `gateway` family — a page must never ask the 10-second `host`
series for a connection count.
The outbound name asked for is an **address** (`127.0.0.1:4443`), and that is the whole point of
the phase-30 change to `samples()`: a route whose target the config wrote as a domain publishes
no series at all, so a name-keyed `gateway.out_conns@<domain>:<port>` is no longer stored and
the store's `gateway` family is exactly the endpoints the join could count.
The outbound name asked for is an **address** (`127.0.0.1:4443`) because this fixture answers as
a 1.2.0 agent. The rule `samples()` keeps is about the **address**, not the spelling: a route
publishes a gauge when the kernel can name its far end — an address the config wrote, or (since
phase 32 task 03, pinned beside this one on the `resolved` arm) a name the gateway itself
resolved. A name nobody resolved publishes nothing, which is what
`test_gateway_history_answers_empty_for_a_pipe_it_never_measured` still proves.
"""
store: SampleStore = gateway_client.app.state.ctx.store # type: ignore[attr-defined]
body = gateway_client.get("/api/gateway/history", params={
@@ -5743,6 +5938,65 @@ def test_gateway_history_answers_empty_for_a_pipe_it_never_measured(gateway_clie
assert series_source(body) == {"gateway"}
def test_a_resolved_name_publishes_its_gauge_and_history_answers_it(
resolved_gateway_client) -> None:
"""The name-keyed series phase 30 stopped writing, written again on an honest footing.
Phase 30 task 02 retired `gateway.out_conns@<domain>:<port>` because the only figure it could
have carried was the *port's* — the port-only join's count filed under whatever domain happened
to share it. Phase 32 task 03 gives the name a figure of its own: the sockets to the address the
**gateway** resolved that name to. So the name is stored, `/api/metrics` lists it, and
`/api/gateway/history` answers points for it — read through `store.query`, the third path, as
every other history pin is.
"""
store: SampleStore = resolved_gateway_client.app.state.ctx.store # type: ignore[attr-defined]
metric = "gateway.out_conns@reeseapps-ntfy.reeselink.com:443"
body = resolved_gateway_client.get("/api/gateway/history", params={
"hosts": "gateway", "metrics": metric, "range": "1h"}).json()
assert [series["metric"] for series in body["series"]] == [metric]
assert series_source(body) == {"gateway"}
rows = store.query("gateway", "gateway", metric, res=RES_RAW)
assert rows, "the resolved route's gauge was not stored"
assert [(point["ts"], point["value"]) for point in body["series"][0]["points"]] == rows
assert metric in resolved_gateway_client.get("/api/metrics",
params={"source": "gateway"}).json()["metrics"]
def test_one_shared_endpoint_costs_one_insert_per_poll_even_through_a_name(
tmp_path: Path, gateway_snapshot: Any) -> None:
"""Two lines, one gauge name, **one row per poll** — counted in the store, not asserted.
The captured ingress reaches `keycloak.reeselink.com:8080` from `:4443` and from `:8443`, and
since task 03 the endpoint is reachable through a **resolved name** rather than a written
address. `shared: true` says the two lines carry one number; `samples()` de-duplicating by
metric is what stops that number being inserted twice for one reading, and the row count is the
only proof worth having. Two polls, two rows: one per reading, never one per line.
"""
db = str(tmp_path / "gateway-shared.sqlite3")
store = SampleStore(db)
now = time.time()
metric = "gateway.out_conns@keycloak.reeselink.com:8080"
# both readings in the past: `store.query` bounds its window at now, so a simulated second poll
# that lands in the future is a row that never comes back, not a lost insert
for offset in (-60.0, -30.0):
snapshot = gateway_snapshot("resolved")
snapshot["ts"] = now + offset
result = gateway_source.parse_gateway_snapshot("gateway", snapshot, now=now + offset)
shared = [row for row in result.payload["connections"] if row["shared"]]
assert {row["metric"] for row in shared} == {metric}, (
"this capture's shared endpoint is the keycloak pair, and one poll writes it once")
assert len({row["connections"] for row in shared}) == 1, (
"both lines carry the same number, which is what makes one insert the honest count")
out = [row for row in result.samples if row.metric == metric]
assert len(out) == 1, "one sample per poll, however many lines reach the endpoint"
store.add_many(result.samples)
rows = store.query("gateway", "gateway", metric, res=RES_RAW)
assert len(rows) == 2, "two polls, two rows — not four (one per line) and not one (lost poll)"
store.close()
def test_gateway_history_names_the_ingress_and_no_other_host(gateway_client) -> None:
"""With no `hosts=` the answer is the one host this source reads — not the whole fleet.
@@ -5853,6 +6107,10 @@ GATEWAY_PAGE_FIELDS = (
"connections.id", "connections.source", "connections.in.port",
"connections.out.host", "connections.out.port", "connections.out.name",
"connections.out.kind", "connections.out.selectors", "connections.out.address",
# Phase 32 task 04: the provenance a counted line prints and the dash refuses. `resolved` is what
# the filter matches an IP against, `via` + `resolved_ts` are the tooltip's `address from libc,
# resolved 3m` — so a rename of any of the three fails by name here, not as a permanent silence.
"connections.out.resolved", "connections.out.via", "connections.out.resolved_ts",
"connections.connections", "connections.shared", "connections.metric",
"warnings.id", "warnings.kind", "warnings.subject", "warnings.message",
)
@@ -6415,11 +6673,16 @@ def test_the_trunk_pipe_is_an_accessible_image_of_its_port(client, gateway_clien
def _leaf_haystack(row: dict[str, Any]) -> str:
"""The one string the filter can see about one route, as `page-gateway.js` composes it."""
"""The one string the filter can see about one route, as `page-gateway.js` composes it.
Phase 32 task 04 adds the **sixth** field: `out.resolved`, the addresses the gateway published for
a name target. Typing an IP is how an operator asks "which route owns the socket I am looking at",
and a haystack of names alone cannot answer it.
"""
out = row.get("out") or {}
port = (row.get("in") or {}).get("port")
parts = ["" if port is None else str(port), out.get("host"), out.get("name"), out.get("kind"),
*(out.get("selectors") or [])]
*(out.get("selectors") or []), *(out.get("resolved") or [])]
return " ".join(p for p in parts if isinstance(p, str) and p).lower()
@@ -6434,7 +6697,8 @@ def filter_view(snapshot: dict[str, Any], query: str) -> dict[str, Any]:
There is no JS runner (a locked anchor), so this is `rendered_leaf`'s idiom: the rules are
re-applied here from the payload, and the served-byte pins in the test below are what keep the
mirror honest — `haystack` indexes `in.port`, `out.host`, `out.name`, `out.kind`, `out.selectors`;
mirror honest — `haystack` indexes `in.port`, `out.host`, `out.name`, `out.kind`,
`out.selectors`, `out.resolved`;
`trunkHaystack` indexes `:${port}`, `domains`, `clients`; a trunk that matches keeps **all** its
leaves; a group stands when it or one of its routes answers. A rename on either side therefore
fails a pin *and* changes what this mirror prints.
@@ -6476,7 +6740,8 @@ def filter_view(snapshot: dict[str, Any], query: str) -> dict[str, Any]:
return {"groups": groups, "standing": standing, "total": len(snapshot["connections"])}
def test_the_filter_narrows_the_map_without_asking_anything(client, gateway_client) -> None:
def test_the_filter_narrows_the_map_without_asking_anything(client, gateway_client,
gateway_snapshot: Any) -> None:
"""One field, four rules, and not one extra request.
The operator's complaint is a reading-speed one: *"I want to find a domain."* This ingress routes
@@ -6485,9 +6750,12 @@ def test_the_filter_narrows_the_map_without_asking_anything(client, gateway_clie
feature could go wrong:
* **It matches payload text and nothing else.** `in.port`, `out.host`, `out.name`, `out.kind`,
`out.selectors` for a route, `:${port}` + `domains` + `clients` for a trunk. No DNS (the module
still resolves no name), no re-ranking, no rule about `.reeselink.com` — a query can never
invent a route, because there is no other text to match.
`out.selectors` and `out.resolved` for a route, `:${port}` + `domains` + `clients` for a trunk.
The sixth field is phase 32 task 04's: an address the **gateway** published is text the payload
already carries, so typing the far end of a socket finds the route that owns it. No DNS (the
page still resolves no name — it matches a string, it never looks one up), no re-ranking, no
rule about `.reeselink.com` — a query can never invent a route, because there is no other text
to match.
* **It costs nothing.** The repaint runs on `state.snapshot`; a keystroke that re-fetched would be
a poll per character and a lie about what changed.
* **It hides rows, it does not remove them.** `li.hidden` in the patch pass, so a route the query
@@ -6498,18 +6766,27 @@ def test_the_filter_narrows_the_map_without_asking_anything(client, gateway_clie
"""
js = _code(client.get("/static/page-gateway.js").text)
# (1) the matcher's whole vocabulary: five payload fields for a route, three for a trunk.
# (1) the matcher's whole vocabulary: six payload fields for a route, three for a trunk.
hay = js[js.index("function haystack("):js.index("function trunkHaystack(")]
assert "String((conn.in || {}).port ?? '')" in hay, \
"the inbound port is no longer searchable — `4443` is the second thing an operator types"
for field in ("out.host", "out.name", "out.kind", "out.selectors"):
for field in ("out.host", "out.name", "out.kind", "out.selectors", "out.resolved"):
assert field in hay, f"the filter no longer sees {field}"
# Phase 32 task 04: an address the **gateway** resolved is payload text like any other, so typing
# the far end of a socket finds the route that owns it. Names alone could not answer that question.
assert "...(out.resolved || [])" in hay, \
"a name-written route stopped being findable by the address it resolves to"
assert "toLowerCase()" in hay, "the match became case-sensitive on a page of lowercase domains"
for absent in ("conn.connections", "severity", "metric", "getaddrinfo", "split(",
"endsWith", "startsWith", "slice("):
assert absent not in hay, (
f"the matcher reached for {absent} — it may read what the payload calls a route, never "
"what it reports about it, and never resolve a name")
# …and the three things that would stop it being an eye over the document in hand: a dial, a read
# of what is painted rather than of what the payload says, and a literal `null` a query could
# match against a row that published nothing.
for not_here in ("fetchJSON", "textContent", "innerText", '"null"', "'null'"):
assert not_here not in hay, f"the haystack reached for {not_here}"
trunk = js[js.index("function trunkHaystack("):js.index("function filtering(")]
for field in ("group.port", "listener.domains", "listener.clients"):
assert field in trunk, f"the trunk no longer answers for {field}"
@@ -6616,6 +6893,25 @@ def test_the_filter_narrows_the_map_without_asking_anything(client, gateway_clie
assert all(not leaf["hidden"] for leaf in home["leaves"]), \
"a trunk that matched lost leaves of its fan-out"
# (7) phase 32 task 04: an address the **gateway** resolved is searchable too, which is the field
# an operator reaches for when they are holding a socket rather than a domain. Over the 1.2.0
# document there is nothing to match (no resolver section, and the page resolves nothing), so
# this leg runs on the recorded 1.3.0 one.
placed = gateway_source.parse(gateway_snapshot("resolved"), "gateway")
assert not filter_view(payload, "10.1.2.64")["standing"], \
"the page found an address it was never given — it resolves nothing, and must keep not doing it"
brain_ip = filter_view(placed, "10.1.2.64")
found = [leaf["id"] for group in brain_ip["groups"] for leaf in group["leaves"]
if not leaf["hidden"]]
assert found and all("reeseapps-brain-of-reese" in route_id for route_id in found), \
f"typing the brain's address left {found}"
assert all(group["hidden"] or group["port"] == 443 for group in brain_ip["groups"]), \
"an address match let a trunk of another port stand"
shared_ip = filter_view(placed, "10.1.1.78")
expected = sum(1 for row in placed["connections"] if "10.1.1.78" in (row["out"]["resolved"] or []))
assert expected > 1 and shared_ip["standing"] == expected, (
f"one address reached by {expected} routes was found by {shared_ip['standing']} of them")
def test_the_filter_field_is_markup_the_sheet_and_nobody_else_uses(client) -> None:
"""The field is pinned markup, on one page, wearing the sheet's one field skin.
@@ -6972,19 +7268,39 @@ def test_the_page_says_where_a_line_comes_from_and_where_its_count_cannot(client
"""Four footnotes, one per claim the page is allowed to make, and not one more.
Why the page draws a line at all (the config declares the route) and what its motion means (a
socket is open this instant, so a resting line is not a dead one); why an endpoint written as a
**name** carries no number (nothing here resolves one, so a dash is not a fault); what the switch
does **not** do (toggling a route needs a config write path this app does not have); and — since
phase 31 task 04 — what the filter box does to the map and what it leaves alone (the tallies, the
warnings and the charts keep describing the whole ingress, because the field asks for nothing).
Each lives in the footer exactly once — the page does not repeat a why-line per panel, and it
does not need to: an uncounted line prints `—` and the footnote is the sentence that explains it
socket is open this instant, so a resting line is not a dead one); why a route carries no number
(phase 32 task 04: **the gateway could not resolve that name in this reading** — the gateway does
the resolving, so the sentence blames the reading and points at the hover that shows the address a
counted line was matched to, instead of blaming the config for having written a name or the page
for having no resolver); what the switch does **not** do (toggling a route needs a config write
path this app does not have); and — since phase 31 task 04 — what the filter box does to the map
and what it leaves alone (the tallies, the warnings and the charts keep describing the whole
ingress, because the field asks for nothing).
Each lives in the footer exactly once — the page does not repeat a why-line per panel, and it does
not need to: an uncounted line prints `—` and the footnote is the sentence that explains it
(rule 2).
"""
html = client.get(GATEWAY_PAGE).text
footer = html[html.index("<footer"):]
assert footer.count("resolves nothing") == 1, (
"the no-DNS rule is the page's single footnote about a missing count — printed once")
assert footer.count("a dash means") == 1, (
"the page's single footnote about a missing count, printed once, in the phase-32 reason")
assert "could not resolve that name" in footer, (
"a dash means the gateway's reading came back with nothing, not that the config wrote a name")
# The two sentences phase 32 made stale may not come back from either file: the blame on the page
# (`the page knows no`) and its no-DNS limit as a per-row excuse, which task 04 replaced with the
# thing to do about a dash rather than a reason to accept it.
assert "the page knows no" not in footer, (
"the dash stopped blaming the gateway's reading and went back to blaming the page")
# The served source wraps this paragraph, so the two halves are matched separately: what is
# pinned is that the sentence names a cause **and** the place to look.
flat = " ".join(footer.split())
assert "hover a counted line for the address it matched" in flat, (
"the footnote names a cause and no longer says where the address and its age are")
script = client.get("/static/page-gateway.js").text
assert "the gateway could not resolve this name in this reading" in script, (
"the per-row tooltip must say the same thing the footnote says")
assert "nothing here resolves names" not in script, (
"the old tooltip sentence blames the name, which is no longer why a line has no count")
assert "animates" in footer, "the animation rule lost its footnote"
band = footer[:footer.index("</footer>")]
# Four paragraphs and nothing else: one sentence per claim, in one ink band under the panels.
@@ -7054,6 +7370,21 @@ def test_the_gateway_page_renders_each_degradation_arm_in_the_payloads_own_words
assert sum(1 for field in ("listeners", "connections") if stub[field]) == 0, (
"the stub arm's rows are not empty, so the header's `n/a` would not be about the same thing")
# (5) a **resolver** that did not answer (agent 1.2.0, `--no-resolve`, or a pass that could not
# run) is the fifth arm and the quietest: the section is not one of the two config files, so
# it raises no banner and no colour, and every name-written line simply carries the dash with
# the resolution sentence. `full` is that document — no `resolver` key at all.
assert silent["resolver"]["available"] is None and silent["resolver"]["entries"] == [], (
"the `full` capture is supposed to be the no-resolver arm this case measures")
assert silent["why"] is None, "an absent resolver section became a page-wide complaint"
assert "severity" not in json.dumps(silent), "the fifth arm arrived with a verdict on it"
dashes = [row for row in silent["connections"] if row["connections"] is None]
assert dashes, "a document with no resolver counted a name-written route"
assert all(count_surfaces(row)["why"] == COUNT_WHY_UNRESOLVED for row in dashes), \
"a line nobody resolved is being explained by something other than resolution"
assert not any(count_surfaces(row)["glyph"] == "0" for row in dashes), \
"the uncounted became a claimed zero"
def test_the_gateway_page_keeps_its_last_drawing_when_a_poll_fails(client) -> None:
"""Invariant 4 on the fifth page: a failed read leaves the last picture on screen.
@@ -7072,6 +7403,43 @@ def test_the_gateway_page_keeps_its_last_drawing_when_a_poll_fails(client) -> No
"and the reason has to be printed where the operator is already looking"
COUNT_WHY_UNRESOLVED = "the gateway could not resolve this name in this reading"
COUNT_WHY_NO_SOCKETS = "the socket table did not answer this reading"
def count_surfaces(row: dict[str, Any]) -> dict[str, Any]:
"""The count column's two strings for one route, as `countWhy` / `countTitle` build them.
A **second** reading of the same decision, with the served-byte pins below as its other half:
* `glyph` — the em dash for a null, the number otherwise (`0` included, because a resolved
endpoint with no socket is a measurement and not a gap);
* `why` — for a null, which of the two unknowns the payload describes: an address is **known**
when the config wrote one (`out.address is True`) or the gateway published one
(`out.resolved` non-empty), and a row with neither is a name nothing could resolve;
* `title` — for a counted row, the number plus the provenance the tooltip owes it. The **age**
fragment is left out here and pinned as a prefix instead, because it is a clock: the test
asserts what comes before it and what the remainder looks like.
"""
out = row["out"]
shown = row["connections"]
if shown is None:
known = out["address"] is True or bool(out["resolved"])
why = COUNT_WHY_NO_SOCKETS if known else COUNT_WHY_UNRESOLVED
return {"glyph": "\u2014", "why": why, "title": f"no number: {why}",
"aria": f"connections not attributed: {why}", "age": False}
title = f"{shown} open connection(s) on this endpoint"
via = out["via"] if isinstance(out["via"], str) and out["via"] else None
if out["resolved"] is None or via is None:
return {"glyph": str(shown), "why": None, "title": title, "age": False,
"aria": f"{shown} live connections"}
title += f" \u00b7 address from {via}"
# `Dash.duration(Dash.ageSeconds(ts))`, whose text is a clock and therefore not this mirror's to
# reproduce — only the *decision* to print it is.
return {"glyph": str(shown), "why": None, "title": title,
"age": out["resolved_ts"] is not None, "aria": f"{shown} live connections"}
def rendered_leaf(row: dict[str, Any]) -> dict[str, str]:
"""The two lines and the two long strings one leaf row prints, derived as a **second** reading.
@@ -7082,6 +7450,10 @@ def rendered_leaf(row: dict[str, Any]) -> dict[str, str]:
`leafAria` compose those same two from the literals asserted verbatim below. A rename on either
side therefore fails a pin **and** changes what this mirror prints, which is the pair of
guarantees a page with no test runner can actually make.
The row's `aria-label` ends with the count's claim, and phase 32 task 04 made that claim carry its
cause: `count_surfaces()` is the one place that sentence is assembled, so the row and the count
element cannot disagree about which unknown a dash is.
"""
out = row["out"]
host = out["host"] or "?"
@@ -7099,8 +7471,7 @@ def rendered_leaf(row: dict[str, Any]) -> dict[str, str]:
if row["shared"] is True:
bits.append("this endpoint is shared with another route")
bits.append("in the config")
bits.append("connections not attributed" if row["connections"] is None
else f"{row['connections']} live connections")
bits.append(count_surfaces(row)["aria"])
return {"line1": inbound,
"line2": f"→ {endpoint}" + (f" · {kind}" if kind else ""),
"title": pair + shared,
@@ -7440,7 +7811,7 @@ def test_the_stream_map_is_rendered_inside_nginx_and_nowhere_else(
Pinned from both ends: the script ships exactly the two panel definitions and never indexes
`nginx.map_targets`, and the live document's map connections are all nginx's, all on one port,
and exactly as many as the map has routing entries (17 on this capture — 16 `*.reeselink.com`
and exactly as many as the map has routing entries (18 on this capture — 17 `*.reeselink.com`
plus the `default` the file writes for SNI nobody mapped).
"""
html = _code(client.get(GATEWAY_PAGE).text)
@@ -7570,22 +7941,31 @@ def test_warnings_render_in_the_overviews_alert_vocabulary_and_hide_when_clean(
def test_an_unattributed_endpoint_prints_a_dash_and_never_a_zero(client, gateway_client) -> None:
"""A leaf the join cannot count prints `—`, and the tooltip says why in one sentence.
"""A leaf the join cannot count prints `—`, and the tooltip says **which** unknown it is.
Sixteen of this fleet's 17 map entries name a **domain**, and `/proc` reports addresses, so no
count exists for them and none can ever exist (nothing here resolves a name). `0` would be a
claim about traffic (rule 2) and a column of `n/a` would be twelve identical words competing with
the page's one footnote — so the leaf prints an em dash whose `title` names the reason, while the
trunk keeps `n/a`, which is a different fact: the socket table itself went quiet.
`/proc` reports addresses, so a route whose far end has no address cannot be counted, and `0` would
be a claim about traffic (rule 2) while a column of `n/a` would be twelve identical words
competing with the page's one footnote — so the leaf prints an em dash whose `title` names the
reason, while the trunk keeps `n/a`, which is a different fact: the socket table itself went quiet.
Phase 32 task 04 sharpened the mechanism and not only the wording: the dash now has **two** causes
that `countCause` separates from `out.address` / `out.resolved` — a name the gateway could not
resolve, and an address that is known beside a table that answered nothing — and the sentence is
chosen from data, never guessed. A `0` is neither of them: it is a measurement.
"""
js = _code(client.get("/static/page-gateway.js").text)
head = js[:js.index("function countText(")]
assert "const NO_COUNT = '—'" in head, "the unattributed leaf count is no longer an em dash"
assert "nothing here resolves names" in head, "the dash stopped explaining itself"
assert "the gateway could not resolve this name in this reading" in head, \
"the dash stopped explaining itself"
assert "function countCause(" in head and "out.address === true" in head \
and "out.resolved" in head, \
"the dash's cause is no longer read from the two payload fields that decide it"
assert "nothing here resolves names" not in head, (
"the old sentence blamed the name, which phase 32 task 03 made a false reason")
leaf = js[js.index("function renderLeaf("):js.index("function groupElement(")]
assert "shown == null ? NO_COUNT : shown" in leaf, "a null count is not the dash any more"
assert "count.title = shown == null ? NO_COUNT_WHY" in leaf, \
"the dash lost the sentence that separates it from a zero"
assert "count.title = shown == null ? countWhy(conn) : countTitle(conn, shown)" in leaf, \
"the dash lost its sentence, or a counted line lost the provenance beside its number"
trunk = js[js.index("function renderTrunk("):js.index("function renderLeaves(")]
assert "live" in trunk and "n/a" not in trunk, (
"the trunk now prints the leaf's dash too — a quiet socket table is a different fact, and "
@@ -7600,6 +7980,164 @@ def test_an_unattributed_endpoint_prints_a_dash_and_never_a_zero(client, gateway
"table's arm, not the name arm, and it prints a different sentence")
def test_an_unresolved_name_and_an_idle_route_say_two_different_sentences(
client, gateway_snapshot: Any) -> None:
"""The em dash and the measured `0` are two facts, and every surface of the row says so.
Phase 32 task 03 made a name-written route countable, which left the page's one dash sentence
explaining two different things: a name the gateway could not resolve, and a route that **was**
resolved and simply has nothing open. Task 04 splits them, and the split has to be on every
surface a reader can meet — the glyph, the `title`, the `aria-label` — because a `0` read as a
missing measurement (or a dash read as an idle route) sends the operator to the wrong box.
Three arms, all from the recorded capture and one injection, all rendered through
`count_surfaces` (the mirror) with the served-byte pins below as its other half:
* **resolved and idle** — `brain.reeseapps.com` reaches
`reeseapps-brain-of-reese.reeselink.com:443`, the gateway resolved it to `10.1.2.64`, and the
kernel reports no socket: the glyph is `0`, and a `0` is a measurement.
* **resolved and busy** — the ntfy route, whose glyph is its own number.
* **not resolved** — one entry of the recorded document emptied and marked `nxdomain`, exactly
what agent 1.3.0 publishes for a name with no record: the glyph is the em dash and the sentence
is the resolution one, never the socket one.
And a fourth, because the third arm's sentence must not become a catch-all: with the kernel's
table unreadable, a line whose address the **config wrote** is uncounted too, and blaming
resolution there would be false — so it gets the socket-table sentence.
"""
js = _code(client.get("/static/page-gateway.js").text)
head = js[: js.index("function countText(")]
# (1) the two sentences exist, differ, name no `n/a`, and neither is the other's wording.
assert "const WHY_UNRESOLVED = 'the gateway could not resolve this name in this reading'" in head
assert "const WHY_NO_SOCKETS = 'the socket table did not answer this reading'" in head
assert COUNT_WHY_UNRESOLVED != COUNT_WHY_NO_SOCKETS
for sentence in (COUNT_WHY_UNRESOLVED, COUNT_WHY_NO_SOCKETS):
assert "n/a" not in sentence, "an unknown that has a cause stopped naming its cause"
# The choice is data: the same two fields `sources/gateway.py` joins on, and nothing else.
chooser = js[js.index("function countCause("):js.index("function countWhy(")]
assert "out.address === true" in chooser and "out.resolved" in chooser, \
"the dash's cause is no longer chosen from the address the config wrote or the gateway found"
assert "countCause(conn)" in js[js.index("function leafAria("):js.index("function leafTitle(")], \
"the `aria-label` no longer carries the cause the tooltip carries"
# (2) the three arms, rendered.
resolved = gateway_source.parse(gateway_snapshot("resolved"), "gateway")
def route(host: str, port: int) -> dict[str, Any]:
return next(row for row in resolved["connections"]
if row["out"]["host"] == host and row["in"]["port"] == port)
idle = count_surfaces(route("reeseapps-brain-of-reese.reeselink.com", 443))
busy = count_surfaces(route("reeseapps-ntfy.reeselink.com", 443))
assert idle["glyph"] == "0", "a resolved endpoint with nothing open is a measurement, not a gap"
assert busy["glyph"] == "3" and busy["why"] is None
assert idle["title"].startswith("0 open connection(s) on this endpoint") \
and busy["title"].startswith("3 open connection(s) on this endpoint")
broken = gateway_snapshot("resolved")
for entry in broken["resolver"]["entries"]:
if entry["name"] == "reeseapps-immich.reeselink.com":
entry["addresses"] = []
entry["status"] = "nxdomain"
unplaced = gateway_source.parse(broken, "gateway")
dash = count_surfaces(next(row for row in unplaced["connections"]
if row["out"]["host"] == "reeseapps-immich.reeselink.com"))
assert dash["glyph"] == "\u2014", "a name with no record grew someone else's number"
assert dash["why"] == COUNT_WHY_UNRESOLVED and dash["title"] == f"no number: {dash['why']}"
assert dash["aria"] == f"connections not attributed: {COUNT_WHY_UNRESOLVED}"
# (3) the fourth arm: an address the config wrote, beside a kernel that said nothing.
quiet = gateway_source.parse(gateway_snapshot("sockets_silent"), "gateway")
unwritten = count_surfaces(next(row for row in quiet["connections"]
if row["out"]["address"] is True))
assert unwritten["glyph"] == "\u2014" and unwritten["why"] == COUNT_WHY_NO_SOCKETS, (
"a line whose address the config wrote was told to go resolve something — the socket table "
"is what went quiet, and that is the sentence the trunk already prints as `n/a`")
# (4) five lines read aloud: distinct claims, no invented number, no `null` leaking as text.
spoken = [count_surfaces(row) for row in resolved["connections"][:5]]
claims = {entry["title"] for entry in spoken} | {entry["aria"] for entry in spoken}
assert len(claims) > 1, "five lines said one thing"
for entry in [*spoken, idle, busy, dash, unwritten]:
for text in (entry["glyph"], entry["title"] or "", entry["aria"]):
assert "undefined" not in text and "NaN" not in text and "null" not in text, \
f"a row printed {text!r}"
def test_a_counted_line_names_where_its_address_came_from(client, gateway_snapshot: Any,
resolved_gateway_client) -> None:
"""Every counted line says, on hover, which address it was counted to and when that was learned.
A number that arrived through the gateway's own `libc` resolution is a different claim from one
the config wrote as an address, and the tooltip is the one place both can say it without growing
a column of ink per row. Three arms, one rule: the provenance is the payload's (`out.via`,
`out.resolved_ts`) copied, never paraphrased, and a field that is not there prints **nothing**
rather than `n/a` or `NaN`.
* a **resolved name** — `… · address from libc`, and `, resolved <age>` while the entry has a
timestamp (the age is `Dash.duration(Dash.ageSeconds(…))`, so its text is a clock and only its
presence is pinned here);
* the same row with `resolved_ts` removed — the age fragment goes, the rest of the sentence
stays, and nothing prints the shared `n/a` where an age belongs;
* an **address the config wrote** — the bare sentence. It was never resolved, and stamping the
agent's `literal` echo of it as a lookup would be rule 2 with a tooltip's clothes on.
"""
js = _code(client.get("/static/page-gateway.js").text)
builder = js[js.index("function countTitle("):js.index("const NO_TARGET")]
for field in ("out.via", "out.resolved", "out.resolved_ts"):
assert field in builder, f"the counted line's tooltip stopped reading {field}"
for helper in ("duration(", "ageSeconds("):
assert helper in builder, f"the age stopped going through the shared helper {helper}"
assert "out.resolved_ts == null" in builder, \
"a missing timestamp now prints whatever duration() makes of Infinity"
assert "address from" in builder and "resolved " in builder
assert "n/a" not in builder and "\u2014" not in builder, \
"the counted tooltip grew a fallback glyph; a missing fragment is silence, not a mark"
resolved = gateway_source.parse(gateway_snapshot("resolved"), "gateway")
brain = next(row for row in resolved["connections"]
if row["out"]["host"] == "reeseapps-brain-of-reese.reeselink.com")
printed = count_surfaces(brain)
assert printed["title"] == "0 open connection(s) on this endpoint \u00b7 address from libc"
assert printed["age"] is True
served = client.get("/static/page-gateway.js").text
assert "\u00b7 address from ${via}" in served and ", resolved ${age}`" in served, \
"the two provenance fragments are no longer composed from the payload's own words"
aged = dict(brain, out=dict(brain["out"], resolved_ts=None))
lost = count_surfaces(aged)
assert lost["title"] == printed["title"], "losing the timestamp lost the address it came from"
assert lost["age"] is False, "an age was printed for a row that publishes no timestamp"
assert "NaN" not in lost["title"] and "n/a" not in lost["title"]
written = next(row for row in resolved["connections"] if row["out"]["address"] is True)
plain = count_surfaces(written)
assert plain["title"] == f"{written['connections']} open connection(s) on this endpoint", \
"an address the config wrote was given a resolution provenance it never had"
assert "address from" not in plain["title"]
# …and the field table, in **both** directions (the `SLOT_FIELDS` device): the three names the
# tooltip and the filter index are named there, and the 1.3.0 document answers each with real
# data, so a rename in `sources/gateway.py` fails by name here instead of printing a tooltip that
# quietly stopped carrying its provenance.
provenance = ("connections.out.resolved", "connections.out.via",
"connections.out.resolved_ts")
live = resolved_gateway_client.get("/api/gateway/snapshot").json()
for path in provenance:
assert path in GATEWAY_PAGE_FIELDS, (
f"the counted line reads {path.removeprefix('connections.')}, which the page's field "
"table does not name")
found, values = _payload_field(live, path)
assert found and any(value is not None for value in values), (
f"{path} is named but the resolved document never answers it, so the pin above would "
"pass over a payload that carries nothing")
assert any(value is None for path in provenance
for value in _payload_field(live, path)[1]), (
"every row published provenance, including the addresses the config wrote — the arm that "
"must print nothing is no longer distinguishable")
def test_the_docs_name_the_page_its_knob_and_its_gauges() -> None:
"""The phase's own completion criterion, pinned so it cannot rot quietly.