Read-only inventory of all 7 projects (3 reference + 4 target) produced by parallel subagents, every claim cited path:line. No source code changed. - 00-INVENTORY.md consolidated mapping: stack, backend, admin, user apps - PROTECTED.md draft protected list (awaiting user confirmation) - ISSUES.md 26 logged issues (security, money/stock, validation, dead code) - CHANGELOG.md change log scaffold - PROGRESS.md resumable state + Q1-Q5 blocking Phase 1 - _inv-*.md 7 raw per-project inventories (8111 lines) Key finding: the reference is a gold-jewellery B2B/B2C e-commerce system, not a gold bank. Buy/sell, deposit/withdraw, transfer, KYC levels, payment callbacks, cron and websockets do not exist in it, so they cannot be parity gaps. [ref: henza_server, henza_admin, henza_pwa] Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 KiB
10 KiB
ISSUES — logged, NOT fixed during parity
Anything found during the parity audit that is a bug, risk, smell or improvement
opportunity goes here. Do not fix these while parity work is in progress (see
CLAUDE.md §3). They are worked through in Phase 3, only when the user asks.
Severity: Critical (money loss / security / data loss) · High · Medium · Low
Paths are relative to the workspace root (
gold bank/). "REF" = reference project (read-only). "TGT" =gold-banking-monorepo. Items marked [replicated] exist in the reference and were faithfully ported — fixing them is a product decision, not a parity fix.
Security & auth
| ID | Severity | Where | Description | Evidence | Suggested fix |
|---|---|---|---|---|---|
| SEC-01 | Critical | REF henza_server/routes/admin.php:12,272 |
The whole admin route file is unauthenticated — the Route::middleware('auth:api')->group(...) wrapper is commented out at both ends, leaving all ~215 admin routes public (incl. wallet/charge, users, roles). |
Verified by reading both lines. | n/a in reference (read-only). Drives TGT decision below. |
| SEC-02 | Critical | TGT apps/api/src/modules/accounting/accounting.controller.ts:7, orders/orders.controller.ts:7, customers/customers.controller.ts:5, catalog/catalog.controller.ts:5, pricing/pricing.controller.ts:5 |
15 routes carry @Public(), including ledger writes (POST accounting/repayments/*, entries/:id/confirm) and POST /api/orders + PATCH /api/orders/:id/status. The public status DTO accepts a client-supplied actorAdminUserId, so a caller can attribute an action to any admin. |
grep -rn "@Public()"; source comments say "pre-existing unauthenticated endpoint". |
Introduce customer-session auth for the app-facing routes; move ledger/status writes behind the admin guard. [replicated] from SEC-01. |
| SEC-03 | Critical | TGT apps/user-app/src/views/user/LoginView.vue, apps/user-app-v2/ same |
OTP is not verified — any 4-digit code logs the user in. Login also downloads the entire customer list (GET /api/customers) and matches the phone number client-side, exposing every customer's data to any visitor. |
Both inventory agents verified independently; customers.controller.ts:5 is @Public(). |
Implement a real server-side OTP issue/verify pair; never ship the customer list to the client. |
| SEC-04 | High | TGT apps/api (two files) |
JWT_SECRET falls back to 'dev-only-insecure-secret' and is absent from .env.example. enableCors() is wide open. |
Reported by api inventory; see _inv-tgt-api.md §11. |
Require the env var at boot; restrict CORS origins. |
| SEC-05 | High | TGT apps/api/prisma/seed.ts |
Seed creates a SUPER_ADMIN (09128081392) whose password equals the phone number, holding all 144 rids. Marked DEV-ONLY in comments but lives in the normal seed. |
docs/CHANGELOG-PORT-HISTORY.md §4 documents this as intentional for dev. |
Gate behind an explicit env flag so it can never run outside dev. |
| SEC-06 | High | REF henza_server |
Hardcoded secrets committed to the repo: SMS panel password, PayPing bearer token, Tahesab bearer + IP allowlist, wallet hash pepper ("yhoenszeaf"). |
See _inv-ref-server.md §11.4. |
Rotate all of them before any deployment that reuses these credentials. |
| SEC-07 | Medium | TGT apps/api/src/modules/admin-customers/admin-customers.service.ts |
setRoles stores arbitrary strings as rids with no validation against ALL_RIDS. |
api inventory §9. | Validate against the rid catalogue. |
Money, stock & data integrity
| ID | Severity | Where | Description | Evidence | Suggested fix |
|---|---|---|---|---|---|
| MON-01 | Critical | TGT apps/user-app-v2/src/stores/cartStore.ts |
v2 has no capQty / maxQty stock ceiling, which user-app does have (apps/user-app/src/stores/cartStore.ts:62,65; components/product/CartItem.vue:82-83). A v2 user can order more units than exist. |
Verified by grep across both apps — zero hits in v2. | Port the ceiling to v2 (this is also a Phase 2 parity gap, row to be added to 02-APPS-MATRIX.md). |
| MON-02 | High | REF henza_server wallet package |
The wallet balance column mixes grams and toman in one field, and money operations are not wrapped in DB transactions. |
_inv-ref-server.md §7. |
Separate the units; wrap mutations in transactions. |
| MON-03 | High | TGT apps/api/src/modules/accounting/ledger.service.ts:189 |
GRAMS_PER_MESGHAL = 4.331802 is a JS float used inside an otherwise-Prisma.Decimal CASH repayment formula, so the divisor is pre-rounded before the Decimal division. |
Read at ledger.service.ts:182-190; the source comment already flags the formula for stakeholder review. |
Make the constant a Decimal (or a string literal passed to Decimal). |
| MON-04 | High | TGT apps/api/src/modules/orders/orders.service.ts |
Debt is recognised at DELIVERED_TO_AGENT, but the idempotency guard checks deliveredAt, which is only set by DELIVERED_TO_CUSTOMER — so the debt trigger is not reliably idempotent. There is also no status-transition validation anywhere. |
api inventory §8. | Guard on the same field the trigger fires on; add an explicit transition table. |
| MON-05 | Medium | TGT apps/api |
canPlaceOrders and verificationStep are stored and toggled by the admin panel but never enforced on any order path. |
api inventory §8. | Enforce at order creation, or remove the toggles. |
| MON-06 | Medium | TGT apps/api |
Setting rows are never seeded — wallet.config, wallet.birthday, promotion.auto, mobile_main_slider only materialise on first write, so a fresh install has undefined behaviour until an admin saves each one. |
api inventory §7. | Seed the defaults. |
| MON-07 | Medium | REF henza_server/app/Models/ProductVariant.php:183-190 vs SingleCustomerOrders.php:67, Order.php:66 |
Two divergent order-status enums coexist (0–6 and 1–7) for what is conceptually one flow. | Read both models. | Unify, or document the mapping explicitly. |
Validation & contracts
| ID | Severity | Where | Description | Evidence | Suggested fix |
|---|---|---|---|---|---|
| VAL-01 | High | REF henza_server |
Zero FormRequests; all validation is inline, and setOrder, wallet charge, setConst and file upload have no validation at all. The wallet package's own validator is dead code (it calls failed() instead of fails()). |
_inv-ref-server.md §12. |
n/a in reference; informs how much validation the target must add. |
| VAL-02 | High | TGT apps/api |
Only 8 DTO classes exist; most admin request bodies are untyped Record<string, unknown>, so class-validator never runs on them. |
api inventory §5. | Add DTOs for the admin write paths. |
| VAL-03 | Medium | TGT both user apps | HTTP client is a bare fetch wrapper: no interceptors, no Authorization header, no refresh, no 401 handling, no error toasts. Non-2xx throws a generic Error; call sites mostly console.log(e). Order-submit failure is silent to the user. |
apps/user-app/src/api/client.ts, apps/user-app-v2/src/api/client.ts:3. |
Add a shared client with error surfacing; compare against henza_pwa/src/service/* in Phase 2. |
Parity-relevant divergence between user-app and user-app-v2
CLAUDE.md§2: the only allowed difference is visual design. These are logic differences, so they are Phase 2 parity gaps as well as issues.
| ID | Severity | Where | Description | Evidence |
|---|---|---|---|---|
| V2-01 | Critical | v2 missing stock ceiling | see MON-01 | grep |
| V2-02 | Medium | v2 missing getHomeSlider() / GET /home/slider admin banner |
v1 api/client.ts:152-170 defines AdminSlide + getHomeSlider; v2 has neither |
diff apps/user-app/src/api/client.ts apps/user-app-v2/src/api/client.ts |
| V2-03 | Medium | v2 missing per-variant images | v1 defines ProductImageSummary (id/url/sortOrder/role) and imageId/image on the variant; v2's images is just Array<{url:string}> |
same diff |
| V2-04 | Medium | v2 missing orderable-first variant defaulting | v1 composables/useProduct.ts:26 picks the first orderable variant; v2 has no equivalent |
grep |
| V2-05 | Low | v2 missing variant-chip preview cap + per-chip stock badges | v1 components/product/SingleProduct.vue:388-392 |
grep |
Dead code & maintenance
| ID | Severity | Where | Description |
|---|---|---|---|
| DEAD-01 | Low | TGT apps/user-app |
8 components with no importers, loadingStore, api.getBalance (zero call sites), unused gsap dependency. |
| DEAD-02 | Low | TGT apps/user-app-v2 |
11 dead components, loadingStore, getBalance, priceUnit/priceUnitRialLess, 12 of 15 validators, unused gsap + @ant-design/icons-vue deps. |
| DEAD-03 | Low | TGT gold-banking-monorepo/user-app-v2/ (repo root, outside apps/) |
Stray leftover folder containing only package.json and an older vite.config.ts (hardcoded port 5179, hardcoded proxy, imports a themeConfig that doesn't exist there). Not matched by pnpm-workspace.yaml, so pnpm ignores it. Left in place — deleting it needs user approval. |
| DEAD-04 | Low | REF henza_server/app/Console/Kernel.php |
schedule() is empty — no cron. Price refresh / expiry / birthday logic must be driven by OS cron or the GET / web route. |
| DEAD-05 | Low | REF henza_server |
PayPing returnUrl points at site/transaction/verify, which is not registered in any route file — the payment callback is unreachable. |
Known stubs in the target (not bugs, but not parity either)
| ID | Where | Description |
|---|---|---|
| STUB-01 | TGT both user apps | Registration persists nothing; favourites are in-memory only (lost on reload); notifications is a static empty state; home slides are 3 hardcoded placeholders in v2. |
| STUB-02 | TGT apps/api |
No OTP, no SMS provider, no payment gateway/callbacks, no cron, no websockets. SMS and notifications are DB-persistence only. |
| STUB-03 | TGT both user apps | All list filtering and "pagination" is client-side over one flat GET /catalog/products; no server page/limit params. "Pagination" is a 20-item IntersectionObserver window. |
| STUB-04 | TGT both user apps | Live price is pull-on-mount only (GET /pricing/latest?karatPerMille=750) — no socket, no SSE, no polling interval. quotedAt renders as a Jalali date only, so a stale rate looks current. |