Merge branch 'main' into docs/gitea-operational-quirks-runbook
All checks were successful
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 18s
CI / Detect changes (pull_request) Successful in 44s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 18s
sop-tier-check / tier-check (pull_request) Successful in 22s
E2E API Smoke Test / detect-changes (pull_request) Successful in 1m7s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 59s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 53s
CI / Platform (Go) (pull_request) Successful in 7s
CI / Canvas (Next.js) (pull_request) Successful in 9s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 8s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 9s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 10s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 8s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
All checks were successful
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 18s
CI / Detect changes (pull_request) Successful in 44s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 18s
sop-tier-check / tier-check (pull_request) Successful in 22s
E2E API Smoke Test / detect-changes (pull_request) Successful in 1m7s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 59s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 53s
CI / Platform (Go) (pull_request) Successful in 7s
CI / Canvas (Next.js) (pull_request) Successful in 9s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 8s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 9s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 10s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 8s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
This commit is contained in:
commit
bcda9dbf2e
@ -63,12 +63,21 @@ jobs:
|
||||
sweep:
|
||||
name: Sweep e2e orgs
|
||||
runs-on: ubuntu-latest
|
||||
# Phase 3 (RFC #219 §1): surface broken workflows without blocking.
|
||||
continue-on-error: true
|
||||
# NOTE: Phase 3 (RFC #219 §1) `continue-on-error: true` removed
|
||||
# 2026-05-11. The "surface broken workflows without blocking"
|
||||
# rationale was correctly applied to advisory/lint workflows but
|
||||
# wrong for this janitor — silent failure here masks real-money
|
||||
# tenant leaks. Hongming observed 15 leaked EC2 in molecule-canary
|
||||
# (004947743811) us-east-2 at 11:05Z 2026-05-11 because the sweep
|
||||
# had been exiting 2 every tick and the failure was swallowed.
|
||||
# See `feedback_strict_root_only_after_class_a` — critical janitors
|
||||
# must fail loud. A follow-up `notify-failure` step below also
|
||||
# surfaces breakage to ops even if branch-protection wiring is
|
||||
# adjusted to keep this off the required-checks list.
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
MOLECULE_CP_URL: https://staging-api.moleculesai.app
|
||||
ADMIN_TOKEN: ${{ secrets.MOLECULE_STAGING_ADMIN_TOKEN }}
|
||||
ADMIN_TOKEN: ${{ secrets.CP_STAGING_ADMIN_API_TOKEN }}
|
||||
MAX_AGE_MINUTES: ${{ github.event.inputs.max_age_minutes || '30' }}
|
||||
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
|
||||
# Refuse to delete more than this many orgs in one tick. If the
|
||||
@ -81,7 +90,7 @@ jobs:
|
||||
- name: Verify admin token present
|
||||
run: |
|
||||
if [ -z "$ADMIN_TOKEN" ]; then
|
||||
echo "::error::MOLECULE_STAGING_ADMIN_TOKEN not set"
|
||||
echo "::error::CP_STAGING_ADMIN_API_TOKEN not set"
|
||||
exit 2
|
||||
fi
|
||||
echo "Admin token present ✓"
|
||||
@ -242,3 +251,17 @@ jobs:
|
||||
if: env.DRY_RUN == 'true'
|
||||
run: |
|
||||
echo "DRY RUN — would have deleted ${{ steps.identify.outputs.count }} org(s) AND triggered orphan-tunnels cleanup. Re-run with dry_run=false to actually delete."
|
||||
|
||||
- name: Notify on sweep failure
|
||||
# Fail-loud companion to dropping `continue-on-error: true`.
|
||||
# If any prior step failed (missing token, CP 5xx, safety-cap
|
||||
# tripped, etc.) emit a clearly-tagged ::error:: line so the
|
||||
# Gitea runs UI + any log-tail consumer (Loki SOPRefireRule)
|
||||
# flags this. Without this step, an early `exit 2` shows as a
|
||||
# red run but the message can scroll past in busy log windows;
|
||||
# the explicit tag here is greppable from the orchestrator
|
||||
# triage loop.
|
||||
if: failure()
|
||||
run: |
|
||||
echo "::error::sweep-stale-e2e-orgs FAILED — staging tenants are LEAKING. See prior step logs. Common causes: (a) CP_STAGING_ADMIN_API_TOKEN secret missing/rotated, (b) staging-api.moleculesai.app 5xx, (c) safety-cap tripped (CP admin API returning malformed orgs). Manual cleanup of leaked EC2 + DNS may be required while this is broken."
|
||||
exit 1
|
||||
|
||||
@ -41,9 +41,10 @@ const pendingApproval = (id = "a1", workspaceId = "ws-1"): {
|
||||
created_at: "2026-05-10T10:00:00Z",
|
||||
});
|
||||
|
||||
// Shared spy reference so individual tests can call mockGet.mockRestore()
|
||||
// without needing to pass it through beforeEach → it scope chain.
|
||||
// Shared spy references so individual tests can reset or reject the POST mock
|
||||
// without needing to call spyOn again (which would create a duplicate spy).
|
||||
let mockGet: ReturnType<typeof vi.spyOn>;
|
||||
let mockPost: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
// ─── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@ -139,8 +140,8 @@ describe("ApprovalBanner — renders approval cards", () => {
|
||||
describe("ApprovalBanner — decisions", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.spyOn(api, "get").mockResolvedValueOnce([pendingApproval("a1")]);
|
||||
vi.spyOn(api, "post").mockResolvedValue({});
|
||||
mockGet = vi.spyOn(api, "get").mockResolvedValueOnce([pendingApproval("a1")]);
|
||||
mockPost = vi.spyOn(api, "post").mockResolvedValue({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -196,7 +197,7 @@ describe("ApprovalBanner — decisions", () => {
|
||||
});
|
||||
|
||||
it("shows an error toast when POST fails", async () => {
|
||||
vi.mocked(api.post).mockRejectedValueOnce(new Error("Network error"));
|
||||
mockPost.mockReset().mockRejectedValue(new Error("Network error"));
|
||||
render(<ApprovalBanner />);
|
||||
await act(async () => { await vi.runOnlyPendingTimersAsync(); });
|
||||
fireEvent.click(screen.getAllByRole("button", { name: /approve/i })[0]);
|
||||
@ -208,8 +209,9 @@ describe("ApprovalBanner — decisions", () => {
|
||||
});
|
||||
|
||||
it("keeps the card visible when the POST fails", async () => {
|
||||
// Use mockRejectedValueOnce on the same spy as beforeEach (don't call spyOn again)
|
||||
vi.mocked(api.post).mockRejectedValueOnce(new Error("Network error"));
|
||||
// Reset the post mock before rejecting so the beforeEach's resolved value
|
||||
// is gone and we get a clean rejection instead of a resolved→rejected queue.
|
||||
mockPost.mockReset().mockRejectedValue(new Error("Network error"));
|
||||
render(<ApprovalBanner />);
|
||||
await act(async () => { await vi.runOnlyPendingTimersAsync(); });
|
||||
fireEvent.click(screen.getAllByRole("button", { name: /approve/i })[0]);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user