Skip to content
Closed
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
13 changes: 13 additions & 0 deletions .github/workflows/api-route-typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,16 @@ jobs:
run: |
node --import tsx/esm --test tests/unit/antigravity-resilience-tab-wiring.test.ts
npx vitest run tests/unit/antigravity-resilience-settings-card-render.test.tsx
- name: Order Forge scope-gated image artifact retention
env:
API_KEY_SECRET: ci-only-retention-api-key
JWT_SECRET: ci-only-retention-jwt
DISABLE_SQLITE_AUTO_BACKUP: "true"
OMNIROUTE_DISABLE_BACKGROUND_SERVICES: "true"
run: |
node --import tsx/esm \
--import ./open-sse/utils/setupPolyfill.ts \
--import ./tests/_setup/isolateDataDir.ts \
--test --test-force-exit --test-concurrency=1 \
tests/unit/order-forge-image-artifact-sink.test.ts \
tests/unit/order-forge-image-generation-route-scope.test.ts
37 changes: 37 additions & 0 deletions docs/maintenance/ORDER_FORGE_IMAGE_RETENTION_PORT_20261025.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Fork-only Order Forge image artifacts — upstream-first port

**Isolated candidate / non-deployable phase 1.** Restore this explicitly
scoped feature from the currently deployed fork; do not submit customer-specific
application naming / directories to the official upstream repository.

## Included

- `src/lib/usage/orderForgeImageArtifactSink.ts`: original bounded
PNG/JPEG/WEBP payload validation (25 MiB), atomic private writes (0600),
per-attempt metadata without prompt/body text, 72-hour expiry, 2 GiB cap,
duplicate-safe IDs and cancellable stream drain.
- Original standalone sink regression suite from fork production.
- Narrow integration in **direct** `/v1/images/generations` (without changing
the response body, status, quota eligibility or original image accounting).
Capture needs the authenticated API key's explicit
`image_artifact_retention` scope **and** a syntactically valid
`X-EtsyTrello-Artifact-Id` header. A bare header is not sufficient.
Retention disk failures never turn a paid image success into a 500.
- Hosted regression gate for scope and basic route placement.

## Remaining before release (hard requirements)

- Port into non-streaming and streaming `open-sse/handlers/chatCore.ts`
including safe tee/drain across client disconnect and normalized
`image_generation_call` outputs.
- Port `/v1/images/edits` on direct AND combo/custom-provider branches,
preserving existing host proxy / account fallback and billing.
- Port independent Codex image quota telemetry, account availability and
quota header merging only after comparing modern upstream accounting.
- Run focused image pipeline tests, full hosted build and packaged artifact
smoke, isolated migration rehearsal and review.
- Never copy stored customer image payloads or prompts into GitHub artifacts.

Existing custom artifact directory and metadata are operational data; preserve
the configured volume/directory during rollout and validate backup/rollback
without uploading customer files.
23 changes: 23 additions & 0 deletions src/app/api/v1/images/generations/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ import { generateRequestId } from "@/shared/utils/requestId";
import { getSpecialtyModelsResponse } from "@/app/api/v1/_shared/specialtyCatalog";
import { enforceClientApiRouteAuth } from "@/shared/utils/clientApiRouteAuth";
import { runImageRequestWithAccounting } from "@/lib/usage/imageRequestAccounting";
import {
persistOrderForgeImageArtifacts,
resolveOrderForgeImageArtifactCapture,
} from "@/lib/usage/orderForgeImageArtifactSink";
import { executeImageWithCredentialFallback } from "@/sse/services/imageCredentialRetry";
import { AUTHZ_HEADER_PEER_LOCALITY } from "@/server/authz/headers";
import {
Expand Down Expand Up @@ -172,6 +176,13 @@ async function postHandler(request, context) {
if (endpointMismatch) return errorResponse(HTTP_STATUS.BAD_REQUEST, endpointMismatch);
const { allowedConnections } = scope;

// Strictly opt-in, per-authenticated-principal retention. A caller-supplied
// artifact ID does nothing without the dedicated API-key scope.
const artifactCapture = resolveOrderForgeImageArtifactCapture({
apiKeyScopes: policy.apiKeyInfo?.scopes,
headers: request.headers,
});

const modelPrefix = body.model.includes("/")
? body.model.slice(0, body.model.indexOf("/"))
: body.model;
Expand Down Expand Up @@ -431,6 +442,18 @@ async function postHandler(request, context) {

if (result.success) {
await clearRecoveredProviderState(credentials);
if (artifactCapture) {
try {
persistOrderForgeImageArtifacts({
capture: artifactCapture,
responseBody: (result as { data?: unknown }).data,
});
} catch {
// Retention is optional; its local disk should never turn a successful
// image response into a provider/API failure or expose customer data.
log.warn("IMAGE", "Optional image artifact retention failed");
}
}
// Bill the images actually delivered: a provider that returns fewer than the
// requested `n` must not be charged for the missing ones (#16398 follow-up).
const n = (result as { data?: { data?: unknown[] } }).data?.data?.length || 1;
Expand Down
Loading