# Todo imports

Import an uploaded JSON file with durable claims, version checks and safe replay.

<Diagram name="imports" alt="Starting an import records intent; cron publishes and leases the run, reads its fixed R2 version, commits Todos and success together, then acknowledges delivery." />

## Input and admission [#input-and-admission]

Upload and confirm a JSON file in the Active Organization, then call `imports.start` with its key and a UUID `requestKey`. The operation records an Import Run and returns. HTTP never executes the import in the background. Reusing the Organization/request key returns the existing run; changing its actor or source conflicts.

```json
[
  { "text": "Review the API contract", "priority": "high" },
  { "text": "Write the release note", "priority": "medium" }
]
```

The payload is an array of 1 to 200 Todos, at most 1 MiB. The reader bounds streamed bytes independently of Content-Length and compares the ETag from the same GET against the version frozen when the run started.

## Claim, commit and acknowledge [#claim-commit-and-acknowledge]

Each cron tick publishes up to 25 unpublished runs and processes at most 25 claims with two consumers. Postgres owns delivery state through `FOR UPDATE SKIP LOCKED`, a random lease token and a 60-second lease. Stable Effect Queue, Stream and Schedule coordinate local execution.

Before inserting, the transaction rechecks current membership, the confirmed upload, the token and lease expiry. It inserts all Todos and marks the run succeeded atomically. Acknowledgement happens later. If its response is lost, the next claim sees terminal success and acknowledges without creating duplicates.

## Failures and limits [#failures-and-limits]

Changed or missing sources, revoked membership, malformed JSON and oversized payloads become persisted failures. Transient reads or database failures defer the claim. Three claims exhaust the run; retrying a terminal failure requires a new request key.

An import reserves a 15-second work budget inside the 25-second tick. Its GET timeout is 10 seconds. Acknowledged queue rows expire after 30 days; Import Runs retain idempotency history until the Organization is deleted. Imports currently do not emit the interactive router's per-Todo audit events. Concurrent interactive writes can interleave positions with imports.

## Extend the example [#extend-the-example]

Keep validation, source-version fencing, lease ownership and atomic commit together. Add a source field to the Zod contract and persistence transaction, then cover replay and a lost response in real workerd. [ADR-0006](/docs/adr/0006-effect-persisted-queue) explains why native PersistedQueue was rejected.


## Key files [#key-files]

- [`packages/api/src/routers/imports.schema.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/routers/imports.schema.ts)
- [`packages/api/src/imports/start.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/imports/start.ts)
- [`packages/api/src/imports/queue.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/imports/queue.ts)
- [`packages/api/src/imports/worker.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/imports/worker.ts)
- [`packages/api/src/imports/commit.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/imports/commit.ts)
- [`packages/api/src/imports/policy.ts`](https://github.com/CarlosZiegler/doro/blob/main/packages/api/src/imports/policy.ts)

