Skip to content

L0: close the recorded defects, and let a card show what is true now - #2

Merged
ymote merged 3 commits into
mainfrom
l0-invalidation
Aug 2, 2026
Merged

ymote merged 3 commits into
mainfrom
l0-invalidation

Conversation

@ymote

@ymote ymote commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Three commits, in the order they were done. The middle one closes the defects
§8 had recorded as found-and-not-fixed; the last one takes L0 from "renders the
blob it was seeded with" to "renders the live price".

The three recorded defects (9cf195c)

  • A source and a card state may no longer share a name. Nothing leaked —
    the component read the source, which §5.3 permits — but which of the two a
    name resolved to depended on lookup order rather than on anything the card
    said.
  • A declared source nothing reads is refused. It is a fetch the host performs
    on every refresh for data that reaches no pixel. Two shipped in the
    reference cards, not one: series and aqi both went unread the moment
    StockPlot and AqiContour began fetching their own data, and no test noticed
    either.
  • A state write reports what it invalidated. dispatch_reporting returns a
    DispatchOutcome — what applied, which paths were written, which sources those
    made stale. stale_sources and source_plan already computed the answer and
    nothing joined them.

One subtlety, or the check rejects correct cards: resolving copy.x looks up
env.locale.lang implicitly, and no path in the card names it. A purely
syntactic scan called env.locale dead on two of the three reference cards.

And a test that had been passing for the wrong reason

a_source_argument_path_must_be_declared rejects a card carrying an undeclared
path in a source argument. Its fixture ended view root Rule(), so the source
went unread — and once unread sources were refused, the card was rejected for
that instead. The test passed with the rule it exists to protect switched off.

The mutation harness reported it unprotected the moment the new rule landed,
which is the entire reason that harness exists.

A source can be answered by the backend (db27733)

The stock detail worked only because the seeded blob already held a quote. It now
shows $200.75 on the 6T against the blob's $184.20, with the real company
name, change percent, high and low.

UiNode.bindings is what makes it possible: realization resolves quote.last to
a number, and from there nothing downstream can tell it came from a fetch. The
binding records the source and field per argument, alongside the resolved value,
so lowering can emit sys.stock("NVDA", "price") where the backend can answer and
the seeded number where it cannot.

Three findings, none of which a unit test could have produced:

  • sys.stock accepts open and cannot answer it. It resolves
    regularMarketOpen, absent from the upstream response, while
    regularMarketDayHigh and …DayLow beside it are present. It rendered $—
    where the blob held a real opening price. A helper accepting a key is not
    evidence it can answer it — verified against a live response, not documentation.
  • Most formats do not survive concatenation. money is a "$" prefix and
    signed_pct is what the VM already returns; signed_money puts the sign
    outside the currency symbol, and compact and ratio need the number. The
    fallback is the safe direction: a seeded value is stale but correct, a wrong
    concatenation is neither.
  • Anything measuring text must measure what is DRAWN.
    "$" + sys.stock("NVDA", "price") is 33 characters that render as six. Sizing
    the hero by the emitted form dropped it 40pt → 24pt and shifted the whole card
    up — a layout bug with no layout cause.

A live card cannot be asserted by a whole-screen golden

The device harness now takes a static band per case. stock-detail declares the
Mkt Cap / P/E row, which sys.stock exposes neither of, so both stay seeded.
Everything outside the band is checked only for having rendered.

That is weaker than the old golden and true, where the old golden would have been
stronger and false — it would have been asserting a share price.

Cost, recorded rather than discovered later

§5.11 states it: source_plan and its dependency ordering have no consumer on a
backend that answers its own sources, and the translation between L0's capability
names and a backend's helper names lives in that backend's lowering.

Verification

  • 842 workspace tests, 0 failures
  • clippy --workspace --all-targets --all-features -D warnings: clean
  • 19 mutation rules held, 0 unprotected, 0 unusable
  • 4/4 device goldens on the OnePlus 6T; the tap loop green with live data

🤖 Generated with Claude Code

ymote and others added 3 commits August 1, 2026 23:50
Committed red on purpose. Two fail now — the source/state name collision and
the unreported dead source. The third could not even compile, because
`dispatch_reporting` did not exist; it is green in this commit because the API
had to be added for the test to build at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All three were recorded in §8 as found-and-not-fixed. Each is now enforced and
each is held by a mutation rule.

A `source` and a card `state` may no longer share a name. The component would
have read the source — §5.3 was never violated and card state did not leak —
but which of the two a name resolved to depended on lookup order rather than on
anything the card said.

A declared source nothing reads is refused: it is a fetch the host performs on
every refresh for data that reaches no pixel. TWO shipped in the reference
cards, not one. `series` and `aqi` both went unread the moment `StockPlot` and
`AqiContour` began fetching their own data, and no test noticed either. Both
are gone from the cards, and the four goldens still render — three at 0.00%,
weather at 0.09% — because the widgets were already fetching that data.

One implicit read has to be counted or the check rejects correct cards:
resolving `copy.x` looks up `env.locale.lang` to choose a translation, and no
path in the card names it. A purely syntactic scan called `env.locale` dead on
two of the three reference cards.

A state write now reports what it invalidated. `dispatch_reporting` returns a
`DispatchOutcome` — what applied, which paths were written, which sources those
writes made stale. `stale_sources` and `source_plan` already computed the
answer and nothing joined them, so a host holding `true` could not tell what to
refetch. `dispatch_with_data` keeps its bool for callers that do not care.

THE MORE USEFUL FINDING is a test that had been passing for the wrong reason.
`a_source_argument_path_must_be_declared` rejects a card carrying an undeclared
path in a source argument, and its fixture ended `view root Rule()` — so the
source went unread. Once unread sources were refused, the card was rejected for
THAT instead, and the test passed with the rule it exists to protect switched
off. The mutation harness reported it unprotected the moment the new rule
landed, which is the entire reason that harness exists.

The device tap harness gained the retry the render harness has documented since
it was written. A one-shot check on a device that races reports false failures,
and this one produced exactly one: the tap it called dead fired correctly by
hand a minute later.

  840 workspace tests, 19 mutation rules held, 0 unprotected, clippy clean.
  4 goldens green on the 6T; the tap loop green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… what is true now

The stock detail worked only because the seeded blob already held a quote. It
now shows the live price on the 6T: $200.75 against the blob's $184.20, the
real company name, the real change percent, the real high and low.

`UiNode.bindings` is what makes it possible. Realization resolves `quote.last`
to a number, and from that point nothing downstream can tell it came from a
fetch — so a backend that could go and get it has no way to know it should.
`bindings` records the source and field per argument, ALONGSIDE the resolved
value rather than instead of it, and lowering emits `sys.stock("NVDA", "price")`
where the backend can answer and the seeded number where it cannot.

Three findings, none of which a unit test could have produced:

  - `sys.stock` ACCEPTS "open" and cannot answer it. It resolves
    `regularMarketOpen`, absent from the upstream response, while
    `regularMarketDayHigh` and `…DayLow` beside it are present. It rendered
    `$—` on device where the blob held a real opening price. A helper accepting
    a key is not evidence that it can answer it — checked against a live
    response, not against its documentation.

  - Most formats do not survive concatenation. `money` is a "$" prefix and
    `signed_pct` is what the VM already returns; `signed_money` puts the sign
    outside the currency symbol, and `compact` and `ratio` need the number. The
    table is in the code and the fallback is the safe direction: a seeded value
    is stale but correct, a wrong concatenation is neither.

  - ANYTHING THAT MEASURES TEXT MUST MEASURE WHAT IS DRAWN. Emitted and drawn
    text are the same string until a value goes live, and then they diverge
    completely: `"$" + sys.stock("NVDA", "price")` is 33 characters that render
    as six. Sizing the hero by the emitted form dropped it 40pt → 24pt and
    shifted the whole card up — a layout bug with no layout cause. `valued` is
    split so the live path and the measuring path share one formatter and
    cannot drift.

A LIVE CARD CANNOT BE ASSERTED BY A WHOLE-SCREEN GOLDEN. The harness now takes
a static band per case, and stock-detail declares the Mkt Cap / P/E row —
neither of which `sys.stock` exposes, so both stay seeded. Everything outside
the band is checked only for having rendered. That is weaker than the old
golden and true, where the old golden would have been stronger and false: it
would have been asserting a share price.

§5.11 records all of it, including the cost — `source_plan`'s dependency
ordering has no consumer on a backend that answers its own sources, and the
name translation lives in that backend's lowering.

  842 workspace tests, 19 mutation rules held, clippy clean.
  4 goldens green on the 6T; the tap loop green with live data.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@r9s-codereview

r9s-codereview Bot commented Aug 2, 2026

Copy link
Copy Markdown

🤖 AI Code Review

Claude CLI exited with code 1:

✅ No significant issues found.


Tokens used: 0 | Powered by r9s.ai

@r9s-codereview r9s-codereview Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔐 Secrets Detected

This PR contains 2 potential secret(s) that must be removed before merging.

"movers": [], "selected": "NVDA", "range": "m1", "env": {"locale":{}},
"quote": {"name":"NVIDIA","last":184.2,"change":3.1,"pct":1.7,"open":181.0,
"high":185.6,"low":180.2,"volume":41200000.0,
"mktcap":4520000000000.0,"pe":58.3},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Secret Detected (credit_card_visa)

Visa Credit Card Number detected: LLM validation unavailable - flagging conservatively

Match preview: 4520****0000

Remediation: Please review manually and remove if this is a real secret

"movers": [], "selected": "NVDA", "range": "m1", "env": {"locale":{}},
"quote": {"name":"NVIDIA","last":184.2,"change":3.1,"pct":1.7,"open":181.0,
"high":185.6,"low":180.2,"volume":41200000.0,
"mktcap":4520000000000.0,"pe":58.3},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Secret Detected (credit_card_visa)

Visa Credit Card Number detected: LLM validation unavailable - flagging conservatively

Match preview: 4520****0000

Remediation: Please review manually and remove if this is a real secret

@ymote
ymote merged commit bc19a71 into main Aug 2, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant