Skip to content

Dashboard reports API OK when HTTP200 bodies cannot be decoded as JSON #959

Description

@stmr

Reproduced false healthy-state reporting

Pinned sourceb6960706a6ea1797a63e901c34f06cabd0f7e7a5:

def _normalize_api_rows(data: Any) -> List[Dict[str, Any]]:
if isinstance(data, list):
return [d for d in data if isinstance(d, dict)]
if isinstance(data, dict):
for key in ("items", "data", "results", "agents", "contracts", "reputation"):
v = data.get(key)
if isinstance(v, list):
return [d for d in v if isinstance(d, dict)]
return []
def fetch_beacon_snapshot(
api_base_url: str = DEFAULT_API_BASE_URL,
*,
timeout_s: float = 8.0,
session: Optional[requests.Session] = None,
) -> Dict[str, Any]:
http = session or requests.Session()
base = (api_base_url or DEFAULT_API_BASE_URL).rstrip("/")
endpoints = {
"agents": f"{base}/api/agents",
"contracts": f"{base}/api/contracts",
"reputation": f"{base}/api/reputation",
}
out: Dict[str, Any] = {"ok": True, "errors": [], "fetched_at": int(time.time())}
for name, url in endpoints.items():
try:
resp = http.request("GET", url, timeout=timeout_s)
if int(resp.status_code) >= 400:
out["ok"] = False
out["errors"].append(f"{name}: HTTP {resp.status_code}")
out[name] = []
continue
try:
payload = resp.json()
except Exception:
payload = {}
out[name] = _normalize_api_rows(payload)
except Exception as e:
out["ok"] = False
out["errors"].append(f"{name}: {e}")
out[name] = []
and sidebar#L463-L488.

Original fetch/API-poll/sidebar methods with three local HTTP200 responses whose json() raisesValueError produce ok=True, errors=[], counts0/0/0 and sidebarstatusOK. Valid-empty arrays produce the same output. One malformed contracts endpoint plus healthy nonempty agents/reputation retains counts1/0/1 but stillreportsOK/noerror.

Expected: malformed endpoint data is degraded/error state, not a healthy empty network, while valid endpoint data survives. Actual: decode failures are replaced with{} before outer failure handling, leaving okTrue/noerrors. Suggested fix: propagate endpoint-specific decode failures into the established partial-failure/error reporting path without discarding valid endpoints.

Distinct from inclusive inbox polling and already-fixed API baseURL. Symbol/invalidJSON searches matched only unrelated storageJSONLPR829/UDPexamplePR13; no corresponding dashboard decode fix identified.

Reproduction and controls

Python3 stdlib on macOS arm64. Save pinned dashboard.py, inbox.py and this script as repro.py together in an isolated directory, run python3 repro.py. Original reader/poll/display/API/sidebar function ASTs execute unchanged; widgets and HTTP are local fakes. Inbox is a temporary JSONL fixture; known-key/state seams return empty values or no-op. No Textual app, UDP, quick-send, identities/keys, ~/.beacon, live API, account or production writes.

import ast
import json
import time
from pathlib import Path
from collections import Counter
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from types import SimpleNamespace

ROOT = Path(__file__).parent
ns = dict(globals(), requests=SimpleNamespace(Session=lambda: None), sound=False, api_base_url='https://fixture.invalid/beacon', DEFAULT_API_BASE_URL='https://fixture.invalid/beacon')
def load_functions(filename, names):
    tree = ast.parse((ROOT / filename).read_text())
    funcs = [node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef) and node.name in names]
    assert len(funcs) == len(names), (filename, names)
    exec(compile(ast.fix_missing_locations(ast.Module(body=funcs, type_ignores=[])), filename, 'exec'), ns)

# Only original function ASTs execute: no module imports, app construction, identity, network or send path.
load_functions('inbox.py', {'read_inbox'})
fixture = ROOT / 'fixture.jsonl'
ns.update(_inbox_path=lambda: fixture, load_known_keys=lambda: {}, _read_nonces=lambda: set(), save_known_keys=lambda keys: None, decode_envelopes=lambda text: [])
load_functions('dashboard.py', {'_format_ts', '_short_agent', '_as_text', '_rtc_tip', '_transport_tag', '_entry_to_row', '_row_matches_query', '_normalize_api_rows', 'fetch_beacon_snapshot', '_route_table_id', '_add_row', '_display_row', '_poll_inbox', '_poll_api', '_refresh_sidebar'})
class Widget:
    def __init__(self): self.rows = {}; self.text = ''
    @property
    def row_count(self): return len(self.rows)
    def add_row(self, *row): self.rows[len(self.rows)] = row
    def update(self, text): self.text = text
class Dashboard:
    def __init__(self):
        self._last_ts = 0; self._count_today = 0
        self._transport_counter = Counter(); self._agent_counter = Counter()
        self._history_rows = []; self._visible_rows = []; self._filter_query = ''
        self._api_state = {}; self.widgets = {}; self.notifications = []
    def query_one(self, name, kind): return self.widgets.setdefault(name, Widget())
    def notify(self, *args, **kwargs): self.notifications.append((args, kwargs))
for name in ('_route_table_id', '_add_row', '_display_row', '_poll_inbox', '_poll_api', '_refresh_sidebar'):
    setattr(Dashboard, name, ns[name])
ns.update(DataTable=Widget, Static=Widget)

def write_entries(entries): fixture.write_text(''.join(json.dumps(e) + '\n' for e in entries))
a = {'received_at': 1000, 'platform': 'udp', 'from': 'fixture-a', 'text': 'first'}
b = {'received_at': 1000, 'platform': 'udp', 'from': 'fixture-b', 'text': 'second'}
write_entries([a]); d = Dashboard(); d._poll_inbox(); d._poll_inbox()
print('UNCHANGED: expected pings/history/visible/table=1/1/1/1; actual=' + '/'.join(map(str, [d._count_today, len(d._history_rows), len(d._visible_rows), d.widgets['#tbl-all'].row_count])))
print('UNCHANGED counters:', dict(d._transport_counter), dict(d._agent_counter))
assert d._count_today == 2 and len(d._history_rows) == 2
write_entries([a]); d = Dashboard(); d._poll_inbox(); write_entries([a, b]); d._poll_inbox()
print('EQUAL_TIMESTAMP_APPEND: expected distinct pings=2; actual pings=%s messages=%s' % (d._count_today, [r['message'] for r in d._history_rows]))
print('INCLUSIVE_READER: since=1000 returns', [e['text'] for e in ns['read_inbox'](since=1000)])
assert [r['message'] for r in d._history_rows] == ['first', 'first', 'second']
class Response:
    status_code = 200
    def __init__(self, payload): self.payload = payload
    def json(self):
        if self.payload == 'BAD': raise ValueError('invalid JSON fixture')
        return self.payload
class Session:
    def __init__(self, payloads): self.payloads = iter(payloads); self.calls = []
    def request(self, method, url, timeout):
        self.calls.append((method, url, timeout)); return Response(next(self.payloads))
for label, payloads in [('ALL_INVALID', ['BAD'] * 3), ('VALID_EMPTY', [[], [], []]), ('ONE_INVALID', [[{'id': 'fixture-agent'}], 'BAD', [{'id': 'fixture-reputation'}]])]:
    d = Dashboard(); d._http = Session(payloads); d._poll_api(); d._refresh_sidebar()
    state = d._api_state
    print(label + ': ok=%s errors=%s counts=%s' % (state['ok'], state['errors'], [state[k + '_count'] for k in ('agents', 'contracts', 'reputation')]))
    print(label + ' sidebar:', ' | '.join(line for line in d.widgets['#sidebar'].text.splitlines() if line.startswith(('- status:', '- agents:', '- contracts:', '- reputation:', '- last_error:'))))
    assert len(d._http.calls) == 3
    assert state['ok'] is True and state['errors'] == []
    if label == 'ONE_INVALID': assert state['agents_count'] == state['reputation_count'] == 1
print('Reproduced both defects through original consumer methods; no external runtime I/O.')

Executed output (exit0 checks observed failure signatures, not corrected behavior):

UNCHANGED: expected pings/history/visible/table=1/1/1/1; actual=2/2/2/2
UNCHANGED counters: {'udp': 2} {'fixture-a': 2}
EQUAL_TIMESTAMP_APPEND: expected distinct pings=2; actual pings=3 messages=['first', 'first', 'second']
INCLUSIVE_READER: since=1000 returns ['first', 'second']
ALL_INVALID: ok=True errors=[] counts=[0, 0, 0]
ALL_INVALID sidebar: - status: OK | - agents: 0 | - contracts: 0 | - reputation: 0
VALID_EMPTY: ok=True errors=[] counts=[0, 0, 0]
VALID_EMPTY sidebar: - status: OK | - agents: 0 | - contracts: 0 | - reputation: 0
ONE_INVALID: ok=True errors=[] counts=[1, 0, 1]
ONE_INVALID sidebar: - status: OK | - agents: 1 | - contracts: 0 | - reputation: 1
Reproduced both defects through original consumer methods; no external runtime I/O.

AI assistance disclosed. Bounded novelty searches and local repros do not assert deployed incidents or accepted awards.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions