Adds computeVariantPrice(), a verbatim port of the live formula at henza_server/app/Models/ProductVariant.php:183-190, with the per-customer making-charge offset as an explicit input defaulting to 0. The three superseded formulas commented out above it apply VAT to the whole price and are deliberately not ported (D4). 11 unit tests assert against hand calculations derived from the PHP source rather than from the implementation, covering: with and without the offset, VAT on the non-metal portion only, floor-to-nearest-1000, the extraPercent default of 7, and the equivalence between the app folding the offset into percent_pay and the server deriving it. 18 tests pass overall. Also adds the D0 name-split review report script (read-only) and two investigations the owner asked for: - SMS: the reference uses the Trez panel (smspanel.trez.ir, Sms.php:69,97) with production credentials committed to git (SEC-12 — rotate them). All 12 message templates catalogued; env var names proposed; nothing implemented pending approval. - MON-08: henza_pwa computes percent_pay, discount and rial_pay rather than asking the user (cart.vue:170-179, Mixins/product/index.ts:69-75), so Phase 2 item #1 is to derive them server-side and ignore the client's values. rash_weight stays read from the DB as a recorded deviation. Records the owner's two new standing rules: migrations only through the standard Prisma workflow, and every data deletion logged in CHANGELOG. [ref: henza_server/app/Models/{ProductVariant,Sms}.php, henza_pwa/src/views/cart.vue] Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 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 — stages 1 and 2 ✅ complete |
| Branch | parity/security-hotfix |
| Status | Both stages done and verified live: 46/46 smoke assertions, 7/7 jest unit tests, typecheck + build clean on all three projects. |
| Next step | Migrations are written and shown — awaiting approval to run them (migrate dev), then 01-ADMIN-MATRIX.md field-by-field (D12) |
| Blocked on | approval to run the two migrations; approval to implement the SMS provider |
| Last updated | 2026-09-22 |
Security hotfix — final result
| Route | Was | Now |
|---|---|---|
GET accounting/customers/:id/balance |
public | admin, rid wallet.list |
POST accounting/customers/:id/reconcile |
public write | admin, rid wallet.charge |
POST accounting/repayments/gold |
public write | admin, rid document.create |
POST accounting/repayments/rial |
public write | admin, rid document.create |
POST accounting/entries/:id/confirm |
public write | admin, rid document.list.confirm |
PATCH orders/:id/status |
public write | admin, rid order.list.sts.submit |
GET customers |
every customer, 18 fields | route deleted |
GET customers/:id |
public | route deleted → GET customer/me (token) |
GET customers/:id/orders |
public | route deleted → GET customer/me/orders (token) |
GET customers/:id/ledger-entries |
public | route deleted → GET customer/me/ledger-entries (token) |
| — | — | new POST customer/auth/request-otp · verify-otp |
Still public by design: catalog/products, pricing/latest, home/slider,
uploads/:filename (ruling T2), and POST /orders + GET /orders/:id — the
last two are logged as SEC-02/B1-B2 and want a customer-token pass of their own.
Tests
apps/api/scripts/auth-smoke.sh(live server): 46 passed, 0 failed. Run it withAPI=http://localhost:3000/api CODE_LOG=/path/to/api.log.pnpm testinapps/api(jest, newly installed): 7 passed.- Typecheck: api ✅ · user-app ✅ · user-app-v2 ✅. Both apps build ✅.
⚠️ Correction worth remembering
Stage 1 reported the customer-book leak as "mitigated" after narrowing the
GET /customers field list. That was wrong: the response still returned every
customer id, and the three /customers/:id* routes were still public, so the
full data was still two requests away. The user caught it. Stage 2 deleted the
whole id-bearing surface instead. Narrowing a response is not closing a leak
while the id and the id-addressed routes remain.
⚠️ Read DECISIONS.md before doing anything
All user rulings live there (D0–D13). 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.
- D13 — the retail storefront (48
customer.php+ 30site.php+ 31 storefront-management admin routes = 109 of the reference's 412 endpoints) is preserved but out of scope: status ⏸️, no gap row, no work row, nothing deleted or disabled. 288 endpoints stay in scope (218 admin + 70 B2B app). Note: the reference total is 412, not 369 —admin.phpwas undercounted in Phase 0; corrected in00-INVENTORY.md§2.1. Ask before changing shared surfaces (product, variant, price, order, customer).
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. |
| D13 | Retail storefront (wallet, cashback, ConfigStatic['percent']) preserved but OUT OF SCOPE. Nothing deleted or disabled; no parity work; matrices list it ⏸️ with no gap/work row; ask before touching shared surfaces. |
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).
Standing rules added 2026-09-22
- Migrations run only via the standard Prisma workflow (
migrate devin development).prisma db executeis not used for schema changes. - Every data deletion is logged in
CHANGELOG.md— including throwaway rows created during a session. - D13 — the retail storefront is preserved but out of scope; see
DECISIONS.md§D13 andPROTECTED.md§H.
Phase 2 — item #1 (fixed by the MON-08 ruling)
Before anything else in Phase 2: the server must derive wagePercent,
discountPercent and flatRialCharge from the DB and ignore the client's
values, because henza_pwa computes them rather than asking the user
(cart.vue:170-179, Mixins/product/index.ts:69-75). A comparison test must
show the total is unchanged for an ordinary customer. Depends on
makingChargeOffsetPercent existing. Full reasoning: DECISIONS.md
§"Report — MON-08".
Waiting on the owner (2026-09-22)
| # | Item | What is ready |
|---|---|---|
| 1 | Run the D0 and percent migrations | Both SQL files written, storefront impact assessed, backfill report script written, 11 unit tests passing. Command: npx prisma migrate dev. |
| 2 | Implement the SMS provider (Trez) | Provider, all 12 templates and env-var names documented in DECISIONS.md. Nothing written yet. SEC-12: the reference's committed credentials must be rotated. |