feat(stream): internal bet sync feed and renderer health#1065
feat(stream): internal bet sync feed and renderer health#1065
Conversation
PR Review: feat(stream): internal bet sync feed and renderer healthGood work on making the streaming lifecycle authoritative for betting consumers. The architecture is sound — separating the source-side feed, adding sequence-aware payloads, and hardening the DuelBettingBridge lifecycle. Here's detailed feedback: Security Concerns1. Timing-unsafe token comparison ( import { timingSafeEqual } from "node:crypto";
const isValid = token.length === requiredToken.length &&
timingSafeEqual(Buffer.from(token), Buffer.from(requiredToken));2. Rate limiting disabled on betting routes 3. 4. const getDatabaseSystem = (): { getDb?: () => any } | null => ...This violates the project's no- Potential Bugs5. Sequence mismatch in 6. // Line 89: always returns 'docker compose', not `${DOCKER_BIN} compose`
return 'docker compose'7. Code Quality8. Duplicated 9. 10. Performance11. 12. Test CoverageTests are solid for the new code — betting feed payload building, replay delivery modes, DuelBettingBridge lifecycle, and phaseVersion advancement are all covered. A few gaps:
Minor Nits
SummaryThe core architecture is well-designed — sequence-aware SSE feeds, source epoch persistence, and the reconciliation loop are all solid patterns. The main items to address before merge are the timing-unsafe auth comparison, the |
PR Review: feat(stream): internal bet sync feed and renderer healthGood work on this PR — the approach of making Hyperscape the authoritative source for duel lifecycle and publishing renderer-health signals is solid. Here's detailed feedback: Code Quality & Best PracticesStrengths:
Issues:
Potential Bugs
Performance Considerations
Security Concerns
Test CoverageGood coverage provided:
Gaps:
Minor Nits
SummaryThis is a well-structured PR that meaningfully hardens the streaming-to-betting pipeline. The main items to address before merge are the |
PR Review: feat(stream): internal bet sync feed and renderer healthGood work on this PR — the overall design is solid. Making Hyperscape the authoritative source for duel lifecycle and renderer health is the right architectural call. Below are findings organized by severity. Security1. Token accepted via query string ( 2. Auth bypass when 3. Potential Bugs4. 5. 6. Race in 7. Non-null assertions in winnerId: data.winnerId!,
loserId: data.loserId!,These are safe because of the early return on Performance8. 9. Fallback avatar creates new materials per instance ( Code Quality10. Duplicated validation logic between client and server 11. 12. Test CoverageTests are solid — good unit coverage for auth, feed payload construction, replay delivery, bridge lifecycle, and renderer health derivation. A few gaps:
SummaryThis is a well-structured PR with good separation between the betting feed protocol, auth, and bridge lifecycle. The main concerns are: (1) the timing side-channel in auth, (2) the potential for duplicate on-chain calls from concurrent reconciliation, and (3) the duplicated validation logic between client and server. None are blockers for a draft PR, but items 2 and 6 should be addressed before merging. |
PR Review: feat(stream): internal bet sync feed and renderer healthGood work on establishing the source-side contract for the betting sync pipeline. The architecture is sound — making Hyperscape authoritative for duel lifecycle and publishing renderer health signals is the right approach. Here's detailed feedback: Security1. Stream token exposed in URL (StreamingMode.tsx) const streamToken = new URLSearchParams(window.location.search).get("streamToken");Tokens in URLs get logged in browser history, server access logs, CDN logs, and potentially referrer headers. Consider moving to a 2. Token comparison in 3. Authorization header case sensitivity (streaming.ts) 4. Input validation gaps (streaming.ts)
Potential Bugs5. Race condition in if (this.reconcileInFlight) return;
this.reconcileInFlight = true;
// ... if an exception occurs here, reconcileInFlight stays true foreverWrap the body in 6. 7. Timer closure captures in 8. onInitError?.(null); // clears error
await world.init(config); // might throwIf the previous error was real and Code Quality9. Unsafe type escapes
Per CLAUDE.md, 10. Unbounded collections
11. Hardcoded timeouts in Performance12. Replay buffer cleanup uses while (this.replayFrames.length > maxSize) {
this.replayFrames.shift();
}
13. 14. Fallback avatar geometry clones (PlayerRemote.ts) Test CoverageThe test suite covers the core paths well:
Gaps to consider:
SummaryThis is a solid foundation for the bet-sync contract. The architecture correctly keeps Hyperscape as the authoritative source. Main items to address before merge:
The rest are quality-of-life improvements that could be addressed in follow-up work. Nice test coverage for a feature of this scope. |
PR Review: feat(stream): internal bet sync feed and renderer healthGood work on this PR — the overall direction of making Hyperscape the authoritative source for duel lifecycle events and publishing renderer health is sound. Here's detailed feedback organized by area. Code Quality & ArchitectureStrengths:
Issues & Suggestions:
Potential Bugs
Performance Considerations
Security
Test CoverageGood coverage for:
Gaps:
Minor Nits
SummaryThis is a well-structured PR that solves a real problem (source-authoritative duel lifecycle for betting consumers). The main actionable items are:
The test coverage is solid for the core logic. Good to merge once the above items are addressed and the paired Hyperbet PR is validated end-to-end. |
PR Review: feat(stream): internal bet sync feed and renderer health |
Security Findings
|
Potential Bugs
|
Code Quality
|
Performance, Test Coverage, and SummaryPerformance (Low): probeRendererHealth runs page.evaluate every 2s reading document.body.innerText (forces layout reflow). Current cadence is fine. trimReplayFrames uses splice(0, N) which is O(N) but fine with max buffer of 8192. Test Coverage: Good unit tests for streaming-betting-auth, streaming-betting-feed, DuelBettingBridge, StreamingMode, LoadingScreen, StreamingDuelScheduler. Missing: deriveBettingRendererHealth branches, probeRendererHealth fallback path, handleBettingEvents SSE lifecycle. Minor Nits: toAgentSnapshot uses agent.characterId for id (server) while toGuardrailAgent uses agent.id (client) -- asymmetry worth a comment. BETTING_SOURCE_EPOCH_STORAGE_KEY could live in a shared constants file. Summary: Solid work. Main actionable items: (1) tighten auth-bypass-when-unset for production, (2) add try/catch around reconcileLiveCycle() in handleStreamingFightStart, (3) consider extracting betting routes from streaming.ts. |
PR Review: feat(stream): internal bet sync feed and renderer healthOverall AssessmentThis is a substantial, well-structured PR that adds an internal betting sync feed, renderer health signaling, and hardens the streaming duel lifecycle. The code quality is generally high with good separation of concerns and solid test coverage for the new functionality. Below are specific findings organized by severity. SecurityGood practices observed:
Concern — Auth bypass when Minor — Potential Bugs
Performance Considerations
Code QualityStrengths:
Suggestions:
Test CoverageGood coverage for the new functionality:
Missing coverage worth considering:
Minor Nits
SummaryThis is a well-engineered PR that significantly improves the streaming-to-betting pipeline reliability. The main areas to address before merge are:
The draft status and paired-PR requirement with |
PR Review: feat(stream): internal bet sync feed and renderer healthSolid PR overall — well-structured streaming lifecycle hardening with good test coverage for the new surfaces. Here are findings organized by severity. Security1. Token in URL fragment is an improvement, but scrubbing has a race window 2. 3. 4. Docker command injection surface in Potential Bugs5. // Current (two separate derivations):
const dedupePayload = buildBettingFeedPayload({ ..., rendererHealth: deriveBettingRendererHealth(cycle) });
// ... dedup check ...
const payload = buildBettingFeedPayload({ ..., rendererHealth: deriveBettingRendererHealth(cycle) }); // could differ!6. const bettingBootstrapAuthPreHandler = async (
request: FastifyRequest,
reply: FastifyReply,
): Promise<void> => {
if (!(await assertBettingAuth(request, reply, { allowQueryToken: false }))) {
return reply; // This returns a FastifyReply from a Promise<void> function
}
};Same for 7. 8. Missing Performance9. 10. 11. Reconciliation loop in Code Quality12. Duplicated 13. 14. Test files use 15. 16. Shared fallback geometry objects in Test CoverageGood coverage for the new code paths:
Missing coverage worth adding:
Minor Nits
SummaryThis is a well-thought-out PR that correctly makes the streaming lifecycle authoritative for betting consumers. The renderer health signaling, sequence-aware payloads, and reconciliation loop are solid design choices. The main areas to address before merge are the double renderer-health derivation bug (#5), the |
PR Review: feat(stream): internal bet sync feed and renderer healthOverall this is a well-structured PR with solid security practices (timing-safe token comparison, URL token scrubbing, Security1. Command injection in 2. Auth bypass in non-production environments 3. Token in query parameter on SSE endpoint (Low — documented trade-off) Bugs & Correctness4. Dedup key includes volatile 5. Missing 6. Performance7. Synchronous file read on hot path 8. Code Quality9. 10. Dual route registration 11. No Test Coverage12. Missing happy-path test for betting routes 13. Private method testing anti-pattern in 14. Incomplete 15. 16. Boilerplate duplication 17. Mock usage vs. CLAUDE.md "NO MOCKS" rule Positive Highlights
SummaryThe PR is well-architected with good separation of concerns between the feed layer, auth, health derivation, and bridge lifecycle. The most actionable items are:
|
Code Review:
|
PR Review: feat(stream): internal bet sync feed and renderer healthGood work on a substantial PR -- the architecture is well-considered, the auth hardening is a clear improvement, and the test coverage is solid for a feature of this scope. Below are findings organized by severity. SecurityHIGH -- postMessage origin not validated (packages/client/src/index.tsx) MEDIUM -- sessionToken accepted via URL params (packages/client/src/index.tsx) MEDIUM -- INTERNAL_BET_SYNC_ALLOWED_ORIGIN not validated (streaming-betting-routes.ts) LOW -- Query-string tokens for SSE appear in access logs LOW -- stream-to-rtmp.ts logs access token to stdout (~line 760) LOW -- --disable-web-security in stream-to-rtmp.ts BugsNo-op ternary: reason (StreamingDuelScheduler/index.ts ~line 1415) No-op ternary: display (GameClient.tsx ~line 486) GPU context lost effect is a complete no-op (GameClient.tsx lines 312-334) parseReplayCursor prioritizes ?since= over Last-Event-Id (streaming-betting-routes.ts) Stale closure on worldReady in StreamingMode.tsx (handleSetup callback) Race Conditions and State ManagementNo concurrency guard on createOrSyncMarket (DuelBettingBridge.ts) handleStreamingAbort deletes market without on-chain cleanup (DuelBettingBridge.ts ~line 659) _endCycleInProgress flag lifecycle is fragile (StreamingDuelScheduler/index.ts) Performance and MemoryDual storage of payload + payloadJson in replay frames (streaming-betting-routes.ts) No backpressure on SSE raw.write() (streaming-betting-routes.ts ~line 450) ~200 lines of inline CSS re-parsed every render (LoadingScreen.tsx) Spectator entity poll interval has no upper bound (EmbeddedGameClient.tsx ~line 438) Code QualitySSE utilities duplicated between streaming.ts and streaming-betting-routes.ts Empty catch blocks in StreamingMode.tsx (lines ~776, 788, 818) as unknown as DatabaseSystemLike double cast (streaming-betting-routes.ts ~line 494) Duplicated createCycle test helper isDevelopment unused in types.ts (StreamingDuelScheduler/types.ts ~line 178) Docker manager default password check (docker-manager.ts ~line 267) Test Coverage GapsThe test suite is good overall -- proper unit tests with mocks (appropriate for unit tests per project conventions), and the integration test covers the scheduler state machine. Key gaps:
Minor / Nits
SummaryThis is a well-structured PR with good separation of concerns between auth, feed mechanics, and lifecycle management. The main items to address before merge:
The rest are improvements that can be addressed incrementally. Nice work on the timing-safe token comparison, the URL token scrubbing, and the thorough test coverage for the betting feed builder functions. |
Code Review:
|
Code Review: PR #1065 —
|
PR Review:
|
Code Review:
|
Summary
This is the Hyperscapes source-side half of the stream-to-bet sync upgrade.
It makes the streaming lifecycle authoritative for betting consumers, hardens the source-side duel/market bridge, and publishes renderer-health signals so downstream consumers can distinguish healthy live arena frames from degraded initialization shells.
Associated Hyperbet consumer PR: HyperscapeAI/hyperbet#28
What This Adds
/api/internal/bet-sync/stateand/api/internal/bet-sync/eventsDuelBettingBridgelifecycle handling so the active market stays aligned with the live streaming duel and is not recreated during resolutionWhy This PR Exists
Hyperbet already has a paired consumer hardening branch, but the consumer cannot be frontier-grade if the source stream is not authoritative and explicit about health.
This PR keeps Hyperscapes as the canonical duel lifecycle source and gives Hyperbet a stable source contract to consume without relying on delayed public spectator snapshots as the primary sync input.
Validation
DuelBettingBridgelifecycle behaviorDraft / Merge Notes
This PR should remain draft until the paired Hyperbet PR and the end-to-end soak both pass together.
Do not merge this PR independently of HyperscapeAI/hyperbet#28.