parity(api,apps): delivery-mode gaps — master OTP, non-blocking SMS, faToEn, v2 cart cap

D20: OTP_MASTER_CODE login bypass read only from the environment, disabled when
unset, audited on every use, with a boot banner while armed. SMS no longer
aborts the boot in production — it warns and declines silently, so no message
can stop an operation.

D19: port Utility::faTOen onto admin-entered mobiles (before the uniqueness
lookup, which could previously be bypassed with Persian digits), and the
create-only welcome SMS, byte-identical to the reference and fire-and-forget.

user-app-v2 was missing the cart stock ceiling entirely: capQty/maxQty, the
un-orderable guard and the qty clamp are ported, leaving v2's own design intact.
The two cart stores are now byte-identical. isVariantOrderable and the
orderable-first default selection ported too.

jest 45/45; typecheck clean on api, admin-panel, user-app, user-app-v2.

[ref: henza_server/app/Http/Controllers/Adm/AdminController.php:333,348,364,378-387]
[ref: henza_server/app/Models/Utility.php:26]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-09-22 18:35:33 +03:30
co-authored by Claude Opus 5
parent 2194b3d4d9
commit 460cf276b6
128 changed files with 756 additions and 181 deletions
+100
View File
@@ -1065,3 +1065,103 @@ parts win** when both change in one statement, and a multi-token review report.
Every `fname: fullName, lname: ''` site is corrected — starting with
`listReagents` (`admin-customers.service.ts:375-376`).
---
# D18 — Standing approval (owner decision, 2026-09-22)
To reduce round-trips, the owner granted a **standing approval**. From now on the
following are done **without stopping and without asking**, with only a short
report at the end of each section before moving straight to the next:
- Implementing gaps whose reference behavior is unambiguous — a field, column,
filter, validation, message, button or page.
- Fixing target/reference mismatches where the reference behavior is clear.
- **Purely additive** migrations (a new column or table) on the development DB,
provided a dry-run inside a transaction with rollback is clean first.
- Writing and running tests.
- Applying the same fix to other sites of the same pattern (as with the six
extra `fname: fullName, lname: ''` sites found during D17).
**STOP and ask** only for:
1. Deleting any data, column, table, file or capability.
2. Any change to a PROTECTED zone or to the storefront surfaces of D13.
3. Any place the reference behavior is ambiguous or open to two readings.
4. Any place a reference bug is NOT being ported and that affects business
logic (the D17 situation).
5. A migration that alters existing data (backfill or update), unless it only
touches test data.
6. The Phase 1 end gate.
Everything else in DECISIONS, PROTECTED, D13 and D15 stays in force. In
particular this approval does **not** loosen: the ban on `migrate reset` or
touching `_prisma_migrations`, the CHANGELOG entry required for every data
deletion, or the UTF-8-from-file rule for tests with Persian input.
---
# D19 — Delivery Mode (owner decision, 2026-09-22)
The owner has a **delivery tonight**. Until further notice:
1. **No more deep field-by-field auditing and no more questions.** Implement the
main gaps and make sure the project works.
2. **The "admin first, then apps" gate is lifted for tonight.** Work on admin and
both apps in the priority order below.
3. **Nothing is deleted or changed** in PROTECTED zones, storefront (D13) areas,
data, features or designs. All earlier security rules remain in force.
4. **When a decision is needed, pick the lowest-risk option that breaks nothing**,
continue, and record it here rather than stopping to ask.
## Answers that closed the Persons audit
| Q | Ruling |
|---|---|
| A — `singleCustomer` diff-send | **Do not touch it** (D13 wins). **D11 is CANCELLED.** |
| B — lock `mobile` on edit | **Not ported.** The target keeps its current editable behaviour. Logged in ISSUES. |
| Gap 2 — `faToEn` on admin mobile | **Implement** (create and update). |
| Gap 1 — welcome SMS | **Implement**, byte-identical text, but non-blocking per D20. |
| Gap 4 — province/city tree | **ISSUES only.** |
| Gap 5 — Jalali birth date | **ISSUES only.** |
**D11 is superseded by this decision and must not be acted on.**
---
# D20 — SMS boot rule reversed, and a master OTP for delivery (2026-09-22)
## 1. SMS must never block anything
The rule from D14 — *"in production, if the SMS key is missing the server must
fail to boot with a clear error"* — is **REMOVED**.
New behaviour, in every environment:
* Missing credentials → log a **warning** and **boot normally**.
* `send()` with no credentials → **silently declines and logs**; it never throws.
* **No SMS, including the welcome SMS, may ever stop an operation.** Customer
creation succeeds whether or not the message goes out.
The live send test to the owner's number is **cancelled**; the SMS panel is not
a priority for this delivery.
## 2. `OTP_MASTER_CODE` — a manual login code for the delivery
A temporary bypass so login works during the demo.
| Rule | Detail |
|---|---|
| Source | **`OTP_MASTER_CODE` env var only.** Never hardcoded in source. |
| Default | Unset or empty → **completely disabled**. |
| Effect | When set, it is accepted for **any active customer's** mobile, **alongside** the real code, which keeps working. |
| Unchanged | Active-customer check, throttling and attempt limits all still apply. |
| Boot | When enabled, print a clear warning: `OTP_MASTER_CODE فعال است — قبل از انتشار اصلی حذف شود`. |
| Audit | Every login through it is logged **with the customer's mobile**. |
Tests required: master code logs in; with the env var absent the same code is
rejected; the real code still works.
**Before the real release: remove `OTP_MASTER_CODE` from the environment and
configure the SMS credentials.** Recorded in `DELIVERY.md` and as a Critical
entry in `ISSUES.md`.