Skip to content

fix: render the OP_RETURN payload verdicts on the public inspect page - #597

Open
Zyrtnin wants to merge 1 commit into
mainfrom
fix/browser-inspect-renders-the-verdict
Open

fix: render the OP_RETURN payload verdicts on the public inspect page#597
Zyrtnin wants to merge 1 commit into
mainfrom
fix/browser-inspect-renders-the-verdict

Conversation

@Zyrtnin

@Zyrtnin Zyrtnin commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

The browser page rendered none of the OP_RETURN payload fields. A forged HashMark v2 showed as an authoritative-looking OP_RETURN-HASHMARK-V2 badge with no signer and no verdict — the affirmative half of the record survived and the part that contradicts it did not. Same defect as the CLI one in #594, on the surface with the widest audience and no install step.

The data was never the problem: glue.py re-uses the same classify_raw_tx the CLI uses, so hashmark, attestation, message and relationships were all in the payload, crossing the bridge, and discarded by the renderer.

renderOutputRow already carried the rule in a comment — "a field that only the standalone-script card shows is a field most readers never see" — so this is the third instance of the shape that comment was written about. There is now one appendOpReturnPayload used by both cards.

Two smaller things the type rename broke, found by reading rather than reported: the title map and _structuralQualifierNote key on the literal "op_return", so renaming to op_return-hashmark-v2 silently dropped the card title and the "unspendable data carrier" note from exactly the outputs that had just gained content.

Why the drift guard didn't catch it

tests/web asserts that every key the classifier emits appears in the rendered text. That is structural about fields and hand-kept about shapes — the check only ever saw payloads that _SHAPE_NAMES produces, and no shape in that tuple produced a hashmark or a message. So "every key must be rendered" was vacuously true for the keys that had just been added.

A hand-kept list of what a guard covers, for the third time in this session (after MarginPolicy's validated fields and the vendored-source digest list).

Fixed at that level: the corpus is now checked against the type values recovered from the classifier's own source, both directions — a type with no corpus shape means every test in that file is blind to it, and a corpus shape whose type no longer exists is guarding nothing.

The v2 shape carries a random signature deliberately, so it decodes and does not verify. "Does not verify" is the line that was missing from this page entirely, and a corpus of only valid records would not have demanded it.

The guard also recurses into nested payload blocks now, rather than asserting the repr of a dict (which no renderer could emit). Leaves rendered as prose are listed with reasons, the same rule the top-level omission tables already use.

Also fixed

glue.py truncated hex and data_hex to 200 chars while inspect.js told the reader "the JSON drawer carries the full bytes" — so the drawer, and the Copy JSON button, held a prefix, next to a header reporting the true byte count. Newly material rather than untidy: data_hex is now the only place a HashMark or msg record's raw bytes appear, and the UI points at it. The row stays scannable because inspect.js truncates for display at 64 chars independently — the right layer, since only the display needs to be short.

Verification

Three plants, each caught: removing the shared renderer from either card (2 failures each), and forgetting to add the new shapes to the corpus — which the coverage check names explicitly:

AssertionError: the classifier can emit ['op_return-hashmark-v', 'op_return-msg'] and no
corpus shape produces it, so every test in this file is blind to those shapes.

Full suite: 11,002 passed, 192 skipped, 1 xfailed.

🤖 Generated with Claude Code

The browser page rendered NONE of the OP_RETURN payload fields. A forged
HashMark v2 showed as an authoritative-looking `OP_RETURN-HASHMARK-V2` badge
with no signer and NO VERDICT: the affirmative half of the record survived and
the part that contradicts it did not. Same defect I just fixed in the CLI, on
the surface with the widest audience and no install step.

The data was never the problem. glue.py re-uses the same classify_raw_tx the CLI
uses, so hashmark, attestation, message and relationships were all in the
payload, crossing the bridge, and discarded by the renderer.

`renderOutputRow` already carried the rule in a comment - "a field that only the
standalone-script card shows is a field most readers never see" - so this is the
third instance of the shape that comment was written about. There is now ONE
`appendOpReturnPayload` used by both cards rather than a second copy.

Two smaller things the rename broke, both found by reading rather than reported:
the title map and `_structuralQualifierNote` key on the literal "op_return", so
renaming the type to `op_return-hashmark-v2` silently dropped the card title AND
the "unspendable data carrier" note from exactly the outputs that had just
gained content.

WHY THE DRIFT GUARD DID NOT CATCH IT. tests/web asserts that every key the
classifier emits appears in the rendered text - which is structural about FIELDS
and hand-kept about SHAPES. The check only ever saw the payloads that
`_SHAPE_NAMES` produces, and no shape in that tuple produced a hashmark or a
message, so "every key must be rendered" was vacuously true for the keys that
had just been added. A hand-kept list of what a guard covers, for the third time
in this session.

Fixed at that level too: the corpus is now checked against the `type` values
recovered from the classifier's own source, both directions - a type with no
corpus shape means every test in that file is blind to it, and a corpus shape
whose type no longer exists is guarding nothing. The v2 shape carries a random
signature deliberately, so it decodes and does NOT verify: "does not verify" is
the line that was missing from this page entirely, and a corpus of only valid
records would not have demanded it.

The guard also now recurses into nested payload blocks rather than asserting the
repr of a dict, which no renderer could ever emit. Leaves rendered as prose are
listed with reasons, the same rule the top-level omission tables already use.

Also fixed: glue.py truncated `hex` and `data_hex` to 200 chars while inspect.js
told the reader "the JSON drawer carries the full bytes", so the drawer and the
Copy JSON button held a prefix, beside a header reporting the true byte count.
Newly material rather than untidy - `data_hex` is now the only place a HashMark
or msg record's raw bytes appear, and the UI points at it. The row stays
scannable because inspect.js truncates for DISPLAY at 64 chars on its own, which
is the right layer: only the display needs to be short.

Verified by planting three defects, each caught: removing the shared renderer
from either card (2 failures each), and forgetting to add the new shapes to the
corpus, which the coverage check names explicitly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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