staging → main: auto-promote cdef893 - #2386
Conversation
…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>
…onse-helper refactor(chat_files): extract streamWorkspaceResponse helper for Upload+Download
Pin the 5 public functions adapters and the runtime hot-path import through ``from platform_auth import``: - ``auth_headers`` — every outbound httpx call merges this in - ``self_source_headers`` — A2A peer + self-message header builder - ``get_token`` — main.py reads on boot to decide register-vs-resume - ``save_token`` — main.py persists the platform-issued token - ``refresh_cache`` — 401-retry path drops in-process cache (#1877) A grep across workspace/ shows 14+ runtime modules import these: main.py, heartbeat.py, a2a_client.py, a2a_tools.py, consolidation.py, events.py, executor_helpers.py (3 sites), molecule_ai_status.py, builtin_tools/memory.py (3 sites), builtin_tools/temporal_workflow.py (2 sites). Renaming any of the five (e.g. ``auth_headers`` → ``bearer_headers``) makes every one of those imports raise ImportError at workspace boot — the failure surface is deep in heartbeat init, nowhere near the rename site. Same drift class as the BaseAdapter signature snapshot (#2378, #2380), skill_loader gate (#2381), runtime_wedge gate (#2383). Reuses the ``_signature_snapshot.py`` helpers shipped in #2381. Defense-in-depth: ``test_snapshot_has_required_functions`` asserts the five names are still present, so removing one even with a synchronized snapshot edit forces an explicit edit here with a justification. ``clear_cache`` is intentionally NOT in the snapshot — it's a test-only helper. Production code MUST NOT depend on it. Verified red on deliberate rename: ``auth_headers`` → ``bearer_headers`` produces a clean diff of the missing function in the failure message. Restored before commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e-snapshot test(platform_auth): module-functions signature snapshot drift gate
|
🔒 Auto-merge disabled — new commit ( |
…space_status enum Migration 043 (2026-04-25) introduced the workspace_status enum but omitted two values application code had been writing for days, so every UPDATE that tried to write either value failed silently in production: 'awaiting_agent' (since 2026-04-24, commit 1e8b5e0): - handlers/workspace.go:333 — external workspace pre-register - handlers/registry.go (via PR #2382) — liveness offline transition - registry/healthsweep.go (via PR #2382) — heartbeat-staleness sweep 'hibernating' (since hibernation feature shipped): - handlers/workspace_restart.go:271 — DB-level claim before stop All four/five sites swallowed the enum-cast error. User-visible impact: external workspaces never transition to a stale state when their agent disconnects (canvas shows them stuck on 'online'/'degraded' indefinitely), new external workspaces never advance past 'provisioning', and idle workspaces never auto-hibernate (resources held forever). PR #2382 didn't *cause* this — it inherited the gap and added two more silent-fail paths on top. The pre-existing two had been broken for five days and went unnoticed because: 1. sqlmock matches SQL by regex, not against the live enum constraint. Every test passed despite the prod-only failure. 2. The handlers either drop the Exec error entirely (workspace.go:333) or log+continue without an alert (the other three). Fix in three pieces: 1. migrations/046_*.up.sql — ALTER TYPE workspace_status ADD VALUE 'awaiting_agent', 'hibernating'. IF NOT EXISTS makes it idempotent across re-runs (RunMigrations re-applies until schema_migrations records the file). ALTER TYPE ADD VALUE doesn't take a heavy lock and commits immediately, safe under live traffic. 2. migrations/046_*.down.sql — full rename → recreate → cast → drop recipe. Postgres has no DROP VALUE so this is the only honest rollback. Pre-flights existing rows to compatible values (awaiting_agent → offline, hibernating → hibernated) before the type swap. 3. internal/db/workspace_status_enum_drift_test.go — static gate that parses every UPDATE/INSERT against `workspaces` in workspace-server/ internal/, extracts every status literal, and asserts each is in the enum union (CREATE TYPE + every ALTER TYPE ADD VALUE). The gate runs in unit tests, no DB required, and would have caught both omissions on the day they shipped. Pattern matches feedback_behavior_based_ast_gates and feedback_mock_at_drifting_layer. Verification: - go test ./internal/db/ -count=1 -race ✓ - go vet ./... ✓ - Drift gate flips red if I delete either ADD VALUE from the migration (validated via local mutation). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HongmingWang-Rabbit
left a comment
There was a problem hiding this comment.
Five-axis pass via back-reference. Auto-promote (github-actions[bot]) bundles two already-reviewed staging-merged commits since #319:
- #2385
refactor(chat_files): extract streamWorkspaceResponse helper for Upload+Download— approved on source PR by hongmingwang-moleculeai. Single-helper dedup of the do-request → defer-close → forward-headers → io.Copy tail; per-handler header set passed in. No behavior delta. - #2387
test(platform_auth): module-functions signature snapshot drift gate— approved on source PR by hongmingwang-moleculeai. Same drift-class as #2378 (BaseAdapter), #2380 (adapter dataclasses), #2381 (skill_loader), #2383 (runtime_wedge); pins the five contract functions (auth_headers,self_source_headers,get_token,save_token,refresh_cache) imported across 14+ runtime hot-path files. Test-only addition.
Five axes:
- Correctness — both source PRs landed green on staging; carrier diff is byte-identical
- Readability — strong inline rationale;
clear_cachecorrectly excluded as test-only with explicit comment - Architecture — reuses shared
_signature_snapshot.pyhelpers; extends established pattern - Security — no credentials, no auth-bypass, no SQL string concat; no deleted tests; no
.github/workflows/*changes - Performance — test-only signature gate; trivial CI cost
Note: mergeStateStatus: BEHIND — main has commits not in staging. Will need a git fetch origin main && git merge origin/main (or rebase) on staging before fast-forward succeeds.
Comment-only per loop policy (substantive carrier on auto-promote). Approval already on source PRs.
…num-awaiting-agent fix(workspaces): add missing 'awaiting_agent' + 'hibernating' to workspace_status enum
…ck workflow_run chain Root cause (verified 2026-04-30): GITHUB_TOKEN-initiated workflow_dispatch creates the dispatched run, but the resulting run's completion event does NOT fire downstream `workflow_run` triggers. This is the documented "no recursion" rule: https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow Evidence (publish-workspace-server-image runs on main): run_id | head_sha | triggering_actor | canary | redeploy ------------+-----------+-----------------------+--------+---------- 25151545007 | 6ef562e | HongmingWang-Rabbit | YES | YES 25171773918 | 21313dc | github-actions[bot] | NO | NO 25173801008 | 59dec57 | github-actions[bot] | NO | NO The 06:52Z run that "worked" was an operator-fired dispatch from the terminal — actor was the operator's PAT. The two runs that "dropped" were dispatched by auto-promote-staging.yml's `gh workflow run` step authenticated via `secrets.GITHUB_TOKEN`, so the actor became `github-actions[bot]` and the workflow_run cascade was suppressed. Same workflow file, same dispatch call, same successful publish run — only the auth token differed. Fix: mint a molecule-ai GitHub App installation token before the dispatch step and use it as `GH_TOKEN`. App-initiated dispatches DO propagate the workflow_run cascade (the App user is a real identity, not the GITHUB_TOKEN bot pseudonym). The molecule-ai App (app_id=3398844, installation 124443072) is already installed on the org with `actions:write` — no new App needed. Only secrets are missing. ## Required setup before merge The following repo secrets must be added at https://github.com/Molecule-AI/molecule-core/settings/secrets/actions or auto-promote will hard-fail at the new "Mint App token" step: - `MOLECULE_AI_APP_ID` = `3398844` - `MOLECULE_AI_APP_PRIVATE_KEY` = contents of a .pem file generated at https://github.com/organizations/Molecule-AI/settings/installations/124443072 (Click "Generate a private key" if one doesn't exist yet.) ## Long-term cleanup The polling tail step still exists because the auto-merge call itself uses GITHUB_TOKEN, so the FF push to main doesn't fire publish-workspace-server-image's `push` trigger naturally. Switching the auto-merge call to use the SAME App token would eliminate the polling tail entirely. Tracked in #2357. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
🔒 Auto-merge disabled — new commit ( |
…-dispatch ci(auto-promote): dispatch publish via molecule-ai App token to unblock workflow_run chain
|
🔒 Auto-merge disabled — new commit ( |
CP Deprovision now routes by ?provider=. Without it, a non-AWS workspace falls through to the AWS terminate path and leaks the box. Changes: - Add resolveProvider helper (queries workspaces.compute->>'provider'). - Append &provider= to the DELETE URL in stopInternal when provider is non-empty. - Add regression tests for both provider-present and provider-absent paths. Fixes #2386.
…osed on lookup error (#2386 sibling-leak) Researcher found CPProvisioner.IsRunning/status omits 'provider' on its control-plane call, misrouting non-AWS workspaces to the AWS status path. Same bug class as deprovision leak #2386/#2387. Changes: - Add resolveProvider helper (workspaces.compute->>'provider') mirroring resolveInstanceID pattern. - IsRunning: resolve provider, fail-closed on error (return true, err so a2a_proxy stays on alive path), URL-encode with url.Values/q.Encode(). - Regression tests: (a) provider threaded to status query, (b) fail-closed on lookup error — no CP call, (c) hostile-slug encoding round-trip. Diff scoped to cp_provisioner.go + cp_provisioner_test.go only. Branch off fresh origin/main (no stacking on #2387/#2388).
Automated promotion of
staging(cdef8932) tomain. All required staging gates green at this SHA: CI, E2E Staging Canvas, E2E API Smoke, CodeQL.This PR is auto-generated by
.github/workflows/auto-promote-staging.ymlwhenever every required gate completes green on the same staging SHA. It exists because main's branch protection requires status checks "set by the expected GitHub apps" — directgit pushfrom a workflow can't satisfy that, only PR merges through the queue can.Merge queue lands this; no human action needed unless gates fail. Reverse-direction sync (the merge commit on main → staging) is handled by
auto-sync-main-to-staging.yml.