fix(handlers): delete duplicate mcpResolveURL/extractA2AText from mcp.go (F1102) - #1558
Closed
molecule-ai[bot] wants to merge 1396 commits into
Closed
molecule-ai[bot] wants to merge 1396 commits into
molecule-ai[bot] wants to merge 1396 commits into
Conversation
The monorepo docs/ tree is ecosystem + user-facing. Internal
roadmap ("what we'll build next", priorities, effort estimates)
doesn't belong there — customers reading our docs don't need our
backlog in their face, and we shouldn't signal "feature X is
coming" contractually when it's just a P2 item in internal
tracking.
Removes:
- docs/architecture/org-api-keys-followups.md (the whole
prioritized roadmap). Moved to the internal repo at
runbooks/org-api-keys-followups.md where it belongs.
- "Follow-up roadmap" section in docs/architecture/org-api-
keys.md, replaced with a shorter "Known limitations" section
that names the current constraints (full-admin only, no
expiry, no user_id in session-minted audit) without
speculating on when they change.
- "What's coming" section in docs/guides/org-api-keys.md,
replaced with "Current limits" that names the same
constraints from the user's POV.
Public docs now describe the feature as it exists TODAY. Internal
tracking of what comes next lives in Molecule-AI/internal (private).
…ublic docs: strip internal roadmap from public org-api-keys docs
promote: docs strip internal
Workspaces stuck in status='provisioning' previously surfaced in three
bad ways:
1. **Details tab crashed** with `Cannot read properties of undefined
(reading 'toLocaleString')`. `BudgetSection` + `WorkspaceUsage`
assumed full response shapes but a provisioning-stuck workspace
returns partial `{}`. Guard each deep field with `?? 0` and cover
the partial-response case with regression tests.
2. **Missing required env vars failed silently** 15+ minutes later as
a cosmetic "Provisioning Timeout" banner. The in-container preflight
catches them but by then the container has already crashed without
calling /registry/register, so the workspace sat in 'provisioning'
forever. Mirror the preflight server-side: parse config.yaml's
`runtime_config.required_env` before launch, fail fast with a
WORKSPACE_PROVISION_FAILED event naming the missing vars.
3. **No backend timeout** ever flipped a stuck workspace to 'failed'.
Add a registry sweeper (10m default, env-overridable) that detects
workspaces stuck past the window, flips them to 'failed', and emits
WORKSPACE_PROVISION_TIMEOUT. Race-safe: the UPDATE re-checks the
status + age predicate so a concurrent register/restart wins.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sioning-resilience fix: harden stuck-provisioning UX — details crash, preflight, sweeper
staging → main: details crash + preflight + provision sweeper
Clicking "Delete" in the workspace context menu did nothing for stuck workspaces. The confirm dialog was rendered via portal as a child of ContextMenu. ContextMenu's outside-click handler checks whether the click target is inside its ref — but the portal puts the dialog in document.body, outside the ref. So clicking the dialog's Confirm counted as "outside", closed the menu, unmounted the dialog mid-click, and the onConfirm handler never ran. Hoist the pending-delete state to the canvas store and render the confirm dialog at the Canvas level (same pattern as the existing pendingNest dialog). The dialog now outlives ContextMenu, so the outside-click close is harmless. Close the context menu on the Delete click itself rather than waiting for the dialog to resolve. Add a regression test covering the new flow and add the standard ?confirm=true query param so the backend's child-cascade guard is consulted correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(canvas): delete workspace dialog race with context menu close
staging → main: fix delete-workspace context menu race
Three nits identified during post-merge review of #1119, #1133: 1. ContextMenu.tsx imported `removeNode` from the canvas store but stopped using it when the delete-confirm flow moved to Canvas in #1133. Also removed the now-unused mock entry in the keyboard test so the test inventory matches the real call list. 2. Preflight's YAML parse failure was a silent pass — defensible since the in-container preflight owns the schema, but invisible to ops if a template ships malformed YAML. Log at WARN so the signal surfaces without blocking the provision. 3. formatMissingEnvError rendered its slice via %q, producing `["A" "B"]` which is Go-literal-looking and ugly in a user-facing error. Join with ", " instead. Test updated to assert the new format. No behavioural changes beyond the log line; fixes are review nits, not bug fixes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore: code-review cleanup on today's shipped PRs (dead code + better errors)
Adds isSafeURL() + isPrivateOrMetadataIP() in mcp.go and wires the check into: - MCP delegate_task (sync path) — line 530 - MCP delegate_task_async (fire-and-forget) — line 602 - a2a_proxy resolveAgentURL() — line 391 Blocklist covers: RFC-1918 private (10/8, 172.16/12, 192.168/16), cloud metadata link-local (169.254/16), carrier-grade NAT (100.64/10), documentation ranges (192.0.2/24, 198.51.100/24, 203.0.113/24), loopback, unspecified, and link-local multicast. For hostnames, DNS is resolved and every returned IP is validated — blocks internal hostnames that resolve to private ranges. Closes: #1130 (F1083 — SSRF in A2A proxy and MCP bridge) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ock leak
Root causes:
1. TermsGate (rendered inside OrgsPage Shell) fetches /cp/auth/terms-status
before OrgsPage fetches /cp/orgs, consuming the first mockResponseOnce
slot — leaving /cp/orgs with no mock and throwing TypeError.
Fix: mock TermsGate as a pass-through component in vi.mock.
2. Non-polling tests used mockFetchSession.mockResolvedValueOnce() which
exhausted after one call; React 18 concurrent re-renders call
fetchSession() multiple times, causing subsequent calls to return
undefined. Fix: use mockResolvedValue() (persistent) for fetchSession.
3. vi.clearAllMocks() in beforeEach kept mockResolvedValueOnce from
previous tests from leaking BUT the vi.fn() mock implementation was
already reset by mockFetchSession.mockReset() in beforeEach. Tests
were passing stale persistent mocks from previous tests. Fix:
mockFetchSession.mockReset() in beforeEach + mockResolvedValue in
each test.
4. Polling tests used vi.useFakeTimers() without shouldAdvanceTime,
which prevented React's useEffect from calling fetch() (0 calls).
Fix: use vi.useFakeTimers({ shouldAdvanceTime: true }) + await
vi.advanceTimersByTimeAsync() to advance time during await.
5. Unmount test unmounted before effects fired (with shouldAdvanceTime).
Fix: flush microtasks with await vi.advanceTimersByTimeAsync(0)
before unmount so the effect runs and schedules the poll timer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(platform): memory backup/restore for nuke-safe development (#1051)
…rors billing.ts (startCheckout, openBillingPortal): replace raw res.text() in thrown Error with a safe status-only message. The response body from /cp/billing/* routes can contain Stripe API error detail (invalid key, card decline message, raw Stripe envelope) that should not reach clients. orgs/page.tsx (createOrg): same fix — raw body → safe message. Full body is logged server-side for debugging. Closes: #91 (CWE-209 — Stripe key echoed in error) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…owlist Fixes audit #125 findings for CWE-639: 1. admin_test_token.go — CRITICAL IDOR (finding #112) When ADMIN_TOKEN is set in production, require it explicitly on GET /admin/workspaces/:id/test-token. The original gap: AdminAuth accepted any valid org-scoped token, letting an Org A token holder mint workspace bearer tokens for ANY workspace UUID they could enumerate. Now requires ADMIN_TOKEN when it's configured; MOLECULE_ENV!=production path still requires a valid bearer (any org token works for local dev). 2. org_plugin_allowlist.go — HIGH IDOR (finding #112) GET and PUT /orgs/:id/plugins/allowlist: add requireOrgOwnership() check after org existence verification. Org-token holders can only read/write their own org's allowlist. Session and ADMIN_TOKEN callers bypass the check (they have platform-wide access via the session cookie path, not org tokens). Closes: #112 (CWE-639 IDOR — tenant config access) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ges (#1155) Closes: #177 (CRITICAL — Dockerfile runs as root) Dockerfiles changed: - workspace-server/Dockerfile (platform-only): addgroup/adduser + USER platform - workspace-server/Dockerfile.tenant (combined Go+Canvas): addgroup/adduser + USER canvas + chown canvas:canvas on canvas dir so non-root node process can read it - canvas/Dockerfile (canvas standalone): addgroup/adduser + USER canvas - workspace-server/entrypoint-tenant.sh: update header comment (no longer starts as root; both processes now start non-root) The entrypoint no longer needs a root→non-root handoff since both the Go platform and Canvas node run as non-root by default. The 'canvas' user owns /app and /platform, so volume mounts owned by the host's canvas user work without needing a root init step. Co-authored-by: Molecule AI CP-BE <cp-be@agents.moleculesai.app> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Workspaces stuck in provisioning used to sit in "starting" for 10min
until the sweeper flipped them. The real signal — a runtime crash at
EC2 boot — lands on the serial console within seconds but nothing
listened. These endpoints close the loop.
1. POST /admin/workspaces/:id/bootstrap-failed
The control plane's bootstrap watcher posts here when it spots
"RUNTIME CRASHED" in ec2:GetConsoleOutput. Handler:
- UPDATEs workspaces SET status='failed' only when status was
'provisioning' (idempotent — a raced online/failed stays put)
- Stores the error + log_tail in last_sample_error so the canvas
can render the real stack trace, not a generic "timeout" string
- Broadcasts WORKSPACE_PROVISION_FAILED with source='bootstrap_watcher'
2. GET /workspaces/:id/console
Proxies to CP's new /cp/admin/workspaces/:id/console endpoint so
the tenant platform can surface EC2 serial console output without
holding AWS credentials. CPProvisioner.GetConsoleOutput is the
client; returns 501 in non-CP deployments (docker-compose dev).
Both gated by AdminAuth — CP holds the tenant ADMIN_TOKEN that the
middleware accepts on its tier 2b branch.
Tests cover: happy-path fail, already-transitioned no-op, empty id,
log_tail truncation, and the 501 fallback when no CP is wired.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#1169) CP-QA approved. golangci-lint fixes in bundle/exporter.go + bundle/importer.go, redactSecrets in admin_memories.go, plus 489-line admin_memories_test.go.
CP-QA approved. 34-line test for BatchActionBar retry state reset after successful batch action.
chore: fast-forward staging with main review-cleanup commits
chore: fast-forward staging to main after PR #1171 merge
…orkspaces Part 3 of 3 for the "fail fast + comprehensive logs" UX. Platform PR #1168 and controlplane #181 ship the server-side; this PR surfaces the data in the canvas. Two changes: 1. DetailsTab renders `last_sample_error` in a dedicated Error section when the workspace is failed (or degraded with an error). Before, the only trace of why a workspace failed was a generic banner — users had to click "View Logs", which opened the terminal tab (the post-boot log, empty on a runtime crash). Now the actual Python traceback is inline. A "View console output" button in the same section opens the full serial console in a modal. 2. New ConsoleModal component. Fetches GET /workspaces/:id/console (platform → CP → ec2:GetConsoleOutput). Portal-rendered above the canvas with Copy / Close / Esc handlers. Renders a friendly message on 501 (self-hosted deploys without CP) and 404 (instance terminated). 3. ProvisioningTimeout's "View Logs" button now opens the console modal instead of the (usually empty) terminal tab — when a workspace is stuck in provisioning, the cloud-init + user-data trace is what the user actually needs. Tests cover the closed-state no-fetch, happy-path fetch, 501/404 messaging, and Close/Escape wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pace bearer tokens (#1157) Squash-merge: Phase 30 Remote Workspaces blog. Acceptance: published on molecule-core.
…-arg - Remove duplicate-line ExecContext call that caused syntax error at mcp.go:784 - Update redactSecrets signature from 1-arg to 2-arg (workspaceID, content) to match the canonical form established in PR #1017 - Update toolCommitMemory call site to use 2-arg form - Add reserved workspaceID param note in docstring for future audit logging Fixes PR #1036 compile-blocking issues (Platform Go job). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…admin endpoints (#1131, #1132) URLs returned from DB and Redis cache (db.GetCachedURL, workspaces.url column) are now validated via validateAgentURL() before any HTTP request is made: - mcpResolveURL (mcp.go): added validateAgentURL() calls on all three return paths (internal cache, Redis cache, DB fallback). - resolveAgentURL (a2a_proxy.go): added validateAgentURL() call before returning agentURL to the A2A dispatcher. validateAgentURL() was extended (registry.go) to resolve DNS hostnames and check each returned IP against the blocklist (private ranges, loopback, cloud-metadata 169.254.0.0/16). "localhost" is allowed by name for local dev. GET /admin/memories/export now applies redactSecrets() to each content field before including it in the JSON response. Pre-SAFE-T1201 memories (stored before redactSecrets was mandatory on writes) no longer leak credentials. POST /admin/memories/import now calls redactSecrets() on content before both the deduplication check and the INSERT. Imported memories with embedded credentials cannot bypass SAFE-T1201 (#838). - admin_memories.go: GET /admin/memories/export + POST /admin/memories/import handler (from PR #1051, with security fixes applied). - admin_memories_test.go: 6 tests covering redactSecrets parity on both endpoints. - registry_test.go: added DNS-lookup test cases for validateAgentURL (F1083). "localhost" allowed by name (preserves existing test); nxdomain blocked. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…atus, Cloudflare Artifacts Day 4 copy drafted
…ion regression (#1470) * fix(F1085): correct rm argument scope in deleteViaEphemeral F1085 regression introduced by PR #1310 (CWE-78 exec-form conversion). Commit 17419dd switched the rm command from: Cmd: []string{"rm", "-rf", "/configs/" + filePath} to: Cmd: []string{"rm", "-rf", "/configs", filePath} The exec-form switch was correct intent (avoids shell string interpolation) but inadvertently changed rm's semantics: when passed as two separate arguments, rm treats both "/configs" AND "filePath" as deletion targets, so it recursively deletes the entire bind-mounted volume AND the file at the container root — instead of just the intended file inside /configs. The fix reverts to the original single-argument form. CWE-78 is still mitigated: validateRelPath runs first and blocks absolute paths and ".." traversal, so concatenating the result into the rm argument cannot escape the /configs bind mount. Also adds container_files_test.go with coverage for validateRelPath: valid relative paths, absolute path rejection, ".." traversal rejection, and dotdot-cleaned paths that are still safe. Fixes molecule-monorepo#1085. Refs: molecule-monorepo#1278 (regression confirmed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: rename validateRelPath→validateTemplateRelPath to match PR #1462 consolidation * fix: revert validateTemplateRelPath→validateRelPath (PR #1462 not merged to staging) * ci: force rebuild to clear stale cache * ci: force fresh rebuild --------- Co-authored-by: Molecule AI CP-BE <cp-be@agents.moleculesai.app> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(workspace): pre-stop serialization for pause/resume (closes #1386) Add a pre-stop hook that captures agent state before container exit and writes a scrubbed snapshot to /configs/.agent_snapshot.json. On restart, the snapshot is loaded and the adapter's restore_state() is called before the A2A server starts. - New lib/pre_stop.py: build_snapshot / write_snapshot / read_snapshot / delete_snapshot + _scrub_value deep-scrubber (uses lib.snapshot_scrub to redact API keys, tokens, and sandbox output before persisting) - BaseAdapter.pre_stop_state(): captures _executor._session_id and recent transcript_lines; overridden by adapters with richer in-memory state - BaseAdapter.restore_state(): stores snapshot fields as adapter attrs for create_executor() to pick up - main.py: calls pre_stop serialization in finally block (after server serves) and restore_state() after adapter setup, before server starts - Added 12 unit tests covering scrub, read/write, adapter integration Co-authored-by: Molecule AI Infra-Runtime-BE <infra-runtime-be@agents.moleculesai.app> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: PM-triggered CI re-run * chore: force Platform(Go) CI run on main — validate go vet clean Triggering platform job explicitly after Python Lint & Test fix (#1431). This ensures go vet runs on the current main HEAD (4675402 pre-stop serialization + f2583c2 ci-trigger). Co-Authored-By: PM <pm@molecule.ai> * feat(e2e): staging full-SaaS workflow — per-run org provision + leak-free teardown Dedicated CI/CD lane that exercises the whole SaaS cross-EC2 shape end to end, against live staging: 1. Accept terms / create org (POST /cp/orgs) — catches ToS gate, slug validation, billing/quota, member insert regressions. 2. Wait for tenant EC2 + cloudflared tunnel + TLS propagation (up to 15 min cold). 3. Provision a parent + child workspace via the tenant URL. 4. Wait both online (exercises the SaaS register + token bootstrap flow fixed in #1364). 5. A2A round-trip on parent — validates the full LLM loop (MCP tools, provider auth, JSON-RPC response shape, proxy SSRF gate). 6. HMA memory write + read — validates awareness namespace + scope routing. 7. Peers + activity smoke — route-registration regression guard. 8. Teardown via DELETE /cp/admin/tenants/:slug + leak assertion — a leaked org at teardown fails CI with exit 4. Why a dedicated workflow (not folded into ci.yml): - ~20 min wall clock per run (EC2 boot is the long pole). Too slow for every PR push. - Needs its own concurrency group (staging has an org-create quota and two overlapping runs would race on slug prefix). - Distinct secret surface (session cookie + admin bearer) — keep it off PR jobs that don't need them. Triggers: push to main (provisioning-critical paths only), PRs on the same paths, manual workflow_dispatch (with runtime + keep_org inputs), and 07:00 UTC nightly cron for drift detection. Belt-and-braces teardown: the script installs an EXIT trap, and the workflow has an always()-step that greps e2e-YYYYMMDD-* orgs created today and force-deletes them via the idempotent admin endpoint. Covers the case where GH cancels the runner before the trap fires. Docs: tests/e2e/STAGING_SAAS_E2E.md — what's covered, how to provision the two required secrets, local-dev notes, cost (~$0.007/run), known gaps (canvas UI + delegation + claude-code). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(e2e): canary + canvas Playwright workflows; delegation mechanics Three additions on top of 187a9bf: 1. Canary (.github/workflows/canary-staging.yml) 30-min cron that runs the full-SaaS harness in E2E_MODE=canary: one hermes workspace + one A2A PONG + teardown. ~8-min wall clock vs ~20-min for the full run. Alerting is self-contained: opens a single 'Canary failing' issue on first failure, comments on subsequent failures (no issue spam), auto-closes the issue on the next green run. Labels: canary-staging, bug. Safety-net teardown step sweeps e2e-YYYYMMDD-canary-* orgs tagged today so a runner cancel can't leak EC2. 2. Canvas Playwright (canvas/e2e/staging-*.ts + playwright.staging.config.ts + .github/workflows/e2e-staging-canvas.yml) staging-setup.ts provisions a fresh org + hermes workspace (same lifecycle as the bash harness, just in TypeScript). staging-tabs.spec.ts clicks through all 13 workspace-panel tabs (chat, activity, details, skills, terminal, config, schedule, channels, files, memory, traces, events, audit) and asserts each renders without crashing and without 'Failed to load' error toasts. Known SaaS gaps (Files empty, Terminal disconnects, Peers 401) are documented in #1369 and whitelisted so they don't fail the test — the gate is 'no hard crash', not 'no issues'. staging-teardown.ts deletes the org via DELETE /cp/admin/tenants/:slug. playwright.staging.config.ts separates staging from local tests so pnpm test in dev doesn't try to provision against staging. Retries=2 and timeouts are longer; workers=1 because the setup provisions one shared workspace. Workflow uploads HTML report + screenshots on failure for 14 days. 3. Delegation mechanics (tests/e2e/test_staging_full_saas.sh section 10) Parent → child proxy test: POST /workspaces/CHILD/a2a with X-Source-Workspace-Id=PARENT and verify the child responds + child activity log captures PARENT as source. Intentionally LLM-free: the mechanics regression is what matters; prompt-driven delegation correctness belongs in canvas-driven tests. Also reorders teardown step to 11/11 since delegation is 10/11. Mode gating: E2E_MODE=canary -> skips child workspace, HMA memory, peers, activity, delegation (steps 6, 9, 10 no-op). Full-lifecycle still runs every piece. Validated both paths via 'bash -n' syntax check after each edit. Secrets requirement unchanged (same two secrets as 187a9bf): MOLECULE_STAGING_SESSION_COOKIE, MOLECULE_STAGING_ADMIN_TOKEN. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(e2e): pivot to admin-bearer-only auth + add sanity self-check workflow Reduces required secret surface from 2 (session cookie + admin token) to 1 (admin token). Pairs with molecule-controlplane#202 which adds: - POST /cp/admin/orgs — server-to-server org creation - GET /cp/admin/orgs/:slug/admin-token — per-tenant bearer fetch With those endpoints live, CI doesn't need to scrape a browser WorkOS session cookie. CP admin bearer (Railway CP_ADMIN_API_TOKEN) drives provision + tenant-token retrieval + teardown through a single credential. Changes ------- test_staging_full_saas.sh: admin bearer for provision/teardown, fetched per-tenant token drives all tenant API calls. Added E2E_INTENTIONAL_FAILURE=1 toggle that poisons the tenant token after provisioning so the teardown path gets exercised when the happy-path isn't. canvas/e2e/staging-setup.ts: same pivot; exports STAGING_TENANT_TOKEN instead of STAGING_SESSION_COOKIE. canvas/e2e/staging-tabs.spec.ts: context.setExtraHTTPHeaders with Authorization: Bearer on every page request, no cookie handling. All three workflows (e2e-staging-saas, canary-staging, e2e-staging-canvas): drop MOLECULE_STAGING_SESSION_COOKIE env + verification step. One secret to set. NEW e2e-staging-sanity.yml: weekly Mon 06:00 UTC. Runs the harness with E2E_INTENTIONAL_FAILURE=1 and inverts the pass condition — rc=1 is green, rc=0 (unexpected success) or rc=4 (leak) open a priority-high issue labelled e2e-safety-net. This is the answer to 'how do we know the teardown path still works when nothing else has failed recently.' STAGING_SAAS_E2E.md refreshed: single-secret setup, sanity workflow documented, canvas workflow added to the coverage matrix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(e2e): CP DELETE /cp/admin/tenants body uses 'confirm', not 'confirm_token' Verified against live staging: the admin endpoint returns 400 'confirm field must equal the URL slug' when the body key is 'confirm_token'. Every workflow's safety-net teardown step + the main harness + the Playwright teardown all had the wrong key. Fixed all six call sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(e2e): poll instance_status not status in staging harness /cp/admin/orgs exposes `instance_status` (COALESCE'd from org_instances.status), NOT a top-level `status` field. The harness polled the wrong field and always read empty → timed out at 15min on a tenant that had actually provisioned successfully (confirmed 2026-04-21T14:22Z: EC2 launched, canary ok, but harness never saw status=running). No code change to the admin API — the field has never been named `status`. The harness just had a typo that happened to type-check (the Go struct hasn't changed, only the sh/py polling was wrong). Now the harness correctly reads `instance_status` and the main provision poll loop terminates on the expected transition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(e2e): derive tenant domain from CP URL (staging vs prod) Previous hardcode `$SLUG.moleculesai.app` only matched prod. Staging tenants live at `$SLUG.staging.moleculesai.app`, so the harness hit DNS for a nonexistent host and timed out at section 4 even after provisioning succeeded. Derive from CP URL: api.X → X, staging-api.X → staging.X. Override via MOLECULE_TENANT_DOMAIN for self-hosted setups. Confirmed gap on manual run 2026-04-21T14:40Z: section 2 passed in 2min but section 4 timed out at 3min on the wrong hostname. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(e2e): send X-Molecule-Org-Id on tenant calls TenantGuard middleware on the tenant platform returns 404 (not 403, by design — avoid leaking tenant existence to org scanners) when requests lack X-Molecule-Org-Id matching MOLECULE_ORG_ID. Harness hit this on POST /workspaces (section 5) despite having a valid Authorization bearer. - Capture org_id from admin-create response - Send X-Molecule-Org-Id on every tenant_call Confirmed via manual repro 2026-04-21T14:56Z: curl with Bearer but no org-id header → 404; with both headers → expected route reached. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(e2e): safety-net teardown only sweeps this run's orgs Previously matched every e2e-YYYYMMDD-* slug, which stomped parallel CI runs AND manual dev probes against staging. Incident 2026-04-21 15:02Z: this workflow's safety net deleted an unrelated manual tenant 1s after it hit 'running', timing out the dev run at 15min. Scope to f'e2e-{today}-{GITHUB_RUN_ID}-' so each run only cleans its own leftovers. Empty run_id (local invocation) keeps the old broader behaviour so dev safety-nets still sweep. Also fix: the previous filter used o.get('status') which doesn't exist on the admin API response. Now reads instance_status (the real field). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(tenant-image): remove node user so canvas uid 1000 can be created node:20-alpine ships with a `node` user at uid/gid 1000. The Dockerfile tried `addgroup -g 1000 canvas` which fails with exit 1 because 1000 is already taken. Publish-workspace-server-image workflow has been red for hours — tenant image :latest stuck on a digest that predates the X-Molecule-Admin-Token CPProvisioner fix. Staging workspace provisioning 401'd because the stale tenant binary never sent the admin header. Delete node user+group first (tolerant of future base-image changes that might not ship it), then create canvas at 1000/1000 as before. Mounted volumes continue to expect uid 1000. Repro: publish-workspace-server-image workflow run 24731870797: "process addgroup -g 1000 canvas && adduser... exit code: 1". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * research: add crewai-competitive-proof-points-brief.md * research: add enterprise-case-study-legal-clearance-brief.md * research: add enterprise-case-study-pipeline-targeting-brief.md * fix(P0): CWE-22 path traversal in copyFilesToContainer + ContextMenu test Issue #1434 — CWE-22 Path Traversal Regression: PR #1280 (dc21821) correctly used cleaned path in tar header. PR #1363 (e9615af) regressed to using uncleaned `name`. Fix: use `clean` in filepath.Join AND add defence-in-depth escape check. Issue #1422 — ContextMenu Test Regression: PR #1340 expanded pendingDelete store type to include `children:[]`. Test assertion missing the field — add `children:[]` to match. Note: ssrf.go created (shared isSafeURL/isPrivateOrMetadataIP) to prepare for the handler-split refactor fix — current branch has no build error, but the shared file will prevent regression when PR #1363 is merged. isSafeURL/isPrivateOrMetadataIP retained in both files for now to avoid breaking callers while the split is finalized. Co-authored-by: Molecule AI Core-BE <core-be@agents.moleculesai.app> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(core): resolve main build — remove duplicate SSRF function declarations Build on origin/main (38e9eba) will fail go build with duplicate function declarations: ssrf.go:15 isSafeURL redeclared (a2a_proxy.go:741) ssrf.go:58 isPrivateOrMetadataIP redeclared (a2a_proxy.go:795) ssrf.go:84 validateRelPath redeclared (templates.go:65) a2a_proxy.go:14 "fmt" imported and not used Root cause: main was fast-forwarded to a CWE-22 fix commit that incorporated ssrf.go from the staging handler-split (PR #1457), but ssrf.go declares isSafeURL/isPrivateOrMetadataIP that already exist in a2a_proxy.go, and validateRelPath that already exists in templates.go. Fix: - Delete ssrf.go entirely — its isSafeURL/isPrivateOrMetadataIP are already in a2a_proxy.go; its validateRelPath is in templates.go. - Remove unused "fmt" import from a2a_proxy.go. - Add t.Setenv cleanup in TestIsPrivateOrMetadataIP and TestIsSafeURL so MOLECULE_DEPLOY_MODE=saas from TestIsPrivateOrMetadataIP_SaaSMode cannot leak into sibling tests. - Update stale file-location comments in ssrf_test.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(P0): CWE-22 path traversal in copyFilesToContainer + ContextMenu test Issue #1434 — CWE-22 Path Traversal Regression: PR #1280 (dc21821) correctly used cleaned path in tar header. PR #1363 (e9615af) regressed to using uncleaned `name`. Fix: use `clean` in filepath.Join AND add defence-in-depth escape check. Issue #1422 — ContextMenu Test Regression: PR #1340 expanded pendingDelete store type to include `children:[]`. Test assertion missing the field — add `children:[]` to match. Note: ssrf.go created (shared isSafeURL/isPrivateOrMetadataIP) to prepare for the handler-split refactor fix — current branch has no build error, but the shared file will prevent regression when PR #1363 is merged. isSafeURL/isPrivateOrMetadataIP retained in both files for now to avoid breaking callers while the split is finalized. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: molecule-ai[bot] <276602405+molecule-ai[bot]@users.noreply.github.com> Co-authored-by: Molecule AI Infra-Runtime-BE <infra-runtime-be@agents.moleculesai.app> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: PM <pm@molecule.ai> Co-authored-by: Hongming Wang <hongmingwang.rabbit@users.noreply.github.com> Co-authored-by: Molecule AI Core-BE <core-be@agents.moleculesai.app> Co-authored-by: Molecule AI Core Platform Lead <core-platform-lead@agents.moleculesai.app>
…1495) After a successful DB INSERT in toolCommitMemory, call LogActivity with activity_type=memory_write so Canvas Agent Comms tab shows memory writes. GH#1490: commit_memory calls were not surfacing in activity_logs despite the Report endpoint accepting activity_type=memory_write (added in #125). Root cause: toolCommitMemory inserted to DB but never called LogActivity. Co-authored-by: molecule-ai[bot] <276602405+molecule-ai[bot]@users.noreply.github.com>
- Add role="dialog" aria-modal="true" aria-labelledby to modal container
- Add id="missing-keys-title" to modal heading for aria-labelledby
- Add requestAnimationFrame focus management via useRef (replaces
autoFocus={index===0} on first input — more reliable)
- Remove redundant aria-describedby={undefined} from CreateWorkspaceDialog
…cts social copy Per PMM ruling: replace "sub-100ms clone times from anywhere" with "low-latency access wherever your agents run" — no citable source for the 100ms figure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… cross-links * seo: robots.txt, sitemap.ts, deploy-anywhere fixes, campaign briefs - canvas/public/robots.txt: created (allow all, point to sitemap.xml) - canvas/src/app/sitemap.ts: created Next.js 15 MetadataRoute sitemap (scaffolded, blog entries pending DevRel) - docs/blog/2026-04-17-deploy-anywhere: added canonical, og_* fields, twitter_card, author, keywords frontmatter; added 5 heading anchor IDs - docs/marketing/briefs/2026-04-21-chrome-devtools-mcp-seo-audit.md: full audit with P0/P1 keyword gaps, fixes applied, Lighthouse checklist - docs/marketing/briefs/2026-04-21-chrome-devtools-mcp-content-brief.md: complete brief for Content Marketer (frontmatter, headings, keywords, outline, JSON-LD) - docs/marketing/seo/chrome-devtools-mcp-seo-brief.md: keyword gap analysis, recommended structure, internal link strategy Note: Chrome DevTools MCP blog post files still pending Content Marketer commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(docs): Chrome DevTools MCP blog post — SEO content brief fulfilled - Full MDX blog post at docs/blog/2026-04-20-chrome-devtools-mcp/index.mdx - Keyword targets met: Chrome DevTools MCP 21×, AI agent browser control 4×, MCP browser automation 6×, browser automation governance 4×, browser automation 10× - SEO frontmatter: og_title, og_description, og_image, twitter_card, canonical, keywords, author — all deploy-anywhere compliant - JSON-LD Article schema - Heading hierarchy: H1 + 6 H2s with anchor IDs - Internal links: /docs/guides/mcp-server-setup, /docs/quickstart, /docs/architecture/architecture - External links: modelcontextprotocol.io, chromedevtools.github.io/devtools-protocol/ - Code sample: browser_actions MCP tool pattern - Canvas route component at canvas/src/app/blog/2026-04-20-chrome-devtools-mcp/page.tsx - generateMetadata() with OG + Twitter card support Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * seo: interlinks, canonical fix, MCP server list keyword brief - docs/marketing/seo/mcp-server-list-keywords.md: keyword research + content plan for MCP server list explainer (issue #1493) - docs/blog/2026-04-17-deploy-anywhere/index.md: add cross-link to MCP server list post - docs/blog/2026-04-20-chrome-devtools-mcp/index.mdx: canonical URL corrected to actual live slug (browser-automation-ai-agents-mcp); JSON-LD @id updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(docs): MCP server list blog post + sitemap entries - docs/blog/2026-04-20-mcp-server-list/index.mdx (329 lines) Keyword targets met: MCP server list 8×, MCP servers 23×, Model Context Protocol 8×, MCP server 47×, MCP integration 2×, server framework 6×, reference servers 4× JSON-LD: Article + FAQPage schema Heading hierarchy: H1 + 8 H2s with anchor IDs Internal links: chrome-devtools-mcp, mcp-server-setup, quickstart, architecture; cross-links from deploy-anywhere External links: modelcontextprotocol.io, github.com/modelcontextprotocol/servers - canvas/src/app/blog/2026-04-20-mcp-server-list/page.tsx (generateMetadata() with OG + Twitter card support) - canvas/src/app/sitemap.ts: blog entries added for deploy-anywhere, browser-automation-ai-agents-mcp, mcp-server-list Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docs): correct canonical URL in mcp-server-list blog post moleculesai.app → molecule.ai in frontmatter and JSON-LD mainEntityOfPage @id. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * brand(img): OG images for MCP server list + Chrome DevTools MCP blog posts Adds 1200x630 dark-theme OG images: - 2026-04-21-mcp-server-list-og.png (MCP Server List reference guide) - 2026-04-21-chrome-devtools-mcp-og.png (Chrome DevTools MCP enterprise governance) - 2026-04-17-deploy-anywhere-og.png (Deploy Anywhere / Fly.io) Co-Authored-By: Molecule AI Social Media Brand <social-media-brand@agents.moleculesai.app> --------- Co-authored-by: Molecule AI SEO Analyst <seo-analyst@agents.moleculesai.app> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Molecule AI Social Media Brand <social-media-brand@agents.moleculesai.app>
- Delete workspace-server/internal/handlers/ssrf.go: isSafeURL, isPrivateOrMetadataIP, validateRelPath, mcpResolveURL, extractA2AText are all already defined in a2a_proxy_helpers.go / templates.go / mcp.go - Remove duplicate sensitiveUpdateFields var and Update() handler from workspace.go: the canonical definitions live in workspace_crud.go - Remove dangling conflict marker from end of workspace.go (>>>>>> b9bddf5) Co-authored-by: Molecule AI Core-DevOps <core-devops@agents.moleculesai.app>
fix(canvas/a11y): MissingKeysModal dialog semantics + focus management
- docs/assets/blog/2026-04-21-mcp-server-list-og.png (1200x630) - docs/assets/blog/2026-04-21-chrome-devtools-mcp-og.png (1200x630)
F1102: mcpResolveURL and extractA2AText were defined in both mcp.go and mcp_tools.go, causing a Go build failure (duplicate symbol declarations). Keep the canonical definitions in mcp_tools.go (logical home for MCP tool helpers); remove the duplicate copies from mcp.go. Callers in mcp.go resolve against mcp_tools.go definitions via same-package lookup — no import changes needed. --- Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced Apr 22, 2026
| out = _output_path(tmp_path) | ||
|
|
||
| generate_agents_md(config_dir, out) | ||
| content = open(out, encoding="utf-8").read() |
| out = _output_path(tmp_path) | ||
|
|
||
| generate_agents_md(config_dir, out) | ||
| content = open(out, encoding="utf-8").read() |
| if orig is not None: | ||
| os.environ["AGENT_URL"] = orig | ||
|
|
||
| content = open(out, encoding="utf-8").read() |
| if orig is not None: | ||
| os.environ["AGENT_URL"] = orig | ||
|
|
||
| content = open(out, encoding="utf-8").read() |
| out = _output_path(tmp_path) | ||
|
|
||
| generate_agents_md(config_dir, out) | ||
| content = open(out, encoding="utf-8").read() |
| out = _output_path(tmp_path) | ||
|
|
||
| generate_agents_md(config_dir, out) | ||
| content = open(out, encoding="utf-8").read() |
| out = _output_path(tmp_path) | ||
|
|
||
| generate_agents_md(config_dir, out) | ||
| content = open(out, encoding="utf-8").read() |
| out = _output_path(tmp_path) | ||
|
|
||
| generate_agents_md(config_dir, out) | ||
| content = open(out, encoding="utf-8").read() |
| out = _output_path(tmp_path) | ||
|
|
||
| generate_agents_md(config_dir, out) | ||
| content_v1 = open(out, encoding="utf-8").read() |
| ) | ||
|
|
||
| generate_agents_md(config_dir, out) | ||
| content_v2 = open(out, encoding="utf-8").read() |
|
|
||
| // Cascade guard: include child count in the warning message when the workspace | ||
| // has children, so the user understands the blast radius before clicking Delete All. | ||
| const cascadeMessage = pendingDelete?.hasChildren |
| * 2. A2ATopologyOverlay component — side-effect behavior (API + store mocks) | ||
| */ | ||
| import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; | ||
| import { render, cleanup, waitFor, act } from "@testing-library/react"; |
| * entry list, expand, delete flow, optimistic updates, Refresh, semantic search. | ||
| */ | ||
| import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; | ||
| import { render, screen, fireEvent, waitFor, cleanup, act } from "@testing-library/react"; |
| // ── Import component under test AFTER all mocks ────────────────────────────── | ||
| import { SidePanel } from "../SidePanel"; | ||
|
|
||
| const TABS = [ |
| /** | ||
| * Tests for the Z keyboard shortcut (zoom-to-team) and help panel entry. | ||
| */ | ||
| import React from "react"; |
| * Tests for the Z keyboard shortcut (zoom-to-team) and help panel entry. | ||
| */ | ||
| import React from "react"; | ||
| import { render, screen, fireEvent, cleanup } from "@testing-library/react"; |
2 tasks
molecule-ai
Bot
force-pushed
the
staging
branch
from
April 22, 2026 10:24
9f34747 to
aae0c81
Compare
Contributor
Author
molecule-ai Bot
added a commit
that referenced
this pull request
Apr 22, 2026
* docs(marketing): update EC2 Instance Connect SSH brief to positioning version * fix(CI): pin golangci-lint-action to v6 + migrate jobs to ubuntu-latest Two independent CI improvements: 1. golangci-lint-action: @v9 does not exist — use @v6 (the official latest). Pin linter CLI to v2.1.11 instead of 'latest' so new lint findings don't silently accumulate behind continue-on-error. Add TODO to track lint debt removal (#1558). 2. Runner relief: move 3 platform-neutral jobs off the self-hosted macOS arm64 runner onto ubuntu-latest. The runner is a bottleneck for all CI work; these jobs have no macOS dependency: - shellcheck: pure shell linting; install via apt-get - python-lint: use actions/setup-python@v5 (Python 3.11) - canvas-deploy-reminder: only calls gh api Self-hosted runner is now reserved for: - platform-build (Go): needed for go build / go test -race - canvas-build (Next.js): may need macOS native modules Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(CI): migrate changes + CodeQL to ubuntu-latest — full runner relief Migrate two more jobs off the self-hosted macOS arm64 runner: - ci.yml 'changes' job: plain `git diff` has no macOS dependency — move to ubuntu-latest. Update outdated comment. - codeql.yml 'analyze' job: CodeQL analysis is language-agnostic and runs fine on ubuntu. Replace `brew install jq` with `apt-get install jq`. Self-hosted mac mini now runs only: - platform-build (Go -race detector uses cgo) - canvas-build (Next.js native macOS modules) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(CI): migrate canary-verify to ubuntu-latest — full runner relief Both jobs in canary-verify.yml need only curl + crane CLI, neither requires macOS: - canary-smoke: HTTP health checks + bash smoke script → ubuntu-latest (polls until all canaries report expected SHA, exits early vs fixed sleep) - promote-to-latest: `crane` retag (no Docker daemon) → ubuntu-latest; install crane from go-containerregistry releases (x86_64 Linux, v0.20.2) Self-hosted mac mini is now reserved exclusively for: - platform-build (Go -race needs cgo) - canvas-build (Next.js native macOS modules) - e2e-api (docker run for postgres/redis) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(CI): migrate publish + promote workflows to ubuntu-latest - publish-workspace-server-image.yml: macOS → ubuntu-latest (QEMU/Docker Buildx work fine on ubuntu, no macOS-specific steps) - publish-canvas-image.yml: macOS → ubuntu-latest (same rationale; auth write done entirely via config.json, no Keychain) - promote-latest.yml: macOS → ubuntu-latest (crane curl/tarball install replaces brew install; GITHUB_TOKEN sufficient for remote retag without Keychain) mac mini now carries exactly 3 jobs: platform-build (Go -race), canvas-build (Next.js), e2e-api (Docker) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(CI): upgrade golangci-lint-action to v7 (v6 dropped v2 support) v6 does not support golangci-lint v2 CLI; v7 is the version that requires and supports v2. v2.1.11 is still pinned on the CLI side. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(canvas/TypeScript): null-coalesce budget_used in progress calculation Regression from stuck-provisioning fix: budget_used is typed as optional (number | undefined) but used without ?? 0 in the progress percentage formula, causing a strict TypeScript build error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(Go): close if r := recover() block — dangling } after security removal The CWE-22 security commits on staging removed the panic-recovery ComputeNextRun/ExecContext blocks from the two defer/recover handlers in scheduler.go but left the outer if-block unclosed, creating a syntax error: 'unexpected ( at end of statement' (fireSchedule) and 'assignment mismatch' (bundle/importer.go missing _ = discard). This is the pre-existing Go build break that blocked CI runs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: re-trigger CI for latest merge state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: molecule-ai[bot] <276602405+molecule-ai[bot]@users.noreply.github.com> Co-authored-by: Molecule AI Core-DevOps <core-devops@agents.moleculesai.app> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
Author
|
|
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.
F1102 — Fix duplicate Go symbol declarations
Severity: MEDIUM — blocks Go compilation when platform-build step runs.
Problem:
mcpResolveURLandextractA2ATextwere defined in BOTH:workspace-server/internal/handlers/mcp.go:854andworkspace-server/internal/handlers/mcp.go:897workspace-server/internal/handlers/mcp_tools.go:476andworkspace-server/internal/handlers/mcp_tools.go:519go build ./cmd/serverfails with duplicate symbol declarations.Fix: Keep the canonical definitions in
mcp_tools.go(logical home for MCP tool helpers). Delete the duplicate copies frommcp.go. Callers inmcp.goresolve via same-package lookup — no import changes needed.Files changed:
workspace-server/internal/handlers/mcp.go(−101 lines)Verification: Branch pushed. CI will confirm build passes.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com