Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
26f18a1
feat(workspace): pre-stop serialization for pause/resume (closes #1386)
molecule-ai[bot] Apr 21, 2026
cd96e4a
chore: PM-triggered CI re-run
molecule-ai[bot] Apr 21, 2026
6f9d5b9
chore: force Platform(Go) CI run on main — validate go vet clean
molecule-ai[bot] Apr 21, 2026
e6b40b2
feat(e2e): staging full-SaaS workflow — per-run org provision + leak-…
Apr 21, 2026
0ea0918
feat(e2e): canary + canvas Playwright workflows; delegation mechanics
Apr 21, 2026
92277e1
feat(e2e): pivot to admin-bearer-only auth + add sanity self-check wo…
Apr 21, 2026
c2daceb
fix(e2e): CP DELETE /cp/admin/tenants body uses 'confirm', not 'confi…
Apr 21, 2026
a9376d6
fix(e2e): poll instance_status not status in staging harness
Apr 21, 2026
cb7d6f8
fix(e2e): derive tenant domain from CP URL (staging vs prod)
Apr 21, 2026
81a5e5e
fix(e2e): send X-Molecule-Org-Id on tenant calls
Apr 21, 2026
6e2170b
fix(e2e): safety-net teardown only sweeps this run's orgs
Apr 21, 2026
a06dd0e
fix(tenant-image): remove node user so canvas uid 1000 can be created
Apr 21, 2026
70dfb21
research: add crewai-competitive-proof-points-brief.md
molecule-ai[bot] Apr 21, 2026
f7a8b8f
research: add enterprise-case-study-legal-clearance-brief.md
molecule-ai[bot] Apr 21, 2026
3aebc1e
research: add enterprise-case-study-pipeline-targeting-brief.md
molecule-ai[bot] Apr 21, 2026
6752187
fix(P0): CWE-22 path traversal in copyFilesToContainer + ContextMenu …
molecule-ai[bot] Apr 21, 2026
d43d1b3
fix(core): resolve main build — remove duplicate SSRF function declar…
Apr 21, 2026
b0eb56e
fix(P0): CWE-22 path traversal in copyFilesToContainer + ContextMenu …
Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci-trigger/RERUN
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CI re-trigger at Tue Apr 21 15:40:21 UTC 2026\n
153 changes: 153 additions & 0 deletions .github/workflows/canary-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: Canary — staging SaaS smoke (every 30 min)

# Minimum viable health check: provisions one Hermes workspace on a fresh
# staging org, sends one A2A message, verifies PONG, tears down. ~8 min
# wall clock. Pages on failure by opening a GitHub issue; auto-closes the
# issue on the next green run.
#
# The full-SaaS workflow (e2e-staging-saas.yml) covers the broader surface
# but runs only on provisioning-critical pushes + nightly — this one
# catches drift in the 30-min window between those runs (AMI health, CF
# cert rotation, WorkOS session stability, etc.).
#
# Lean mode: E2E_MODE=canary skips the child workspace + HMA memory +
# peers/activity checks. One parent workspace + one A2A turn is enough
# to signal "SaaS stack end-to-end is alive."

on:
schedule:
# Every 30 min. Cron on GitHub-hosted runners has a known drift of
# a few minutes under load — that's fine for a canary.
- cron: '*/30 * * * *'
workflow_dispatch:

# Serialise with the full-SaaS workflow so they don't contend for the
# same org-create quota on staging. Different group key from
# e2e-staging-saas since we don't mind queueing canaries behind one
# full run, but two canaries SHOULD queue against each other.
concurrency:
group: canary-staging
cancel-in-progress: false

permissions:
# Needed to open / close the alerting issue.
issues: write
contents: read

jobs:
canary:
name: Canary smoke
runs-on: ubuntu-latest
timeout-minutes: 15

env:
MOLECULE_CP_URL: https://staging-api.moleculesai.app
MOLECULE_ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}
E2E_MODE: canary
E2E_RUNTIME: hermes
E2E_RUN_ID: "canary-${{ github.run_id }}"

steps:
- uses: actions/checkout@v4

- name: Verify admin token present
run: |
if [ -z "$MOLECULE_ADMIN_TOKEN" ]; then
echo "::error::MOLECULE_STAGING_ADMIN_TOKEN not set"
exit 2
fi

- name: Canary run
id: canary
run: bash tests/e2e/test_staging_full_saas.sh

# Alerting: open an issue on first failure, auto-close on recovery.
# Title includes a stable marker so multiple consecutive failures
# don't spam — they just add comments to the existing issue.
- name: Open issue on failure
if: failure()
uses: actions/github-script@v7
with:
script: |
const title = '🔴 Canary failing: staging SaaS smoke';
const runURL = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body =
`Canary run failed at ${new Date().toISOString()}.\n\n` +
`Run: ${runURL}\n\n` +
`This issue auto-closes on the next green canary run. ` +
`Consecutive failures add a comment here rather than a new issue.`;

// Find an existing open canary issue (stable title match).
const { data: existing } = await github.rest.issues.listForRepo({
owner: context.repo.owner, repo: context.repo.repo,
state: 'open', labels: 'canary-staging',
per_page: 10,
});
const match = existing.find(i => i.title === title);

if (match) {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: match.number,
body: `Canary still failing. ${runURL}`,
});
core.info(`Commented on existing issue #${match.number}`);
} else {
await github.rest.issues.create({
owner: context.repo.owner, repo: context.repo.repo,
title, body,
labels: ['canary-staging', 'bug'],
});
core.info('Opened new canary failure issue');
}

- name: Auto-close canary issue on success
if: success()
uses: actions/github-script@v7
with:
script: |
const title = '🔴 Canary failing: staging SaaS smoke';
const { data: open } = await github.rest.issues.listForRepo({
owner: context.repo.owner, repo: context.repo.repo,
state: 'open', labels: 'canary-staging',
per_page: 10,
});
const match = open.find(i => i.title === title);
if (match) {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: match.number,
body: `Canary recovered at ${new Date().toISOString()}. Closing.`,
});
await github.rest.issues.update({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: match.number,
state: 'closed',
});
core.info(`Closed recovered canary issue #${match.number}`);
}

- name: Teardown safety net
if: always()
env:
ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}
run: |
set +e
orgs=$(curl -sS "$MOLECULE_CP_URL/cp/admin/orgs" \
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
today = __import__('datetime').date.today().strftime('%Y%m%d')
candidates = [o['slug'] for o in d.get('orgs', [])
if o.get('slug','').startswith(f'e2e-{today}-canary-')
and o.get('status') not in ('purged',)]
print('\n'.join(candidates))
" 2>/dev/null)
for slug in $orgs; do
curl -sS -X DELETE "$MOLECULE_CP_URL/cp/admin/tenants/$slug" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"confirm\":\"$slug\"}" >/dev/null || true
done
exit 0
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ jobs:
run: |
# For push events: diff against previous commit (handles merge commits)
# For PR events: diff against the base branch
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${GITHUB_BASE_REF:-${{ github.event.before }}}"
if [ "${{ github.event_name }}" = "pull_request" ] && [ -n "${{ github.event.pull_request.base.sha }}" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
else
BASE="${{ github.event.before }}"
fi
# Fallback: if BASE is empty or all zeros (new branch), run everything
if [ -z "$BASE" ] || echo "$BASE" | grep -qE '^0+$'; then
Expand Down
116 changes: 116 additions & 0 deletions .github/workflows/e2e-staging-canvas.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: E2E Staging Canvas (Playwright)

# Playwright test suite that provisions a fresh staging org per run and
# verifies every workspace-panel tab renders without crashing. Complements
# e2e-staging-saas.yml (which tests the API shape) by exercising the
# actual browser + canvas bundle against live staging.
#
# Triggers: push to main or PR touching canvas sources + this workflow,
# manual dispatch, and weekly cron to catch browser/runtime drift even
# when canvas is quiet.

on:
push:
branches: [main]
paths:
- 'canvas/**'
- '.github/workflows/e2e-staging-canvas.yml'
pull_request:
branches: [main]
paths:
- 'canvas/**'
- '.github/workflows/e2e-staging-canvas.yml'
workflow_dispatch:
schedule:
# Weekly on Sunday 08:00 UTC — catches Chrome / Playwright / Next.js
# release-note-shaped regressions that don't ride in with a PR.
- cron: '0 8 * * 0'

concurrency:
group: e2e-staging-canvas
cancel-in-progress: false

jobs:
playwright:
name: Canvas tabs E2E
runs-on: ubuntu-latest
timeout-minutes: 40

env:
CANVAS_E2E_STAGING: '1'
MOLECULE_CP_URL: https://staging-api.moleculesai.app
MOLECULE_ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}

defaults:
run:
working-directory: canvas

steps:
- uses: actions/checkout@v4

- name: Verify admin token present
run: |
if [ -z "$MOLECULE_ADMIN_TOKEN" ]; then
echo "::error::Missing MOLECULE_STAGING_ADMIN_TOKEN"
exit 2
fi

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: canvas/package-lock.json

- name: Install canvas deps
run: npm ci

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Run staging canvas E2E
run: npx playwright test --config=playwright.staging.config.ts

- name: Upload Playwright report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report-staging
path: canvas/playwright-report-staging/
retention-days: 14

- name: Upload screenshots on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-screenshots
path: canvas/test-results/
retention-days: 14

# Safety-net teardown mirrors the bash-harness workflow — if
# globalTeardown didn't run (worker crash, runner cancel), this
# step sweeps any e2e-canvas-* org tagged with today's date.
- name: Teardown safety net
if: always()
env:
ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}
run: |
set +e
orgs=$(curl -sS "$MOLECULE_CP_URL/cp/admin/orgs" \
-H "Authorization: Bearer $ADMIN_TOKEN" 2>/dev/null \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
today = __import__('datetime').date.today().strftime('%Y%m%d')
candidates = [o['slug'] for o in d.get('orgs', [])
if o.get('slug','').startswith(f'e2e-canvas-{today}-')
and o.get('status') not in ('purged',)]
print('\n'.join(candidates))
" 2>/dev/null)
for slug in $orgs; do
curl -sS -X DELETE "$MOLECULE_CP_URL/cp/admin/tenants/$slug" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"confirm\":\"$slug\"}" >/dev/null || true
done
exit 0
Loading
Loading