# Phone Buzz platform architecture

The new platform is isolated under platform/. The existing HTML/PHP site, deployed credentials, assets and delivery ZIP are not modified by this build.

## Clients and API

- Web: Next.js and TypeScript, Radix Themes, Phone Buzz dark/orange brand. A same-origin server proxy keeps the session token in an HttpOnly, SameSite=Strict cookie. Mutations require a matching Origin. Production cookies require HTTPS.
- iOS and Android: Expo/React Native with TypeScript. A real native client, not an embedded website. Tokens are stored in Expo SecureStore, not AsyncStorage. Release builds reject plain HTTP API URLs.
- Shared contracts: packages/contracts contains the typed user, company, audit, trading and catalogue models used by both clients. The API OpenAPI schema is exported to docs/openapi.json. Permissions always run on the API.
- Backend: FastAPI, SQLAlchemy and Alembic. PostgreSQL is the production database; SQLite enables isolated local work without Docker. No connection to the live MySQL database is made.

## Identity and data

Opaque random 256-bit session tokens are stored as SHA-256 hashes server-side, expire after eight hours by default, and are revoked on logout. Passwords use Argon2. Roles and active status are checked on every request. Administrators can create staff; traders can manage company records; viewers can only read. Company and trading record updates use a version number to reject stale edits. Manual stock/offers/wanted records have staff-reviewed status and server-evaluated expiry. Matching is deterministic, and integer minor units preserve per-device price precision; no currency conversion is performed. IMEI-tracked owned stock supports explicit expiring reservations and confirmed allocations. See TRADING.md for all matching conventions. Audit records are committed with each business mutation and have no public update/delete route.

This is a single-organization staff workspace. External customer/wholesaler access requires company-level data isolation in a later phase. It must not reuse staff endpoints.

Catalogue products have unique normalized brand/model/storage identities. Grade and region entries have unique normalized names per kind and immutable written definitions. Only administrators create/archive/restore entries. Availability updates are version checked and audited. Trading records store catalogue references and canonical text snapshots resolved by the server. New links require active entries; existing links retain their meaning after archival. PostgreSQL row locks coordinate selection with archive updates, but local PostgreSQL concurrency validation now passes (see POSTGRES-VALIDATION.md); hosted load testing remains a pilot gate. Legacy trading rows keep nullable references until explicit staff review; they never qualify as exact matches while unlinked.

## Device inventory and reservations

Owned stock rows serve as lots. StockDevice owns DeviceIdentifier rows, with a single primary key namespace across both IMEI slots and all lots. Each device points to at most one current StockReservation; ReservationItem keeps historical membership and original identifier snapshots after expiry, correction or reassignment. Each item can record physical return receipt without deleting the allocation. Reservation rows snapshot the original customer and price. Physical returns clear the current device allocation and quarantine it; inspection approves or retires it. Customer/carrier dispatch and financial returns remain separate work. Historical identifier rows retain ownership with a NULL slot; only the current primary/secondary identifiers use slots 1/2. Administrator correction is atomic. DeviceEvent stores actor, reason and before/after snapshots for corrections, transfers, receipts and inspections. A persistent inventory_tracked flag prevents empty transferred-out lots from falling back to reported availability.

All inventory writes and trading edits acquire stock/wanted row locks in sorted ID order using no-op UPDATE statements, also providing a write barrier on SQLite. They re-read records, check versions, then revalidate device availability and remaining demand before committing devices, reservations, trading versions and audit together. Company state is locked/rechecked before reserve/allocate. No background process is needed to expire holds. Reads and matching derive availability from server time and reservation state. The same concurrency tests now pass on local PostgreSQL 17; hosted load and operational validation remain production gates. See INVENTORY.md.

## Private evidence

Attachment metadata has exactly one foreign-key parent: owned-stock lot, device or reservation. Original bytes are stored under UUID filenames in a private filesystem directory. Every list/download/mutation requires an active staff session. A per-parent write lock enforces the 20-file active quota, user-scoped retry keys avoid duplicate uploads, and versioned administrator archive/restore actions retain original evidence. Downloads verify size and SHA-256 before serving with attachment disposition. Web uses the authenticated cookie proxy; native uses authenticated binary requests and temporary cache files for explicit system sharing. See ATTACHMENTS.md for formats, limitations and backup consistency.

## Reviewed imports

ImportBatch stores parsed source rows, canonical review values, reference versions, validation messages and created record IDs. Preview is separate from trading writes. Commit locks the batch and catalogue/company references, revalidates the review and duplicate warnings, and atomically creates draft records and audit events. Per-uploader retry keys protect preview retries; a unique canonical batch fingerprint prevents duplicate committed imports. All staff can read history; only the uploader or an administrator can change a batch. CSV/XLSX parsing is bounded and values-only; no formulas or external links run. See IMPORTS.md.

## Operational boundaries

- No production secrets, live customer data or automatic outbound integrations are copied into this project.
- No default user is created by migrations. Use the interactive administrator CLI.
- A local preview account may be generated by scripts/prepare-preview.py; its random password and database are ignored under .local/. The setup script refuses to target production.
- Request bodies and passwords are not logged. Login rate limiting is persisted per email; reverse-proxy IP and distributed limits are a production gate.
- Mobile mutations require connectivity. Expired sessions prompt a new login; offline writes and automatic token refresh are not implemented in this milestone.
- Reviewed staff deactivation/session revocation, optional MFA and server-issued recovery are implemented on web/native. Privately delivered, single-use staff invitations are also implemented (STAFF-INVITATIONS.md). Emailed invitation delivery, emailed self-service recovery, mandatory-MFA policy, operational hardening and independent security review remain production gates. CLI password resets revoke all sessions and retain enrolled MFA.
- Account security also exposes a curated personal history and verified sign-out of other sessions. Existing user-row locks serialize this action with sign-in, MFA and access changes. Shared dashboard/audit feeds exclude credential/account event namespaces; raw operator reasons are not exposed through personal history. No new storage is needed. See ACCOUNT-SECURITY.md.
- Existing portal password hashes require a deliberate migration strategy. Do not replace or reset legacy accounts during this foundation phase.

## Later services

Incoming email and optional AI extraction have separate durable background workers. Incoming sync has bounded automatic retries; uncertain AI provider calls require explicit staff retry. Move the current private filesystem attachment adapter to protected object storage when deploying across instances; add malware scanning before accepting untrusted business uploads. Knowledge retrieval must apply data permissions before assembling prompts. Outgoing actions require server-enforced approval/version checks. Mobile push notification registration is a separate per-device service.

## Deployment

Use separate development, staging and production environments. The user confirmed that current hosting access is control-panel-only, without VPS/SSH. The prepared Docker/SSH deployment cannot run through that access alone; any panel-managed Node.js, Python, PostgreSQL or worker capability remains unverified. Suitable application hosting still needs to be selected, either an administered server or a managed service with an adapted deployment. The existing marketing website can remain on cPanel. A future app.phonebuzz.me and API hostname are proposals, not deployed endpoints. No new hosting purchase or deployment is authorized by this clarification.

The private staging recipe in deploy/staging now separates migration-owner and API runtime roles, packages a standalone Next server, mounts private attachment/MFA storage and exposes only an HTTPS proxy restricted to approved source IPs. Local configuration, standalone HTTP and non-superuser PostgreSQL permission checks pass. No host is deployed, and Linux image/volume/network validation is pending. See STAGING.md for the exact boundaries and operator runbook. API /health remains liveness; /ready checks connectivity and the release's exact migration head without disclosing database details.

## Physical warehouse custody

Warehouse owns a permanent unique code, name, optional address and versioned availability. StockDevice has a nullable location and a transit flag. WarehouseMovement snapshots IMEI, source/destination names and staff dispatch/receipt attestations, retaining history after completion. A unique nullable active-device reference prevents multiple open movements; transit takes precedence in status/counts and is excluded from available_supply. Assignment and dispatch/receipt lock the lot first, then warehouse rows in sorted order. Archive locks the warehouse and rejects on-hand or open movement counts. The API checks roles, quarantine, current versions, active locations and physical confirmation. Web and native share response contracts and workflows; no inferred seed locations. See WAREHOUSES.md for customer fulfillment and return-location limitations.

## Shared staff conversation ledger

Conversation stores an immutable subject/correspondent, optional company/assignee, open/closed status and current reply draft. ConversationEntry retains staff-recorded emails, notes, draft revisions and metadata changes. All active staff can read; writers mutate via version checks under a conversation row lock. Assignment is a responsibility label, not an authorization boundary. Creation retry keys are scoped to the staff actor and checked against a payload hash. Recipient identity does not change with company linking. Web renders plain text and proxies same-origin writes; mobile uses the same API contracts. No IMAP, SMTP, send queue or transport is connected. See INBOX.md for workflow and integration prerequisites.

## Incoming email worker

A separate API-host process reads one verified-TLS IMAP INBOX. Database leases, per-message transactions and persisted UID checkpoints coordinate workers with the staff inbox. Secrets remain server-side; web/native clients use sanitized status and versioned administrator controls. Incoming content is plain text, carries source provenance and never executes tool instructions. This worker has no sending capability. See MAILBOX.md for activation, deduplication, failure handling and pending deployment validation.

## Email trading provenance

Reviewed received-email items create only offer/wanted drafts. A conversation lock, version guard, per-user retry key and unique source-entry/item number protect creation. Trading record, canonical review snapshot, source link and audit history commit together. The source endpoint resolves the immutable conversation entry; edits to trading records do not rewrite the review snapshot. Source content never executes instructions. See EMAIL-TRADING.md.

## AI email suggestions

EmailExtraction has one immutable-source entry reference, requester, pinned model/prompt version, body hash, bounded attempts, leased state and validated result JSON. API requests queue jobs; only the separate configured worker contacts the pinned provider. Local Ollama requests use a loopback-only adapter with no cloud fallback. CAS claims and lease-token completion guards prevent competing active workers from publishing twice. Expired calls are failed rather than automatically repeated. Exact active catalogue resolution happens locally when reading results. Model output cannot mutate trading data; a separately confirmed review can retain the suggestion alongside human-corrected values. The preview can use the downloaded local Qwen3 4B model. Provider identity is retained in jobs and new review snapshots; migration ca617294ce01 labels existing jobs OpenAI without rewriting their results. See AI-EXTRACTION.md and LOCAL-AI.md.

## Reviewed learning memory

LearningExample references an immutable EmailTradingLink. AI-assisted review creation captures one pending candidate transactionally. Only administrators curate/approve or reserve an immutable holdout. Local worker claims select relevant approved examples outside the source conversation and direct copied text; active-admin approvals and version/state are rechecked before publication. Exact bounded demonstrations are stored in the extraction result for provenance. OpenAI requests never include memory. A separate explicit read-only evaluator compares frozen holdouts with/without approved examples, pins the installed model digest and records private metrics without source text. No model-weight training or automatic promotion is implemented. See LEARNING.md.

Incoming email attachments use a separate bounded database-backed store. Validated bytes, extracted text, original source and mailbox checkpoint commit together. Text is parsed in a time-limited child process and released into a new immutable conversation entry only after version-checked staff confirmation. Original email bodies are unchanged. Trading source history includes attachment filename/hash and original email entry. See EMAIL-ATTACHMENTS.md for format restrictions, subprocess limits and production gates.

Incoming WhatsApp uses direct HTTPS webhooks, separate from the polling mailbox. A fixed connection stores account/phone identity and an activation timestamp; a connection row lock and unique remote message IDs serialize atomic ingestion and retries. Sender-to-conversation mappings preserve channel identity without fake email addresses. Supported text enters immutable history and the existing optional AI/review path; unsupported content produces a notice and is rejected by trading/extraction source guards. No sender, access token or media downloader exists. See WHATSAPP.md.

## Billing foundation

Billing documents freeze accepted order prices and separate tax arithmetic from supplier inventory values. Mutations lock the parent order before the billing document, coordinating invoice issuance/receipts with physical returns. Exact integer balances, per-user idempotency keys, active-reference uniqueness, immutable history and retained reversals protect the manual receipt ledger. Invoice issuance is disabled by default pending issuer/jurisdiction requirements; no automatic numbering, tax lookup, bank transaction or refund is performed. See BILLING.md.

Authenticator encryption supports a primary key plus one optional previous decryption key. The offline rotation CLI defaults to read-only inspection, validates all active/pending seeds, and atomically rotates only old-key ciphertext plus private audit events. Password/session/MFA policy state is unchanged. It requires operator-confirmed maintenance and backups; runtime deployment/mount changes are separate. See MFA-KEY-ROTATION.md.

Staff access now supports reviewed role transitions on the existing action endpoint. The API rechecks authority under shared administrator/target locks, blocks self-role changes and last-active-admin demotion, increments the target version and revokes target sessions/recovery tokens. Existing MFA and commercial history remain intact; affected users receive curated personal history. Web/native role selectors use the shared action contract. See STAFF-ACCESS.md.

Supplier-offer expiry reporting uses one SQL CTE statement for search-scoped window counts and a bounded page, with a separate current-source inspection query. Shared web/native definitions keep window labels aligned; all commercial mutations remain in Trading. No schema, worker or integration changes are introduced. See OFFER-EXPIRY.md.

## Reviewed assistant trading filters

The read-only assistant parses bounded storage/quantity/price phrases on the server before sending the remaining question text to the local routing model. The model returns scope and a literal search excerpt only. Reviewed filters enter validated SQL predicates before counts/pagination, using correlated registered-device availability and remaining-demand commitments. Prices compare integer minor units in one explicit currency. Web/native share form validation and applied-filter summaries; unsupported numeric syntax prompts manual lookup. No schema, commercial mutation, chat memory or model-training change is involved. See ASSISTANT.md for supported syntax and evaluation limits.

## Assistant feedback

Explicit assistant feedback uses signed process-local suggestion receipts and separate AssistantFeedback/AssistantFeedbackGroup tables. Staff submits with consent; admins freeze validated routing labels into a permanently assigned teaching or holdout family. Unique normalized questions and family row locks prevent duplicate/split leakage. Optional lexical demonstration selection is bounded and revalidated after inference; the independent operator evaluator freezes cases and pins model identity. `PB_ASSISTANT_LEARNING_ENABLED` defaults false. Multi-worker receipt signing and real staff evaluation remain production work. See ASSISTANT-FEEDBACK.md.

## Published company knowledge

KnowledgeArticle separates an editable administrator draft from immutable KnowledgeRevision snapshots. Reviewed publish/withdraw actions serialize on the actor/article and require the current version. Staff retrieval joins only the active publication within inclusive UTC dates; source inspection rechecks the same constraints. The assistant adds a knowledge route with deterministic excerpts and database-assigned revision citations, without passing document text to the model. References are manually verified plain text. See KNOWLEDGE.md for privacy, history and retrieval limits.

## Current staff notifications

The notification centre unions current assigned conversations/follow-ups and eligible pending quotation/order reviews. NotificationRead stores only the authenticated user’s read version for each kind/source; newer source versions return to unread. Personal marker writes serialize on the active actor, without changing source business state. Fresh source checks remove closed/reassigned work. Web/native use the same filtered pages and workspace navigation. There is no event delivery queue or push transport yet. See NOTIFICATIONS.md.

## Mobile push delivery

PushDevice binds an encrypted Expo token to an active session generation; PushDelivery uniquely queues each eligible source version. Bounded workers recheck unread/current source eligibility before fixed-endpoint sends, claim jobs atomically, poll provider receipts and avoid automatic resend after uncertain sends. Payloads contain generic text and an opaque delivery ID. Authenticated tap resolution and notification record routes recheck access, then reuse existing editors and approval gates. Live delivery is disabled; see PUSH.md and DEPENDENCY-REVIEW.md.
