# Confirmed decisions

Source: backend architecture roadmap v1.1, September 2026. The B2B requirements and working agreement govern business intent and coordination. New business decisions require owner confirmation.

| ID | Decision |
| --- | --- |
| DEC-001–006 | Node.js, Express, MySQL, Prisma, Redis, BullMQ, modular monolith |
| DEC-007–011 | Central canonical model, external adapters, separated product/stock/cost/price, backend RBAC and scopes, machine integration identity |
| DEC-012–019 | fa/en/ar translations, exact decimal money, FIFO, configurable and historically snapshotted commercial policies |
| DEC-020–021 | Staff full re-login after 1 hour; partner 24 hours or explicit remembered 7 days |
| DEC-022–023 | Soft deletion for master data; backend translation fallback by channel/organization |
| DEC-024–029 | Scoped tax/return policies, PartnerGroup, catalog visibility, STANDARD/AUCTION/EXCLUSIVE/PRIVATE sales modes, scoped stock exposure |

Engineering choices for phase 0: CommonJS Node application; standardized `{data,meta}` success and `{error,requestId}` error envelopes; `/health/live`, `/health/ready`; OpenAPI 3.0.3. These are implementation choices, not additional business policy.

Phase 1 implementation choices: explicit STAFF/PARTNER login context; ten-minute signed access JWT with a revocable server-side session; random rotating refresh credential stored as a hash; exact resource scope checks; initial administrator provisioned by a one-time CLI against an empty database. Tokens are returned in JSON for the client to store securely. Future browser transport and cookie/CSRF policy should be settled before production deployment.

Phase 2 implementation choices: staff-only catalog operations use `catalog.read` and `catalog.manage` with an exact organization scope; translations are `fa`, `en`, `ar` with a configurable organization fallback and published-only localized resolution; slugs/codes and SKU remain unique after soft deletion; DELETE requires a reason and writes an audit event. Media registration accepts HTTPS URLs, while binary upload and final storage policy remain separate. Attribute values have either product or variant axis, and a variant accepts at most one value per attribute. These choices describe this implementation and do not settle later partner visibility or commerce policy.

Phase 3 implementation choices: stock quantities are whole units per warehouse/SKU; `available=onHand-reserved`. Only an active SELLABLE warehouse and an active product/variant may accept a reservation. FIFO chooses unreserved layers by receipt time and ID; lot lineage and unit-cost snapshots are retained through transfers and movements, with purchase cost exposed only to future cost-authorized flows. Each stock-changing request uses a unique lowercase Idempotency-Key scoped to the organization. Reservations close in full, and movements cannot be edited/deleted. Damaged stock moves into QUARANTINE and cannot return directly to SELLABLE without a future inspection workflow. Warehouse-scope roles see exact internal stock only within their warehouse; an organization inventory admin can administer all its warehouses. Partner/channel stock remains hidden until catalog eligibility is enforced. The proposed override precedence in the internal visibility resolver is Partner, PartnerGroup, Product, Category (sorted ID), Channel, SalesMode, Organization; owner review is needed before partner/channel exposure.

Phase 4 engineering choices: read cache uses organization-scoped MySQL generations in the same transaction as audited catalog/inventory writes. Every cached read checks the authoritative generation before and after the lookup; catalog TTL is 60 seconds, exact stock TTL is 5 seconds, and Redis failure falls back to MySQL. A durable outbox row is created in the write transaction. A separate worker polls it and dispatches catalog and inventory events to BullMQ, with a stable event ID, retry lease and durable processed marker; optional Redis pub/sub is advisory and can be lost or duplicated for subscribers. Only catalog/inventory consumers exist so far. Until a persistent worker and `noeviction` Redis exist, jobs remain disabled and the outbox is intentionally pending; this does not affect cache correctness.
