Admission and enqueue are separate commits
A crash between them creates a request no worker can see, or an event pointing to state that never committed. The transactional outbox closes that gap.
A browser job is slow, memory-heavy and externally visible. The queue normally provides at-least-once delivery, so a process crash can replay a capture after it rendered, uploaded or billed.
Two workers render the same request, the loser overwrites success with failure, a webhook fires twice, or usage contains a charge with no successful request left to justify it.
Use the database as the ownership boundary: request plus outbox in one transaction, atomic state transition for the lease, and unique constraints for each externally visible effect.
const claimed = await db.$executeRaw`
UPDATE requests
SET status = 'running', started_at = COALESCE(started_at, now())
WHERE id = ${requestId}
AND status IN ('accepted', 'queued')
`;
if (claimed === 0) return currentTerminalResult(requestId);
try {
const artifact = await render(requestId);
await db.$transaction(async (tx) => {
await tx.request.update({ where: { id: requestId }, data: { status: "succeeded" } });
await tx.usageLedger.create({ data: { request_id: requestId, units: 1 } });
});
} catch (error) {
if (isRetryable(error)) await requeueWithBackoff(requestId);
else await markFailedWithoutCharge(requestId);
}A crash between them creates a request no worker can see, or an event pointing to state that never committed. The transactional outbox closes that gap.
Two workers can both read `queued` before either writes `running`. Ownership must be one conditional update whose affected-row count decides the winner.
A bad selector will not improve on attempt three, while a transient network failure might. Retrying terminal work consumes capacity and delays healthy jobs.
Read-then-create races. A unique constraint and conflict handling, not an earlier SELECT, makes concurrent duplicates harmless.
One artifact and one billable ledger entry for a successful non-cached request, even if queue delivery or a caller retry occurs more than once.
One successful, non-cached capture costs one credit. Failures, including platform failures, cost zero; cache hits cost zero.
Admission exceeds the plan's start bucket; honor `Retry-After`.
HTTP 400 · retryable
The platform cannot safely admit work at that moment.
HTTP 503 · retryable
The runner above carries this exact configuration into the playground — no retyping, no starting over.
Written by PageCapture Engineering. Reviewed by PageCapture data and worker maintainers. Verified 2026-08-12 by running the published code against a fixture.
Contract tests inspect atomic bulk admission and idempotency ordering; billing tests assert no error is chargeable; the executor lease and unique-ledger design are linked to repository implementation.
Sources support the browser and API behaviors named above. PageCapture-specific limits and billing are taken from the public contract; external sources are used for the underlying browser behavior.
Defines the atomic uniqueness primitive used for idempotent effects.
Documents retry timing and stable webhook event ids.
Documents bulk `Idempotency-Key` replay semantics.