Authorised by rulings D7 and T1-T3 in docs/parity/DECISIONS.md. This is the CLAUDE.md section 3 carve-out: matching the reference here would keep a money write path open to anonymous callers. Closed, each behind the rid the legacy vocabulary already had: GET accounting/customers/:id/balance -> wallet.list POST accounting/customers/:id/reconcile -> wallet.charge POST accounting/repayments/gold -> document.create POST accounting/repayments/rial -> document.create POST accounting/entries/:id/confirm -> document.list.confirm PATCH orders/:id/status -> order.list.sts.submit Four of the accounting routes WRITE to the ledger. The order-status route was worse in kind: its DTO takes a client-supplied actorAdminUserId, so an anonymous caller could move any order and pin it on any admin. It had no caller - the admin panel posts to admin/orders/status. GET /customers kept its public status (both apps' login still matches the phone number in the browser) but now selects only id/mobile/isActive/ isArchived. It previously returned every active customer's full name, national code, both debts, credit limit and the URLs of their identity-document scans. CustomerSummary narrowed in BOTH user apps in the same commit, per the v1/v2 parity rule. Not changed: /uploads stays public per ruling T2. The filename check requested alongside it passed unchanged - md5(time + name + uuid), no directory listing. Tests: apps/api/scripts/auth-smoke.sh asserts 401 without a token, 403 with a low-privilege token (ut 88, 5 unrelated rids) and pass-through for a super admin on every closed route, plus that the public routes still answer 200 and that GET /customers exposes only the four login fields. 23 passed, 0 failed against a live server. Typecheck clean on api, user-app and user-app-v2. [ref: henza_server/routes/admin.php:12,272; ApiMobileController.php:39-78] Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.9 KiB
6.9 KiB
PROGRESS — Reference Parity Project
Read this file FIRST at the start of every session and after every
/compact. Rules live inCLAUDE.md(workspace root). Method lives in thereference-parity-auditskill.
Current state
| Field | Value |
|---|---|
| Phase | Security hotfix (out-of-band, authorised by D7/T1–T3) — stage 1 ✅ done, stage 2 next |
| Branch | parity/security-hotfix |
| Status | Stage 1 complete and tested live: 23/23 smoke assertions pass. Decisions D0–D12 + T1–T3 recorded. |
| Next step | Stage 2 — customer JWT (typ claim, separate secret) + real OTP + migrate both apps' login; then remove GET /customers from the public surface |
| Blocked on | nothing for stage 2. Still pending later: D0 migration + percent migration must be shown and approved before running |
| Last updated | 2026-09-22 |
Security hotfix — stage 1 result
| Route | Was | Now | Rid |
|---|---|---|---|
GET accounting/customers/:id/balance |
public | admin | wallet.list |
POST accounting/customers/:id/reconcile |
public write | admin | wallet.charge |
POST accounting/repayments/gold |
public write | admin | document.create |
POST accounting/repayments/rial |
public write | admin | document.create |
POST accounting/entries/:id/confirm |
public write | admin | document.list.confirm |
PATCH orders/:id/status |
public write | admin | order.list.sts.submit |
GET customers |
18 fields incl. national ID, debts, doc URLs | 4 fields | still public until stage 2 |
Test: apps/api/scripts/auth-smoke.sh — 23 passed, 0 failed.
Typecheck: api ✅ · user-app ✅ · user-app-v2 ✅.
⚠️ Read DECISIONS.md before doing anything
All user rulings live there (D0–D12). Highlights that change how work is done:
- D12 — the matrix is FIELD-BY-FIELD, not page-by-page, and a 4-step
round-trip test per entity is mandatory. Both rules were added to the
reference-parity-auditskill (Step C rule C0, Step E rule E0). - D0 — split
fullNameintofirstName/lastNamevia an additive migration; show the migration and get approval before running it. - D4 — never port broken behaviour, but match calculations/rounding/messages exactly; annotate each non-replication «عمداً تکرار نشد — ISSUE #n».
- D2 — target-only features get status ➕ and are left alone.
Phase checklist
- P0.1 — All 3 reference folders readable (
henza_server,henza_admin,henza_pwa) - P0.2 — Branch
parity/phase-0+docs/parity/created - P0.3 — Inventory all 7 projects →
_inv-*.md(8111 lines total) → consolidated in00-INVENTORY.md - P0.4 — Mapping tables, missing list, extra list (
00-INVENTORY.md§2–4) - P0.5 — Draft
PROTECTED.md - P0.6 — GATE passed —
PROTECTED.mdconfirmed, D0–D12 recorded - P0.7 — Urgent traces delivered:
percent,@Publiclist, login leak - P0.8 — Skill updated with rules C0 (field-by-field) and E0 (round-trip test)
- P1.0 — user approves: 5 ledger routes,
percentcolumn, D0 migration ← HERE - Phase 1 — Admin parity (branch
parity/phase-1-admin,01-ADMIN-MATRIX.md) - GATE: "Admin parity is complete and tested. Shall we move on to the apps (user-app & user-app-v2)?"
- Phase 2 — Apps parity (
02-APPS-MATRIX.md, both user apps) - Phase 3 — Work through
ISSUES.md(only when user asks)
Answered — see DECISIONS.md for the full text
| # | Ruling |
|---|---|
| D0 | Split fullName → firstName/lastName, additive migration, backfill, sync, report multi-token rows. Approval needed before running. |
| D1 | Target's stricter auth stands. Build the "reference operation ↔ rid" matrix; explain 172 vs 174. |
| D2 | Audit only what the reference has. Target-only features → ➕, untouched. |
| D3 | Build the 5 missing screens in Phase 2, both apps, plus analytics, isLoaded/sts_verify_step_two guards, guest toast+home, and v2's cart ceiling. |
| D4 | Don't port breakage; do match calculations/rounding/messages. Unit-test the price formula. |
| D5 | Leave the stray folder; delete last, own commit. |
| D6 | OTP must verify — parity gap, fix it (plus expiry + attempt limits, which exceed the reference). |
| D7 | @Public ledger routes: list delivered, awaiting approval to protect. |
| D8 | Login leak: target, full customer book incl. national IDs and debts. |
| D9 | percent: trace delivered — the target's implementation is wrong. |
| D10 | Sales expert: keep AdminUser; investigate the 5 missing fields; propose SalesExpertProfile. |
| D11 | singleCustomer.vue → diff-send. |
| D12 | Field-by-field matrix + mandatory round-trip test. Added to the skill. |
Open — awaiting user approval
- Protect the 5
@Publicledger-write routes (DECISIONS.mdgroup A). percentschema change — addmakingChargeOffsetPercenttoCustomer.- D0 migration file — must be shown and approved before it runs.
Key context (survives context loss)
- Domain correction: the reference is a gold-jewellery B2B/B2C e-commerce
system, not a gold bank. No buy/sell order book, no spread, no deposit/withdraw,
no P2P transfer, no KYC levels, no daily limits (only
gram_level), no payment callback route, no cron, no websockets. See00-INVENTORY.md§0. - The gold price formula (Phase 1 row #1) —
henza_server/app/Models/ProductVariant.php:183-190:+extra%→+7%margin →+9%VAT on the non-metal portion only →×grams→floor(/1000)*1000. Three superseded commented-out variants above it apply VAT to the whole price — do not port those. - A large prior port already happened — see
docs/ADMIN-PORT-PLAN.mdanddocs/CHANGELOG-PORT-HISTORY.md(80 views, 25 Nest modules, 29 Prisma models, 154 admin routes, 144/144 rids). Prior port notes no view was ever visually verified in a browser. - Stack changed across the port: Laravel→NestJS/Prisma, Vue2/Vuesax→Vue3/antd. Port behaviour, not code style.
- Reference scale is inflated: 541 admin
.vuefiles but only ~69 reachable (rest are Vuexy template demos); 9 reference router files are dead (not imported); ~28 legacy Django-era DB tables have no migrations. - v1 ⇄ v2 logic gaps (5, all v2 behind v1) are parity work, not design:
ISSUES.mdMON-01, V2-01..05.
Inventory files (Phase 0 working files)
| File | Scope | Lines |
|---|---|---|
_inv-ref-server.md |
henza_server | 1313 |
_inv-tgt-api.md |
apps/api | 1179 |
_inv-ref-admin.md |
henza_admin | 1705 |
_inv-tgt-admin.md |
apps/admin-panel | 1261 |
_inv-ref-pwa.md |
henza_pwa | 1178 |
_inv-tgt-userapp.md |
apps/user-app | 612 |
_inv-tgt-userappv2.md |
apps/user-app-v2 | 700 |
Consolidated into 00-INVENTORY.md. Issues found: ISSUES.md (26 entries).