Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .github/workflows/e2e-staging-saas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ jobs:
# retrieval + teardown. Configure in
# Settings → Secrets and variables → Actions → Repository secrets.
MOLECULE_ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}
# OpenAI key for workspace LLM calls (section 8 A2A). Without it,
# Hermes runtime crashes at boot with "No provider API key found".
# Configure at Settings → Secrets → Actions → MOLECULE_STAGING_OPENAI_KEY.
E2E_OPENAI_API_KEY: ${{ secrets.MOLECULE_STAGING_OPENAI_KEY }}
E2E_RUNTIME: ${{ github.event.inputs.runtime || 'hermes' }}
E2E_RUN_ID: "${{ github.run_id }}-${{ github.run_attempt }}"
E2E_KEEP_ORG: ${{ github.event.inputs.keep_org && '1' || '0' }}
Expand All @@ -93,6 +97,14 @@ jobs:
fi
echo "Admin token present ✓"

- name: Verify OpenAI key present
run: |
if [ -z "$E2E_OPENAI_API_KEY" ]; then
echo "::error::MOLECULE_STAGING_OPENAI_KEY secret not set — workspaces will fail at boot with 'No provider API key found'"
exit 2
fi
echo "OpenAI key present ✓ (len=${#E2E_OPENAI_API_KEY})"

- name: CP staging health preflight
run: |
code=$(curl -sS -o /dev/null -w "%{http_code}" --max-time 10 "$MOLECULE_CP_URL/health")
Expand Down
26 changes: 24 additions & 2 deletions tests/e2e/test_staging_full_saas.sh
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,27 @@ tenant_call() {
}

# ─── 5. Provision parent workspace ─────────────────────────────────────
# Runtimes like hermes crash at boot with "No provider API key found"
# if nothing in the standard env-var list is set. Inject the API key
# from E2E_OPENAI_API_KEY so the runtime can actually start — it's
# per-workspace secret, so it's persisted as a workspace_secret and
# materialized into the container env. Missing key falls through to
# an empty secrets map; workspace will still fail but the error is
# expected and actionable.
SECRETS_JSON='{}'
if [ -n "${E2E_OPENAI_API_KEY:-}" ]; then
# MODEL_PROVIDER is a full model slug in 'provider:model' format per
# workspace/config.py:258. Using just "openai" gets parsed as the
# model name → 404 model_not_found. Also set OPENAI_BASE_URL to
# OpenAI's own endpoint — default is openrouter.ai which would need
# a different key format.
SECRETS_JSON="{\"OPENAI_API_KEY\":\"$E2E_OPENAI_API_KEY\",\"OPENAI_BASE_URL\":\"https://api.openai.com/v1\",\"MODEL_PROVIDER\":\"openai:gpt-4o\"}"
fi

log "5/11 Provisioning parent workspace (runtime=$RUNTIME)..."
PARENT_RESP=$(tenant_call POST /workspaces \
-H "Content-Type: application/json" \
-d "{\"name\":\"E2E Parent\",\"runtime\":\"$RUNTIME\",\"tier\":2,\"model\":\"gpt-4o\"}")
-d "{\"name\":\"E2E Parent\",\"runtime\":\"$RUNTIME\",\"tier\":2,\"model\":\"gpt-4o\",\"secrets\":$SECRETS_JSON}")
PARENT_ID=$(echo "$PARENT_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
log " PARENT_ID=$PARENT_ID"

Expand All @@ -242,7 +259,7 @@ if [ "$MODE" = "full" ]; then
log "6/11 Provisioning child workspace..."
CHILD_RESP=$(tenant_call POST /workspaces \
-H "Content-Type: application/json" \
-d "{\"name\":\"E2E Child\",\"runtime\":\"$RUNTIME\",\"tier\":2,\"model\":\"gpt-4o\",\"parent_id\":\"$PARENT_ID\"}")
-d "{\"name\":\"E2E Child\",\"runtime\":\"$RUNTIME\",\"tier\":2,\"model\":\"gpt-4o\",\"parent_id\":\"$PARENT_ID\",\"secrets\":$SECRETS_JSON}")
CHILD_ID=$(echo "$CHILD_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
log " CHILD_ID=$CHILD_ID"
else
Expand Down Expand Up @@ -359,8 +376,13 @@ print(json.dumps({
}))
")
set +e
# Raw curl (not tenant_call) because this call carries an extra
# X-Source-Workspace-Id header. Must still send X-Molecule-Org-Id
# or TenantGuard 404s — previously missing, caused section 10 to
# fail rc=22 despite everything upstream being correct (2026-04-21).
DELEG_RESP=$(curl "${CURL_COMMON[@]}" -X POST "$TENANT_URL/workspaces/$CHILD_ID/a2a" \
-H "Authorization: Bearer $EFFECTIVE_TENANT_TOKEN" \
-H "X-Molecule-Org-Id: $ORG_ID" \
-H "X-Source-Workspace-Id: $PARENT_ID" \
-H "Content-Type: application/json" \
-d "$DELEG_PAYLOAD")
Expand Down
Loading