Files
tehran_taj/docs/parity/PROGRESS.md
T
s.esmaeiliandClaude Opus 5 fdf833565f parity(api,apps): security hotfix stage 1 - close unauthenticated write paths
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>
2026-09-22 00:43:11 +03:30

6.9 KiB
Raw Blame History

PROGRESS — Reference Parity Project

Read this file FIRST at the start of every session and after every /compact. Rules live in CLAUDE.md (workspace root). Method lives in the reference-parity-audit skill.

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-audit skill (Step C rule C0, Step E rule E0).
  • D0 — split fullName into firstName/lastName via 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 in 00-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.md confirmed, D0–D12 recorded
  • P0.7 — Urgent traces delivered: percent, @Public list, login leak
  • P0.8 — Skill updated with rules C0 (field-by-field) and E0 (round-trip test)
  • P1.0 — user approves: 5 ledger routes, percent column, 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

  1. Protect the 5 @Public ledger-write routes (DECISIONS.md group A).
  2. percent schema change — add makingChargeOffsetPercent to Customer.
  3. 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. See 00-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.md and docs/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 .vue files 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.md MON-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).