feat(reactor-devtools): export a run as one self-contained HTML file (gist view) - #140
Draft
rawwerks wants to merge 1 commit into
Draft
feat(reactor-devtools): export a run as one self-contained HTML file (gist view)#140rawwerks wants to merge 1 commit into
rawwerks wants to merge 1 commit into
Conversation
…HTML file The gist view: the copyable .prose.md contract on top, the receipt timeline (frames, per-node chain-verify, cost by surprise-cause, raw receipts) expandable in the middle, and each node's published world-model assets below — JSON pretty-printed, .html/.svg previewed in a no-token sandboxed iframe with an open-in-tab blob button on the collapsed summary row. Pure formatter over the existing data layer; no new dependencies. Exit codes mirror --describe: tamper exits 1 with the file still written carrying the verdict. --source embeds the contract until state-dirs snapshot authored sources (auto-detect already wired). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #139
Summary
reactor-devtools <state-dir> --export <file.html>flag: renders a saved run as one self-contained HTML file — the gist view: copyable.prose.mdcontract on top, expandable receipt timeline in the middle, output assets below.--source <file-or-dir>embeds the contract block (opt-in, since reactor state-dirs carry no source snapshot today; state-dirroot.prose.md/sources/auto-detect is wired for when they do).--titlesets the page title..html/.svgassets get a live preview in a no-token sandboxed iframe (scripts blocked) plus an open in tab button on the collapsed summary row (ablob:URL from the embedded source, detached vianoopener).--describe: clean or legitimately-empty chain → 0; detected tamper → 1, with the file still written carrying the tamper verdict. Existing targets refused without--force.openStateDir→buildSnapshot/describeStateDir/readNodeWorldModel); re-derives nothing, adds no dependencies, changes no existing public surface.Use Case / Run Evidence
agent-experience— this came out of an agent session exploring how prose logs runs and asserts output-type contracts. The friction: a run's trust story (contract → chain-verified receipts → outputs) is only tellable to someone with the package installed and the state-dir on disk. The unit of sharing is a claim about the run, never the run itself. Full motivation in the linked issue.Demonstrated on the shipped
masked-relayfixture paired with itsskills/open-prose/examples/masked-relay/src/contracts (12 sources, 77 receipts, 13 node outputs), and on a minimal chain-valid state-dir whose node publishesreport.html(the "prose in → HTML out" story).Design Boundary
This belongs in
packages/reactor-devtools(the keyless replay viewer): it is a presentation of what the harness already persists, exactly like the SPA and--describe. It does not touch the SDK, thereactorCLI, or VM/skill semantics. The one adjacent gap it names but does not implement: reactor state-dirs don't snapshot authored sources (the VM run-envelope convention) — that belongs in compile/run, and this PR's--sourceflag plus auto-detect bridges until then.Examples
Without a contract source, the export states the absence honestly and stdout hints at
--source; it never fabricates a contract.Testing
pnpm -C packages/reactor-devtools test(typecheck +node --test, keyless/offline by nature — no model calls in this package): 108/108 pass on this branch in a clean worktree based on currentorigin/main. 12 new tests insrc/export/export.test.ts:masked-relayfixture (CLI-level, spawnsdist/cli.jslikecli.test.ts)--sourceembedding with HTML-escaping assertions (hostile<script>payload never appears live).htmlasset path: builds a chain-valid state-dir from real SDK primitives (commitPublished→createReceipt→appendReceipt), asserts the sandboxed iframe, the summary-row open button, and thenoopenerblob handlercontent_hash→ exit 1, file still written carrying thechain ✗ TAMPEREDverdict--forceoverwrite contract, ambiguous-flag refusals (--export+--describe/--copy-to,--sourcewithout--export), missing flag values (--exportwith no value never falls through to the blocking server), missing or empty--sourcefails loudly with no half-exportcollectSources(file / dir /src/child /index.prose.mdordering), self-containment (no externalsrc=/href=URLs)Also verified in a real browser (Chromium via Playwright, over both
http://andfile://): copy button puts the actual prose source on the clipboard, open-in-tab renders the artifact in its own tab without toggling the accordion, zero console errors, zero external requests; a hostile asset script executed nowhere (top window and sandboxed iframe both clean, zero dialogs, zero network).An adversarial pre-PR audit pass reviewed the diff and exercised the binary; its actionable findings are folded in: missing-value flag refusal, loud empty-
--sourcefailure, basename-only state-dir in the shareable artifact (no absolute local paths), escaped receipt status, accurate script-inventory wording in docs, and the tamper-path test above.Residual Risk / Follow-ups
noopener) — a deliberate, documented trust boundary distinct from the always-sandboxed inline preview; an explicit user click, same model as viewing a raw gist file.receipts.json— no silent truncation.root.prose.md/sources/into the state-dir at compile/run so every export carries its contract without--source.🤖 Generated with Claude Code