Commit Graph

2887 Commits

Author SHA1 Message Date
Hongming Wang
184f8256cd ci(redeploy): fire post-main tenant fleet redeploy via CP admin endpoint
Closes the "main merged but prod tenants still on old image" gap.

## Trigger chain

  main merge
   └─> publish-workspace-server-image (builds + pushes :latest + :<sha>)
        └─> redeploy-tenants-on-main (this workflow)
             └─> POST https://api.moleculesai.app/cp/admin/tenants/redeploy-fleet
                  └─> Canary hongmingwang + 60s soak, then batches of 3
                       with SSM Run Command redeploying each tenant EC2

## Features

- Auto-fires on every successful publish-workspace-server-image run.
- Manual dispatch with optional target_tag (for rollback to an older
  SHA), canary_slug override, batch_size, dry_run.
- 30s delay before calling CP so GHCR edge cache serves the new
  :latest consistently to every tenant's docker pull.
- Skips when publish job failed (workflow_run fires on any completion).
- Job summary renders per-tenant results as a markdown table so ops
  can see which tenant, if any, broke the chain.
- Exits non-zero on HTTP != 200 or ok=false so a broken rollout marks
  the commit status red.

## Secrets + vars required

- secret CP_ADMIN_API_TOKEN  — Railway prod molecule-platform / CP_ADMIN_API_TOKEN
                               Mirrored into this repo's secrets.
- var    CP_URL (optional)   — defaults to https://api.moleculesai.app

## Paired with

- Molecule-AI/molecule-controlplane branch feat/tenant-auto-redeploy
  which adds the /cp/admin/tenants/redeploy-fleet endpoint + the SSM
  orchestration. This workflow is a no-op until that lands on prod CP.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 14:34:28 -07:00
Hongming Wang
a59f1a6ce4
Merge pull request #2036 from Molecule-AI/sync/staging-to-main-2026-04-24-final
chore: promote sync-to-main-final → main (finish #1981)
2026-04-24 11:00:41 -07:00
molecule-ai[bot]
fa1536e2f8
chore: sync staging to main — 2026-04-24 04h (71 commits)
chore: sync staging to main — 2026-04-24 04h (71 commits)
2026-04-24 17:13:22 +00:00
ca7fa3b65e fix(e2e): increase hermes workspace wait from 20 to 30 min
Root cause of PR #1981 E2E failures (step 7 timeout):
- hermes-agent install from NousResearch (Node 22 tarball + Python
  deps from source) + gateway health wait takes 15-25 min on staging
2026-04-24 17:11:37 +00:00
2e92152c34 fix(e2e): increase hermes workspace wait from 20 to 30 min
Root cause of PR #1981 E2E failures (step 7 timeout):
- hermes-agent install from NousResearch (Node 22 tarball + Python
  deps from source) + gateway health wait takes 15-25 min on staging
- install.sh runs BEFORE molecule-runtime launches, blocking heartbeats
- bootstrap-watcher fires at 5 min (cp#245) → workspace=failed
- workspace never recovers because molecule-runtime never starts in time

Fix: increase WS_DEADLINE from 1200s (20 min) to 1800s (30 min) to
give hermes cold-boot enough runway. Also bump job timeout-minutes
from 30 → 45 to accommodate the longer wait.

Medium-term: fix cp#245 (bootstrap-watcher hermes deadline too short)
in molecule-controlplane to reduce false-failed noise.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 14:12:40 +00:00
Hongming Wang
6b62391e5d
Merge pull request #1989 from Molecule-AI/fix/canvas-a11y-final
fix(canvas/a11y): type=button campaign + aria fixes (batch 1-3)
2026-04-24 14:05:27 +00:00
Hongming Wang
cb2bfe1c6d
Merge pull request #2012 from Molecule-AI/test/a2a-queue-phase1-regression-tests
test(handlers): regression tests for A2A queue Phase 1 (#1870)
2026-04-24 13:52:21 +00:00
c63810939c test(handlers): fix A2A queue drain tests — all pass locally
Two changes:

1. a2a_proxy.go: non-2xx agent responses now return a proxyErr so
   DrainQueueForWorkspace calls MarkQueueItemFailed (not silently
   marking completed). Previously, agent 5xx responses returned
   (status, body, nil) and DrainQueueForWorkspace's final fallback
   called MarkQueueItemCompleted for anything not 202/proxyErr.
   Also extracts error string from JSON response body before
   falling back to http.StatusText.

2. a2a_queue_test.go: fixes for broken queue drain tests:
   - Switch to QueryMatcherEqual (exact string) from MatchSs (v1.5.2
     API: QueryMatcherOption(QueryMatcherEqual))
   - Add github.com/Molecule-AI/molecule-monorepo/platform/internal/db import
   - drainSetup(t, workspaceID): registers budget-check expectation
     via expectQueueBudgetCheck helper; callers call it AFTER
     expectDequeueNextOk (DequeueNext runs before proxyA2ARequest)
   - drainItem: use NULL CallerID so CanCommunicate is skipped
     (avoids needing hierarchy mocks)
   - add allowLoopbackForTest() so httptest.Server URLs pass SSRF guard
   - Sequential claim-guarding test instead of concurrent goroutine
     (sqlmock is not goroutine-safe for ordered expectations)

Also adds the nil-safe error extraction regression tests from
the original PR #2012 test plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 13:47:27 +00:00
9029b1bc24 test(handlers): add DB mock + nil-safe regression tests for A2A queue Phase 1
Extends the skeletal a2a_queue_test.go from PR #1892 with:
- sqlmock-based tests for EnqueueA2A idempotency (ON CONFLICT DO NOTHING)
- Tests for DequeueNext (SELECT FOR UPDATE SKIP LOCKED, FIFO/priority order)
- Tests for MarkQueueItemCompleted and MarkQueueItemFailed (attempt bounding)
- DrainQueueForWorkspace nil-safe error extraction regression test: the
  unchecked proxyErr.Response["error"].(string) type assertion in the
  original Phase 1 caused a panic when the "error" key was absent or
  non-string (GH incident). This test pins the defensive .(string)
  guard and the fallback to http.StatusText.
- Priority constant ordering sanity checks.
- extractIdempotencyKey edge cases: malformed JSON, missing fields,
  empty messageId, and the successful messageId extraction path.

Uses alicebob/miniredis for Redis setup matching the existing
setupTestRedis pattern in this package.
2026-04-24 13:05:02 +00:00
Hongming Wang
bf62a68fef
Merge pull request #1774 from Molecule-AI/fix/orgtoken-mocks-clean
fix: sync orgtoken.Validate mocks to 3-column scan pattern
2026-04-24 13:04:08 +00:00
Molecule AI Core Platform Lead
a053f67ddf test(middleware): add last_used_at ExpectExec for WorkspaceAuth org-token tests
orgtoken.Validate() runs a synchronous UPDATE org_api_tokens SET
last_used_at after every successful auth scan. Tests were missing the
sqlmock ExpectExec for this call — the code discards the error
(_, _ = ExecContext) so CI passed, but ExpectationsWereMet() could
not detect a regression where the UPDATE was accidentally removed.

Adds strict mock expectations for all four WorkspaceAuth+org-token
test cases: SetsOrgIDContext, OrgIDNULL_DoesNotSetContext,
DBRowScanError_DoesNotPanic, and SetsAllContextKeys.

Fixes: GH#1774

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 13:01:42 +00:00
Molecule AI Core Platform Lead
4db7f6f024 fix(canvas): define MAX_NESTING_DEPTH constant in WorkspaceNode.tsx
TeamMemberChip used MAX_NESTING_DEPTH to cap recursive sub-agent
rendering at depth 3, but the constant was never declared — causing
a TypeScript build error ('Cannot find name MAX_NESTING_DEPTH') that
blocked Canvas CI on PR #1989.

Add the constant above EmbeddedTeam with a doc comment explaining its
purpose (guards against circular parentId cycles + readability cap).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:52:28 +00:00
Hongming Wang
df51ddc45e
Merge pull request #2014 from Molecule-AI/fix/cwe78-templates-deleteFile-sharedContext
fix(handlers): CWE-78 hardening for DeleteFile and SharedContext
2026-04-24 12:48:56 +00:00
Hongming Wang
a539cec592
Merge pull request #2015 from Molecule-AI/fix/canvas-a11y-tab-buttons
fix(canvas/a11y): add type=button to 24 buttons across DetailsTab, ConfigTab, FilesTab, MemoryTab
2026-04-24 12:48:54 +00:00
0cfba19c84 fix(test): TestDeleteFile_WorkspaceNotFound uses relative path "old-file.txt"
The test was passing "/old-file.txt" (with leading slash) which now triggers
the filepath.IsAbs guard in DeleteFile before the DB lookup, returning 400
instead of the expected 404. Use a relative path so the DB lookup is reached.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:45:29 +00:00
9f52ee1777 fix(canvas/WorkspaceNode.tsx): add missing useMemo import
CI failure: "Cannot find name 'useMemo'" at line 363.
useMemo was called but not imported — likely dropped during refactor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:40:52 +00:00
6a96641c37 fix(canvas/a11y): add type="button" to remaining canvas component buttons (batch 3)
WCAG 4.1.2 / bug #1669 follow-up — final batch completing the campaign.
Added type="button" to all buttons missing it across 14 canvas components.

Files changed (14, all additions):
- Toolbar.tsx: Stop All, Restart All, A2A toggle, Audit shortcut, Quick help, Search shortcut, Help close (7)
- MemoryInspectorPanel.tsx: scope tabs, refresh, search clear ×2, expand, delete (6)
- TemplatePalette.tsx: org refresh, toggle, Import Agent, org import, deploy template, palette refresh (6)
- ProvisioningTimeout.tsx: Retry, Cancel Request, View Logs, Keep, Remove Workspace (5)
- ConsoleModal.tsx: close, Copy output, Close (3)
- OnboardingWizard.tsx: Skip guide, action, Next (3)
- ConversationTraceModal.tsx: close ×2 (2)
- WorkspaceNode.tsx: Restart banner, Extract from team (2)
- CommunicationOverlay.tsx: toggle, close panel (2)
- Toaster.tsx: dismiss ×2 (2)
- SearchDialog.tsx: search result button (1)
- TermsGate.tsx: accept (1)
- ErrorBoundary.tsx: Reload (1)
- BundleDropZone.tsx: import trigger (1)

Total campaign (batches 1-3): 27 + 42 = 69 buttons fixed across 24 components.
All 477 canvas vitest tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:40:52 +00:00
32a3b84147 fix(canvas/a11y): add type="button" to MissingKeysModal, ContextMenu, CreateWorkspaceDialog tier radio
WCAG 4.1.2 / bug #1669 follow-up — modal + menu buttons need explicit type="button".

- MissingKeysModal.tsx: Save, Open Settings Panel, Cancel Deploy, Add Keys+Deploy (4)
- ContextMenu.tsx: all menuitem buttons (1 — inner menu items loop)
- CreateWorkspaceDialog.tsx: tier radio buttons in dialog (1)

56 vitest tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:40:52 +00:00
e14b6d2de4 fix(canvas/a11y): add type="button" to BatchActionBar, EmptyState, SidePanel, CreateWorkspaceDialog
WCAG 4.1.2 / bug #1669 follow-up — buttons without explicit type="button"
default to type="submit", risking accidental form submission.

Added type="button" to all action buttons in:
- BatchActionBar.tsx: Restart All, Pause All, Delete All, Clear Selection (4)
- EmptyState.tsx: template deploy buttons + Create blank (all)
- SidePanel.tsx: close panel, tab switches, Restart Now (3)
- CreateWorkspaceDialog.tsx: open trigger, Cancel, Create (3)

Total this commit: +12 insertions / 2 deletions across 4 files.
Prior commit (c5590c0c): ConfirmDialog + AuditTrailPanel + DeleteCascadeConfirmDialog (+7).
Combined batch: 19 buttons fixed across 7 components.

86 vitest tests pass across all touched test files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:40:52 +00:00
2ff15a38a8 fix(canvas/a11y): add type="button" to ConfirmDialog, AuditTrailPanel, DeleteCascadeConfirmDialog
WCAG 4.1.2 / bug #1669 follow-up — buttons without explicit type="button"
default to type="submit", which triggers accidental form submission when
the button is rendered inside a <form> element.

Added type="button" to all action buttons in:
- ConfirmDialog.tsx: Cancel + confirm buttons (lines 123, 130)
- DeleteCascadeConfirmDialog.tsx: Cancel + Delete All buttons (lines 145, 151)
- AuditTrailPanel.tsx: filter buttons, refresh, load-more (lines 140, 154, 194)

All 51 component tests pass (5 ConfirmDialog, 46 AuditTrailPanel+DeleteCascadeConfirmDialog).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:40:52 +00:00
e355f447bb fix(canvas/a11y): add aria-hidden to 6 decorative SVGs + aria-label to OrgTokensTab input
WCAG 1.3.1 — inputs without visible text labels need aria-label.
WCAG 4.1.2 — decorative SVGs inside interactive elements need
aria-hidden so screen readers ignore icon content.

Changes:
- ErrorBoundary: warning triangle SVG — aria-hidden=true
- Toolbar: 4 decorative SVGs — aria-hidden=true
  (Stop All square, Restart Pending arrow, Search magnifier, Help circle)
- SettingsButton: gear icon SVG — aria-hidden=true (parent has aria-label)
- RevealToggle: EyeIcon + EyeOffIcon SVGs — aria-hidden=true
- OrgTokensTab: name input — aria-label="Organization API key label"

Bonus fix: removed duplicate title/aria-label props on Restart All button.

Note: ConsoleModal and DeleteCascadeConfirmDialog do not exist in current
staging (aae0c81) — tab trapping fix inapplicable to this codebase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:40:52 +00:00
59feb65252 fix(canvas/a11y): add type=button to 24 buttons across DetailsTab, ConfigTab, FilesTab, MemoryTab
WCAG 4.1.2 / bug #1669 follow-up — DetailsTab, ConfigTab, FilesTab, and
MemoryTab had buttons without explicit type="button", causing accidental
form submission in any surrounding <form> context.

Changes:
- DetailsTab (9 buttons): Save, Cancel (edit), Restart/Retry, Edit,
  View console output, peer select, Confirm Delete, Cancel (delete), Delete Workspace
- ConfigTab AgentCardSection (3): Save, Cancel, Edit Agent Card
- ConfigTab footer (3): Save & Restart, Save, Reload
- ConfigTab textareas (2): aria-label added to Agent Card JSON editor and Raw YAML editor
- FilesTab (4): Delete All, Cancel, Delete, Cancel
- MemoryTab (11): Expand/Collapse, Open, Expand (collapsed state), Advanced,
  Refresh, Add, Save, Cancel (add form), expand entry, Delete entry, Show

Total: 32 interactive elements corrected across 4 tab components.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:39:43 +00:00
c5da3f1be9 fix(handlers): CWE-78 — reject absolute paths before strip in DeleteFile; drop null_byte test
- Add filepath.IsAbs guard in DeleteFile BEFORE the leading-slash strip so that
  absolute paths like "/etc/passwd" are rejected with 400 rather than silently
  accepted after the prefix is stripped.
- Remove the null_byte sub-case from TestCWE78_DeleteFile_TraversalVariants —
  httptest.NewRequest panics on \x00 in URLs (URL-layer concern, not handler).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:38:28 +00:00
Molecule AI Core Platform Lead
7d837dec74 fix(handlers): CWE-78 hardening for DeleteFile and SharedContext (#2011)
Replace string concatenation with safe exec-form path construction in
two remaining locations in templates.go:

1. DeleteFile (container-running path):
   - Before: `containerPath := "/configs/" + filePath` → `rm -rf containerPath`
   - After:  `rm -f filepath.Join("/configs", filePath)`
   - Also tightens rm flag from -rf to -f (no recursive delete on a file endpoint)

2. SharedContext (container-running path, per-file cat loop):
   - Before: `[]string{"cat", "/configs/" + relPath}`
   - After:  `[]string{"cat", "/configs", relPath}` (separate args, no shell join)

In both cases validateRelPath is already the primary guard (rejects traversal
inputs before reaching exec). filepath.Join / separate args is defence-in-depth
so that a bypass of validateRelPath cannot produce a dangerous concatenated path
in the exec argument list.

ReadFile was already fixed (PR #1885, merged to main at 12:08Z).

Regression tests added:
- TestCWE78_DeleteFile_TraversalVariants: 7 traversal patterns all → 400
- TestCWE78_SharedContext_SkipsTraversalPaths: traversal paths in
  shared_context config are silently skipped, only safe files returned

Fixes: #2011

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:29:57 +00:00
Hongming Wang
4597ab06fc
Merge pull request #2007 from Molecule-AI/fix/cwe22-restart-template
fix(handlers): CWE-22 path traversal in Tier 4 runtime-default template resolution
2026-04-24 12:18:48 +00:00
Hongming Wang
9b3e042fe3
Merge pull request #2010 from Molecule-AI/fix/ci-block-paths-shallow-clone
ci(block-paths): fetch PR base SHA to fix shallow-clone diff failure
2026-04-24 12:18:47 +00:00
Molecule AI Core Platform Lead
5a70659fdc ci(block-paths): fetch PR base SHA to fix shallow-clone diff failure
The checkout uses fetch-depth=2, which works for push events (only need
HEAD^1). But for pull_request events the diff base is
github.event.pull_request.base.sha — the tip of the target branch —
which can be many commits behind and therefore absent from the shallow
clone, producing:

  fatal: bad object <sha>   (exit 128)

Fix: add an explicit `git fetch --depth=1 origin <base-sha>` step that
runs only on pull_request events, keeping push events fast.

Unblocks: PR #1996 (and any other PR targeting a fast-moving staging).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 12:01:53 +00:00
Hongming Wang
fa70ba6ffd
Merge pull request #1996 from Molecule-AI/core-fe-ki005-regression-tests
test(handlers): KI-005 regression suite for terminal.go
2026-04-24 11:58:31 +00:00
Molecule AI Core Platform Lead
47117fbf77 fix(handlers): restore ssrfCheckEnabled after setupTestDB to prevent state leak
`setupTestDB` was calling `setSSRFCheckForTest(false)` without restoring
the previous value, causing all subsequent `TestIsSafeURL_*` tests to run
with SSRF disabled and pass unconditionally — masking real validation
failures.

Replace the fire-and-forget call with a `t.Cleanup(restore)` so the flag
is restored to its original state after each test that calls `setupTestDB`.

Fixes: CI Platform (Go) failures — 20+ TestIsSafeURL_* tests failing on
       core-fe-ki005-regression-tests (PR #1996).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 11:56:21 +00:00
d7901bb831 fix(handlers): apply sanitizeRuntime allowlist before Tier 4 filepath.Join (CWE-22)
CWE-22 path traversal in restartTemplateInput Tier 4: dbRuntime was joined
directly into the template path without sanitisation.

  runtimeTemplate := filepath.Join(configsDir, dbRuntime+"-default")

An attacker holding a workspace token could set runtime to a path-traversal
string (e.g. "../../../etc") via the PATCH /workspaces/:id Update handler,
which only validates length and newlines.  If a matching directory existed
on the host (e.g. /configs/../../../etc-default), the restart would load
files from an arbitrary host path into the workspace container.

Fix: call sanitizeRuntime(dbRuntime) — the existing allowlist in
workspace_provision.go — before filepath.Join.  Unknown values are
remapped to "langgraph", so the attacker cannot choose an arbitrary host
path.  Defense-in-depth: the path is still inside configsDir after
sanitisation.

Regression tests added:
- CWE-22 traversal strings fall through to existing-volume
- langgraph-default is used when traversal string is sanitised to langgraph

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 11:37:19 +00:00
Molecule AI Core Platform Lead
adb9c68185 fix(tests): path validation before docker check + a2a queue mock in tests
- container_files.go: move validateRelPath before h.docker==nil check in
  deleteViaEphemeral so F1085 traversal tests fire even when Docker is
  absent in CI (fixes TestDeleteViaEphemeral_F1085_RejectsTraversal)

- a2a_proxy_test.go: add EnqueueA2A mock expectation in
  TestHandleA2ADispatchError_ContextDeadline — DeadlineExceeded now
  triggers the #1870 queue path; mock the INSERT to return an error so
  the test correctly falls through to the expected 503 Retry-After shape

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 11:07:43 +00:00
Hongming Wang
30d8f0cf36
Merge pull request #2006 from Molecule-AI/fix/canvas-e2e-20min-deadline
fix(canvas/e2e): raise deadline 15→20 min — matches SaaS E2E tolerance
2026-04-24 08:28:16 +00:00
Hongming Wang
46fbffb95b fix(canvas/e2e): raise staging-setup deadline 15 min → 20 min
Matches tests/e2e/test_staging_full_saas.sh's 20-min budget (#1930).
Canvas E2E was still stuck at 900s (15 min) which regularly flakes on
tenant cold boots in 12-15 min range — especially on staging where
workspace-server image pulls + AMI bootstrapping add 3-5 min vs prod.

Concrete blocker: 2026-04-24 staging→main sync (#1981) kept failing on
"tenant provision: timed out after 900s" in canvas/e2e/staging-setup.ts
despite the actual sync E2E going green. Canvas-side timeout was
strictly tighter than the sync-side timeout.

Also raises WORKSPACE_ONLINE_TIMEOUT_MS to 20 min to cover the case
where the workspace EC2 is provisioned but hermes cold-install (apt +
uv + hermes-agent clone + gateway boot) takes longer than the original
10-min budget — matches the 20-min workspace deadline in SaaS E2E.

No behavior change when things are fast. Just covers the tail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 01:26:13 -07:00
Hongming Wang
3770d4d68c
Merge pull request #2005 from Molecule-AI/chore/remove-forbidden-marketing-paths
chore: remove all forbidden marketing paths from staging (unblocks #1981)
2026-04-24 07:58:31 +00:00
Molecule AI App & Docs Lead
561b1c2c0d chore: remove all forbidden marketing/docs/marketing paths from staging
71 files across docs/marketing/ and marketing/ are blocked by the
Block-internal-flavored-paths CI gate (CEO directive 2026-04-23).
These paths must live in Molecule-AI/internal, not the public monorepo.

Unblocks PR #1981 (staging→main sync).

Public-facing blog/devrel content should be re-added via correct paths:
  docs/blog/<slug>.md, docs/devrel/<slug>.md, docs/tutorials/<slug>.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:52:04 +00:00
Hongming Wang
0a70430b5c
Merge pull request #2004 from Molecule-AI/feat/list-templates-loud-on-half-clone
feat(org): log loud when org-template dir is a half-clone
2026-04-24 07:42:10 +00:00
rabbitblood
d0080b0e98 feat(org): log loud when org-template dir is a half-clone
Audit 2026-04-24 case: org-templates/molecule-dev/ contained only .git/
(working tree wiped). ListTemplates silently skipped the directory and
the molecule-dev template silently disappeared from the Canvas palette.
No log trail; CEO discovered hours later when looking for the registry
listing manually.

This commit adds a one-line log warning when a directory under orgDir
has a .git/ subdir but no org.yaml/.yml — that's almost always a manifest
clone that got truncated. The warning includes the recovery command
(`git checkout main -- .`) so operators can self-fix without re-cloning.

Doesn't change the response behavior — the directory is still skipped
to keep ListTemplates a fail-soft endpoint. Just makes the failure
visible in `docker logs platform`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:39:11 -07:00
Hongming Wang
92ce37ae99
Merge pull request #2003 from Molecule-AI/ci/gh-wrapper-identity-shim
ci(gh-wrapper): translate --assignee @me → --label team:<role> (fixes #1957)
2026-04-24 07:36:36 +00:00
Hongming Wang
b5c93cff4f
Merge pull request #2002 from Molecule-AI/ci/merge-group-trigger-linter
ci: linter to catch missing merge_group triggers on required workflows
2026-04-24 07:35:23 +00:00
rabbitblood
7b662d2494 ci(gh-wrapper): translate --assignee @me → --label team:<role>
Fixes #1957. All agents share one PAT, so `gh issue create --assignee @me`
resolves to the CEO. Today's "6 issues @me for 7 cycles" defect signal
turned out to be CEO-load misclassified as team-stagnation.

Translation rules:
- `--assignee @me` → `--label team:<role-slug>`
- `--reviewer @me` → dropped (review-bot scans labels, not requests)
- `--assignee user` (real user) → unchanged

role-slug derived from GIT_AUTHOR_NAME ("Molecule AI Core-BE" → "core-be").
The wrapper already handled the title-prefix + body-footer transforms;
these are just two more cases in the existing arg-walk loop.

Backward compat: any agent prompt that doesn't use @me passes through
unchanged. Agents don't need prompt updates — the wrapper is transparent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:34:21 -07:00
Hongming Wang
3bbcc96bce
Merge pull request #2000 from Molecule-AI/fix/tenant-image-staging-latest-autobump
ci(publish-image): auto-tag :staging-latest so CP picks up new builds
2026-04-24 07:33:12 +00:00
rabbitblood
5ddeca2c0a ci: add linter that fails when required workflow lacks merge_group trigger
Pre-merge guard against the deadlock pattern that hit twice today:
adding a workflow's check to required_status_checks while the workflow
itself doesn't have a `merge_group:` trigger → merge queue stalls
forever in AWAITING_CHECKS because the required check can't fire on
gh-readonly-queue/* refs.

Each time today this happened it cost 30-60min of debug + a hot-fix PR
+ temporary removal of the required check. This workflow runs on every
PR touching .github/workflows/ and on push to staging/main, listing
required checks for staging and verifying each one's owning workflow
declares merge_group.

Self-listens on merge_group so the linter passes its own queue runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:33:05 -07:00
Hongming Wang
24bfced630 ci(publish-image): also tag :staging-latest so CP auto-picks up new builds
Root cause of the 2026-04-24 all-day E2E failure chain: Railway staging
CP had TENANT_IMAGE pinned to :staging-a14cf86 — a static SHA that had
silently drifted 10+ days stale. Every new tenant (including every E2E
run's fresh tenant) was spawned with that stale image, which predated
applyRuntimeModelEnv. Without applyRuntimeModelEnv, HERMES_DEFAULT_MODEL
never reached the workspace EC2 user-data, so install.sh fell back to
nousresearch/hermes-4-70b → openrouter → 401 "Missing Authentication
header" in every A2A reply.

Four correct fixes shipped today all got shadowed by this single stale
pin:
  • template-hermes#19 (provider priority for openai/*)
  • template-hermes#20 (decouple prefix-strip from bridge guard)
  • molecule-controlplane#247 (force fresh /opt/adapter clone)
  • molecule-core#1987 (E2E pins HERMES_CUSTOM_* as workaround)

Fix: publish each main build under both :staging-<sha> AND :staging-latest.
Change Railway staging CP's TENANT_IMAGE env to :staging-latest (done via
`railway variables --set` as part of this incident). Future main builds
then auto-propagate to new tenant provisions without any human in the
loop.

Safety: :staging-latest is the "most recent main build" — NOT a
canary-verified promotion. That distinction is preserved:
  • Prod tenants still pull :latest (canary-verified, retagged by
    canary-verify.yml only after the canary fleet green-lights a digest)
  • Staging tenants now pull :staging-latest (every main build, pre-canary)

So staging becomes the canary: if a :staging-latest build regresses,
the staging canary fleet catches it before it can be promoted to :latest
for prod. This is what the canary design intended; the missing
:staging-latest tag was the hole.

Zero impact on image size / build time: Docker tags point at the same
digest, no duplicate push.

Follow-up: filed an issue tracking the need for CP's TENANT_IMAGE to
NEVER be pinned to a SHA in any environment — it must always float on a
named tag (:staging-latest for staging, :latest for prod).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:29:55 -07:00
Hongming Wang
5f85c7f567
Merge pull request #1997 from Molecule-AI/ci/block-paths-merge-group-trigger
ci: add merge_group trigger to block-internal-paths workflow
2026-04-24 07:21:46 +00:00
Hongming Wang
757337d644
Merge pull request #1613 from Molecule-AI/docs/saas-federation-tutorial
docs(tutorial): SaaS federation — multi-tenant control plane setup
2026-04-24 07:21:39 +00:00
rabbitblood
d9f69a8fd5 ci: add merge_group trigger to block-internal-paths workflow
Re-do of the fix that was originally bundled into PR #1995 but never
landed — the second commit on that branch got rejected by GH006
(branch locked by merge queue) after the first commit was already
queued. Only the file-removal commit made it to staging.

Without this trigger, adding "Block forbidden paths" to
required_status_checks deadlocks the queue: every PR sits in
AWAITING_CHECKS forever waiting on a check that can't fire on
gh-readonly-queue/* refs.

Sequence to land safely:
1. (already done) Removed "Block forbidden paths" from required_status_checks
2. (this PR) Add merge_group trigger
3. (after merge) Re-add "Block forbidden paths" to required_status_checks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 00:19:38 -07:00
9d5115b5db test(handlers): add 5 TestKI005 regression tests to terminal_test.go
Port terminal hierarchy guard regression suite from fix/ki005-terminal-auth:
- TestKI005_SelfAccess_AlwaysAllowed: own workspace token always passes
- TestKI005_CanCommunicatePeer_Allowed: sibling workspace access granted
- TestKI005_CanCommunicateNonPeer_Forbidden: cross-org access blocked (403)
- TestKI005_TokenMismatch_Unauthorized: token/Workspace-ID mismatch blocked (401)
- TestKI005_NoXWorkspaceIDHeader_LegacyAllowed: legacy access no header → proceeds

Refs: F1085, KI-005, PR #1701

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:17:26 +00:00
3c401ab913 fix(handlers): add empty/dot-only path guard to validateRelPath
Tech-Researcher conditional approval for PR #1496:
- Reject filePath == "" and filePath == "." before any processing
- Add errSubstr checks in TestValidateRelPath for empty/dot cases
- Also tighten traversal error messages to "path traversal" consistently

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:17:26 +00:00
1b3454f7e9 fix(handlers): simplify SSRF disable in setupTestDB; fix Windows path test
1. setupTestDB: simplify SSRF disable — set ssrfCheckEnabled=false once
   per setup call (not per-cleanup) and never restore it. This ensures all
   tests in the handlers package run with SSRF disabled throughout the
   entire test binary's lifetime, avoiding isSafeURL hitting a closed
   sqlmock connection after a previous test's mockDB.Close().

2. container_files_test.go: fix Windows absolute path test case.
   On Linux/Unix CI, Go's filepath.IsAbs treats "C:\\..." as a relative
   path (no drive letter meaning on Unix). Mark wantErr=false to match
   Unix behavior. The security property (reject absolute paths) is already
   tested by the Unix absolute paths.
2026-04-24 07:17:26 +00:00
b01957fbc4 fix(handlers): validateRelPath checks both raw and cleaned path for ..
The previous approach only checked the cleaned path, but filepath.Clean
resolves ".." upward so "foo/../bar" becomes "bar" and "foo/.." becomes
"." — making strings.Contains(clean, "..") pass when it shouldn't.

Fix: also check strings.Contains(filePath, "..") on the raw path.
This catches "foo/..", "foo/../bar", "../foo" etc. before Clean resolves them.

Update test case "path ends in .." to wantErr=true (raw path has "..").
2026-04-24 07:17:26 +00:00