Skip to content

refactor(chat_files): extract streamWorkspaceResponse helper for Upload+Download - #2385

Merged
HongmingWang-Rabbit merged 1 commit into
stagingfrom
auto/chat-files-stream-response-helper
Apr 30, 2026
Merged

refactor(chat_files): extract streamWorkspaceResponse helper for Upload+Download#2385
HongmingWang-Rabbit merged 1 commit into
stagingfrom
auto/chat-files-stream-response-helper

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Summary

The "do request → check err → defer close → forward headers → set status → io.Copy → log mid-stream errors" tail was duplicated between Upload and Download. Each handler had ~12 lines that differed only in:

  • the op label in log messages ("upload" vs "download")
  • the set of response headers forwarded (Upload: Content-Type only; Download: Content-Type + Content-Length + Content-Disposition)

Changes

Hoist into `ChatFilesHandler.streamWorkspaceResponse(c, op, workspaceID, forwardURL, req, forwardHeaders)`. Each call site reduces to one line. Future changes — request-id forwarding, observability metric, response-size cap, bytes-streamed log — go in ONE place rather than two.

Same drift-prevention rationale as `resolveWorkspaceForwardCreds` (#2372) and `readOrLazyHealInboundSecret` (#2376), applied to the response-streaming layer of the same handlers.

Behavior preservation

  • Existing TestChatUpload_* and TestChatDownload_* integration tests (8 across both handlers) pass unchanged.
  • Log message format unified to "chat_files {op}: ..." — operators grep one prefix for both features instead of separate prefixes per handler.

Test plan

  • `go test ./internal/handlers/` green
  • `go test ./...` full module green
  • Net -2 lines (24 added, 26 deleted)

🤖 Generated with Claude Code

…ad+Download

The "do request → check err → defer close → forward headers → set
status → io.Copy → log mid-stream errors" tail was duplicated between
Upload and Download. Each handler had ~12 lines that differed only in:

  - the op label in log messages ("upload" vs "download")
  - the set of response headers to forward verbatim
    (Upload: Content-Type only; Download: Content-Type +
    Content-Length + Content-Disposition)

Hoist into ChatFilesHandler.streamWorkspaceResponse(c, op,
workspaceID, forwardURL, req, forwardHeaders). Each call site
reduces to one line. Future changes — request-id forwarding,
observability metric, response-size cap, bytes-streamed log —
go in ONE place rather than two.

Same drift-prevention rationale as resolveWorkspaceForwardCreds
(#2372) and readOrLazyHealInboundSecret (#2376), applied to the
response-streaming layer of the same handlers.

Behavior preserved: existing TestChatUpload_* and TestChatDownload_*
integration tests (8 across both handlers) all pass unchanged. The
log message format is consistent across both handlers now (single
"chat_files {op}: ..." string template) — operators can grep one
prefix for both features instead of separate prefixes per handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@HongmingWang-Rabbit
HongmingWang-Rabbit added this pull request to the merge queue Apr 30, 2026
Merged via the queue into staging with commit cdef893 Apr 30, 2026
18 checks passed
@HongmingWang-Rabbit
HongmingWang-Rabbit deleted the auto/chat-files-stream-response-helper branch April 30, 2026 15:35
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
…org root (priv-esc)

Independent security review of #2385 found a privilege-escalation path: POST
/registry/register is bootstrap-allowed for a fresh workspace id and wrote the
caller-supplied kind, while workspaces_platform_root_check only enforces
'platform => parent_id IS NULL' (NOT a single root). So an ordinary in-VPC
workspace could register a fresh UUID as {"kind":"platform"}, mint a second
org root, and POST /workspaces/:id/restart it — the shared provision path then
injects MOLECULE_API_KEY=ADMIN_TOKEN (tenant-wide org-admin credential) into any
kind='platform' workspace, on self-host AND SaaS. That breaks the invariant that
only the concierge gets the org MCP + admin token.

Defense in depth:
- migration 20260607000000_one_platform_root: partial UNIQUE index
  (kind) WHERE kind='platform' — at most one platform root per (single-org)
  tenant DB. isPlatformRootViolation now also maps the 23505 to a friendly 409.
- registry.go Register: app-layer guard refusing to CREATE or PROMOTE a row to
  kind='platform' via the public path (reserve that for the AdminAuth/boot-gated
  install paths); a platform agent re-registering its already-platform row is
  unaffected. Placed after the token check to avoid side-channeling row existence.
- corrected the false 'CHECK structurally guarantees one per org' claims in the
  20260606 migration + integration-test header.

Tests:
- registry_test.go: rejects fresh kind=platform (403), rejects workspace->platform
  promotion (403), allows already-platform re-register (200).
- kind_platform_root_integration_test.go: real-PG test that a SECOND platform
  root is rejected by the unique index (the CHECK alone accepts it).
- canvas-topology-pure.test.ts: cover stripPlatformRootForMap (QA HIGH gap) —
  abs-position reparent math, platform-edge drop, grandchild preservation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
…rom the Canvas gate

Two pre-existing Canvas-gate fragilities (both on main, surfaced by #2385's CI)
that blocked the required CI / all-required gate on resource/timing, not on a
real test result:

1. DisplayTab.test.tsx "forwards browser paste events into the noVNC clipboard"
   raced: it fired paste as soon as the "Workspace desktop" title rendered, but
   the component sets rfbRef.current synchronously after new RFB() INSIDE the
   async connect() (which awaits a lease/token first). When the race lost under
   CI runner load, the window paste handler's rfbRef.current?.clipboardPasteFrom
   no-op'd -> 0 calls. Wait for mockRFBConstructor before pasting -> deterministic.

2. The Canvas gate ran "npx vitest run --coverage" as the pass/fail step. v8
   coverage + JSDOM under vitest maxWorkers:1 accumulates memory across all 228
   files and OS-OOM-killed the run mid-suite on the shared runner. Split: the
   GATE is now plain "npx vitest run" (light, deterministic); coverage moves to a
   separate continue-on-error artifact step (no threshold gate per #1815, so it
   was never a real gate). Removes the OOM from the required path.

Verified: DisplayTab 13/13 (5x); full canvas suite 3358/0; coverage run still
produces the artifact when memory allows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
…lf-host platform-agent backend (BYOK · user-tasks · boot-provision)' (#2385) from feat/canvas-concierge-ui into main
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