The page builds heimdall.auth.complete itself rather than through the
vendored client, and its routeHint carried only sourceVersion. Ghostlight
refuses any invocation without the route transport ("invocation metadata
is incomplete"), so every completion was denied before it reached
Heimdall. The page now takes the transport the provider advertises for
ghostlight.eve.commands, as the vendored client takes it from a command
descriptor. Verified on Yggdrasil: the corrected shape reaches Heimdall's
private plane and gets Heimdall's own answer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Heimdall's Discord callback issues a random completion code distinct from
the attempt id, but the vendored adapter required the two to be equal and
rejected every real return as a malformed witness, so no player could sign
in. Re-vendored from Heimdall e8e2832, which completes by attempt id and
never reads the completion code; the test fixture issues a distinct code as
the server does.
A player who finishes Discord elsewhere and switches back to this tab never
reloads it, so the load-time resume never ran. The page now reruns the same
resume when it becomes visible with an attempt in flight.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main.ts subscribed to the api/eve/events revision stream exactly once, in an
`if (api.hasAuthenticatedSurface())` check that ran right after host.start()
and the redirect-flow resumeHeimdallAccess() call resolved. authenticatedSurface
reflects whatever the *last* surface() fetch saw, and it can flip from false
to true well after that point: a player who signs in through the in-page gate
(heimdall.auth_completion_status.v1, resolved entirely inside the vendored
host's own submit()/refresh() cycle, on its own 100ms-later timer) never
retriggers that one-shot check. That tab never got a revision subscription
for the rest of the session, so every answer after the first was refused as
stale and the card looked frozen.
trySubscribeToRevisions() replaces the one-shot check: idempotent, called
after host.start(), after the redirect flow's own refresh, and on a 2s
recheck interval, since main.ts has no direct hook into the in-page flow's
own completion moment.
pollMs moved from 0 to 5000: the app relied entirely on SSE for live
updates, so a dropped stream (closed by an intermediary, or simply never
subscribed) had no fallback at all. The vendored host's own poll already
no-ops when the surface version has not moved, so this is a low-frequency
safety net, not a second primary channel.
Verified: npm run typecheck (clean) and npm test (3/3, unchanged) in web/.
No headless-browser harness exists for main.ts's transport/host wiring; this
was verified by tracing the vendored host's own submit()/refresh()/consumeCommandResult
control flow (vendor/eve/packages/eve-browser-lowering/dist/index.js) against
the fix, not by an automated end-to-end test. Flagging as a follow-up in the
Cut 15 report rather than adding test infrastructure out of scope for this cut.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>