Commit Graph

4968 Commits

Author SHA1 Message Date
Molecule AI Core Platform Lead
b97bda13e9 Merge remote-tracking branch 'origin/main' into trig-221 2026-05-10 02:34:18 +00:00
4474ddc189 fix(workspace): add SSRF validation before writing external workspace URL
Issue #212: POST /workspaces with runtime=external and a URL wrote the
URL directly to the DB without validateAgentURL checking (the same check
that registry.go:324 applies to the heartbeat path). An attacker with
AdminAuth could register a workspace URL at a cloud metadata endpoint
(169.254.169.254) and exfiltrate IAM credentials when the platform
fires pre-restart drain signals.

Changes:
- workspace.go: add validateAgentURL(payload.URL) guard before the
  UPDATE at line 386. 400 on unsafe URL, no DB write occurs.
- workspace_test.go: add 3 regression tests:
  - TestWorkspaceCreate_ExternalURL_SSRFSafe: safe public URL → 201
  - TestWorkspaceCreate_ExternalURL_SSRFMetadataBlocked: 169.254.169.254 → 400
  - TestWorkspaceCreate_ExternalURL_SSRFLoopbackBlocked: 127.0.0.1 → 400
  Both unsafe tests assert zero DB calls (the handler rejects before
  any transaction).

Ref: issue #212.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:30:18 +00:00
a7278abad4 Merge pull request 'docs(runbook): add admin-auth.md covering test-token route lockdown' (#220) from infra/add-admin-auth-runbook into main 2026-05-10 02:24:02 +00:00
Molecule AI Core Platform Lead
14afa58606 trigger 2026-05-10 02:23:40 +00:00
Molecule AI Core Platform Lead
4615298eca Merge remote-tracking branch 'origin/main' into trig-220 2026-05-10 02:23:40 +00:00
b5d9f13ab1 docs(runbook): add admin-auth.md covering test-token route lockdown
Issue #214: documents the MOLECULE_ENV=production requirement for
staging/prod tenants to lock the /admin/workspaces/:id/test-token route.
Also adds a startup INFO log in main.go when the route is enabled, so
operators can confirm the setting in boot logs without having to probe
the endpoint directly.

Ref: issue #214.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:20:30 +00:00
c22e45049e Merge pull request 'test(canvas): add tests for StatusBadge, ValidationHint, Spinner' (#218) from test/canvas-context-search-tests into main 2026-05-10 02:18:04 +00:00
Molecule AI Core Platform Lead
6bf901b391 Merge remote-tracking branch 'origin/main' into trig-218 2026-05-10 02:17:26 +00:00
7ae3ee786f feat(workspace): add static .github-token fallback to git credential helper
Adds a 4th fallback step to the token chain (cache > API > env > static)
so workspace git/gh operations survive a platform outage without requiring
a restart or platform-side fix. Addresses the 2026-05-08 incident where
every workspace lost git+gh auth simultaneously when the
/github-installation-token endpoint returned 500.

Operator places a PAT in ${CONFIGS_DIR:-/configs}/.github-token
(no root needed — /configs is agent-writable). Both _fetch_token
(git credential helper path) and _refresh_gh (gh CLI daemon path)
gain the static fallback so git and gh both recover post-incident.

Pure additive — existing cache > API > env chain is unchanged.
Empty static file is rejected (whitespace-stripped before use).
Static path never writes the cache, so the API recovers transparently
on the next refresh cycle when it comes back online.

Ref: issue #140.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:17:22 +00:00
Molecule AI Core Platform Lead
9313fc82ac trigger 2026-05-10 02:17:06 +00:00
Molecule AI Core Platform Lead
a4c314bea5 Merge remote-tracking branch 'origin/main' into trig-218 2026-05-10 02:17:05 +00:00
6b3ab63bc0 test(canvas): add tests for StatusBadge, ValidationHint, Spinner
StatusBadge: all 3 status variants, aria-label, role=status, config class names.
ValidationHint: error/valid/neutral states, warning icon, valid icon, class names.
Spinner: sm/md/lg size classes, aria-hidden, motion-safe:animate-spin.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:15:02 +00:00
2fb6044d96 Merge pull request 'test(canvas): add component tests for SearchDialog and ContextMenu' (#216) from test/canvas-context-search-tests into main 2026-05-10 02:13:53 +00:00
Molecule AI Core Platform Lead
df7a7560cf Merge remote-tracking branch 'origin/main' into trig-216 2026-05-10 02:13:27 +00:00
Molecule AI Core Platform Lead
0ee6317c0c trigger 2026-05-10 02:13:02 +00:00
f7833f1643 Merge pull request 'fix(ci): migrate canary-verify from GHCR to ECR + add POST route smoke tests' (#217) from infra/fix-canary-verify-ecr-migration into main 2026-05-10 02:12:47 +00:00
Molecule AI Core Platform Lead
862819dc65 Merge remote-tracking branch 'origin/main' into trig-217 2026-05-10 02:12:37 +00:00
Molecule AI Core Platform Lead
67310828e7 trigger 2026-05-10 02:12:21 +00:00
af5406d29e fix(ci): migrate canary-verify from GHCR to ECR + add POST route smoke tests
Root cause of issue #213: canary-verify.yml still used GHCR
(ghcr.io/molecule-ai/platform-tenant) while
publish-workspace-server-image.yml migrated to ECR on 2026-05-07
(commit 10e510f5). Canary smoke tests were silently testing a stale
GHCR image while actual staging/prod tenants ran the ECR build.
The POST /org/import and POST /workspaces routes were missing from
the ECR binary (likely a Docker layer-caching artefact during the
staging push window) but smoke tests passed because they never tested
the ECR image at all.

Changes:
- canary-verify.yml: migrate promote-to-latest from GHCR crane tag
  ops to the CP redeploy-fleet endpoint (same mechanism as
  redeploy-tenants-on-main.yml). The wait-for-canaries step already
  read SHA from the running tenant /health (registry-agnostic), so
  no change needed there. Pre-fix promote step used `crane tag` against
  GHCR, which was never updated after the ECR migration.
- redeploy-tenants-on-main.yml: update stale comments that reference
  GHCR to reflect ECR; replace the 30s GHCR CDN propagation wait
  with a no-op comment (ECR has no CDN cache to wait for).
- scripts/canary-smoke.sh: add POST /org/import and POST /workspaces
  smoke tests (steps 6-8). These assert HTTP 401 unauthenticated
  (proves AdminAuth enforced AND the route is compiled in — 404 would
  mean route missing from binary). GET /workspaces was already covered;
  POST was the untested gap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:10:12 +00:00
2549c4cbcc test(canvas): add component tests for SearchDialog and ContextMenu
SearchDialog: Cmd+K/Ctrl+K shortcut, Escape close, input focus via rAF,
text filtering by name/role/status, arrow-key navigation, Enter select,
aria-combobox/listbox/option attributes, footer workspace count.

ContextMenu: null guard, node header, outside-click/Escape/Tab close,
conditional items (online vs offline vs paused), team items, dividers,
danger Delete styling, keyboard navigation, Pause/Resume API calls.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:09:43 +00:00
511bc7c01d Merge pull request 'test(canvas): add component tests for OnboardingWizard and PurchaseSuccessModal' (#215) from test/canvas-onboarding-purchase-modal-tests into main 2026-05-10 01:53:55 +00:00
Molecule AI Core Platform Lead
ee5648b3d1 trigger 2026-05-10 01:53:43 +00:00
b23ca65d35 test(canvas): add component tests for OnboardingWizard and PurchaseSuccessModal
OnboardingWizard: visibility gates, 4-step flow, skip/dismiss,
localStorage persistence, progress bar, aria-live announcements,
auto-advance from welcome→api-key on nodes change.

PurchaseSuccessModal: URL param gating, portal rendering,
item name display, 5s auto-dismiss (fake timers), backdrop/Escape
close, replaceState URL stripping, aria-modal/focus management.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 01:50:29 +00:00
2893c4c2aa Merge pull request 'feat(ci): port publish-runtime.yml to .gitea/workflows/ (issue #206)' (#211) from ci/port-publish-runtime-to-gitea-actions into main 2026-05-10 01:29:41 +00:00
Molecule AI Core Platform Lead
b04e7b39a0 Merge remote-tracking branch 'origin/main' into trig-211 2026-05-10 01:29:23 +00:00
Molecule AI Core Platform Lead
66d3bb9f2f trigger 2026-05-10 01:29:10 +00:00
25d3b1a2f3 feat(ci): port publish-runtime.yml to .gitea/workflows/ (issue #206)
publish-runtime.yml was dead on Gitea Actions because Gitea reads
.gitea/workflows/, not .github/workflows/ (the GitHub Actions paths are
ignored). Issue #206 identified this as one of three bugs blocking the
runtime versioning pipeline.

Changes:
- Add .gitea/workflows/publish-runtime.yml (canonical Gitea version)
  - Drop environment: + id-token: write (Gitea has no OIDC/OAuth)
  - Replace pypa/gh-action-pypi-publish with twine upload using PYPI_TOKEN secret
  - Replace github.ref_name with ${GITHUB_REF#refs/tags/} (Gitea exposes github.ref)
  - Drop merge_group trigger (Gitea has no merge queue)
  - Drop staging branch trigger (staging branch does not exist)
  - Cascade step unchanged (DISPATCH_TOKEN + Gitea API already compatible)
- Add DEPRECATED notice to .github/workflows/publish-runtime.yml

Required secrets (repo Settings → Actions → Variables and Secrets):
  PYPI_TOKEN: PyPI API token for molecule-ai-workspace-runtime
  DISPATCH_TOKEN: Gitea PAT with write:repo on template repos (already used)

Closes #206 (publish-runtime Gitea port).
2026-05-10 01:26:13 +00:00
9b53b70b48 Merge pull request 'test(canvas): add component tests for ThemeToggle and BundleDropZone' (#210) from test/canvas-component-tests-2 into main 2026-05-10 01:22:25 +00:00
Molecule AI Core Platform Lead
85a8ab428c Merge remote-tracking branch 'origin/main' into trig-210 2026-05-10 01:22:17 +00:00
Molecule AI Core Platform Lead
124e1a6f04 trigger 2026-05-10 01:22:03 +00:00
Molecule AI Core Platform Lead
02c2226e46 Merge remote-tracking branch 'origin/main' into trig-210 2026-05-10 01:22:02 +00:00
9452123d78 Merge pull request 'feat(workspace-server): pre-restart A2A drain signal (core#125)' (#207) from feat/a2a-pre-restart-drain-125 into main 2026-05-10 01:18:51 +00:00
Molecule AI Core Platform Lead
422d621e3c Merge remote-tracking branch 'origin/main' into trig-207 2026-05-10 01:18:43 +00:00
Molecule AI Core Platform Lead
27a94f0b79 trigger 2026-05-10 01:18:30 +00:00
a3e437b43f Merge pull request 'fix(ci): replace dorny/paths-filter with shell-based git diff (Gitea Actions compatibility)' (#208) from infra/fix-harness-replays-paths-filter-and-failure into main 2026-05-10 01:18:25 +00:00
Molecule AI Core Platform Lead
9c35057c98 trigger 2026-05-10 01:18:14 +00:00
ad1a4a2d49 test(canvas): add component tests for ThemeToggle and BundleDropZone
- ThemeToggle.test.tsx (13 tests): renders radiogroup with 3 options,
  aria radiogroup/radio semantics, aria-checked per option, setTheme
  calls on click, custom className prop
- BundleDropZone.test.tsx (11 tests): hidden file input + keyboard
  accessibility (WCAG 2.1.1), drag-over state, import success/error
  toast, auto-clear timeouts (3s error, 4s success), importing
  status indicator, file input reset on re-select

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 01:18:10 +00:00
d0126662c7 docs: cycle report 2026-05-10
Cycle summary:
- Assigned: core#125 (feat: preserve in-flight A2A messages across restart)
- Implemented: Phase 1 of #125 — pre-restart drain signal
- Opened: PR #207
- Reviewed: PR #140 (static-token fallback, approved)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 01:15:07 +00:00
796201e09f fix(ci): replace dorny/paths-filter with shell-based git diff (Gitea Actions compatibility)
dorny/paths-filter is GitHub-Actions-only and does not work correctly on
Gitea Actions — it silently returns no file changes regardless of what
files were modified, causing the harness-replays workflow to silently
skip on Gitea even when workspace-server/** or canvas/** files change.

Verified: zero harness-replays statuses on PR #188 and #168 (both changed
workspace-server files) vs GitHub Actions where the same workflow
correctly detects changes.

Replace with a shell-based approach that uses:
- github.event.pull_request.base.sha  (Gitea + GitHub: merge-base for PRs)
- github.event.before                (Gitea + GitHub: previous tip for pushes)
- git diff --name-only <BASE> github.sha (portable git, works on both platforms)

Also adds detect-changes.debug output so future no-op passes show WHY
the workflow decided to skip, and the first real run on Gitea will
confirm the diff detection is working.

Closes #141 (followup: root-cause fix still TBD — failure logs
inaccessible via Gitea Actions API).
2026-05-10 01:11:45 +00:00
c6e286e081 Merge pull request 'test(canvas): add component tests for Tooltip, Legend, TermsGate, ApprovalBanner' (#205) from test/canvas-component-tests into main 2026-05-10 00:47:28 +00:00
Molecule AI Core Platform Lead
4524f4aeb1 Merge remote-tracking branch 'origin/main' into trig-205 2026-05-10 00:46:56 +00:00
Molecule AI Core Platform Lead
3549a38d10 trigger: re-run sop-tier-check 2026-05-10 00:46:33 +00:00
cdc5522b3e docs(canvas-audit): record PR #205 test coverage addition
Adds a note to the audit doc footer tracking the new component tests
(PR #205: Tooltip, Legend, TermsGate, ApprovalBanner) and bumps the
updated date.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:45:47 +00:00
29c6be81bd test(canvas): add component tests for Tooltip, Legend, TermsGate, ApprovalBanner
Adds vitest tests for 4 previously untested canvas components:

- Tooltip.test.tsx (17 tests): portal rendering, 400ms hover delay,
  keyboard focus reveal, Esc dismiss (WCAG 1.4.13), aria-describedby
- Legend.test.tsx (10 tests): open/closed state, localStorage persistence,
  palette-offset positioning, status/tier/comm items, aria labels
- TermsGate.test.tsx (14 tests): loading→accepted, pending modal (WCAG
  2.4.3 focus), accept flow, error state, children always rendered
- ApprovalBanner.test.tsx (15 tests): empty state, approval card render,
  polling cleanup, approve/deny decisions, toast notifications, error recovery

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:44:45 +00:00
4725606560 Merge pull request 'feat(plugins): plugin drift detector + queue + admin apply endpoint (#123)' (#204) from feat/plugin-drift-queue-123 into main 2026-05-10 00:43:17 +00:00
Molecule AI Core Platform Lead
e97a6b43d8 Merge remote-tracking branch 'origin/main' into trig-204 2026-05-10 00:42:57 +00:00
Molecule AI Core Platform Lead
5475940ebe trigger: re-run sop-tier-check 2026-05-10 00:42:39 +00:00
Molecule AI Core Platform Lead
cf09233202 Merge remote-tracking branch 'origin/main' into trig-204 2026-05-10 00:42:38 +00:00
ada1008012 feat(plugins): plugin drift detector + queue + admin apply endpoint (#123)
## Summary

Adds the version-subscription drift detection and operator-apply workflow for
per-workspace plugin tracking (core#113).

## Components

**Migration** (`20260510000000_plugin_drift_queue`):
- Adds `installed_sha` column to `workspace_plugins` — records the commit SHA
  installed so the drift sweeper can compare against upstream.
- Creates `plugin_update_queue` table with status: pending | applied | dismissed.
- Adds partial unique index to prevent duplicate pending rows per
  (workspace_id, plugin_name).

**GithubResolver** (`github.go`):
- `LastFetchSHA` field + `LastSHA()` getter — populated by `Fetch` after a
  successful shallow clone (captured before `.git` is stripped). Used by the
  install pipeline to seed `installed_sha`.
- `ResolveRef(ctx, spec)` method — resolves a plugin spec to its full commit
  SHA using `git fetch --depth=1 + git rev-parse`. Used by the drift sweeper
  to get the current upstream SHA for a tracked ref (tag:vX.Y.Z, tag:latest,
  sha:…, or bare branch).

**Drift sweeper** (`plugins/drift_sweeper.go`):
- Periodic sweep every 1h: SELECTs rows where `tracked_ref != 'none' AND
  installed_sha IS NOT NULL`, resolves upstream SHA, queues drift if different.
- `ListPendingUpdates()` — reads pending queue rows for the admin endpoint.
- `ApplyDriftUpdate()` — marks entry applied (idempotent).
- ctx.Err() guard on ticker arm to avoid post-shutdown work.

**Install pipeline** (`plugins_install_pipeline.go`, `plugins_tracking.go`,
`plugins_install.go`):
- `stageResult.InstalledSHA` field — carries the SHA from Fetch to the DB.
- `recordWorkspacePluginInstall` now accepts and stores `installed_sha`.
- `deleteWorkspacePluginRow` — removes tracking row on uninstall so a stale
  SHA doesn't prevent the next install from creating a fresh row.
- Both Docker and EIC uninstall paths call `deleteWorkspacePluginRow`.

**Admin endpoints** (`handlers/admin_plugin_drift.go`):
- `GET /admin/plugin-updates-pending` — list all pending drift entries.
- `POST /admin/plugin-updates/:id/apply` — re-installs plugin from source_raw
  (re-fetching the same tracked ref), records the new SHA, marks entry applied,
  triggers workspace restart. Idempotent (already-applied returns 200).

**Router wiring** (`router.go`, `cmd/server/main.go`):
- Plugin registry created in main.go and shared between PluginsHandler and drift
  sweeper.
- `router.Setup` accepts optional `pluginResolver` param.
- `PluginsHandler.Sources()` export for the sweeper wiring pattern.

## Tests

- `plugins/github_test.go` — `ResolveRef` coverage (invalid spec, git error,
  not-found mapping, no-panic for all ref shapes).
- `plugins/drift_sweeper_test.go` — `ResolveRef` happy path, stub resolver
  interface compliance.
- `handlers/admin_plugin_drift_test.go` — ListPending (empty, non-empty, DB
  error), Apply (not found, already applied, already dismissed, workspace_plugins
  missing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:39:50 +00:00
96a9868bf5 Merge pull request 'test(canvas): add StatusDot component tests' (#203) from test/canvas-status-dot into main 2026-05-10 00:33:12 +00:00