89e2d7937edaed0c9594e2cee7185de75496b4c6
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
89e2d7937e |
feat(apps): tell a guest why they were bounced to the login page (D3)
A guest tapping a personal page — orders, transactions, profile, favourites, complete-profile — was redirected in silence, so they landed on the login box with nothing explaining what happened. Legacy showed «ابتدا وارد شوید» (henza_pwa/src/router/index.ts:259). The message is now shown in both apps. The destination deliberately stays the target's /login?redirect=…, which legacy did not have: legacy sent the guest to home and lost where they were going, while here signing in returns them to the page they asked for. The owner chose that trade-off, so it is logged as «عمداً تکرار نشد — D3/الف» rather than quietly left as a divergence. The guard also keys off meta.requiresAuth instead of legacy's hand-kept list of five route names, so the two sales-expert pages are covered too. Legacy's isLoaded router guard is NOT ported, and the reasoning is written into both apps' configStore beside the line that says so: it guarded legacy's one big bootstrap request, which this target does not have, so it would guard nothing and only add a bounce — a shared product link would open home instead of the product. That is porting the breakage, not the behaviour (D4). Route analytics is deferred at the owner's request; its design is parked in docs/PHASE2-D3-PLAN.md so it need not be re-derived, along with the two reference defects not to copy (log/create was unauthenticated and accepted any title; log/update was a GET that wrote) and the open retention and IP questions. Seven of D3's ten items turned out to be built already — all five screens in both apps, the sts_verify_step_two guard, v2's cart ceiling — verified in the code rather than from the checkmarks. Neither app has a test runner: verified with vue-tsc -b on both (clean) plus confirming the toast can actually appear, not merely compile — toast.info exists and <ToastHost /> is mounted in each App.vue. اپ: وقتی بدون ورود صفحه شخصی را باز کنید، پیام «ابتدا وارد شوید» نشان داده میشود. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fd9191eca8 |
feat(orders): log the client price figures the server discards (MON-08)
Step 2 of the owner's MON-08 ruling. An app order is priced by the server (steps 1, 3, 4 landed with the wage-formula work), but the client's own wagePercent, discountPercent and flatRialCharge were being thrown away in silence — so a stale app build, a promotion that expired between the product page and checkout, and a deliberately tampered request were indistinguishable and left no trace anywhere. They are now logged with both numbers, the same pattern already used for the body's customerId. The pricing itself is untouched: the server's figures are what the order is charged, and an operator ordering from the panel still keeps the numbers they typed. Also adds the comparison test the ruling asked for, which step 3 never actually had — the suite only proved a tampered request gets overridden. The new test places one honest order as a customer and the same order as an operator and asserts the totals are identical, which is the real claim: an ordinary customer's total does not move. Two more cover the logging, including that an honest order logs nothing — so the quiet case cannot pass merely because the method never ran. Closes STUB-05 in the docs as well: the reference's six hardcoded staff numbers have been behind PROFILE_ADMIN_MOBILES / ORDER_ADMIN_MOBILES for a while, but its status still read "pending". api: 286/286 tests, 26 suites, typecheck clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
83e5d2868b |
feat(orders): send the order-placement SMS and close Phase 1
The reference texted three audiences on every placed order
(henza_server Api/ApiMobileController::setOrder:733-766). None of it was
wired here — OrdersService had no SmsService at all, so a customer could
place an order and nobody was told: not the customer, not the admins, not
the referring sales expert.
Now, after the transaction commits (so a rolled-back order texts nobody)
and without ever being awaited (D20 — a dead gateway must not fail an
order):
- every mobile in ORDER_ADMIN_MOBILES, with the customer's name and
the rounded gold weight;
- the customer, with distinct text for a normal and an over-limit
suspended order;
- the referring sales expert, looked up on AdminUser (the target's
referring expert is an admin, not a customer as in the reference).
The reference's four hardcoded personal numbers stay out of git, behind
the same env-var pattern as PROFILE_ADMIN_MOBILES.
The «در سیستم ویرایش شد» variant was deliberately not ported: the target
has no order-edit route, so it would be dead code — «عمداً تکرار نشد».
Also fills §2 of the admin matrix, the D1 deliverable that had been
(pending) since the phase opened: 163 rid-guarded routes, 68 distinct
rids, 176 grantable tree keys, and a per-module map to the reference
controllers. Only two enforced rids are ungrantable from the permission
screen (newsletters, persons.list.sellers.view) — filed as PERM-02,
awaiting an owner ruling because it changes the permission vocabulary.
Phase 1 (admin parity) is at its gate. api: 283/283 tests, 26 suites.
پنل: پیامک ثبت سفارش برای مشتری، ادمینها و کارشناس فروش فعال شد.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
53436ec855 |
chore(release): prepare release/delivery + fix docker env passthrough
DEPLOY-01: docker-compose.yml passed only DATABASE_URL and PORT to the api service, so JWT_SECRET, JWT_CUSTOMER_SECRET, OTP_MASTER_CODE and every SMS_TREZ_* variable were silently dropped in Docker. A deployed build would have used the development JWT fallback and would not have had the delivery master code at all, so the demo login would have failed with no obvious cause. All are now forwarded, defaulting to empty (which the code treats as unset); verified with `docker compose config`. TEST-01: test-sales-expert-roundtrip.cjs had localhost:3077 hardcoded and could only connect by accident. Now defaults to the repo's API_PORT. 33/33. DELIVERY.md documents env vars (names only), run/deploy commands, how migrations run inside the container, the verification results and the known issues. release/delivery contains both parity branches with no conflicts (fast-forward). build 5/5, jest 45/45, admin smoke 43/43, apps smoke 43/43, B2B scan 34/34, B2B writes 25/25, auth-smoke 32/32, typecheck clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6d0f5c5359 |
test(api): B2B admin scan + write-path smoke, both green
scan-admin-b2b.cjs (34 ok, 0 broken) hits every read endpoint the wholesale desk uses daily. smoke-admin-b2b-writes.cjs (25/25) covers the money-touching writes: admin order placement and a cash payment document, asserting the ledger and the customer balance rather than the HTTP status. Verified: the making charge computes exactly, a document is created unconfirmed, confirming moves the gold debt, and confirming again toggles back and restores the balance to the byte. Retail/storefront surfaces were deliberately not scanned (D13). No B2B breakage found. Three first-draft failures were test bugs, corrected against the real callers: cities/list -> admin/cities/list, by-ids takes products_id, and document type 4 is cash (2 is a transfer). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8b7da90f51 |
test(apps): customer-flow smoke test for both apps + 4-digit master-code guard
smoke-apps.cjs walks login (master OTP), catalogue, cart cap, order placement and order history, with the empty-order and unauthenticated negatives: 43/43. The cart rules are executed from BOTH apps' own source so the result cannot be carried by v1 alone. No behavioural divergence found: all 9 stores are byte-identical, and the router, API client and composables match except v1's protected variant-image picker. APP-01: both apps' OTP box is exactly 4 inputs and only submits at 4, so a longer OTP_MASTER_CODE cannot be typed into them. The login UI was left alone; main.ts now warns at boot when the code is not 4 digits and .env.example states the rule. APP-02 logged, not fixed: user-app-v2 has no getHomeSlider, so admin-configured home slides show in v1 only. Changing v2's hero during a delivery is not worth the risk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0983c42788 |
fix(admin-panel): retail order status editing posted a body with no orderId
views/order/List.vue handed the timeline activity entry straight to
single/order-sts/edit. That entry is {status, title, proceed, uaj, note} and has
no order id, so UpdateOrderStatusDto rejected every call with 400 — neither
"ثبت وضعیت" nor "حذف وضعیت" worked on the retail order screen. Both handlers now
take the id from tempRow, matching OmdeList.vue and TaminList.vue, which were
already correct.
Found by the new scripts/smoke-admin.cjs (43/43), which walks login, customer
create, sales-expert create/edit, product+variant create/edit and order
list/detail/status, asserting the database changed rather than trusting a 200.
[ref: henza_server/routes/admin.php:59]
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
676ec3ed72 |
test(api): live D20 verification + document every env var
Two live scripts against a running API, covering both halves of the master-OTP
contract:
* with OTP_MASTER_CODE set (8/8): signs in, the JWT works on customer/me,
Persian digits accepted, and the RULE E1 negatives — near-miss code,
unknown mobile, inactive customer — all still rejected.
* without it (6/6): the same code returns 401, no boot banner, a real code
still logs in, and replaying it fails.
Also confirmed from the boot log that with no SMS credentials the server starts
normally and declines to send instead of erroring (D20).
.env.example gained the auth, SMS and upload variables, names only, no values,
with OTP_MASTER_CODE marked delivery-only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
460cf276b6 |
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> |
||
|
|
2194b3d4d9 |
parity(admin): D17 — SalesExpertProfile, AdminUser name parts, agent grid fields
Restores the sales-expert fields that were lost when a sales expert became
an AdminUser instead of a type=1 Customer row.
New SalesExpertProfile holds cityId, gramLevel, phone and nationalCode. A
separate table rather than columns on AdminUser, because these mean
something only for role = SALES_EXPERT. gramLevel is DECIMAL(14,4), matched
to Customer.goldCreditLimitGrams so the same credit limit has identical
precision on both tables. phone and nationalCode copy the reference exactly:
nullable, NOT unique, no format rule — createCustomer validates only mobile
and percent, and both fields are required:false on the reference form.
Inventing a uniqueness constraint would reject data the reference accepts.
reagent_id is deliberately NOT ported (D17). Every use of it in the
reference reads a CUSTOMER's reagent_id, which the target already models as
Customer.referredByAgentId. On a sales expert's own form it was a side
effect of the shared customers form: filling it removed that expert from
the selectable-agent list, because queryReagent filters on
whereNull('reagent_id'). The target's equivalent filters on role and active
only, so no agent can be dropped — strictly better than the reference.
AdminUser gains firstName/lastName with the same additive migration,
backfill and sync trigger as Customer, for all admins rather than only
sales experts. This closes D0 requirement 7, and every remaining
`fname: fullName, lname: ''` was fixed — six further sites across
admin-users, admin-comments, admin-documents, admin-orders and
admin-preorders.
listReagents now returns real city_id, city, gram_level, phone and ncode,
and left-joins so an expert without a profile still lists with empty cells
rather than vanishing. The two grid filters map through the relation. The
service upserts a profile on create and on a role change to SALES_EXPERT,
and the migration backfilled one for every existing expert.
ReagentCustomer.vue gains the four fields plus the city -> commission
autofill, ported verbatim including the reference's falsy test so a city
percent of 0 also falls through to the aux lookup (replicated, not fixed).
Tests: new sales-expert round trip 33/33, covering create with every field,
an edit form with each field populated separately, a byte-identical no-op
save, single-field edits, both grid filters with negative cases, the
left-join case and the referrer dropdown. «محمد علی» + «احمدی» survives
intact. Plus jest 32/32, auth-smoke 53/53, customer trigger 9/9, typecheck
clean on api and admin-panel. Migration dry-run twice in a rolled-back
transaction before applying; drift is zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e3af31948e |
parity(docs): D16 — stop PRICE-01 as storefront, reorder Phase 1, verify SEC-02
Owner ruling: computing and displaying price / price_with_discount is retail storefront work, so it falls under D13 and is marked out of scope. The B2B app works in making charge only. variant-price.ts and its 11 hand-checked tests are kept and still run, just unused. Nothing needed reverting: computeVariantPrice was never called from outside its own file and spec, verified by grep over apps/api/src and apps/admin-panel/src, so no price had been wired into any screen. PRICE-01 is re-marked in ISSUES.md rather than deleted so the evidence survives if the storefront is ever taken up. Making-charge work stays fully in scope: percentage_pay, makingChargeOffsetPercent, and wage display on variants and order lines. New Phase 1 order: Persons -> products/categories/variants/order detail -> financial documents -> users and permissions -> admin SMS -> promotions, notifications, cities, branches, workshops -> reports -> gate. Per section: implement the gaps, then field-by-field test that section, and show a gap summary before fixing. SEC-02 re-verified live rather than quoted: 9/9 assertions, plus 32/32 in auth-smoke.sh. POST /orders is 401 without a token; a body customerId of "customer-2" sent with a customer-dev-test token still lands the order on customer-dev-test and is warn-logged; a foreign order id returns 404 rather than 403 so ids stay unenumerable; the admin read rejects a customer token. Client-supplied wagePercent/discountPercent/flatRialCharge remain as MON-08, already scheduled as Phase 2 item #1. Also records why the dev OTP log briefly looked broken: a stale API process still held port 3077, so the new instance died with EADDRINUSE and the tailed log belonged to the old process. Not a regression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8133a2bdf4 |
parity(api,admin): split customer name, add making-charge offset, wire SMS
Applies the two approved additive migrations and fixes every consumer.
D0 — Customer.firstName / lastName
The reference has had two columns since 2020
(2020_10_01_120430_customers_table.php:18-19); the port collapsed them, so
the edit form showed the whole name in the "first name" box and saved an
empty last name. Adds both columns, backfills first-token/rest, and keeps
fullName in sync with a customer_sync_full_name trigger. Nothing dropped.
The trigger is SQL, so Prisma cannot see it: writing the parts rebuilds
fullName, writing fullName re-derives the parts, and writing both in one
statement lets THE PARTS WIN. Documented in the migration header, the
Customer model doc comment and DECISIONS.md §D14, because a write can now
store something other than what was passed.
D9 — Customer.makingChargeOffsetPercent
Legacy customers.percent, the per-customer making-charge offset that the
app adds to the variant's making charge (henza_pwa
Mixins/product/index.ts:69-75). toRow() was returning the referring
AGENT's commission rate instead (CUST-02). DB default 0 reproduces
today's behaviour exactly, so no existing customer's price moves; the
reference's application-layer default of 3 for NEW customers
(AdminController.php:338) lives in the create form, and the field is
required on create with the reference's own message (:354).
update() and updateCredit() both accepted percent and silently discarded
it; both now persist it. Sort/filter aliases fname/lname pointed at
fullName and now point at the real columns.
SMS — Trez provider (Sms.php:57-82)
New global SmsModule/SmsService: form-urlencoded POST, 20s timeout, body
trimmed, every message persisted. send() never throws, matching the
reference, which discarded Sms::Send's result so a gateway outage never
aborted the order or approval that triggered it. The OTP text is
byte-identical, including the Android SMS Retriever markers. Credentials
come from env only; production refuses to boot without them rather than
silently locking every customer out. .env.example carries names, no values.
Admin panel: the split(' ') fallback is removed from singleCustomer.vue,
and bulkCustomer.vue defaults percent to 3, requires it, coerces the
Decimal on load so diff-send works, and only re-sends `name` when a name
part changed. ReagentCustomer.vue is untouched — its «درصد کمیسیون» is
AdminUser.wagePercent, a different field.
Tests: 32 jest (incl. 14 new SMS config/template), 9 trigger assertions,
53 auth smoke, and the full D12 round-trip on the customer entity —
«محمد علی» + «احمدی» survives create, edit, a byte-identical no-op save,
and single-field edits. Typecheck clean on api and admin-panel.
Note: migrate dev is interactive-only and could not run here, so the
migrations were applied with migrate deploy after a read-only drift check.
The only drift is the pre-existing _goldbank_migrations table, which
Prisma would DROP; it was NOT dropped (DEAD-07).
[ref: henza_server/app/Models/Sms.php, app/Http/Controllers/Adm/AdminController.php]
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
7b598e9e8e |
parity(api): port the reference price formula with tests; SMS + MON-08 reports
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> |
||
|
|
024e3dd045 |
parity(api,apps): close SEC-02 - orders take the customer from the token
The user declined to defer this to Phase 2, correctly: a public POST /orders with customerId in the body let anyone place an order on any customer's account, and an order lands on that customer's gold debt and ledger. POST /orders is now behind CustomerAuth. OrdersService.createOrder takes the customer id as a separate argument that the controller fills from the token; the body's customerId is ignored and a mismatch is logged. The field stays optional in the DTO and in shared-types only so both apps keep validating while they still send it -- they drop it in Phase 2. The admin builder is unaffected: it legitimately places orders for a chosen customer and passes that id explicitly, behind the admin guard. GET /orders/:id is scoped to the owner. A foreign or nonexistent id both return 404, never 403, so ids stay unenumerable. Operators read through the new GET /orders/admin/:id behind rid order.list, declared before the :id route because Nest matches in declaration order. Both apps' CartView now gates on session.isLoggedIn rather than a remembered customerId, which is no longer a credential. OTP audit (requested): no master code, no bypass, no hardcoded code anywhere; one code path issues a token and only after bcrypt.compare. No SMS provider is wired up -- in development the code is logged once, guarded by NODE_ENV !== 'production'. Fixed while auditing (SEC-11): deliver() persisted the real message text, code included, into SmsMessage.body, which the admin SMS grid lists and full-text searches. Any operator with the sms rid could read live codes, defeating the hashing of codeHash. The stored row is now a redacted audit line. Also logged MON-08: wagePercent/discountPercent/flatRialCharge are still taken from the client on order creation. The reference is worse -- it takes rash_weight from the client too -- so this is already a partial improvement; logged rather than changed, per D4. Tests: auth-smoke.sh now 53 passed, 0 failed, including a spoofed body customerId being ignored in favour of the token. Typecheck clean on api, user-app, user-app-v2 and shared-types. [ref: henza_server ApiMobileController.php:533,536-545,615-632] Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4efe543d62 |
parity(api,apps): security hotfix stage 2 - real customer sessions, close IDOR
Stage 1 narrowed GET /customers but did not close the leak: the response still
carried every customer id, and /customers/:id, /:id/orders and
/:id/ledger-entries were still public, so the full customer book was still two
requests away. The user caught this. Stage 2 deletes the id-bearing surface.
Customer JWT, separate from the admin one (T1):
- new src/modules/customer-auth/ with its own passport strategy,
CustomerJwtGuard, @CustomerAuth() and @CurrentCustomer()
- signed with JWT_CUSTOMER_SECRET, and every token carries typ:'customer'
which the strategy verifies; both checks are present deliberately
- customer state re-read per request, so deactivating takes effect at once
- mirrors the reference's own split: auth:api vs auth('api-customer')
Real OTP (D6), porting ApiMobileController::userAuth/userVerfiy (:39-64):
- new CustomerOtp table, additive migration, nothing existing touched
- parity behaviour: rand(1000,9999), active-customer filter, faTOen digit
normalisation on both number and code, rejection on mismatch
- approved improvements over the reference: bcrypt-hashed code, 2-minute
expiry, 5 attempts then lockout, single use, send throttle (60s per
number, 5/number and 20/IP per 10 min), and no number enumeration
Routes removed, replaced by token-derived equivalents:
GET customers, customers/:id, /:id/orders, /:id/ledger-entries
-> GET customer/me, customer/me/orders, customer/me/ledger-entries
No route accepts a customer id any more, so the IDOR is closed structurally
rather than by a check. The admin panel was unaffected (admin/customers-omde/*).
Both apps migrated in the same commit per the v1/v2 parity rule: token-based
session store, Bearer header, 401 -> clear session and route to login, real
two-step OTP login. Each app keeps its own markup and styling; only logic moved.
Tests: auth-smoke.sh extended with the stage-2 assertions, 46 passed 0 failed
against a live server. jest installed dev-only (user-approved) so pnpm test
works for the first time: 7 passed covering faToEn, which moved to its own
digits.ts to be testable without booting Nest. Typecheck clean on api,
user-app and user-app-v2; both apps build.
[ref: henza_server ApiMobileController.php:39-78]
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
71192ba2ba |
parity(docs): Phase 0 inventory, mapping and draft PROTECTED list
Read-only inventory of all 7 projects (3 reference + 4 target) produced by parallel subagents, every claim cited path:line. No source code changed. - 00-INVENTORY.md consolidated mapping: stack, backend, admin, user apps - PROTECTED.md draft protected list (awaiting user confirmation) - ISSUES.md 26 logged issues (security, money/stock, validation, dead code) - CHANGELOG.md change log scaffold - PROGRESS.md resumable state + Q1-Q5 blocking Phase 1 - _inv-*.md 7 raw per-project inventories (8111 lines) Key finding: the reference is a gold-jewellery B2B/B2C e-commerce system, not a gold bank. Buy/sell, deposit/withdraw, transfer, KYC levels, payment callbacks, cron and websockets do not exist in it, so they cannot be parity gaps. [ref: henza_server, henza_admin, henza_pwa] Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |