Phase 0 gate passed. All rulings recorded in docs/parity/DECISIONS.md and applied to PROTECTED.md, PROGRESS.md and ISSUES.md. No source code changed. Urgent investigations requested by the user, all evidence-cited: - percent trace: the target's implementation is WRONG. `percent` is the customer's OWN column in the reference (customer_add.php:32), a per-customer making-charge offset used to shift catalogue filter bounds (ApiMobileController.php:391-396,76-77). The target reads the referring agent's wagePercent instead (admin-customers.service.ts:245) and never writes the field at all. It is NOT part of the price formula -- that extra percent comes from a global ConfigStatic row (ProductVariant.php:50,183). Two unrelated `percent` keys exist and must not be confused with it. - @Public audit: 15 routes across 7 controllers, grouped by risk. Group A is 5 ledger routes (4 writes) that contradict decision D1; group B is 3 order routes incl. a public status change accepting a client-supplied actorAdminUserId; group C is 8 reads, of which only the catalogue, pricing, slider and uploads are legitimately public. - Login leak: it is in the TARGET. GET /customers is public and returns every active customer with national ID numbers, debts and ID-document URLs. passwordHash is correctly excluded. Not possible in the reference. Also logged 5 customer-module defects found in the deep check the user asked for (CUST-01..05), including the fullName collapse the user reported. Skill updated per D12: Step C gains rule C0 (field-by-field matrix, never page-by-page) and Step E gains rule E0 (mandatory 4-step round-trip test). [ref: henza_server, henza_admin] Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 KiB
PROTECTED — intentional monorepo additions (additive-only)
CONFIRMED by the user at the Phase 0 gate (2026-09-21).
Rulings are recorded in DECISIONS.md (D0–D12).
Items here are intentional changes in gold-banking-monorepo. Per CLAUDE.md §4
they must not be removed, restyled, restructured, or have their behaviour
changed. You may only ADD what the reference has and they lack, in a way that
doesn't break them. Any fix that touches a protected item needs explicit approval
with a diff plan first.
Legend: [brief] = named as protected in the kickoff brief · [found] = discovered in Phase 0, proposed for protection · [?] = needs your ruling.
A. Admin panel — Dashboard · [brief]
Location: apps/admin-panel/src/views/Dashboard.vue (327 lines) — one file
serving both /dashboard/ecommerce and /dashboard/analytics.
Gate: rid dashboard.view; lacking it redirects to my-data (not a 403 page).
Data: a single GET admin/dashboard/overview on mount, then client-side sorts
(reagent desc by grams_sell; lastOrder desc by w).
Contents, in render order:
- 4 KPI tiles — مشتریان جدید (نفر) · طلای فروشرفته (گرم) · تعداد سفارشات · نرخ هر گرم طلای ۱۸ عیار
- 2 KPI tiles — سفارشات تحویلشده · مجموع بدهی طلای مشتریان (گرم)
- Area chart «روند سفارشها» — 2 series (تک / عمده); x-labels rotated -45° with
rotateAlways+hideOverlappingLabels(a deliberate overlap fix — do not "tidy" it) - 2 donuts — وضعیت سفارشات (8-entry colour array) · تفکیک عمده/تک
- Horizontal bar + an always-rendered agent table (کارشناس فروش / میزان فروش گرم / تعداد سفارش)
- Vertical bar «محصولات پرفروش» with an
a-emptyfallback a-descriptions«میزان فروش» — 3 columns incl. a computed average- Table «سفارشات هفته جاری» —
pageSize10,sts_stras a coloureda-tag,cajviajdate
Fixed palette #1c2d56 / #28c76f / #ff9f43 / #ea5455. No filters, no date range,
no export, no refresh — that is the intended design, not a gap.
Why this matters for parity: the reference's two dashboards (
DashboardECommerce.vue,DashboardAnalytics.vue) were found in the earlier port to be mostly commented-out Vuexy demo markup, with ApexCharts imported but never used in live markup (docs/CHANGELOG-PORT-HISTORY.md). So there is essentially nothing to port into this dashboard. Phase 1 will confirm and record that, and will not touch this file.
B. Admin panel — the extra option in product Variants · [brief]
Location: apps/admin-panel/src/views/apps/product/AddProduct.vue:151-270, 315-361, 519-568 (Create/Edit Product).
Gate: the card is gated product.list.variants; add/edit/remove on
.add / .edit / .remove.
Variant table columns: تصویر | عنوان | وزن | اجرت درصدی | تعداد | فعال (tag) | « پیش سفارش» (tag) | عملیات.
The protected extra field is تصویر تنوع (variant image). It is a picker,
not an uploader: a 64×64 flex grid over the product's already-saved gallery
(data.gallery, each {id, url, role, sortOrder}), whose first tile is a
بدون تصویر option and whose selected tile gets a blue .is-on border.
pickVariantImage() sets image_id (posted) plus image_url (thumbnail only,
stripped before POST).
It is populated only in edit mode — on create the gallery is empty and an
a-alert shows a context-dependent message (ابتدا محصول را ذخیره کنید… vs
برای این محصول تصویری ثبت نشده است.).
Variants are edited purely in memory (row key __key = v-<ts>-<rand>) and
saved with the product via PUT product/update/{id} / POST product/create under
a variants[] array — there is no per-variant endpoint. Only validation is
عنوان تنوع الزامی است; delete has no confirm dialog.
Related, and NOT protected by itself: the same
imageId/imagecontract on the user-app side.user-appconsumes it;user-app-v2does not (gap V2-03). Closing that gap in v2 is additive and does not touch this admin code.
C. User apps — new designs and new features · [brief]
Both apps/user-app and apps/user-app-v2 keep their own visual design. The
following were identified in Phase 0 as design/feature additions with no
henza_pwa equivalent.
C.1 Shared by both apps
| Item | Location |
|---|---|
/notifications route + screen (self-documented "no legacy equivalent") |
views/user/NotificationsView.vue |
ui/PageBar.vue replacing the legacy AppHeader |
components/ui/ |
| Redesigned CSS bottom nav (5 tabs; legacy was a 4-tab GSAP notch) | layout/ |
GallerySlider.vue rewrite + teleported full-screen image preview |
components/product/ |
| Variant chips with per-variant images, stock badges, «نمایش همه» | components/product/SingleProduct.vue |
The whole stock / pre-order rule (qty, isPreOrder, capQty, maxQty) |
stores/cartStore.ts, composables/useProduct.ts — v1 only; porting to v2 is gap MON-01 |
| vant pull-to-refresh on 5 screens | various views |
| IntersectionObserver incremental rendering (20-item window) | list views |
| Ledger-based «اسناد» screen | views/user/TransactionsView.vue |
Order-detail drawer + 4-step tracker + useOrderItemProduct join |
views/user/OrdersView.vue |
9-chip OrderStatus enum |
types/ |
| Dotlottie update dialog | app shell |
New API contracts: GET /home/slider, GoldPriceQuote, productCode, collections[], gold-bank:* session keys |
api/client.ts |
C.2 user-app (design v1) only
Home family: components/home/RateStrip.vue, ShowcaseSlider.vue,
ShowcaseCard.vue, CategoryRail.vue, SectionHead.vue, AccountStrip.vue;
admin banner via GET /home/slider + AdminSlide; the «برگزیده منتزر» and
«تازهها» home sections; trust-badge rows.
C.3 user-app-v2 (design v2) only
components/home/HeroCarousel.vue, ProductCarousel.vue, CategoryGrid.vue,
ProductPanel.vue; the IRANSans FaNum font family (12 files,
src/assets/styles/fonts/iransans/); redesigned HomeView structure; redesigned
DashboardView (hero card + 4 settings rows, replacing card + tile grid); the
staggered first-paint reveal system (module-scope hasRevealed) in
HomeView/CartView/DashboardView; the compact RateStrip pill variant;
SingleProduct's scroll-driven solid header + category deep-link pill; the
pill-shaped SearchInput.
Important boundary. C.2 and C.3 are design differences and stay separate. The five items in
ISSUES.md§V2-01..05 are logic differences and are not protected —CLAUDE.md§2 requires logic to be identical across both apps.
D. Backend — stricter auth than the reference · CONFIRMED (D1)
Protected. The user ruled the target's stricter auth stands as an accepted deviation. Additional obligation from D1: build a full "reference admin operation ↔ target rid" matrix so no operation lacks a permission, and explain the 172-vs-174 key difference.
⚠️ Not yet satisfied: 5 ledger-write routes are still @Public()
(DECISIONS.md §"@Public audit" group A). They contradict D1 and are awaiting
the user's go-ahead to be protected.
The reference leaves every admin route unauthenticated (henza_server/routes/admin.php:12
and :272 — the auth:api group wrapper is commented out at both ends, verified).
apps/api instead applies a global JwtAuthGuard + PermissionsGuard with
144 server-enforced rids, and only 15 routes opt out via @Public().
Strict parity would mean removing authentication from ~215 admin endpoints.
CLAUDE.md §3 already carves out exactly this case ("if matching the reference
would cause … a clear security hole in money flows → STOP and ask").
Proposed: protect the target's stricter auth as an accepted deviation; log the
reference behaviour as SEC-01 and the residual @Public() routes as SEC-02.
E. Backend — Decimal money discipline · CONFIRMED
The reference uses PHP floats throughout. apps/api has zero Float columns
in prisma/schema.prisma; ledger and order math run on Prisma.Decimal.
Reverting to floats for "parity" would introduce rounding error into money and
gram values. Proposed: protect the Decimal discipline. The one leak
(GRAMS_PER_MESGHAL as a JS float, ledger.service.ts:189) is logged as MON-03,
to be fixed in Phase 3 — not now.
Note this does not exempt the target from matching the reference's arithmetic,
including the floor(price/1000)*1000 rounding in ProductVariant.php:183-190.
Same result, better representation.
F. Prior deliberate non-ports · CONFIRMED (D4)
Per D4: broken/destructive reference behaviour is not replicated, only logged — but business behaviour, calculations, rounding and messages must match the reference exactly, and every deliberate non-replication carries a matrix note «عمداً تکرار نشد — ISSUE #n».
Recorded in docs/CHANGELOG-PORT-HISTORY.md as intentionally not ported:
- Unauthenticated file upload (
file/createhad no middleware in the reference) - SMS sent inside
toggleActive - Six destructive data behaviours — deleting a confirmed document, editing a confirmed amount, deleting a category that has dependents, creating a category cycle, orphaning the audit trail — each replaced with a Persian 400 message
- The reference's avatar-load crash (
ReagentCustomer.vue:280dereferenceddata.image.idunguarded) - The branch-mode horizontal layout (
HorizontalNavMenu, 4 files never ported;mainLayoutTypenarrowed to'vertical')
Items 1–4 are security/data-loss carve-outs consistent with CLAUDE.md §3 and are
proposed for protection. Item 5 is different — it is a missing feature, not
a safety carve-out, and Phase 1 should treat it as a real gap unless you say
otherwise.
G. Target-only capabilities · CONFIRMED (D2) — status ➕, do not touch
Per D2, features that exist only in the target are listed with status ➕ and left alone. Ask before touching any of them.
| Capability | Where | Note |
|---|---|---|
Dual-currency ledger (LedgerEntry, gold grams + rial) |
apps/api/src/modules/accounting/ |
replaces the reference's single mixed-unit balance column |
Gram / rial debt split (goldDebtGrams, fiatDebtRial) |
prisma/schema.prisma |
reference mixed both in one field |
Prisma.Decimal money types throughout |
schema + services | see §E |
| Server-enforced rid permissions | auth/acl.constants.ts |
see §D |
UploadedFile model + authenticated upload |
admin-uploads/ |
reference upload was anonymous |
| Status-label / transition helpers | admin-orders.service.ts |
reference had two divergent enums |
This list grows as Phase 1 and Phase 2 identify more. It is not a to-do list — it is a keep-out list.
Summary — confirmed status
| Section | Item | Status |
|---|---|---|
| A | Admin Dashboard | ✅ protected (brief) |
| B | Product Variants image picker | ✅ protected (brief) |
| C | User-app designs + new features | ✅ protected (brief), excluding the 5 v1↔v2 logic gaps (D3) |
| D | Stricter admin auth in apps/api |
✅ protected (D1) — 5 ledger routes still open, pending approval |
| E | Decimal money types | ✅ protected |
| F.1–4 | Prior security / data-loss non-ports | ✅ protected (D4) |
| F.5 | Branch horizontal layout | ❌ a real gap, not protected |
| G | Target-only capabilities | ✅ ➕ leave untouched (D2) |
Explicitly NOT protected (parity work, per DECISIONS.md)
- The
fullNamecollapse — being reversed under D0 percentread from the referring agent — wrong, being corrected under D9- The 5 v1↔v2 logic gaps — D3
- Unverified OTP — D6
- The public customer list — D8
singleCustomer.vue's full-form send — D11