From 3dd73315eb3265e4641fa9b51fe6b320d005429f Mon Sep 17 00:00:00 2001 From: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> Date: Sat, 5 Sep 2026 00:25:30 -0700 Subject: [PATCH 1/6] fix(core10): bound the L1 item query, persist the theme choice (8vv, dg3) Two Core 10 anti-goals of contracts/operator-surface.v1.md, closed together because they are the same promise from two directions: a view must not ask for an unbounded read, and a view must not hold state that dies on refresh. OSV1-015 -- the unbounded query (work_item_pipeline-8vv) `project_view` called `bd.list(..., limit=0)` -- bd's own "unlimited" -- on a page that re-renders every 20 seconds, so every open tab re-materialised the whole item set three times a minute. It now passes `_L1_ITEM_QUERY_LIMIT + 1` (500, the repo's existing `LIST_MAX_LIMIT` ceiling, and exactly ten of this view's own pages). The `+ 1` is the one-past-the-window probe: it establishes whether more rows exist as a fact rather than inferring it from a full window. A bound is a truncation, so the view confesses it: `_truncation_note_html` renders "Showing 50 of 500+ items - read capped at 500 ..." when the ceiling binds, and never dresses a bounded window as a measured total. The L2 detail page's `bd.activity(item.id)` inherited its bound from the seam's default; it now states it (`limit=A.HISTORY_LIMIT`). Audit of every adapter read reachable from a GET handler: 4 calls, all now explicit. OSV1-016 -- the theme that died on refresh (work_item_pipeline-dg3) `wtSetTheme` set an attribute and remembered nothing, so a chosen Light came back Dark on the next load. It now persists to `localStorage` under `webtheme.THEME_STORAGE_KEY` -- one declaration, shared by the writer and the reader -- and `webtheme.theme_boot_js()` resolves it in ``, before the body paints, so there is no flash. Stored choice first, then `prefers-color-scheme`, then the server's `data-theme="dark"` default, which the resolver only ever REPLACES (PR #55 stays fixed). Apply and persist are split: `wtApplyTheme` never writes, so the sync call that re-derives the toggle's `aria-pressed` after each 20s body swap cannot freeze a first visit's OS preference into a choice nobody made. Tests tests/unit/test_view_query_bounds.py -- static audit of every bounded adapter read reachable from a GET handler; confirmed red against `limit=0`, against a removed `limit=`, and against the inherited-default activity call. tests/unit/test_theme_persistence.py -- executes the emitted scripts under node against a fake DOM and does the real round trip (load, click Light, reload, assert Light); confirmed red against removing either half. Plus L1 route tests for the cap and its absence. Ledger OSV1-015 and OSV1-016 flipped VIOLATION -> CONFORMS with evidence, probes retargeted from pins to invariants in the same change, mutations inverted to the regression direction (+1 new one). `pytest ledger/checks` 60 passed; `make ledger-mutate` 54/54 proven. Cross-lane, unavoidable, flagged for the integrator: OSV1-031's red-row tally 10 -> 8, and the line-anchored specimens in OSV1-005/-006 re-anchored where this change shifted them (same specimens, new line numbers). --- ledger/checks/mutation_harness.py | 52 ++-- ledger/checks/test_operator_rows.py | 321 ++++++++++++++++++---- ledger/rows.yaml | 236 ++++++++++------ src/amplifier_work_tracker/webapp.py | 38 ++- src/amplifier_work_tracker/webbrowse.py | 87 +++++- src/amplifier_work_tracker/webtheme.py | 56 +++- tests/integration/test_observatory_web.py | 17 +- tests/integration/test_webbrowse_route.py | 37 +++ tests/unit/test_theme_persistence.py | 251 +++++++++++++++++ tests/unit/test_view_query_bounds.py | 297 ++++++++++++++++++++ 10 files changed, 1212 insertions(+), 180 deletions(-) create mode 100644 tests/unit/test_theme_persistence.py create mode 100644 tests/unit/test_view_query_bounds.py diff --git a/ledger/checks/mutation_harness.py b/ledger/checks/mutation_harness.py index 505c75f..c75868a 100644 --- a/ledger/checks/mutation_harness.py +++ b/ledger/checks/mutation_harness.py @@ -461,11 +461,15 @@ def _mo006_an_unregistered_computed_site_appears(w: World) -> None: def _mo007_a_get_handler_reaches_a_write(w: World) -> None: """REGRESSION: the L1 GET view acquires a mutating adapter call.""" + # Re-anchored 2026-09-04: this used to hang off the L1 view's `limit=0` + # call, which OSV1-015's fix (work_item_pipeline-8vv) removed. The anchor + # is incidental to what this mutation proves -- it needs ANY line inside + # the L1 GET handler to hang a write off -- so it moved to the bounded + # call that replaced it rather than the row being re-derived. w.replace( WEBBROWSE, - "all_matching = bd.list(status=status_filter, include_resolved=True, limit=0)", - "all_matching = bd.list(status=status_filter, include_resolved=True, limit=0)\n" - " bd.update(name)", + "query_capped = len(fetched) > _L1_ITEM_QUERY_LIMIT", + "query_capped = len(fetched) > _L1_ITEM_QUERY_LIMIT\n bd.update(name)", ) @@ -516,19 +520,26 @@ def _mo014_a_chart_library_is_declared(w: World) -> None: w.replace(PYPROJECT, '"itsdangerous>=2.1",', '"itsdangerous>=2.1",\n "plotly>=5.20",') -def _mo015_the_unbounded_query_is_bounded(w: World) -> None: - """FIXED: the L1 view stops asking for everything.""" - w.replace(WEBBROWSE, "include_resolved=True, limit=0)", "include_resolved=True, limit=200)") +def _mo015_the_bounded_query_goes_unbounded_again(w: World) -> None: + """REGRESSION: the L1 view goes back to asking for everything -- literally + the shape this row closed (`limit=0` is bd's own "unlimited"). + """ + w.replace(WEBBROWSE, "limit=_L1_ITEM_QUERY_LIMIT + 1,", "limit=0,") -def _mo016_the_theme_starts_persisting(w: World) -> None: - """FIXED: a chosen theme survives a refresh.""" - w.replace( - WEBAPP, - "document.documentElement.setAttribute('data-theme', t);", - "document.documentElement.setAttribute('data-theme', t);\n" - " localStorage.setItem('wt-theme', t);", - ) +def _mo016_the_theme_stops_persisting(w: World) -> None: + """REGRESSION: the chosen theme goes back to dying on refresh -- the setter + applies it and remembers nothing, exactly as before dg3. + """ + w.replace(WEBAPP, " try{ localStorage.setItem(WT_THEME_KEY, t); }catch(e){}\n", "") + + +def _mo016_the_first_paint_resolver_moves_out_of_head(w: World) -> None: + """REGRESSION, the OTHER half: the choice is still stored, but nothing + reads it before the body paints -- persistence written and never applied + is not persistence. + """ + w.replace(WEBTHEME, ' f""\n', "") def _mo017_a_second_push_call_site_appears(w: World) -> None: @@ -776,13 +787,18 @@ def _mo034_the_changelog_records_a_look(w: World) -> None: ), Mutation( "OSV1-015", - "the L1 view's unbounded query acquires a real bound", - _mo015_the_unbounded_query_is_bounded, + "the L1 view's bounded query goes unbounded again", + _mo015_the_bounded_query_goes_unbounded_again, + ), + Mutation( + "OSV1-016", + "the theme choice stops surviving a refresh", + _mo016_the_theme_stops_persisting, ), Mutation( "OSV1-016", - "the theme choice starts surviving a refresh", - _mo016_the_theme_starts_persisting, + "the first-paint resolver leaves , so the stored theme is never applied", + _mo016_the_first_paint_resolver_moves_out_of_head, ), Mutation( "OSV1-017", diff --git a/ledger/checks/test_operator_rows.py b/ledger/checks/test_operator_rows.py index b6fe543..f6a8c25 100644 --- a/ledger/checks/test_operator_rows.py +++ b/ledger/checks/test_operator_rows.py @@ -41,14 +41,17 @@ from __future__ import annotations +import ast import re from ._support import ( + ADAPTER, CHARTSVG, LITERAL, OPERATOR_CONTRACT_PATH, PYPROJECT, REPO_ROOT, + ROUTE_MODULES, SUPERVISOR, WEBAPP, WEBBROWSE, @@ -57,6 +60,8 @@ WEBTHEME, WEBTRUST, WIDGETS, + _called_names, + _route_methods, collapse, contains, count, @@ -310,8 +315,8 @@ def test_row_osv1_005() -> None: "OSV1-005 (Core 4): webpwa.py:121's retired-palette inline body is no longer " "counted as a literal site -- the worst specimen in the census. Re-derive." ) - assert "webbrowse.py:741" in literal, ( - "OSV1-005 (Core 4): webbrowse.py:741's textarea (literal font-size, max-width " + assert "webbrowse.py:814" in literal, ( + "OSV1-005 (Core 4): webbrowse.py:814's textarea (literal font-size, max-width " "and padding) is no longer counted. Re-derive." ) total = len(inline_style_sites()) @@ -389,11 +394,11 @@ def test_row_osv1_006() -> None: "webapp.py:2266", "webapp.py:2572", "webapp.py:3376", - "webapp.py:4393", - "webapp.py:4908", - "webtheme.py:4120", - "webtheme.py:4139", - "webtheme.py:4146", + "webapp.py:4421", + "webapp.py:4936", + "webtheme.py:4174", + "webtheme.py:4193", + "webtheme.py:4200", "widgets.py:704", "widgets.py:831", "widgets.py:834", @@ -697,26 +702,175 @@ def test_row_osv1_014() -> None: # --------------------------------------------------------------- OSV1-015 +#: Every adapter read that ACCEPTS a `limit` -- the calls Core 10's "every +#: adapter call reached from a view passes an explicit limit" is about. A +#: scalar read has nothing to bound, so it is not listed. +_BOUNDED_READS = frozenset( + { + "list", + "list_bounded", + "activity", + "attention_items", + "attention_items_from_rows", + "recent_activity_feed", + } +) + +#: A helper that makes an unbounded listing call but is reached by NO route. +#: Dead code is not "reached from a view", so the clause as written does not +#: condemn it -- but the exemption is re-earned every run below, by proving it +#: is still dead. +_UNREACHED_UNCAPPED = ("_oldest_ready_item", WEBAPP) + + +def _module_int_constants(path) -> dict[str, int]: # type: ignore[no-untyped-def] + """Module-level `NAME = ` assignments, plus one alias hop through the + adapter (`NAME = A.LIST_MAX_LIMIT`), read by PARSING -- never importing. + Static reading is what lets this kit measure source it does not execute. + """ + adapter_consts: dict[str, int] = {} + for node in ast.parse(read(ADAPTER)).body: + if ( + isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + and isinstance(node.value, ast.Constant) + and isinstance(node.value.value, int) + ): + adapter_consts[node.targets[0].id] = node.value.value + out: dict[str, int] = {} + for node in ast.parse(read(path)).body: + if not ( + isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + ): + continue + name = node.targets[0].id + value = node.value + if isinstance(value, ast.Constant) and isinstance(value.value, int): + out[name] = value.value + elif isinstance(value, ast.Attribute) and value.attr in adapter_consts: + out[name] = adapter_consts[value.attr] + return out + + +def _limit_passed(call: ast.Call, consts: dict[str, int]) -> object: + """The `limit=` this call passes: an int where it resolves, `None` when no + `limit` keyword is present at all, `"?"` when one IS passed from an + expression this static reading cannot evaluate. + + `"?"` is not a failure: the clause asks for an EXPLICIT limit at the call + site, and a value computed from a parameter is explicit there. `None` is + the failure -- an inherited default is a bound nobody at the call site + can see. + """ + + def evaluate(node: ast.expr) -> object: + if isinstance(node, ast.Constant): + return node.value + if isinstance(node, ast.Name): + return consts.get(node.id, "?") + if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add | ast.Sub | ast.Mult): + left, right = evaluate(node.left), evaluate(node.right) + if isinstance(left, int) and isinstance(right, int): + if isinstance(node.op, ast.Add): + return left + right + return left - right if isinstance(node.op, ast.Sub) else left * right + return "?" + + for kw in call.keywords: + if kw.arg == "limit": + return evaluate(kw.value) + return None + + +def view_listing_calls() -> list[tuple[str, int, str, object]]: + """`(module, line, handler, limit)` for every listing call a read-only + route reaches -- the same module-local, depth-4 name-following the route + audit above already uses, and the same honest bound: a call reached + through a callable handed in from another module is invisible to it. + """ + found: list[tuple[str, int, str, object]] = [] + for path in ROUTE_MODULES: + tree = ast.parse(read(path)) + funcs: dict[str, ast.FunctionDef | ast.AsyncFunctionDef] = {} + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef): + funcs.setdefault(node.name, node) + consts = _module_int_constants(path) + for handler in funcs.values(): + methods: list[str] = [] + for dec in handler.decorator_list: + methods += _route_methods(dec) or [] + if not any(m in {"GET", "HEAD"} for m in methods): + continue + reached = [handler] + seen: set[str] = set() + frontier = _called_names(handler) + for _ in range(4): + nxt: set[str] = set() + for name in frontier - seen: + seen.add(name) + helper = funcs.get(name) + if helper is not None and helper is not handler: + reached.append(helper) + nxt |= _called_names(helper) + frontier = nxt - seen + if not frontier: + break + for fn in reached: + for node in ast.walk(fn): + if ( + isinstance(node, ast.Call) + and isinstance(node.func, ast.Attribute) + and node.func.attr in _BOUNDED_READS + ): + found.append( + (path.name, node.lineno, handler.name, _limit_passed(node, consts)) + ) + return found + + def test_row_osv1_015() -> None: - """Core 10 VIOLATION pin: the L1 view's unbounded query, and the dead - uncapped helper beside it. + """Core 10 CONFORMS: every adapter listing call a read-only route reaches + passes an explicit, finite limit -- MEASURED here, not asserted from a + remembered line number. + + Retargeted from the pin on `webbrowse.py`'s `limit=0` when + work_item_pipeline-8vv landed. The pin named ONE call; this audits the + population, so the next unbounded view query fails here too rather than + slipping in beside a fixed one. + + `limit=0` is bd's own "unlimited" (`adapter.Beads.list`'s docstring says + so outright) and an omitted `limit` leaves bd's default in place + implicitly -- the clause asks for an EXPLICIT bound, so both are failures. """ - assert contains(WEBBROWSE, "bd.list(status=status_filter, include_resolved=True, limit=0)"), ( - "OSV1-015 (Core 10) PIN BROKE THE RIGHT WAY: webbrowse.py's `limit=0` call is " - "gone. If the L1 view now passes a bound that actually bounds (e.g. via " - "`adapter.list_bounded`), flip OSV1-015 to CONFORMS and retarget this probe IN " - "THE SAME CHANGE (work_item_pipeline-8vv). A passing pin is not conformance." - ) - app = read(WEBAPP) - assert app.count("_oldest_ready_item") == 1, ( - f"OSV1-015 (Core 10): `_oldest_ready_item` now has " - f"{app.count('_oldest_ready_item') - 1} caller(s). It calls `bd.list(...)` with " - f"NO limit at all -- dead code was the only reason it did not violate this " - f"clause. Give it a bound or delete it." + calls = view_listing_calls() + assert any(m == "webbrowse.py" and h == "project_view" for m, _, h, _ in calls), ( + f"OSV1-015 (Core 10): the traversal no longer reaches `project_view`'s item " + f"listing -- an audit that matches nothing passes forever while proving " + f"nothing. Re-derive this row. Found: {calls}" + ) + offenders = [c for c in calls if c[3] is None or (isinstance(c[3], int) and c[3] <= 0)] + assert not offenders, ( + "OSV1-015 (Core 10) REGRESSION -- a view-reached adapter read no longer " + "passes an explicit, finite limit (`None` = no `limit=` at all, so the call " + "silently inherits the seam's default; `0` = bd's own \"unlimited\"):\n " + + "\n ".join(f"{m}:{ln} in {h}() -> limit={lim!r}" for m, ln, h, lim in offenders) + + "\n This surface re-renders every 20 seconds; an unbounded read here runs " + "three times a minute per open tab." + ) + name, module = _UNREACHED_UNCAPPED + app = read(module) + assert app.count(name) == 1, ( + f"OSV1-015 (Core 10): `{name}` now has {app.count(name) - 1} caller(s). It " + f"calls `bd.list(...)` with NO limit at all -- being reached by nothing was " + f"the only reason it did not violate this clause. Give it a bound or delete it." ) assert contains(WEBAPP, 'items = bd.list(lane=A.LANE_WORK, status="open")'), ( "OSV1-015 (Core 10): the uncapped `bd.list` in `_oldest_ready_item` is gone -- " - "welcome, and the row's second pinned fact just changed. Re-derive." + "welcome, and the row's exemption just changed. Re-derive." ) @@ -724,51 +878,98 @@ def test_row_osv1_015() -> None: def test_row_osv1_016() -> None: - """Core 10 VIOLATION pin: the theme choice persists nowhere. - - Pinned narrowly, on theme only. At seed the density preference raised a - genuine reading question (localStorage survives a refresh but IS 'only in - the browser') that the reconcile returned to the root rather than deciding, - so this probe deliberately said nothing about it. - - SETTLED 2026-09-04 by the owner-ratified DRAFT true-up #1: the machine - check now reads "no view holds state that does not survive a refresh - (state persisted in `localStorage` or on the server survives; ...)". Under - that wording density is CONFORMANT and theme is still a violation, so the - probe now asserts BOTH -- the pin on theme, and the persistence that makes - density conformant, since a row whose notes rule on density must notice if - density stops persisting. + """Core 10 CONFORMS: a chosen theme survives a refresh -- and so does the + other preference on this surface. + + Retargeted from the pin on `wtSetTheme` persisting NOTHING, when + work_item_pipeline-dg3 landed. Three facts hold it up, and all three are + load-bearing: + + * the choice is WRITTEN (`wtSetTheme` -> `localStorage`); + * it is READ BACK AT FIRST PAINT, from ``, before `` is + parsed -- a body-end read applies it one paint too late, which is a + flash, not a fix; + * writer and reader name the SAME key, taken from ONE declaration. Two + spellings would fail silently and look exactly like "the toggle does + nothing". + + The server's `data-theme="dark"` default is asserted UNCHANGED: the + resolver only ever replaces that attribute, never removes it, so PR #55's + fix (a light-OS browser silently winning the token cascade because + `` carried no `data-theme` at all) stays fixed. + + Density is checked alongside, unchanged in meaning from the pin: under + the 2026-09-04 aligned wording its `localStorage` persistence is exactly + why it is conformant, and a row whose notes rule on density must notice + if density stops persisting. """ app = read(WEBAPP) - setter = app[app.index("function wtSetTheme(t){") :][:340] - assert "setAttribute('data-theme', t)" in setter, ( - "OSV1-016 (Core 10): `wtSetTheme` no longer sets the theme attribute -- the " - "mechanism this row measures moved." - ) - for persistence in ("localStorage", "sessionStorage", "document.cookie", "fetch("): - assert persistence not in setter, ( - f"OSV1-016 (Core 10) PIN BROKE THE RIGHT WAY: `wtSetTheme` now uses " - f"{persistence!r}. If the theme choice survives a refresh, flip OSV1-016 to " - f"CONFORMS and retarget this probe IN THE SAME CHANGE " - f"(work_item_pipeline-dg3). A passing pin is not conformance." - ) + theme_src = read(WEBTHEME) + + key_decl = 'THEME_STORAGE_KEY = "wt-theme"' + assert key_decl in theme_src, ( + "OSV1-016 (Core 10): `webtheme.THEME_STORAGE_KEY` is gone or renamed. It is " + "the ONE declaration the writer and the first-paint reader both take their " + "key from -- re-derive this row before letting them drift apart." + ) + + assert "function wtSetTheme(t){" in app, ( + "OSV1-016 (Core 10): `wtSetTheme` is gone or renamed -- the mechanism this row " + "measures moved. Re-derive." + ) + setter = app[app.index("function wtSetTheme(t){") :][:200] + assert "localStorage.setItem(" in setter, ( + "OSV1-016 (Core 10) REGRESSION: `wtSetTheme` no longer persists the choice. " + "A theme held only in page memory dies on the next load -- that is this " + "clause's anti-goal in its own words (work_item_pipeline-dg3)." + ) + + assert "def theme_boot_js()" in theme_src, ( + "OSV1-016 (Core 10) REGRESSION: `webtheme.theme_boot_js` is gone. Without a " + "first-paint resolver a stored theme is written and never read back." + ) + boot = theme_src[theme_src.index("def theme_boot_js()") :] + boot = boot[: boot.index("\ndef ")] + assert "localStorage.getItem(" in boot and "setAttribute('data-theme'" in boot, ( + "OSV1-016 (Core 10) REGRESSION: the first-paint resolver no longer reads the " + "stored theme and applies it. Written-but-never-read is not persistence." + ) + assert "removeAttribute" not in boot, ( + "OSV1-016 (Core 10): the resolver now REMOVES `data-theme` rather than only " + "replacing it -- that is how a light-OS browser silently wins the token " + "cascade again (PR #55). Re-derive." + ) + + page_src = theme_src[theme_src.index("def page(") :] + page_src = page_src[: page_src.index("\n# ---")] + assert "" in page_src, ( + "OSV1-016 (Core 10) REGRESSION: `page()` no longer inlines the first-paint " + "resolver at all, so a stored theme is never applied on load." + ) + head_at = page_src.index('') + script_at = page_src.index("") + body_at = page_src.index("` ahead of the body. Applying a stored theme after the body is " + "parsed is a flash of the wrong theme on every single load." + ) assert contains(WEBTHEME, ''), ( - 'OSV1-016 (Core 10): the server no longer hardcodes `data-theme="dark"` on ' - "every page. That hardcoding is the other half of why a chosen theme dies on " - "refresh -- re-derive the row." + 'OSV1-016 (Core 10): the server no longer renders `data-theme="dark"` as the ' + "first-paint default. That attribute is what the resolver REPLACES; without " + "it a light-OS browser wins the cascade before any script runs (PR #55)." ) + # The OTHER preference on this surface, and the one the true-up's aligned - # wording rules CONFORMANT: density persists, so it survives a refresh. Not - # a pin -- a live check on the fact this row's notes rule on. - theme_src = read(WEBTHEME) - assert theme_src.count("wt-density") == 2 and contains( + # wording rules CONFORMANT: density persists, so it survives a refresh. + assert "var KEY='wt-density';" in theme_src and contains( WEBTHEME, "localStorage.setItem(KEY, next ? 'compact' : 'comfortable')" ), ( "OSV1-016 (Core 10): the density preference no longer persists in " "`localStorage`. Under the 2026-09-04 aligned wording that persistence is " - "exactly why density is CONFORMANT while theme is not -- if it stopped, " - "density became a second violation of this clause and this row's ruling note " - "is stale. Re-derive." + "exactly why density is CONFORMANT alongside theme -- if it stopped, density " + "became a violation of this clause and this row's ruling note is stale. " + "Re-derive." ) @@ -1002,8 +1203,8 @@ def test_row_osv1_031() -> None: "to CONFORMS and retarget this probe to assert no Core row is red " "(work_item_pipeline-umm)." ) - assert len(red) == 10, ( - f"OSV1-031 (Freeze 5): pinned 10 red Core-carrying rows, observed {len(red)}: " + assert len(red) == 8, ( + f"OSV1-031 (Freeze 5): pinned 8 red Core-carrying rows, observed {len(red)}: " f"{red}. Movement in either direction means this gate's tally changed -- update " f"the pin and the row's notes in the same change." ) diff --git a/ledger/rows.yaml b/ledger/rows.yaml index bd3fa2b..a1a56c0 100644 --- a/ledger/rows.yaml +++ b/ledger/rows.yaml @@ -1567,115 +1567,166 @@ OSV1-016 (no view stores state only in the browser). - id: OSV1-015 - title: the L1 project view runs an unbounded item query on every 20s poll + title: every adapter listing call reached from a view passes an explicit, finite limit contract: file: contracts/operator-surface.v1.md clause: Core 10 quote: | every adapter call reached from a view passes an explicit limit - disposition: VIOLATION + disposition: CONFORMS work: work_item_pipeline-8vv assertion: kind: probe ref: test_row_osv1_015 notes: > - PINNING ROW -- the probe asserts the CURRENT, KNOWN-WRONG shape on purpose; - a passing probe here is NOT conformance. Flip direction VIOLATION-MOVEMENT. - - MEASURED 2026-09-04 against 4aaee50: - webbrowse.py:339 inside `project_view`, the L1 Project Observatory GET - handler: `bd.list(status=status_filter, - include_resolved=True, limit=0)`. - adapter.py:3958-3963 defines the semantics without ambiguity -- "`limit`, - when given, behaves like bd's own `--limit`/`-n` (0 - means unlimited)". The parameter is explicit; its VALUE - is unbounded. Every L1 render materialises the entire - item set, resolved included, then filters and paginates - in Python. - webbrowse.py:605 L1 is on the 20s whole-body self-poll, so that - unbounded read runs three times a minute per open tab. - The cost is documented in this repo's own code: `adapter.list()`'s docstring - cites cortex at 465 items as the size where materialising the full set - reliably loses a dolt serialization conflict. - - THE OTHER UNBOUNDED CALL, AND WHY IT IS NOT SCORED HERE: webapp.py:909, + FLIPPED VIOLATION -> CONFORMS 2026-09-04 (work_item_pipeline-8vv). The pin + this row carried at SEED asserted the known-wrong shape on purpose; it + broke the right way when the fix landed, and probe + row were retargeted + in the SAME change, per the VIOLATION-MOVEMENT rule. + + WHAT WAS WRONG (seed, measured against 4aaee50): webbrowse.py:339, inside + `project_view` -- the L1 Project Observatory GET handler -- called + `bd.list(status=status_filter, include_resolved=True, limit=0)`, and + adapter.py:3958-3963 defines that value without ambiguity: "`limit`, when + given, behaves like bd's own `--limit`/`-n` (0 means unlimited)". The + parameter was explicit; its VALUE was unbounded. L1 sits on the 20s + whole-body self-poll, so every open tab re-materialised the entire item + set -- resolved included -- three times a minute, then filtered and + paginated it in Python. + + WHAT IS TRUE NOW (measured 2026-09-04): + webbrowse.py:91 `_L1_ITEM_QUERY_LIMIT = A.LIST_MAX_LIMIT` (500) -- + not a number invented for this view: it is the + ceiling the CLI's own `list --limit` clamps to, + and exactly ten pages of this view's own + `LIST_DEFAULT_LIMIT` page size, so paging reaches + every row the query returns. + webbrowse.py:403-409 `bd.list(..., limit=_L1_ITEM_QUERY_LIMIT + 1)`. + The `+ 1` is the one-past-the-window probe: it + establishes whether more rows exist beyond the + ceiling as a FACT, without a second query, so a + project holding exactly 500 matching items is + never mislabelled as truncated. + webbrowse.py:227-252 `_truncation_note_html` -- the view says what it + is NOT showing. Uncapped: "Showing 50 of 137 + items". Capped: "Showing 50 of 500+ items - read + capped at 500 ...". A bounded window is never + rendered as a measured total. + + THE COST THIS BOUND PAYS, recorded rather than buried: the SQL orders by + `priority ASC, created_at DESC, id ASC` (`adapter._list_rows_via_sql`) and + the view re-sorts by `updated_at DESC`. On a project with MORE than 500 + matching items the table therefore shows the most-recently-updated of the + first 500 BY PRIORITY, not of all of them. No project on the shared server + is near that ceiling today -- `adapter.Beads.list`'s own docstring cites + the largest, cortex, at 465 items -- and the footer confesses the cap + whenever it binds. Making it exact would need an offset/count-aware + listing on the adapter seam, which is out of this row's scope; filed as a + residual, not silently accepted. + + HOW IT IS ASSERTED: the probe no longer names one call site. It walks + every GET/HEAD handler in webapp.py/webbrowse.py/webtrust.py, follows + module-local helpers four deep (the same traversal and the same honest + bound as the route audit OSV1-007 uses), and requires every reached + `list`/`list_bounded` call to pass a limit that resolves to a positive + int. `limit=0` and an omitted `limit` both fail. Product-side, the same + audit gates a merge in `tests/unit/test_view_query_bounds.py`, which also + covers the truncation note; both were confirmed to go red against + `limit=0` and against a removed `limit=` before this row was flipped. + + THE OTHER UNCAPPED CALL, AND WHY IT IS STILL NOT SCORED: webapp.py:909, inside `_oldest_ready_item`, calls `bd.list(lane=A.LANE_WORK, - status="open")` with no `limit` at all -- but the function has ZERO callers - (the only occurrence of the name in `src/` is its own definition at - webapp.py:902). Dead code is not "reached from a view", so the check as - written does not condemn it. The probe pins BOTH facts, so wiring it up - without a limit fails just as loudly as the L1 call being fixed. - - RE-READ 2026-09-04 against the DRAFT true-up #1's Core 10 wording. NO - CHANGE, and the reason is that the true-up touched a DIFFERENT conjunct of - the same machine check: this row quotes "every adapter call reached from a - view passes an explicit limit", which is byte-identical before and after - (only the client-side-state conjunct, OSV1-016's, was reworded). Quote - re-verified against the new bytes rather than assumed; disposition stays - VIOLATION; probe untouched. + status="open")` with no `limit` at all -- but the function has ZERO + callers (the only occurrence of the name in `src/` is its own definition). + Dead code is not "reached from a view", so the check as written does not + condemn it. Unchanged from seed, and the probe still re-earns that + exemption every run by proving the function is still uncalled -- the + instant it gains a caller, this row goes red. - id: OSV1-016 - title: the theme choice is client-side state that dies on refresh + title: the theme choice is persisted in localStorage and applied at first paint contract: file: contracts/operator-surface.v1.md clause: Core 10 quote: | no view holds state that does not survive a refresh - disposition: VIOLATION + disposition: CONFORMS work: work_item_pipeline-dg3 assertion: kind: probe ref: test_row_osv1_016 notes: > - PINNING ROW -- the probe asserts the CURRENT, KNOWN-WRONG shape on purpose; - a passing probe here is NOT conformance. Flip direction VIOLATION-MOVEMENT. - - MEASURED 2026-09-04 against 4aaee50: - webapp.py:3503-3508 `wtSetTheme(t)` sets `data-theme` on - `document.documentElement` and updates the toggle - buttons' `aria-pressed`. THAT IS ALL. No cookie, no - localStorage, no server round-trip -- the repo's only - four `localStorage` occurrences are the density - toggle's (webtheme.py:3866, :3873) and its two - docstring mentions. - webtheme.py:3423 every page is server-rendered - ``. - So a visitor who chooses Light gets Dark back on the next refresh and on - every navigation. That is client-side state, and it dies -- which is Core - 10's anti-goal in its own words. It is NOT a body-swap defect: the - attribute sits on `` and the swap replaces ``, so it survives - the poll and dies on reload. - - THE OPEN READING THIS ROW RETURNED TO THE ROOT AT SEED IS NOW SETTLED, and - settled the way this row hoped. The seed note read: the density toggle - persists `wt-density` in `localStorage`, which SATISFIES Core 10's prose - ("no client-side state that dies on refresh" -- it survives) while being, - literally, state "stored only in the browser" -- the machine check's words - as they then stood, and this row's former quote. `density_toggle_html`'s - own docstring states the consequence: the server "has no way to know a - visitor's stored preference (no cookie is involved, only `localStorage`)". - - RULING, owner-ratified 2026-09-04 in DRAFT true-up #1 ("yep, do it all."): - the machine check's wording was ALIGNED TO ITS OWN CLAUSE -- "no view holds - state that does not survive a refresh (state persisted in `localStorage` or - on the server survives; state held only in page memory does not)". Quote - RE-ANCHORED to that new sentence, same clause, same conjunct. - - WHAT THE ALIGNED WORDING DECIDES: - DENSITY CONFORMANT. `wt-density` is persisted in `localStorage` - (webtheme.py:3859 declares the key, :3866 reads it, :3873 - writes it), so the preference survives a refresh. Under the old - phrasing it was a literal-but-unintended violation; under the - aligned phrasing it is simply conformant, and the row's probe - asserts that persistence so a regression re-opens the question - rather than passing silently. - THEME STILL A VIOLATION, unchanged. `wtSetTheme` (webapp.py:3503-3508) - persists NOTHING -- no cookie, no localStorage, no server - round-trip -- so a chosen theme is held only in page memory and - dies on the next load. That is the disposition this row carries, - and the fact its pin is filed on (work_item_pipeline-dg3). - The probe was already pinned narrowly on theme, deliberately, so the - ruling arrived to a row that had not pre-empted it. + FLIPPED VIOLATION -> CONFORMS 2026-09-04 (work_item_pipeline-dg3), probe + retargeted in the SAME change per the VIOLATION-MOVEMENT rule. + + WHAT WAS WRONG (seed, measured against 4aaee50): webapp.py:3503-3508, + `wtSetTheme(t)` set `data-theme` on `document.documentElement` and updated + the toggle's `aria-pressed`. THAT WAS ALL -- no cookie, no localStorage, + no server round-trip -- while webtheme.py:3423 server-rendered every page + as ``. So a visitor who chose Light got + Dark back on the next refresh and on every navigation: state held only in + page memory, which is this clause's anti-goal in its own words. + + WHAT IS TRUE NOW (measured 2026-09-04): + webtheme.py:3403 `THEME_STORAGE_KEY = "wt-theme"` -- ONE + declaration, so the writer and the first-paint + reader cannot drift onto two names (a drift that + fails silently and looks exactly like "the toggle + does nothing"). webapp substitutes it into its own + JS rather than re-spelling it. + webapp.py:3524-3527 `wtSetTheme` applies AND persists + (`localStorage.setItem`, wrapped -- a browser with + storage disabled throws, and a theme preference is + never worth breaking a page over). + webtheme.py:3406-3440 `theme_boot_js()` -- the first-paint resolver: + stored choice first, else `prefers-color-scheme: + light`, else nothing at all. + webtheme.py:3478 it is inlined in ``, immediately after the + two required metas and AHEAD of the stylesheet. A + body-end read would apply the stored theme one + full paint too late, which is a flash, not a fix. + webapp.py:3518-3523, apply and persist are two functions on purpose: + webapp.py:3540 `wtApplyTheme` never writes, so the sync call at + the bottom cannot freeze a first visit's ambient + OS preference into a stored "choice" nobody made. + That sync call is also what keeps the toggle + honest across the 20s body swap: `data-theme` + lives on ``, which the swap never touches, + but the BUTTONS come back server-rendered with + Dark pressed every tick, and the swap re-executes + body scripts, so it re-derives `aria-pressed` from + the live attribute (verified, not assumed -- + `auto_refresh_js` replaces + `document.body.innerHTML` and re-creates every + script in the fresh body). + + PR #55 NOT REGRESSED: the server still renders `data-theme="dark"` as the + first-paint default (webtheme.py:3472) and the resolver only ever REPLACES + that attribute, never removes it -- so the DOM-measured defect where a + light-OS browser silently won the token cascade because `` carried + no `data-theme` at all cannot return through this path. The probe asserts + both halves. + + HOW IT IS ASSERTED, AND THE HONEST LIMIT: the ledger probe is a + source-shape assertion (this kit runs no browser and no JS). The real + round trip is asserted product-side in + `tests/unit/test_theme_persistence.py`, which EXECUTES the emitted + scripts under `node` against a small fake DOM and does the actual + sequence the clause is about -- load, click Light, reload, assert Light + came back -- plus the harder direction (a stored choice beating a + CONTRADICTING OS preference) and the cases that must NOT persist (an OS + preference is not a chosen theme). Those tests were confirmed to go red + against the removal of the `setItem` line and against the removal of the + head script before this row was flipped. `node` is preinstalled on the CI + runner; where it is absent those tests skip and the structural half still + runs. + + DENSITY, unchanged from the true-up's ruling: `wt-density` is persisted in + `localStorage` (webtheme.py:3913 declares the key, :3927 writes it), so it + survives a refresh and is CONFORMANT under the aligned wording. The probe + still asserts that persistence, so a regression re-opens the question + rather than passing silently. Theme now joins it rather than sitting + beside it as the exception. - id: OSV1-017 title: exactly one call site fires the push channel, and it is the reclaim path @@ -2084,7 +2135,7 @@ failure. - id: OSV1-031 - title: Freeze 5 -- 10 of the 19 Core-carrying rows read VIOLATION or GAP + title: Freeze 5 -- 8 of the 19 Core-carrying rows read VIOLATION or GAP contract: file: contracts/operator-surface.v1.md clause: Freeze 5 @@ -2120,6 +2171,17 @@ rather than re-derived from the kit's real results. All five GAP rows are GAP precisely because they ARE assertable and simply are not asserted yet. + RE-MEASURED 2026-09-04 after work_item_pipeline-8vv and -dg3 landed: OSV1-015 + and OSV1-016 (both Core 10) flipped VIOLATION -> CONFORMS, each re-derived + from real measurement and each with its probe retargeted in the same change. + The tally is now: + CONFORMS 9 OSV1-002, -006, -007, -011, -013, -014, -015, -016, -017 + NOT-ASSERTABLE 2 OSV1-018 (Core 12), OSV1-019 (Core 13) -- unchanged + VIOLATION 3 OSV1-001, -005, -009 + GAP 5 OSV1-003, -004, -008, -010, -012 + Eight red. Freeze 5 is met when all eight are green and the two + NOT-ASSERTABLE rows still name their cadence. + - id: OSV1-032 title: >- Freeze 6 -- 66 literal inline sites and 40 \n" diff --git a/tests/integration/test_observatory_web.py b/tests/integration/test_observatory_web.py index f5b093a..5bc541a 100644 --- a/tests/integration/test_observatory_web.py +++ b/tests/integration/test_observatory_web.py @@ -113,18 +113,31 @@ def test_l0_unknown_window_param_falls_back_to_7d_not_a_500(client, shared_proje assert r.status_code == 200 -def test_l0_page_declares_dark_theme_by_default(client, shared_project_name): +def test_l0_page_declares_dark_theme_when_nothing_is_stored(client, shared_project_name): """Regression: `` must carry `data-theme="dark"` from first render -- without it, a browser/OS whose own `prefers-color-scheme` is light silently wins the CSS token cascade (see `:root[data-theme="dark"]`'s docstring in webtheme.py), producing the live-dashboard defect where the verdict hero read as a light surface with dark text even though - the page's own theme toggle showed "Dark" as active.""" + the page's own theme toggle showed "Dark" as active. + + Dark is the default the SERVER can render, not the last word: the + first-paint resolver inlined in `` replaces this attribute when + the visitor has a stored choice or a light OS preference (Core 10 -- + a chosen theme must survive a refresh). It only ever REPLACES it, which + is why the defect above cannot return. Asserted here as well, because + the two facts only make sense together. + """ _login(client) r = client.get("/") assert r.status_code == 200 assert '' in r.text assert '