feat(admin): Storage tab with a close-archive-breaker button - #2135
Conversation
Add Admin → Storage (/admin/storage): lists per-project ProjectData archive circuit breakers (new superadmin GET /api/admin/project-data/storage/archive-sharding/circuit-breakers) and storage telemetry, with a phone-usable Close breaker control that calls the existing POST .../:projectId/archive-sharding/circuit-breaker with state=closed and an operator reason. Until now the only way to close a tripped breaker was a superadmin API call from a desktop browser; nothing auto-closes it, so a stopped archive drain stayed stopped after the underlying fix shipped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e audit Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: raphaeltm/simple-agent-manager/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Playwright Screenshot Evidence — Admin → Storage tabCaptured by
Quality review (every image opened)
|
…l-mock crash task Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|



Summary
/admin/storage): a superadmin-only tab that lists every project's ProjectData archive circuit breaker (state, reason, opened/updated, storage usage) plus storage telemetry, with a Close breaker button that works from a phone.opensince 2026-09-21, root DO at 10.10 GB vs 10.0 GB soft limit).GET /api/admin/project-data/storage/archive-sharding/circuit-breakers(D1-only, 1 round trip, bounded by the existingPROJECT_DATA_ARCHIVE_ROLLOUT_LIST_LIMIT_*config, malformed rows skipped and counted per rule 50). Lives in a new fileapps/api/src/routes/admin/project-data-archive-breakers.tsbecauseproject-data-storage.tsis already over the 500-line ceiling;parseArchiveRolloutLimitmoved there and is reused.POST .../:projectId/archive-sharding/circuit-breakerwithstate: "closed"and an operator-entered reason (default "Closed from admin UI"), then invalidates the list. Server gate:adminRoutes.use('/*', requireAuth(), requireApproved(), requireSuperadmin())(apps/api/src/routes/admin.ts:27); client gate:SuperadminRoute+Adminredirect, tab only visible to superadmins.01M35R2B08V0BBDK80TPC4PC5P.Validation
pnpm lint— eslint on all changed files: cleanpnpm typecheck—tsc --noEmitforapps/apiandapps/web: cleanpnpm test—apps/apiadmin-project-data-archive-breakers.test.ts+admin-project-data-archive-sharding.test.ts: 14/14;apps/webAdminStorage.test.tsx: 4/4, plusapp-routes,AdminUsers,AdminTrials: 16/16. Full suites run in CI.admin-storage-audit.spec.tsat iPhone SE 375x667, iPhone 14 390x844, Desktop 1280x800: 12/12,assertNoOverflowon every captureStaging Verification (REQUIRED for all code changes — merge-blocking)
Deploy Stagingrun 35910276585 succeeded for this branch (2026-09-23 19:43Z)SAM_PLAYWRIGHT_PRIMARY_USER(superadmin) againstapi.sammy.party, thenapp.sammy.party/admin/storageat 375x667 and 1280x800N/A: no infra changesStaging Verification Evidence
Real end-to-end exercise of the feature on the disposable staging fixture project
pr2008-validation-fixture(01M1JPTX00FZNR1GNRXNXMS594), whose breaker was alreadyclosed:GET /api/admin/project-data/storage/archive-sharding/circuit-breakers(new route) returned the one staging breaker with project name and repository joined,skippedRows: 0.state: "open", reason "staging validation: opened to test the admin Close breaker button (PR feat(admin): Storage tab with a close-archive-breaker button #2135)")./admin/storageon a 375x667 mobile context: the card rendered with the Open badge, the reason, opened/updated timestamps and storage usage.0close buttons on the card).state: "closed",openedAt: null, reason equal to the text entered in the dialog,updatedAtadvanced. The fixture is left in its originalclosedstate.pageerror/console.errorcount: 0;document.documentElement.scrollWidth > innerWidth: false.Local captures of steps 3 to 5 (
staging-e2e-1-open.png,-2-dialog.png,-3-closed.png) were opened and reviewed; the only visual nit was an unroundedestimated_days_to_limit(fixed in the follow-up commit).UI Compliance Checklist (Required for UI changes)
sm)aria-labelledby,<label>-wrapped reason input,role="alert"errors,aria-modal, Escape closes;autoFocusavoided perjsx-a11y)Card,Button,Dialog,Input,Alert,Spinnerfrom@simple-agent-manager/ui;formatBytesreused fromdeployment-card-format)<script>tag, empty, error (500), plus the click-through close flow, at 375x667 and 1280x800 (and 390x844); no horizontal overflowUI Screenshot Evidence
Screenshots were taken with Playwright (
apps/web/tests/playwright/admin-storage-audit.spec.ts).Surface: Admin → Storage tab (breaker list, telemetry list, close-breaker dialog)
<script>alert(1)</script>and an emoji, a frozen breaker, an open breaker whose project was deleted so name/repository arenull, and a closed breaker), a 90-char project name, three telemetry rows (101% degraded, 42% warning, a single-character name at 0% with alast_error), an empty response for both lists, a 500 error for both lists, and the stateful close flow (dialog, reason edit, POST body assertion, refreshed list).End-to-End Verification (Required for multi-component changes)
Data Flow Trace
AdminStorage.tsxBreakerCard"Close breaker" →Dialogreason form →useMutation→closeAdminProjectDataArchiveCircuitBreaker()(apps/web/src/lib/api/admin-project-data-storage.ts) →POST /api/admin/project-data/storage/:projectId/archive-sharding/circuit-breakerbehindrequireSuperadmin()(apps/api/src/routes/admin.ts:27) →setProjectDataArchiveCircuitBreaker()(apps/api/src/services/project-data-archive-rollout-controls.ts) D1 upsertstate='closed', opened_at=NULL→invalidateQueries(adminProjectDataStorageQueryKeys.all)→GET .../archive-sharding/circuit-breakers(apps/api/src/routes/admin/project-data-archive-breakers.ts) → card re-renders as Closed. The scheduled sweep's selector queries requirestate='closed', so the drain resumes on the next hourly pass.Capability coverage: the API test closes via the real POST route and reads the change back through the real GET route against SQLite; the Playwright close-flow test drives the real button, dialog and form and asserts the exact POST path/body and the refreshed list; the vitest page test covers the mutation, error path and refetch.
Untested Gaps
The live production breaker close itself is performed by Raphaël after deploy (agents have no production admin login).
Post-Mortem (Required for bug fix PRs)
N/A: feature PR, not a bug fix.
Specialist Review Evidence (Required for agent-authored PRs)
needs-human-reviewlabel added and merge deferred to humanCodeRabbit Review Evidence (Required for agent-authored PRs)
coderabbit-reviewlabel appliedCodeRabbit Notes
Waived by Raphaël for this PR (same shape as #2119/#2120/#2121/#2133); CodeRabbit has not produced a review on any SAM PR since 2026-09-14.
Exceptions (If any)
Agent Preflight (Required)
Classification
External References
N/A: no external service or third-party API involved; internal routes, schemas and UI primitives were read directly (
apps/api/src/routes/admin/project-data-storage.ts,apps/api/src/schemas/admin.ts,packages/ui/src/components/*,apps/web/tests/playwright/audit-helpers.ts).Codebase Impact Analysis
packages/shared/src/types/admin.ts(+ index): breaker/telemetry response types.apps/api/src/routes/admin/project-data-archive-breakers.ts(new) andapps/api/src/routes/admin/project-data-storage.ts(mounts it,parseArchiveRolloutLimitmoved): new bounded D1-only GET; existing POST untouched. I/O budget: GET list = 1 D1 round trip.apps/web/src/pages/AdminStorage.tsx(new, lazy-loaded),apps/web/src/pages/Admin.tsx(tab),apps/web/src/App.tsx(route),apps/web/src/lib/api/admin-project-data-storage.ts+index.ts,apps/web/src/lib/query-options/admin-project-data-storage.ts+index.ts: TanStack Query surface, stale-while-revalidate (spinner only with no data, refetch never unmounts content).apps/api/tests/unit/routes/admin-project-data-archive-breakers.test.ts,apps/web/tests/unit/AdminStorage.test.tsx,apps/web/tests/playwright/admin-storage-audit.spec.ts.Documentation & Specs
apps/www/src/content/docs/docs/guides/self-hosting.mdx: new "Storage and archive circuit breakers" subsection under First Login & Admin Access describing the tab and the route it calls. No spec files touched.Constitution & Risk Check
Principle XI: no hardcoded URLs/timeouts/limits; the list limit resolves through the existing
PROJECT_DATA_ARCHIVE_ROLLOUT_LIST_LIMIT_DEFAULT/MAXenv config; the client usesAPI_URL. Risk: the close action is a superadmin mutation, gated server-side by the existing admin middleware and confirmed via a dialog with a required reason; it never thaws frozen migrations (documented in the dialog). Rule 50 fault isolation on the new list read; rule 18 respected by placing the route in a new file.