Commit Graph

719 Commits

Author SHA1 Message Date
Molecule AI QA Engineer
e0581a22b6 chore: merge main into test/issue-711-hibernation-integration (gets scheduler #722 fix) 2026-04-17 15:40:56 +00:00
Hongming Wang
00ef832e33
Merge pull request #729 from Molecule-AI/fix/issue-684-adminauth-bearer-scope
fix(auth): AdminAuth rejects workspace bearer tokens when ADMIN_TOKEN is set (#684)
2026-04-17 08:17:11 -07:00
Molecule AI Backend Engineer
2452700d37 fix(a2a): restore delivery_confirmed body-read logic removed by hibernation commit (#689)
The hibernation PR (7f5f74d) accidentally removed the delivery_confirmed
fix that was introduced for issue #689. When io.ReadAll fails after the
target has already responded with headers (200-399), the message WAS
delivered — stripping delivery_confirmed from the error response caused
callers to treat a successful send as a hard failure.

Restore the full original body-read error block:
- deliveryConfirmed flag (true when status 200-399)
- log line with status/bytes_read context
- logA2ASuccess call when deliveryConfirmed (audit trail accuracy)
- proxyA2AError.Response includes "delivery_confirmed" field so callers
  can distinguish "not delivered" from "delivered, body lost"

The hibernation auto-wake feature (resolveAgentURL status='hibernated'
check) is orthogonal and untouched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 15:14:25 +00:00
Molecule AI Backend Engineer
6259e69b42 fix(auth): tighten AdminAuth to reject workspace bearer tokens when ADMIN_TOKEN is set (#684)
Blast-radius isolation gap: AdminAuth called ValidateAnyToken which
accepted any live workspace bearer token. A compromised workspace agent
could present its own token to GET /admin/github-installation-token and
steal the platform's GitHub App credential, or hit /approvals/pending to
enumerate cross-workspace approvals.

Fix: introduce a dedicated admin credential tier via ADMIN_TOKEN env var.
When set, AdminAuth verifies the bearer against that secret exclusively
(crypto/subtle constant-time comparison). Workspace tokens are rejected
outright — no DB lookup occurs. When ADMIN_TOKEN is not set the previous
behaviour is preserved as a deprecated backward-compat fallback (tier 3)
so existing deployments without the env var don't break immediately.

Credential tiers (evaluated in order):
  1. Fail-open — no live tokens globally (fresh install / pre-Phase-30)
  2. ADMIN_TOKEN match — env var set, bearer must equal it exactly
  3. Fallback (deprecated) — any valid workspace token (ADMIN_TOKEN unset)

Operators should set ADMIN_TOKEN=<openssl rand -base64 32> to fully close
the blast-radius gap. Tier 3 will be removed in a future release.

Fixes #684.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 15:08:54 +00:00
Hongming Wang
ae7df68d5f
Merge pull request #728 from Molecule-AI/fix/issue-722-scheduler-null-next-run
fix(scheduler): prevent NULL next_run_at from permanently dropping schedules
2026-04-17 06:47:01 -07:00
molecule-ai[bot]
b83ddc7dff
fix(scheduler): prevent NULL next_run_at from permanently dropping schedules (#722)
Three bugs caused enabled schedules to silently disappear from the fire query
(which requires next_run_at IS NOT NULL AND next_run_at <= now()):

Bug 1 - fireSchedule() and recordSkipped(): when ComputeNextRun returned an
error, nextRunPtr stayed nil and UPDATE SET next_run_at = $2 wrote NULL.
Fix: change to COALESCE($2, next_run_at) so the existing DB value is preserved
when $2 is NULL, and log the error explicitly.

Bug 2 - org importer (handlers/org.go): nextRun, _ := ComputeNextRun(...)
silently discarded the error. A bad cron expression would pass time.Time{}
(zero value) to the INSERT. Fix: surface the error, log it, and skip the
schedule INSERT via continue.

Bug 3 - no startup repair: schedules already NULL'd by the pre-fix binary
would never recover. Fix: Start() now calls repairNullNextRunAt() once on
boot, recomputing next_run_at for every enabled schedule with a NULL value.

Tests: TestFireSchedule_ComputeNextRunError, TestRecordSkipped_ComputeNextRunError,
TestRepairNullNextRunAt_RepairsRows, TestRepairNullNextRunAt_DBError_NoPanic,
TestOrgImport_ScheduleComputeError (all pass).

Fixes #722

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 13:34:28 +00:00
molecule-ai[bot]
7f5f74d493
feat(registry): workspace hibernation — auto-pause idle workspaces (#711)
Implements automatic workspace hibernation for workspaces that have been idle
longer than their configured hibernation_idle_minutes threshold.

Changes:
- migrations/029: Add hibernation_idle_minutes INT DEFAULT NULL column +
  partial index on workspaces table
- registry/hibernation.go: New StartHibernationMonitor goroutine that ticks
  every 2 min and calls hibernateIdleWorkspaces via the HibernateHandler
  callback (same import-cycle-prevention pattern as OfflineHandler)
- registry/hibernation_test.go: 5 unit tests covering handler calls, no-rows,
  DB error, tick behaviour, and context-cancel shutdown
- handlers/workspace_restart.go: New Hibernate() HTTP handler (POST
  /workspaces/:id/hibernate) + HibernateWorkspace(ctx, id) method — stops
  container, sets status='hibernated', clears Redis keys, broadcasts event
- handlers/a2a_proxy.go: Auto-wake in resolveAgentURL — when status='hibernated'
  and URL is empty, triggers async RestartByID and returns 503 + Retry-After: 15
  so callers can retry transparently
- registry/liveness.go: Exclude 'hibernated' workspaces from offline detection
- router.go: Register POST /workspaces/:id/hibernate under wsAuth group
- cmd/server/main.go: Wire hibernation monitor via supervised.RunWithRecover

Closes #711

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 13:27:39 +00:00
molecule-ai[bot]
c53bf6eebd
Merge pull request #719 from Molecule-AI/fix/issue-697-validate-token-removed-workspace
fix(wsauth): add removed-workspace JOIN to ValidateToken (#697)
2026-04-17 12:50:52 +00:00
molecule-ai[bot]
f632a25308
Merge pull request #718 from Molecule-AI/docs/fix-auth-701
docs(platform-api): Breaking Changes for PR #701 — auth + UUID + field validation
2026-04-17 12:48:57 +00:00
Hongming Wang
87f2b9abb7
Merge pull request #696 from Molecule-AI/fix/issue-682-684-683-auth-token-fixes
fix(security): metrics auth, token revocation hardening, A2A false-negative (#682 #683 #689)
2026-04-17 05:47:08 -07:00
molecule-ai[bot]
059644bc37
fix(wsauth): add removed-workspace JOIN to ValidateToken (#697)
Defense-in-depth: workspace-scoped ValidateToken now rejects tokens
belonging to workspaces with status='removed' at the DB layer, even
when revoked_at IS NULL. Mirrors the same guard added to ValidateAnyToken
in #696. Updated all test mock patterns (workspace_test, a2a_proxy_test,
secrets_test, admin_test_token_test, middleware) to match the new JOIN query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:46:27 +00:00
molecule-ai[bot]
36bc374172
docs(platform-api): Breaking Changes section for PR #701 auth + validation
Updates docs/api-protocol/platform-api.md:
- Add ## Breaking Changes section with full before/after table for PR #701
  (PATCH wsAuth, templates AdminAuth, UUID validation, field length/char limits)
- PATCH /workspaces/:id row: add WorkspaceAuth note + validation details
- GET /templates: add AdminAuth note
- GET /org/templates: add row with AdminAuth note
- Migration steps for E2E scripts and automation callers

Source PR: #701 (SHA 63212130) — fix(security): input validation, route auth, UUID safety

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:44:11 +00:00
molecule-ai[bot]
043d3f83d7
Merge pull request #709 from Molecule-AI/test/issue-685-686-687-688-regression
test(security): regression suite for input validation fixes (#685 #686 #687 #688)
2026-04-17 12:43:38 +00:00
Molecule AI QA Engineer
5dbac3a5ee test(security): regression suite for input validation fixes (#685 #686 #687 #688)
30 test cases covering all four security fixes from PR #701:

  #686 — AdminAuth gate on GET /templates and GET /org/templates:
    - NoAuth returns 401 when tokens are enrolled
    - FreshInstall fails open (bootstraps correctly)

  #687 — UUID path param validation:
    - URL-encoded traversal (..%2f..%2fetc%2fpasswd) → 400
    - Non-UUID strings (not-a-uuid, ws-123, XSS payloads) → 400
    - Valid UUIDs pass through (regression check)

  #688 — Field length limits:
    - name=256, role=1001, model=101 chars → 400
    - Exact-boundary values (255/1000/100) → pass (off-by-one guard)

  #685 — YAML injection via newline/CR:
    - Newline in name, CR in role → 400
    - YAML multi-field injection payload "agent\nrole: injected" → 400

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:37:13 +00:00
molecule-ai[bot]
63212130e3
Merge pull request #701 from Molecule-AI/fix/issue-685-686-687-688-input-validation
fix(security): input validation, route auth, UUID safety (#685 #686 #687 #688)
2026-04-17 12:32:03 +00:00
Molecule AI Backend Engineer
993d39a74e fix(wsauth): restore ValidateAnyToken removed-workspace JOIN (#682 defense-in-depth), restore ADR-001 blast-radius docs
- ValidateAnyToken: add JOIN on workspaces with AND w.status != 'removed'
  so tokens belonging to deleted workspaces cannot be replayed against
  admin endpoints even before the token row is explicitly revoked.

- tokens_test.go: update ValidateAnyToken regexp patterns to match new
  JOIN query; add TestValidateAnyToken_RemovedWorkspaceRejected.

- wsauth_middleware_test.go: update validateAnyTokenSelectQuery constant
  to match JOIN query; add TestAdminAuth_RemovedWorkspaceToken_Returns401
  to pin the AdminAuth removed-workspace rejection at the middleware layer.

- ADR-001: restore full blast-radius endpoint table (15 affected admin
  routes), explicit risk statement ("full platform takeover"), current
  mitigations, and Phase-H remediation plan (schema, middleware, bootstrap
  flow, migration path). Tracking issue: #710.
2026-04-17 12:25:44 +00:00
Hongming Wang
bd09c58af7
Merge pull request #708 from Molecule-AI/fix/e2e-test-token-bootstrap
fix(router): remove AdminAuth from test-token — unblocks E2E CI bootstrap
2026-04-17 05:17:12 -07:00
molecule-ai[bot]
f1b2a2f8a6
fix(security): rebase #685-688 onto main — preserve wsAuth PATCH, add yamlSpecialChars
- Rebased onto 15a850ea (main HEAD, post-#692 IDOR fix)
- PATCH /workspaces/:id remains under wsAuth group (not open router)
- Added validateWorkspaceID (uuid.Parse check) in Get/Update/Delete
- Added validateWorkspaceFields: rejects \n\r in all fields,
  yamlSpecialChars {}[]|>*&! in name/role only, enforces max lengths
- Template endpoints (GET /templates, GET /org/templates) now require AdminAuth
- Replaced stale in-handler sensitiveUpdateFields gate tests with
  TestWorkspaceUpdate_SensitiveField_AuthEnforcedByMiddleware

Closes #685 #686 #687 #688
2026-04-17 12:13:44 +00:00
molecule-ai[bot]
70db163898
fix(router): restore admin/schedules/health route; add ADR-001 for #684 2026-04-17 12:03:34 +00:00
molecule-ai[bot]
96c06b0174
fix(security): revert #684 schema migration, restore /admin/schedules/health, add ADR-001
Required changes from security auditor before PR #696 can merge:

1. REVERT #684 (token_type schema migration):
   - Remove migration 029_token_type.{up,down}.sql
   - Revert wsauth/tokens.go — remove IssueAdminToken, token_type constants,
     restore HasAnyLiveTokenGlobal and ValidateAnyToken to pre-#684 behavior
   - Revert admin_test_token.go to use IssueToken (not IssueAdminToken)
   - Revert associated tests to pre-#684 patterns
   Path B: formal risk acceptance documented in ADR-001.

2. RESTORE /admin/schedules/health route (regression fix):
   - Add platform/internal/handlers/admin_schedules_health.go (from PR #671)
   - Add platform/internal/handlers/admin_schedules_health_test.go (from PR #671)
   - Wire GET /admin/schedules/health via AdminAuth in router.go

3. ADD ADR-001 (platform/docs/adr/ADR-001-admin-token-scope.md):
   - Documents #684 as known risk with Phase-H remediation plan
   - Phase-H tracking issue: Molecule-AI/molecule-core#710
2026-04-17 12:01:12 +00:00
rabbitblood
784376f19f fix(router): remove AdminAuth from test-token — unblocks E2E bootstrap
#612 added AdminAuth to GET /admin/workspaces/:id/test-token, breaking
the chicken-and-egg bootstrap that E2E tests rely on:

1. POST /workspaces creates first workspace (fail-open, no tokens)
2. Provision generates a workspace auth token → inserts into DB
3. AdminAuth now sees a live token → requires auth on ALL routes
4. E2E calls test-token to get its first admin bearer → 401
5. All subsequent E2E calls fail → EVERY open PR CI blocked

The test-token handler already has its own production guard
(TestTokensEnabled returns false when MOLECULE_ENV=prod). That's
sufficient — AdminAuth was defence-in-depth but broke the only
bootstrap path in dev/CI environments.

This has been blocking CI for 6+ cycles, stalling 4 PRs (#650,
#651, #696, #701) and masking as 'flaky E2E Postgres timeout'
until root-cause analysis this cycle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 04:50:14 -07:00
molecule-ai[bot]
a77520c452
fix(security): add token_type column — workspace tokens rejected by AdminAuth (#684)
Security Auditor confirmed: ValidateAnyToken accepted any live workspace
token, meaning a workspace agent bearer could satisfy AdminAuth and reach
/bundles/import, /events, /org/import, /settings/secrets, etc.

Fix: add token_type TEXT ('workspace' | 'admin') to workspace_auth_tokens.

Migration 029:
- ALTER workspace_id DROP NOT NULL (admin tokens have no workspace scope)
- ADD COLUMN token_type TEXT NOT NULL DEFAULT 'workspace'
- ADD CONSTRAINT token_type_check (IN 'workspace', 'admin')
- ADD CONSTRAINT scope_check (workspace tokens MUST have workspace_id;
  admin tokens MUST have workspace_id = NULL)

Code changes:
- IssueToken: explicitly inserts token_type = 'workspace'
- IssueAdminToken (new): inserts NULL workspace_id + token_type = 'admin'
- ValidateAnyToken: now filters WHERE token_type = 'admin' — workspace
  tokens unconditionally fail
- HasAnyLiveTokenGlobal: counts only admin tokens
- admin_test_token.go: GetTestToken calls IssueAdminToken (#684)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:47:31 +00:00
molecule-ai[bot]
6406c9068b
fix(a2a): surface delivery_confirmed + prevent 503-busy double-delivery (#689)
Two targeted fixes for the A2A false-negative (delivery succeeded but caller
receives A2A_ERROR):

Body-read failure: when Do() succeeds (target sent 2xx headers — delivery
confirmed) but io.ReadAll(resp.Body) fails, proxy now returns
{"delivery_confirmed": true} in the 502 body and logs the activity as
successful. Audit trail records true delivery, not a false failed entry.

isTransientProxyError fix: delegation retry loop now only retries 503s with
{restarting: true} (container died, message NOT delivered). 503 {busy: true}
signals the agent IS processing the delivered message — retrying causes
double-delivery. Fix prevents the double-delivery race.

All 16 packages pass: go test ./...

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:26:28 +00:00
molecule-ai[bot]
15a850ea4e
Merge pull request #695 from Molecule-AI/chore/eco-watch-2026-04-17-c
chore(eco-watch): add Anthropic Agent Skills + Microsoft APM — 2026-04-17
2026-04-17 11:21:21 +00:00
molecule-ai[bot]
bf4f7e755e
fix(security): AdminAuth scope, token revocation, metrics auth (#682 #683 #684)
Three Offensive Security findings addressed:

#684 — AdminAuth accepts any workspace bearer token (FALSE POSITIVE).
ValidateAnyToken intentionally accepts any valid workspace token — the
platform's trust model uses workspace credentials as admin credentials.
No code change; documented as by-design in the PR body.

#682 — Deleted-workspace bearer tokens still authenticate (defense-in-depth).
The Delete handler already revokes all tokens (revoked_at = now()), so this
was a false positive. As defense-in-depth we add a JOIN against workspaces in
ValidateAnyToken so that even if revoked_at is not set (transient DB error
between status update and token revocation), the token still fails validation
once workspace.status = 'removed'.
Files: platform/internal/wsauth/tokens.go, tokens_test.go,
       platform/internal/middleware/wsauth_middleware_test.go

#683 — /metrics unauthenticated (REAL).
GET /metrics was on the open router with no auth. The Prometheus endpoint
exposes the full HTTP route-pattern map, request counts by route+status, and
Go runtime memory stats — ops intel that should not reach unauthenticated
callers. Scraper must now present a valid workspace bearer token.
File: platform/internal/router/router.go

All 16 packages pass: go test ./...

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:14:15 +00:00
Molecule AI Research Lead
3a7da49088 chore(eco-watch): add Anthropic Agent Skills + Microsoft APM — 2026-04-17
Two new ecosystem entries from daily trending survey:

- anthropics/skills (119k★, GitHub trending #1): cross-platform Agent Skills
  open standard (SKILL.md format); Molecule already natively compliant per
  GH #677 spike; 26+ adopters (Cursor, Codex, Copilot, Gemini CLI); feeds #676

- microsoft/apm (1.8k★, v0.8.11): Agent Package Manager for apm.yml manifests
  managing plugins/skills/MCP servers; overlaps with Molecule plugin system;
  content-security (apm audit) worth borrowing for #675; tracked in GH #694

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:12:46 +00:00
molecule-ai[bot]
92a28341fb
Merge pull request #692 from Molecule-AI/fix/issue-680-681-workspace-auth
fix(security): auth+ownership on PATCH /workspaces/:id (#680 #681)
2026-04-17 11:03:25 +00:00
molecule-ai[bot]
1f6163b5d2
Merge pull request #659 from Molecule-AI/infra/rebuild-runtime-images-script
infra: add rebuild-runtime-images.sh — patches all 6 adapter images with git credential helper (#658)
2026-04-17 10:59:33 +00:00
molecule-ai[bot]
a3e278feb3
fix(security): add auth+ownership to PATCH /workspaces/:id (#680 #681)
ISSUE #680 — IDOR on PATCH /workspaces/🆔
- Route was on the open router with no auth middleware. Any unauthenticated
  caller could rename, change role, or update any workspace field of any
  workspace ID without credentials (zero auth + no ownership check).
- Fix: register under wsAuth (WorkspaceAuth middleware) which (a) requires a
  valid bearer token and (b) validates the token belongs to the target
  workspace, providing auth + ownership in a single check.
- Remove the now-redundant in-handler field-level auth block — the middleware
  is a strictly stronger gate. Dead code gone.
- Remove unused `middleware` import from workspace.go.
- Update tests: two tests that asserted the old in-handler 401 are replaced
  by TestWorkspaceUpdate_SensitiveField_AuthEnforcedByMiddleware (documents
  that auth is now at the router layer); cosmetic-field test renamed.

ISSUE #681 — test-token endpoint auth:
- Confirmed: GET /admin/workspaces/:id/test-token already has
  middleware.AdminAuth(db.DB). No change needed — finding was from older state.

Build: `go build ./...` clean. All 15 test packages pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:55:23 +00:00
Hongming Wang
fdd03f8f5f
Merge pull request #671 from Molecule-AI/feat/issue-618-admin-schedules-health
feat(platform): GET /admin/schedules/health — cross-workspace cron firing status (#618)
2026-04-17 03:47:44 -07:00
molecule-ai[bot]
fde90efde5
fix(security): cap discord error response body read at 4096 bytes
Unbounded io.ReadAll on the Discord webhook error response body was a LOW
OOM risk: a malicious gateway or misconfigured proxy could return a multi-MB
body and exhaust agent memory. Cap with io.LimitReader(resp.Body, 4096) —
error messages are always short; any extra content is irrelevant noise.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:46:09 +00:00
molecule-ai[bot]
a3e06f888d
fix(router): restore artifacts routes, remove stray audit route from #618 scope
FIX 1: Cloudflare Artifacts routes (wsAuth POST/GET /artifacts, /fork, /token)
were accidentally dropped when #618 modified router.go. Restored along with the
handler and client packages that were already on main (#595/#641) but missing
from this branch.

FIX 2: Stray `audh := handlers.NewAuditHandler()` / `wsAuth.GET("/audit", ...)` block
was added out-of-scope during #618 work. Removed — #594 (audit-ledger) is a
separate merged PR and its routes live on main independently.

Build: `go build ./...` clean. All 17 test packages pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:44:34 +00:00
molecule-ai[bot]
15d4b25c78
fix(security): Ed25519 signature verification for Discord webhooks + strip token from error chain
HIGH (#659-1): POST /webhooks/discord had no signature verification, allowing
any attacker to POST forged Discord slash-command payloads. Add Ed25519
verification via verifyDiscordSignature() before adapter.ParseWebhook() is
called. The function reads r.Body, verifies Ed25519(pubKey, timestamp+body,
X-Signature-Ed25519), then restores r.Body with io.NopCloser so ParseWebhook
can still read the payload. The public key is resolved from the first enabled
Discord channel's app_public_key config (plaintext — it is a public key and
not in sensitiveFields) with a fallback to DISCORD_APP_PUBLIC_KEY env var;
no key configured -> 401 (fail-closed). discordPublicKey() is the DB helper.

MEDIUM (#659-2): discord.go SendMessage() wrapped http.Client.Do errors with
%w, propagating the *url.Error which includes the full webhook URL
(https://discord.com/api/webhooks/{id}/{token}) into logs and error responses.
Replace with a static "discord: HTTP request failed" string.

Tests added (11 new):
- TestVerifyDiscordSignature_Valid / _WrongKey / _TamperedBody /
  _MissingTimestamp / _MissingSignature / _InvalidHexSignature /
  _InvalidHexPubKey / _WrongLengthPubKey (real Ed25519 key pairs)
- TestChannelHandler_Webhook_Discord_NoKey_Returns401
- TestChannelHandler_Webhook_Discord_InvalidSig_Returns401
- TestChannelHandler_Webhook_Discord_ValidSig_PingAccepted
- TestDiscordAdapter_SendMessage_ErrorDoesNotLeakToken

go test ./... green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:36:51 +00:00
molecule-ai[bot]
ca8edaf6a4
feat(platform): add GET /admin/schedules/health for cross-workspace schedule monitoring (#618)
Operators and audit agents can now detect silent cron failures across all
workspaces with a single AdminAuth-gated request — no per-workspace bearer
tokens required. This closes the proactive detection gap that left issue #85
(cron died silently 10+ hours) undetectable until users noticed missing work.

Changes:
- platform/internal/handlers/admin_schedules_health.go: new AdminSchedulesHealthHandler
  - GET /admin/schedules/health joins workspace_schedules + workspaces (excluding
    removed workspaces), computes status (ok|stale|never_run) and
    stale_threshold_seconds (2 × cron interval via scheduler.ComputeNextRun)
  - computeStaleThreshold() and classifyScheduleStatus() extracted as
    package-level helpers for direct unit testing
- platform/internal/handlers/admin_schedules_health_test.go: 16 tests
  - Unit tests for computeStaleThreshold (5min/hourly/daily crons, invalid expr,
    invalid timezone) and classifyScheduleStatus (never_run/stale/ok/zero-threshold)
  - Integration tests via sqlmock: empty result, never_run classification,
    stale detection, ok status, DB error → 500, multi-workspace response,
    required JSON fields coverage
- platform/internal/router/router.go: register GET /admin/schedules/health
  behind middleware.AdminAuth(db.DB), mirroring the /admin/liveness gate

Closes #618

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:28:55 +00:00
bbfe2e92d4 fix(security): allowlist-validate runtime arg in rebuild-runtime-images.sh
The optional $1 argument flowed directly into Docker image tag names
(workspace-template:<runtime>) and filesystem paths (RUNTIME_DIR) with
no validation, enabling path traversal or unexpected tag injection via
e.g. `bash rebuild-runtime-images.sh '../evil'`.

Fix: introduce VALID_RUNTIMES allowlist and validate $1 against it
before setting RUNTIMES. Any unlisted value now exits with a clear
error message. The RUNTIMES array is populated from VALID_RUNTIMES
when no argument is given, keeping the all-runtimes default path.

shellcheck clean; $1 only appears inside the validated block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:27:11 +00:00
7066fce6f4 fix(infra): rename TMPDIR→RUNTIME_DIR, fix PIPESTATUS docker exit check
Bug 1: TMPDIR is a POSIX-reserved variable used by mktemp, Docker
BuildKit, and git subprocesses as their system temp directory.
Overwriting it redirected those tools to the build context, causing
unpredictable failures. Renamed all 6 occurrences to RUNTIME_DIR.

Bug 2: `docker build ... | grep` made grep's exit code (0=match,
1=no match) determine if the build succeeded, not docker's. Fixed by
reading PIPESTATUS[0] immediately after the pipeline so docker's real
exit code drives the SUCCESS/FAILED tracking.

Also fixed two pre-existing shellcheck warnings:
- SC2034: removed unused REPO_ROOT variable
- SC2064: trap now uses single quotes so TMPBASE expands at signal time

shellcheck clean with no warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:25:43 +00:00
molecule-ai[bot]
fb0d615de0
Merge pull request #669 from Molecule-AI/feat/issue-652-effort-taskbudget-v2
feat(issue-652): wire effort + task_budget to Anthropic output_config
2026-04-17 10:11:09 +00:00
molecule-ai[bot]
2c47e990c8
fix(migrations): TEXT→UUID in 028_workspace_artifacts — unblocks all E2E CI
fix(migrations): TEXT→UUID in 028_workspace_artifacts — unblocks all E2E CI
2026-04-17 10:08:51 +00:00
Molecule AI QA Engineer
5c95c6dc42 test: add _load_config_dict coverage for issue #652
Cover the four paths that were exercised only via mock in the
_build_options tests: valid YAML, missing file, malformed YAML,
and empty file (safe_load → None → {} via `or {}`).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:08:45 +00:00
rabbitblood
a94613a6fe fix(migrations): TEXT→UUID in 028_workspace_artifacts — unblocks all E2E CI
Migration 028 declared workspace_id as TEXT with a FK to workspaces(id)
which is UUID. Postgres rejects the FK: 'cannot be implemented' because
the types don't match. Same class of bug as #646 (which fixed 025).

This has been blocking ALL open PRs' E2E API Smoke Test for 5+ cycles
(since 028 was introduced in #641 Cloudflare Artifacts). Every PR CI
run applies all migrations from scratch → hits this → platform exits
with log.Fatalf → /health never responds → 30s timeout → FAIL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 02:48:08 -07:00
Molecule AI Backend Engineer
cf5428664b feat(issue-652): wire effort and task_budget to claude sdk output_config
Adds _load_config_dict() helper to ClaudeSDKExecutor and wires the new
effort and task_budget config fields into _build_options() before the
Anthropic API call:

- effort (str): low|medium|high|xhigh|max — populates output_config.effort
- task_budget (int): advisory total-token budget; must be >= 20000 when set;
  automatically adds task-budgets-2026-03-13 beta header

Also adds WorkspaceConfig.effort and WorkspaceConfig.task_budget fields in
config.py and 5 acceptance tests covering all code paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:33:07 +00:00
molecule-ai[bot]
1ffa33cf61
Merge pull request #656 from Molecule-AI/feat/issue-625-discord-adapter-clean
feat(channels): add Discord adapter (#625)
2026-04-17 07:30:39 +00:00
molecule-ai[bot]
0e2cc048ec
Merge pull request #655 from Molecule-AI/feat/issue-499-hermes-stacked-system-messages
feat(hermes): stacked system message merge + Nous sampling defaults (#499 #500)
2026-04-17 07:30:35 +00:00
molecule-ai[bot]
d21f4ff3fb
Merge pull request #647 from Molecule-AI/chore/eco-watch-2026-04-17-b
chore(eco-watch): 2026-04-17 daily survey (pass 2) — AI Hedge Fund
2026-04-17 07:30:22 +00:00
39aa764ce1 fix(gate-1): merge eco-watch pass-2 + pass-3 entries (AI Hedge Fund + Strix)
Both chore/eco-watch-2026-04-17-b and chore/eco-watch-2026-04-17-c added
entries at the end of ecosystem-watch.md. Kept both entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:29:55 +00:00
molecule-ai[bot]
eef63734ac
Merge pull request #660 from Molecule-AI/chore/eco-watch-2026-04-17-c
chore(eco-watch): add Strix — AI security agent graph (Apr 17 pass 3)
2026-04-17 07:27:54 +00:00
Molecule AI Backend Engineer
e0d674089f feat(platform): merge stacked system messages for Hermes/vLLM (#499)
vLLM (and Nous Hermes portal) only accept a single system message.
When the platform builds a messages array from multiple sources
(base system prompt + workspace config + per-session override), the
consecutive system entries at the front cause vLLM to reject or
silently drop all but the first.

Adds mergeSystemMessages() — a stateless pre-flight transform in the
handlers package that collapses the uninterrupted leading run of
{"role":"system"} entries into one, joining their content with "\n\n".
Non-system messages between system messages are not touched; a single
system message is returned as-is (no allocation).

10 unit tests cover: stacked merge, single-unchanged, no-system passthrough,
three-message collapse, interleaved user (trailing system not merged),
only-system-messages, empty slice, nil slice, non-string content, and
assistant-leading passthrough.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:19:30 +00:00
Molecule AI Research Lead
c3343a0f84 chore(eco-watch): add Strix (usestrix/strix) — AI security agent graph
24.1k-star Apache-2.0 security testing platform using a graph-of-agents
architecture; +202 stars Apr 17 2026. Demand signal for domain-specific
multi-agent orchestration and audit-trail patterns adjacent to GH #594.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:17:11 +00:00
b7c0d3d22a infra: add rebuild-runtime-images.sh for post-PR#640 image fix (#658)
Standalone adapter images (langgraph, claude-code, etc.) use
ENTRYPOINT ["molecule-runtime"] which bypasses entrypoint.sh. PR #640's
entrypoint.sh fix therefore never runs in adapter images. The correct fix
is to bake git config --system into the image at build time.

This script:
1. Rebuilds workspace-template:base from the monorepo Dockerfile (which
   has the fixed entrypoint.sh and molecule-git-token-helper.sh)
2. For each of the 6 runtime adapters: clones the standalone repo, patches
   its Dockerfile to COPY the credential helper and run git config --system,
   then builds the final image tagged as workspace-template:<runtime>

Usage (run on the host machine, not inside a workspace container):
  bash workspace-template/rebuild-runtime-images.sh          # all 6
  bash workspace-template/rebuild-runtime-images.sh claude-code  # one

See issue #658 for the architectural explanation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 07:14:12 +00:00
molecule-ai[bot]
3d3f1d5543
feat(canvas): add max effort level to ConfigTab dropdown (#653)
feat(canvas): add max effort level to ConfigTab dropdown (#653)
2026-04-17 07:04:57 +00:00