feat: architecture refactor — core layer, spreadsheet engine, envelope bidding, tests
CI / quality (push) Waiting to run
CI / quality (push) Waiting to run
- Add src/core/ (config, errors, events, http, observability, storage, query) - Add src/Spreadsheet/ engine with native POI backend and Excel bridge - Add Envelope C spreadsheet flow and tender bidding envelope modules - Consolidate components into src/components/, remove legacy Component/ and Hooks/ - Add registration state/validation, auth session guard, profile edit modules - Add unit tests across axios, redux, bidding, registration, encryption, core - Add CI workflow, docs (architecture, audit, DSC API spec), keystore example - Remove tracked keystore binary; ignore scripts.zip Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9acec328a7
commit
1d7646d938
@@ -0,0 +1,153 @@
|
||||
# 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<T>()` | 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.<level>(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<NativeStackNavigationProp<RootStackParamList>>`);
|
||||
broaden accessibility labels; i18n scaffolding.
|
||||
Reference in New Issue
Block a user