feat: browser case import and replay with real browser workflow tests - #34
Merged
Conversation
An operator receiving an exported case can now inspect and replay it in the local GUI: file selection, a replay control, per-check results, and explicit unavailable/conflicting/unsupported/failed states. - Server: POST /api/replay accepts an exported case as JSON, bounded to 1 MiB enforced from the byte stream (not from any client claim), with strict content-type and object checks. It calls the existing replayBundle, so verification (rail bundle verify, MandateBound review) is reused rather than reimplemented in the browser. Only one replay runs at a time, and rejected uploads are drained to a hard ceiling so the caller still receives its error. - Import/replay never executes an action or remedy: the path spawns exactly two verification commands, asserted by a test that records their argv. - Imported identity is rendered as untrusted, escaped text and labelled separately from any live run; results state synthetic keys, no provenance, source truth unknown, and no legal effect. Imported results clear when a new run starts. - Fixes a swapped record() argument that reported a failing review-request check as passing. - Playwright (Chromium) workflow tests drive real clicks, file selection, and async responses against pinned components: full run/export/import/replay journey, refusal, repeated runs, stale clearing, malformed, unavailable, oversized, and tampered imports.
| const replayButton=document.getElementById('replay'); | ||
| let latestToken=0; | ||
| let importToken=0; | ||
| function clearImported(){ importToken++; importResult.innerHTML=''; importStatus.textContent=''; } |
There was a problem hiding this comment.
🟡 New runs permanently disable replay
When a run starts during an import, clearImported invalidates it without restoring the replay button. The replay handler exits on the stale token, leaving replay disabled until reload.
Suggested change
| function clearImported(){ importToken++; importResult.innerHTML=''; importStatus.textContent=''; } | |
| function clearImported(){ importToken++; importResult.innerHTML=''; importStatus.textContent=''; replayButton.disabled=false; } |
Was this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
+340
to
+342
| // Replay runs child verifiers synchronously; allow only one at a time so | ||
| // repeated clicks cannot queue unbounded blocking work. | ||
| let replaysInFlight = 0; |
There was a problem hiding this comment.
🟡 Replay requests queue without limit
replaysInFlight starts at zero for every request, so concurrent replays never observe another replay. Each request queues two synchronous verifiers instead of receiving the intended 503 response.
Prompt for agents
The replay concurrency state in bin/aas-gui.mjs is scoped to each request callback, so every POST /api/replay sees zero. Move replay admission state to the createGuiServer closure and reserve a replay before body processing or verifier work begins. Ensure every validation, parsing, verifier, and response path releases the reservation. Because replayBundle uses synchronous child processes that block the event loop, account for requests already waiting while a verifier runs so they are rejected rather than processed sequentially. Add a test that overlaps two replay requests and verifies one receives 503 without invoking its verifier commands.
Was this helpful? React with 👍 or 👎 to provide feedback.
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.
Capability (approved milestones 1 and 2)
Import an exported case in the local GUI and replay it without a
terminal and without executing the original action: file selection,
replay control, per-check results, and explicit unavailable /
conflicting / unsupported / failed states. Verification reuses the
existing
replayBundle(railbundle verify+ MandateBoundreview);no verifier is reimplemented in the browser.
Real browser workflow tests (Playwright, Chromium) drive actual clicks,
file selection, and async responses against the pinned components.
Trust and safety properties (tested)
Content-Length; strict content-type; non-object/empty bodies rejected;
no attacker-controlled paths and no URL fetch from imported evidence;
embedded keys are never trusted for integrity.
recording argv. No action execution or remediation is reachable.
separately from any live run and states synthetic keys, no
provenance, source truth unknown, no legal effect.
caller still gets its error (verified: 1.5 MiB and 3 MiB both return
413, server stays healthy).
Fixes found during review
record(false, "review-request", …)swapped arguments made a failingcheck render as
pass."verified"; now asserts the headline exactly.
Verification
npm test: 91/91 (endpoint tests for real exported case, conflicting,unavailable, malformed, oversized, non-object, wrong content-type,
repeated imports, no-execution argv, status mapping, escaping).
npm run test:browser: 7/7 real browser journeys.npm run gui:smokepasses. CI adds a bounded browser job (Chromium,Node 22.12.0, Python 3.13) alongside the existing integration jobs.