# Persistence review requiring confirmation

Status: approved by the user's "ya lanjutkan" after review of the three schema changes and explanation of quarantine. Consumer changes and deployment to the application database remain outside this approval.

## Concrete migration candidate

1. `prisma/models/Part.prisma`: add nullable `photoKey String? @db.VarChar(255)` and `version Int @default(0) @db.UnsignedInt`. Existing rows get null photo/zero version. Version guards master/photo writes. File content remains in the existing media storage; no base64 column, place/rack field, or financial field is added.
2. New `prisma/models/InventoryRequest.prisma`: durable idempotency record with `id String @id @default(cuid()) @db.VarChar(30)`, `key String @unique @db.VarChar(100)`, `actorId String @db.VarChar(30)`, `operation String @db.VarChar(100)`, `payloadHash String @db.Char(64)`, `result Json`, `createdAt DateTime @default(now()) @db.DateTime(3)`, and actor relation with Restrict. Add the inverse relation to User. The unique key protects all mutations, including zero-stock creation and rejection with no ledger row. No TTL deletion in MVP, so old retries cannot repost stock.
3. `prisma/enums.prisma`: extend QuarantineStatus with `CLOSED_MIXED`. When remaining held is zero, net releases only -> RELEASED, net scraps only -> SCRAPPED, both -> CLOSED_MIXED. A reversal restoring held quantity returns the case to HELD, or IN_REPAIR if active linked repair work requires it. Detailed counts remain ledger-derived. Existing enum values and rows are preserved.

Prepare a new additive migration, update schema-design.md, generate the client, and verify against isolated MySQL databases only after approval. Do not apply the migration to the configured application database without a deployment instruction. Keep existing cost columns untouched and absent from public inputs/outputs.

## Durable retry protocol

- Validate current authentication/authorization before accessing replay records.
- Normalize validated input (trim permitted strings, resolve explicit defaults, recursively order JSON object keys; preserve arrays); hash operation, target and normalized payload with SHA-256. Do not include cookies/tokens. Keep the actor separate and verify it on replay.
- In the same transaction as all business writes, claim the globally unique key, perform the operation, and store its explicit business response and resource identity. A temporary result placeholder is never committed: failure rolls back both request and business state.
- Concurrent identical requests contend on the unique index. After the winner commits, a losing transaction reads the committed request in a fresh transaction and verifies actor, operation and hash before replaying the stored result. Different actor or payload -> safe 409, with no foreign result disclosed.
- Store the business result, not the HTTP envelope/requestId; replay gets the new requestId. No new ledger/audit effects on replay. Repeat decisions under a different key still use state locking and preserve the original reviewer when the same effective decision has already committed.
- Retain StockMovement.idempotencyKey. Derive a distinct deterministic movement key from the operation key plus effect index for operations producing multiple rows; InventoryRequest owns the logical replay boundary.
- Connection failure with an uncertain commit is resolved by retrying the same retained key. Retry only known transient transaction conflicts; never rerun under a new key.

This replaces the tentative AuditLog-based retry option in design.md if approved. AuditLog has no unique request-key constraint; treating audit JSON as the sole deduplication mechanism would leave zero-stock creation and unrelated mutations without a common concurrency-safe claim.

## Consumer boundary

Consumer files remain unchanged. Future integration requires separate confirmation for: approval queue and own-request states, QR deep links/catalogue/search, photo upload on Save, reason/version for stock adjustment, and full movement type/delta/export rendering. Existing optional location text is not used for stock routing and no rack/place input is added. Backend/schema approval does not grant frontend-edit permission.
