# ProcureLinc — Architecture Guide (as-built) This is the **as-built** companion to [`ARCHITECTURE_AUDIT.md`](./ARCHITECTURE_AUDIT.md). The audit is the blueprint (what was wrong, what to build); this document is the map of what now exists and the rules for working within it. Read this before adding a feature or "fixing" something that looks odd — several things that look like smells are deliberate, documented below under **Deferred / do not touch blindly**. --- ## 1. Layering ``` Presentation src/Screens/**, src/components/** (UI only) │ Application src/Services/**, feature hooks (use-cases, orchestration) │ Domain slices, reducers, pattern configs (business rules, pure) │ Infrastructure src/core/** (http, storage, crypto, events) │ Platform react-native, native modules ``` Dependencies point **inward**. `src/core/**` must never import from `Screens`, `Services`, or `Redux`. It reaches outward only through injection/events (see §3). --- ## 2. The `core/` layer — the single source of infrastructure truth | Module | Responsibility | Rule | |--------|----------------|------| | `core/config/env.ts` | All hosts/base URLs, selected by build type | **No hardcoded URLs anywhere else.** | | `core/http/client.ts` | THE axios instance; `request()` | Throws `AppError`; never returns a sentinel. All new network code uses this. | | `core/http/AppError.ts` | Normalized typed error | Catch with `isAppError()`; branch on `.kind`. | | `core/observability/logger.ts` | The only sanctioned log sink | **Never `console.*` in app code.** DEV-only; release-stripped. | | `core/observability/redact.ts` | PII/secret masking | Every log payload is redacted; extend `SENSITIVE_KEY_PATTERN` when adding sensitive fields. | | `core/storage/secureStorage.ts` | Keychain-backed persist storage for the auth session | Token is never in plaintext AsyncStorage. | | `core/events/appEvents.ts` | Typed cross-layer event bus | How infra signals up without importing the store. | | `core/query/queryClient.ts` | Module-singleton React Query client | Never construct a QueryClient in a component. | | `core/errors/ErrorBoundary.tsx` | App-root render-error guard | Wraps the tree in `App.tsx`. | | `core/platform/polyfills.ts` | Buffer / TextEncoder | Imported first in `index.js`; nothing may depend on load order otherwise. | --- ## 3. Hard rules (enforced by review; some by CI) 1. **Networking:** use `request()` from `core/http/client`. It injects the JWT, handles 401 (emits `auth/unauthorized`), times out, and throws `AppError`. `src/Axios/Axios.ts` (`apiCall`) is a **legacy compatibility adapter** that preserves the old never-throws `{success:false}` envelope for un-migrated call sites; its contract is pinned by tests. Do not add new `apiCall` sites. 2. **Auth token:** read only via `selectAuthJwt(state)`. The transport reads it through a provider registered in `Redux/Store.ts` — never `import { store }` into `core`. 3. **Logout:** call `performLogout()` from `Services/Auth/session.ts`. It clears redux + keychain + the query cache in one place. `AuthSessionGuard` (mounted in `App.tsx`) resets navigation to Login whenever the session ends. 4. **Post-auth routing:** `Services/Auth/registrationRouting.ts` is the single decision function. Splash and Login both call it. Do not re-implement. 5. **Logging:** `logger.(scope, message, data?)`. Never log key material, plaintext bids, OTPs, or full PII (the redactor is a safety net, not a licence). 6. **Theme/colors:** use `useTheme()`. `Constant/Color.tsx` is **deprecated** (see §6). There is one brand orange, sourced from `theme/colors`. 7. **Components:** one root — `src/components/**`. `src/Component/` no longer exists. 8. **Navigation:** route param shapes live in `src/Navigation/types.ts` (`RootStackParamList`). `NavigationString` is the `as const` route-name registry. 9. **Forward/Reverse bidding:** every behavioural difference lives in `Screens/Bidding/biddingPattern.ts`. The screen/card contain zero `if (pattern === ...)`. --- ## 4. Feature anatomy (pattern to copy) The tender-bidding and registration features show the intended shape — logic is extracted out of the screen into pure, testable units: ``` Screens/Tender/tenderBidding/ TenderBidding.tsx presentation + state only envelopeTypes.ts types envelopeCrypto.ts AES/RSA (never logs key material) envelopePayload.ts pure payload build + validation ← unit-tested envelopeSubmission.ts API calls components/* memoized, module-scope sub-components Screens/Ragistration/ Ragistration.tsx orchestration + render registrationState.ts typed useReducer (was ~20 useStates) ← unit-tested registrationValidation.ts pure per-stage validation ← unit-tested ``` When you build a feature: keep the screen thin; put decisions in pure functions; test the pure functions. --- ## 5. Testing & CI - `npm test` (jest). 75 tests across 11 suites, concentrated on the money/crypto/auth/storage paths: `envelopePayload`, `biddingPattern`, `registrationState`/`registrationValidation`, `registrationRouting`, `Token` slice, `apiCall` envelope contract, `AppError`, `redact`, `appEvents`, and the `secureStorage` keychain-migration path. - CI (`.github/workflows/ci.yml`) runs **install → typecheck → lint → test**, all **blocking**. `tsc --noEmit` is green; `eslint . --quiet` (errors) is green. Style *warnings* still exist and are being burned down; tighten to `--max-warnings=0` once clean. - New pure logic should ship with tests. UI can follow with RNTL later. --- ## 6. Deferred / do not touch blindly These look like leftovers but were left deliberately. Read before "fixing". - **`Constant/Color.tsx`** is deprecated but still imported by ~20 files for neutral grays. Its brand tokens are now sourced from the theme, but its neutral values differ from `theme/colors` in several places. A blind find/replace to `useTheme()` **will change appearance** — migrate screen-by-screen with visual review. - **`EnvelopeCExcelPreview.tsx`** still uses raw axios (`responseType: 'arraybuffer'`) and `react-native-fs` with `'ascii'` write encoding. The RNFS → `react-native-blob-util` dedupe and the axios → `core/http` migration are deferred because this sealed-bid document flow can't be runtime-verified here and the encodings aren't 1:1. Needs a device. - **Sealed-bid crypto** is AES-ECB with the symmetric key echoed to the server (`randomKeyGenerated`). This is a backend-contract weakness (audit §11); fixing it requires a coordinated backend change, not a client edit. - **`ResetPassword`** sends the new password as a URL query param. Migrated to `core/http` (so it's redacted in logs) but the query-param placement is a backend contract — see the `TODO(backend)` at the call site. - **`PurchaseTenderTile` "details" tap** navigates to `'TenderDetail'`, an unregistered route (a pre-existing latent bug). The intended destination is ambiguous (the tile already renders an in-tile modal), so it awaits a product decision rather than a guessed target. --- ## 7. Open work (gated on device or backend/ops) - **Device/emulator needed:** full Auth/App two-stack navigator split (`AuthSessionGuard` + `registrationRouting` are the groundwork); release minify/shrink + App Bundle + ABI splits; verifying the Envelope C flow. - **Backend/ops needed:** HTTPS for `eproc-backend.lincpay` (then remove its cleartext exemption in `network_security_config.xml` and add pinning); rotate the previously-committed keystore + Sonar token and purge git history. - **Incremental:** finish `Constant/Color` → `useTheme`; per-screen typed navigation adoption (`useNavigation>`); broaden accessibility labels; i18n scaffolding.