Gallery
Enterprise PDF Ledger
Transactional inventory, exact allocation and refunds, tenant isolation, replay-safe commands and immutable PDF snapshots under conflicting concurrent operations.
http
hard
Postgres
120-180 min

Task Brief
Raw Markdown
# Enterprise PDF Ledger
Build an HTTP service for inventory-backed orders and immutable financial PDF
reports. Target implementation time: **120–180 minutes**. All requirements are
available immediately; there are no later waves. The difficult part is preserving
the same invariants across retries, stale revisions, refunds and concurrent writes.
## Execution and persistence
Listen on `0.0.0.0:8080`; become ready within 5 seconds. Use PostgreSQL for all
authoritative state, including successful idempotency results and report snapshots.
Support `DATABASE_URL`, `POSTGRES_URL`, or `DB_HOST`, `DB_PORT`, `DB_NAME`,
`DB_USER`, `DB_PASSWORD`, `DB_SSLMODE`. Create your own schema at startup.
No external services are available. Go must declare `go 1.24` or lower and use
dependencies compatible with Go 1.24 (`GOTOOLCHAIN=local`); Node.js 20 and
Python 3.12 are available. Do not use newer runtime APIs.
Put every source file, dependency manifest and entrypoint in the **solution root**:
`workspace/src/` in a live match, or `<challenge>/src/` after pulling an Open
Challenge. If already in that directory, write directly there. Never nest another
`src/` or `workspace/src/`, or put implementation packages beside the root.
Entrypoint: `main.go` + `go.mod` with package main, `package.json` with `start`
or `index.js`, or `main.py` + optional `requirements.txt`.
## Common protocol
`GET /health` returns `200 {"status":"ok"}`; `GET /ready` returns
`200 {"status":"ready"}` only when storage is usable. Other endpoints require
`X-Tenant-ID`: 1–64 ASCII letters, digits, `_` or `-`. Tenants are created lazily.
This header represents an upstream authenticated identity; do not implement login.
All data, SKU uniqueness, sequences and idempotency keys are tenant-local.
Foreign or missing resource IDs return `404`, without revealing their existence.
Every POST/PUT additionally requires nonempty `X-Request-ID` and `Idempotency-Key`
(1–128 printable ASCII characters). JSON responses, including errors, use
`application/json`. Error body: `{"error":{"code":"...","message":"..."}}`.
Codes: `validation_error` (400), `not_found` (404), `conflict` (409),
`precondition_failed` (412), `unprocessable` (422), `precondition_required` (428).
GET errors use the same shape. Unknown routes return 404. Unknown JSON fields,
duplicate JSON keys, malformed JSON, booleans/fractions where integers are required,
and out-of-range values return 400. All specified fields are required. IDs are
server-generated UUIDs. Timestamps are not part of this contract.
### Atomic command and replay rules
A successful POST/PUT commits its data, exactly **one tenant sequence increment**,
and its idempotency record in one transaction. Tenant sequence starts at 0.
Failures, reads and replays never change it. `/state` returns `{"sequence":N}`.
Idempotency keys are scoped to the tenant across **all** POST/PUT routes.
The fingerprint is HTTP method + path + parsed body + parsed If-Match value
(or absence). JSON object key order and insignificant whitespace do not matter;
array order does matter. Integers are JSON integer tokens, not `1.0` or `1e0`.
Replaying the same fingerprint returns the original success status and identical
JSON value, even after later mutations make that revision stale. Reusing a key
with a different fingerprint returns 409. Concurrent identical requests must all
return the same success; concurrent different requests with the same key have
one winning fingerprint. Failed requests do **not** reserve a key.
Validation precedence: common headers and JSON syntax/types first; then successful
replay/key conflict; then resource visibility; then revision precondition; then
domain invariants. Errors do not partially write state. Invalid domain operations
must not poison a key for a corrected retry.
### Revision preconditions
All order PUTs and order action POSTs require `If-Match: "N"`, a quoted positive
decimal revision. Missing -> 428; malformed -> 400; stale -> 412. Revision checks
and writes are atomic. A successful new order has revision 1; every successful
order mutation increases revision by exactly 1. Replays do not. All order response
objects include `revision` and `sequence` (the last sequence that changed that order).
## Products and inventory
`POST /products`: `{"sku":"A","unit_price":101,"tax_bps":1000,"stock":10}`
returns 201 product; duplicate SKU -> 409. SKU matches `[A-Za-z0-9_-]{1,32}`;
price is integer 1..1,000,000,000 cents; tax_bps is integer 0..10000;
stock is integer 0..1,000,000. Prices and tax rates are immutable.
`GET /products/{sku}` returns 200:
`{"sku":"A","unit_price":101,"tax_bps":1000,"on_hand":10,"reserved":0,"available":10}`.
Product creation returns the same shape. Always `available = on_hand - reserved`;
all three quantities are nonnegative.
## Orders and allocation
`POST /orders` returns 201; `PUT /orders/{id}` replaces an entire draft and returns
200. Both accept `{"items":[{"sku":"A","quantity":3}],"discount":1}`.
There must be 1..20 distinct SKUs (duplicates -> 400), quantity integer 1..1000,
discount integer 0..sum(gross). Missing SKU -> 404; discount above gross -> 422.
Drafts do not reserve stock and may exceed currently available stock.
Replacing a non-draft -> 409. Validate the entire replacement before writing.
`GET /orders/{id}` and all successful order mutations return the full order:
```json
{"order_id":"uuid","status":"draft","revision":1,"sequence":2,
"discount":1,"subtotal":303,"tax":30,"total":332,"refunded_total":0,
"lines":[{"sku":"A","quantity":3,"unit_price":101,"tax_bps":1000,
"gross":303,"discount":1,"net":302,"tax":30,"total":332,"refunded_quantity":0}]}
```
Always sort lines by ASCII SKU. Keep original totals unchanged after refunds.
Calculate using exact integers (no floating-point rounding):
1. Line gross `g[i] = price[i] * quantity[i]`; subtotal `S = sum(g)`.
2. Distribute order discount `D` proportionally: initial allocation
`d[i] = floor(D*g[i]/S)`. Give the remaining cents one per line by decreasing
remainder `(D*g[i]) mod S`, ties by ascending ASCII SKU. This is largest remainder.
3. Line net `n[i] = g[i]-d[i]`. Line tax is **round half to even** of
`n[i]*tax_bps[i]/10000`; ties go to the even integer. Tax is rounded per line.
4. Line total = net + tax. Order tax/total are sums of line tax/total.
Intermediate `D*g[i]` may exceed both JavaScript safe integers and signed int64.
Use arbitrary precision or a mathematically equivalent overflow-safe algorithm.
JSON result values remain safe integers within the declared limits.
## State transitions and stock
Each action accepts `{}` except refund; each needs If-Match and normal mutation
headers. Domain-invalid transitions return 409 after the revision check.
| POST route | Allowed state | Result and inventory effect |
|---|---|---|
| `/orders/{id}/confirm` | draft | confirmed; reserve every line atomically; insufficient available stock -> 409 |
| `/orders/{id}/pay` | confirmed | paid; decrease on_hand and reserved by each quantity |
| `/orders/{id}/cancel` | draft, confirmed | cancelled; confirmed releases reserved; draft has no stock effect |
| `/orders/{id}/refund` | paid, partially_refunded | described below |
Different orders competing for stock must never oversell. Rejected multi-product
confirmation must not reserve even its satisfiable lines. Opposite line ordering
in concurrent commands must not cause an unbounded deadlock. A successful HTTP
response is a committed operation: subsequent reads must observe it.
### Partial refunds
Body: `{"items":[{"sku":"A","quantity":1}]}`, 1..20 distinct SKUs,
positive integer quantity <=1000. Every SKU must be on the order (otherwise 422).
If any cumulative refunded quantity would exceed the original quantity, reject the
whole command with 422. Duplicate SKUs -> 400. Refunded units return to on_hand.
For original line total `T`, original quantity `Q` and cumulative refunded units
`k`, the cumulative refunded cents are `f
Preview truncated.