Skip to content

test(smoke): verify bytes-after-delete and bytes-after-expiry#8

Merged
isuttell merged 2 commits into
mainfrom
worktree-agent-a5e1c39fabb781169
May 22, 2026
Merged

test(smoke): verify bytes-after-delete and bytes-after-expiry#8
isuttell merged 2 commits into
mainfrom
worktree-agent-a5e1c39fabb781169

Conversation

@isuttell

@isuttell isuttell commented May 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Backlog item #7: smoke tests now assert R2 bytes are actually purged after artifact delete and after expiry-driven cleanup, closing the ADR 0048 partial state.

Done

  • pnpm smoke:local and pnpm smoke:hosted (preview target) cover the full purge path: artifact has R2 objects → admin delete (or force-expire + scheduled cleanup) → R2 prefix empty → signed URL 404 → denylist KV entry present.
  • apps/api/src/index.ts cleanup handler + deleteArtifact route now call purgeArtifactBytes(env, artifactId); previously cleanup wrote denylist entries but left bytes.
  • packages/db/src/index.ts gains forceExpireArtifact on both Local and Postgres repositories.
  • pnpm verify green (49/49).

Test-only routes

POST /__test__/force-expire, GET /__test__/r2-list, GET /__test__/denylist. Double-gated:

  1. isNonProductionEnv(env) returns false for AGENT_PASTE_ENV === "production" | "live" and when the var is unset (fail-closed). apps/api/wrangler.jsonc sets "production" on the production env and "preview" on preview.
  2. authenticateAdmin bearer-token check.

Files

  • apps/api/src/index.ts
  • apps/api/wrangler.jsonc
  • packages/db/src/index.ts
  • scripts/local-mvp-server.mjs
  • scripts/smoke-local-mvp.mjs
  • scripts/smoke-hosted.mjs

Summary by CodeRabbit

  • New Features
    • Added R2-backed artifact storage and a way to force artifact expiration, with backend purging of stored artifact bytes when artifacts are deleted or expired.
  • Chores
    • Added environment flag to distinguish preview/dev/production behavior.
  • Tests
    • Expanded smoke/local tests and added verification helpers to confirm artifact bytes, listings, and denylist entries are purged after deletion or expiry.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 15bd83e7-d608-48a4-aeea-10c0e8dcfd9b

📥 Commits

Reviewing files that changed from the base of the PR and between 04149da and 747541b.

📒 Files selected for processing (1)
  • scripts/deploy-pr-preview.mjs

Walkthrough

This PR implements artifact expiration and R2 object cleanup across the system. It adds a forceExpireArtifact method to database repositories, updates API contracts to define R2Bucket and refine deletion response types, adds environment-gated test-only admin routes for verification, and integrates R2 object purging into both artifact deletion and TTL cleanup workflows. The changes include environment configuration for production/preview, local MVP infrastructure extensions, and comprehensive smoke tests that verify end-to-end byte purging behavior after both deletion and expiry.

Sequence Diagram: Artifact Deletion with R2 Purge

sequenceDiagram
  participant Client
  participant ApiHandler as API Handler (deleteArtifact)
  participant Database as Database
  participant R2 as R2 Bucket
  Client->>ApiHandler: DELETE /artifacts/{id}
  ApiHandler->>Database: deleteArtifact(id)
  Database-->>ApiHandler: {artifact_id, deleted_at}
  ApiHandler->>R2: list(prefix: artifacts/{id}/...)
  R2-->>ApiHandler: [keys...], cursor
  ApiHandler->>R2: delete([keys...])
  R2-->>ApiHandler: deleted count
  ApiHandler-->>Client: {artifact_id, deleted_at, deleted_r2_objects}
Loading

Sequence Diagram: Artifact TTL Cleanup with R2 Purge

sequenceDiagram
  participant Admin
  participant ApiHandler as API Handler (cleanup)
  participant Database as Database
  participant R2 as R2 Bucket
  participant KV as Denylist KV
  Admin->>ApiHandler: POST /admin/cleanup
  ApiHandler->>Database: getExpiredArtifacts()
  Database-->>ApiHandler: [artifact_ids...]
  loop per artifact_id (string)
    ApiHandler->>ApiHandler: deny(artifact_id)
    ApiHandler->>KV: put(denykey)
    ApiHandler->>R2: purgeArtifactBytes(prefix: artifacts/{id}/...)
    R2-->>ApiHandler: deleted count
  end
  ApiHandler-->>Admin: {deleted_r2_objects: total_count}
Loading

Possibly related PRs

  • zaks-io/agent-paste#4: Refactors the deleteArtifact admin mutation to run inside the runCommand idempotency wrapper and updates the ApiDatabase contract, which connects to this PR's changes to the same handler and addition of forceExpireArtifact method to ApiDatabase.

Poem

🐰 I hopped through code with nimble paws,
I set new timers and fixed the laws,
R2 crumbs swept clean in tidy rows,
Tests that check where the cold wind blows,
Purged and done — the rabbit knows!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: adding smoke test verification for bytes purged after artifact deletion and expiry, which is the core objective of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-agent-a5e1c39fabb781169

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

The PR-preview deploy uses ad-hoc per-PR worker names (agent-paste-api-pr-N),
so the env-scoped vars in apps/api/wrangler.jsonc don't apply. Without
AGENT_PASTE_ENV the new isNonProductionEnv check defaults to fail-closed and
returns 404 on /__test__/* routes, breaking the bytes-after-delete smoke.
Inject the var here so PR previews behave like the preview environment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@isuttell isuttell merged commit 7b95bc3 into main May 22, 2026
4 checks passed
@isuttell isuttell deleted the worktree-agent-a5e1c39fabb781169 branch May 22, 2026 01:39
isuttell added a commit that referenced this pull request May 22, 2026
The new assertActorRateLimitFires helper exhausts the actor's 60/min
budget by hammering an upload mutation. The next assertion,
assertBytesPurgedAfterExpiry, republishes through the same user API key
and was hitting rate_limited_actor at the publish step.

Reorder so the rate-limit probe runs last; bytes-after-delete and
-after-expiry now run on a fresh budget.

Also reconcile docs/ops/project-status.md: PR #6 (CSP) and PR #8
(bytes-after-delete/expiry) closed two backlog items without updating
this doc. Moved both into Recently Completed and renumbered.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
isuttell added a commit that referenced this pull request May 22, 2026
…ting (#9)

* fix(api,upload): replay idempotent mutations before rate-limit accounting

Adds a SELECT-only peekIdempotentReplay helper to @agent-paste/commands
and uses it in both upload mutation routes to short-circuit known-good
retries before either rate-limit binding is touched. Hosted smoke now
hammers POST /v1/upload-sessions with unique idempotency keys to confirm
429 with the rate_limited_actor envelope on preview targets.

* docs(status): close out item #1 and renumber backlog

PR #9 finished the rate-limit work, so mark ADR 0039/0064 done, drop
the backlog entry, and shift items #2-#11 down one slot. Updates the
phase partition note to match the new numbering.

* debug: dump content fetch context on failure

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: drop temporary debug print from smoke-hosted

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(smoke): run rate-limit probe after purge tests; reconcile backlog

The new assertActorRateLimitFires helper exhausts the actor's 60/min
budget by hammering an upload mutation. The next assertion,
assertBytesPurgedAfterExpiry, republishes through the same user API key
and was hitting rate_limited_actor at the publish step.

Reorder so the rate-limit probe runs last; bytes-after-delete and
-after-expiry now run on a fresh budget.

Also reconcile docs/ops/project-status.md: PR #6 (CSP) and PR #8
(bytes-after-delete/expiry) closed two backlog items without updating
this doc. Moved both into Recently Completed and renumbered.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(smoke): fire rate-limit probe in parallel waves

Sequential 120 attempts at ~2/sec spread across multiple Cloudflare
worker isolates means no single isolate ever sees the 60-in-60s
threshold. Replace the serial loop with four parallel waves of 80
requests each. The burst forces a single isolate's counter past the
limit before the trailing window slides.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
isuttell added a commit that referenced this pull request May 22, 2026
biome lint rule noUnusedFunctionParameters surfaces on cache miss.
PR #8 landed via cache hit; downstream PRs with cache misses fail Validate.
isuttell added a commit that referenced this pull request May 22, 2026
* docs(ops): bootstrap hosting checklist for click-ops hand-off

Enumerates DNS verification, Bitwarden vault entries, GitHub Production
environment approval policy, and end-to-end verification for backlog
item #8 so the human click-ops takes minutes instead of hours.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(ops): correct non-sensitive identifier guidance for GH org inheritance

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant