Skip to content

feat(upload): add ephemeral key prefix to POST /v1/upload#36

Open
thomashebrard wants to merge 1 commit into
devfrom
feature/sandbox-ephemeral-upload
Open

feat(upload): add ephemeral key prefix to POST /v1/upload#36
thomashebrard wants to merge 1 commit into
devfrom
feature/sandbox-ephemeral-upload

Conversation

@thomashebrard

@thomashebrard thomashebrard commented Jul 8, 2026

Copy link
Copy Markdown
Member

Add an optional ephemeral: bool = False field to UploadRequest. When true, the stored object lands under {user_id}/ephemeral/{uuid}.{ext} instead of {user_id}/assets/…. Everything else (auth gate, base64 decode, size cap, provider store, pipelex-storage:// return) is unchanged.

The key keeps user_id as its first segment so the storage-URL resolver (storage.py::parse_storage_uri) still gates ownership on segments[0] == requester user_id — ephemeral files remain resolvable by their owner. The /ephemeral/ segment separates synthetic, throwaway sandbox inputs from durable user attachments under /assets/.

A 7-day auto-delete lifecycle for ephemeral objects is planned but deferred: because the key is user_id-first, no single S3 prefix rule can match */ephemeral/, so the lifecycle must be tag-based — which needs pipelex storage-provider tag-on-upload support (a release-gated core change). This PR ships the prefix separation; the lifecycle lands later.

Additive optional field, extra="forbid" preserved — no breaking change.


Summary by cubic

Add an optional ephemeral flag to POST /v1/upload to route files under {user_id}/ephemeral/... when true, keeping durable uploads under /assets/. Ownership checks and all other upload behavior are unchanged.

  • New Features
    • Adds UploadRequest.ephemeral (default false) to select storage prefix.
    • Storage key format: {user_id}/{ephemeral|assets}/{uuid}.{ext}; user_id stays first for ownership gating.
    • Adds unit tests to assert default /assets/ and ephemeral=true /ephemeral/ paths.
    • No lifecycle changes in this PR; 7‑day auto-delete for ephemerals will ship later.

Written for commit a0be00f. Summary will update on new commits.

Review in cubic

Add an optional `ephemeral: bool = False` field to `UploadRequest`. When
true, the stored object lands under `{user_id}/ephemeral/{uuid}.{ext}`
instead of `{user_id}/assets/…`. Everything else (auth gate, base64
decode, size cap, provider store, pipelex-storage:// return) is unchanged.

The key keeps `user_id` as its first segment so the storage-URL resolver
(`storage.py::parse_storage_uri`) still gates ownership on
`segments[0] == requester user_id` — ephemeral files remain resolvable by
their owner. The `/ephemeral/` segment separates synthetic, throwaway
sandbox inputs from durable user attachments under `/assets/`.

A 7-day auto-delete lifecycle for ephemeral objects is planned but
deferred: because the key is user_id-first, no single S3 prefix rule can
match `*/ephemeral/`, so the lifecycle must be tag-based — which needs
pipelex storage-provider tag-on-upload support (a release-gated core
change). This PR ships the prefix separation; the lifecycle lands later.

Additive optional field, `extra="forbid"` preserved — no breaking change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/unit/test_uploader.py">

<violation number="1" location="tests/unit/test_uploader.py:134">
P3: The test docstring implies the existing or planned S3 lifecycle rule filters on the `/ephemeral/` prefix, but the PR description explicitly says that's not possible — because the key is `user_id`-first, no single prefix rule can match `*/ephemeral/`, so the lifecycle must be tag-based and is deferred. Consider aligning the docstring with the actual plan (tag-based, deferred) to avoid misleading future readers.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

assert "/ephemeral/" not in key

def test_ephemeral_upload_lands_under_ephemeral_prefix(self, mocker: MockerFixture):
"""`ephemeral=true` routes the object under '/ephemeral/', the exact key segment the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The test docstring implies the existing or planned S3 lifecycle rule filters on the /ephemeral/ prefix, but the PR description explicitly says that's not possible — because the key is user_id-first, no single prefix rule can match */ephemeral/, so the lifecycle must be tag-based and is deferred. Consider aligning the docstring with the actual plan (tag-based, deferred) to avoid misleading future readers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/unit/test_uploader.py, line 134:

<comment>The test docstring implies the existing or planned S3 lifecycle rule filters on the `/ephemeral/` prefix, but the PR description explicitly says that's not possible — because the key is `user_id`-first, no single prefix rule can match `*/ephemeral/`, so the lifecycle must be tag-based and is deferred. Consider aligning the docstring with the actual plan (tag-based, deferred) to avoid misleading future readers.</comment>

<file context>
@@ -116,6 +116,35 @@ def test_storage_key_uses_authenticated_user_id(self, mocker: MockerFixture, fil
+        assert "/ephemeral/" not in key
+
+    def test_ephemeral_upload_lands_under_ephemeral_prefix(self, mocker: MockerFixture):
+        """`ephemeral=true` routes the object under '/ephemeral/', the exact key segment the
+        pipelex-api-infra S3 lifecycle rule filters on for 7-day auto-deletion.
+        """
</file context>

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds an ephemeral upload option for stored files. The main changes are:

  • Adds an optional ephemeral field to UploadRequest.
  • Stores ephemeral uploads under {user_id}/ephemeral/{uuid}.{ext}.
  • Keeps default uploads under {user_id}/assets/{uuid}.{ext}.
  • Adds tests for default and ephemeral storage prefixes.

Confidence Score: 5/5

Safe to merge with minimal risk.

The change is small and additive. Existing auth, base64 decoding, size validation, storage calls, and response behavior are unchanged. Tests cover both the default upload path and the new ephemeral prefix path.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex captured the old routing behavior by running the HEAD^ worktree, confirming that default_ephemeral_omitted stored under /assets with HTTP 200 OK and ephemeral_true returned HTTP 422 Unprocessable Entity with no storage call.
  • T-Rex validated the current code behavior by running the after-state harness, showing both POST /upload requests succeed with HTTP 200 OK, with omitted fields stored under /assets and ephemeral fields under /ephemeral/.
  • T-Rex generated a focused runtime harness using FastAPI TestClient, with a mocked authenticated RequestUser and a mocked storage provider that records storage.store kwargs and returns a pipelex-storage URL.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
api/routes/uploader.py Adds the optional ephemeral request flag and switches upload storage keys between assets and ephemeral while preserving user-first ownership scoping.
tests/unit/test_uploader.py Adds tests for default assets uploads and explicit ephemeral uploads under the new ephemeral key segment.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant UploadAPI as POST /upload
participant Auth as get_request_user
participant Storage as Storage Provider

Client->>UploadAPI: filename, data, content_type?, ephemeral?
UploadAPI->>Auth: resolve requester
Auth-->>UploadAPI: RequestUser
UploadAPI->>UploadAPI: decode base64 and enforce size cap
alt "ephemeral == true"
    UploadAPI->>UploadAPI: "key = {user_id}/ephemeral/{uuid}.{ext}"
else default
    UploadAPI->>UploadAPI: "key = {user_id}/assets/{uuid}.{ext}"
end
UploadAPI->>Storage: store(data, key, content_type)
Storage-->>UploadAPI: pipelex-storage:// URI
UploadAPI-->>Client: UploadResponse(uri, filename)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant UploadAPI as POST /upload
participant Auth as get_request_user
participant Storage as Storage Provider

Client->>UploadAPI: filename, data, content_type?, ephemeral?
UploadAPI->>Auth: resolve requester
Auth-->>UploadAPI: RequestUser
UploadAPI->>UploadAPI: decode base64 and enforce size cap
alt "ephemeral == true"
    UploadAPI->>UploadAPI: "key = {user_id}/ephemeral/{uuid}.{ext}"
else default
    UploadAPI->>UploadAPI: "key = {user_id}/assets/{uuid}.{ext}"
end
UploadAPI->>Storage: store(data, key, content_type)
Storage-->>UploadAPI: pipelex-storage:// URI
UploadAPI-->>Client: UploadResponse(uri, filename)
Loading

Reviews (1): Last reviewed commit: "feat(upload): add ephemeral key prefix t..." | Re-trigger Greptile

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