Commit Graph

587 Commits

Author SHA1 Message Date
358996cbf2 ci: stream releases over ssh
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
2026-08-20 14:42:29 +04:00
640360d63c fix(api): derive host from storefront domain
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Every storefront, including nested subdomains, uses its matching api.<hostname> endpoint.
2026-08-20 14:31:22 +04:00
f4ea4c7af8 fix(api): route tenants through origin gateway
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
2026-08-20 14:23:12 +04:00
sdarbinyan
bbf12cad33 docs: add §0 - auth + payment authorization was missing entirely
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Direct question caught a real gap: "everything is there? payment auth?"
The census only grepped src/app/ - auth moved into the external
@marketplaces/auth package this session, and its HTTP calls were never
captured. Seven real endpoints were silently absent from a doc that called
itself "complete":

- 3 Telegram QR/session endpoints (POST/GET/DELETE .../users/sessions) -
  live today, customer and admin login share them, which is exactly why
  every admin endpoint must independently verify authorization server-side
- 4 ed25519 admin challenge/response endpoints - specified in
  BACKEND-HANDOFF.md §3 and the package's own auth-api.model.ts, but not
  built server-side. Client shows backend-unavailable until they exist.

Added §0.3 stating plainly what actually connects auth to payment: there is
no separate payment login. Checkout, order pricing (§20), and partner
credentials (§16) each ride on whichever of the two sessions above is
active, or on the partner API's own separate signed-request auth (§6 of
that contract - unrelated to Telegram/ed25519, already built, not a gap).
The real payment gap is §1 (QR/card creation and polling, undocumented
anywhere), not auth.

Counts corrected: 51->54 specified, 90->97 total. Added item 0 to the
action list, ahead of everything else: the ed25519 endpoints are the single
most serious open issue named anywhere in docs/backend/, and every other
item on the list assumes a working admin session to authorize against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:12:22 +04:00
sdarbinyan
2149e6435a docs: final backend handoff - revision endpoints, response-shape additions, close-out
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Updates FRONTEND-API-SURFACE-COMPLETE.md to cover everything built after it
was first generated - this is the final version for this pass, not a
snapshot mid-way through.

Added, none of which existed in the doc before:

- §18: the 4 marketplace revision endpoints (create draft/validate/
  publish/rollback) - built this session, missing from the census entirely.
  Flags the same draft/validated/preview/published ambiguity already
  documented in the model itself, so backend sees it without having to
  read source.
- §19: RoutingContext as an optional field addition on GET
  /api/admin/v2/orders/{id} - not a new endpoint, a response-shape ask.
- §20: order pricing-breakdown fields (unitPriceMinor, lineTotalMinor,
  priceSnapshotId, discountMinor, fxQuoteId, deliveryMinor) feeding the new
  total-formula panel. States plainly that the panel shows nothing rather
  than a wrong number while these are absent.
- §21: 8 optional dashboard-metrics fields (GMV, conversion, moderation
  queue, etc.) - explicitly labelled a genuine ask, not a confirmed
  contract, since no spec exists for this endpoint at all (§15).
- §22: states directly that the frontend's new double-click guard does not
  replace backend idempotency enforcement and was never meant to - closes
  one UI race, does nothing for a retried request or a duplicate webhook.

Counts updated (47->51 specified, 86->90 total; +3 response-shape asks that
aren't new endpoints). Closing section states the one item genuinely
blocked pending a live backend (F60, the full acceptance-path E2E) so
nobody mistakes "frontend backlog complete" for "nothing left to build."

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:08:41 +04:00
sdarbinyan
de6bef8e9a ci: coverage floor + gate; CI never ran a single test before this (F64)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Delivery plan Q9: "set a justified coverage floor and CI gate. Deliberately
unset today." Both parts of that were still true - karma.conf.js had no
check thresholds, and architecture-governance.yml built and checked
boundaries but never invoked `ng test` at all.

Floor set 5 points below the measured level right after this session's
facade-test pass (43.3%/29.0%/34.1%/43.7% statements/branches/functions/
lines): 40/25/30/40. A deliberate floor, not an aspiration - meant to be
ratcheted up as coverage grows, and to fail a PR that drops below it
rather than silently accept a lower number.

Verified the gate actually fails, not just logs a warning: set
statements to an impossible 99% locally, confirmed `npm run test:coverage`
exits 1 (my first attempt at this check was wrong - piping through `tail`
meant the $? I read back was tail's exit code, not npm's; fixed by
capturing it directly). Restored the real floor and confirmed a clean
exit 0 before committing.

CI changes: added the Setup Chrome + coverage-gated test step
karma.conf.js's CHROME_BIN needs (its fallback is a Windows path, useless
on ubuntu-latest), plus an E2E step. Structurally verified locally (no
tabs, step count, manual read-through) - a real GitHub Actions run on
Anthropic's infrastructure could not be executed from this session, so
this is not confirmed end-to-end the way the coverage gate itself was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 22:03:29 +04:00
sdarbinyan
e5ed1c96e5 fix: checkout double-click created two sessions; F59/F62 E2E coverage
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
E2E found a real, pre-existing bug, not a test artifact: isCheckoutDisabled
only checked terms/auth/delivery-selection, never whether a checkout was
already in flight. A double-click (or any rapid repeat click) fired two
handler calls before showPaymentPopup's change detection had a chance to
cover the button, producing two separate POST /api/v2/storefront/checkout
requests for one click.

Fixed with checkoutInFlight, set synchronously at the top of checkout()
before anything async happens, checked in isCheckoutDisabled. Released in
both closePaymentPopup() (every retry/close path routes through it) and
setPaymentError() directly, since the popup can stay open to show an error
rather than closing - relying on only one of those would leave a failed
attempt unable to retry.

Track Q coverage (F59, F62):

- admin-dev-bypass.spec.ts - proves ?devBypassAdmin=true (already shipped
  in app.ts, gated by @marketplaces/auth's isDevMode() check at runtime)
  actually gets an E2E run into the admin shell without a Telegram login.
  This was the missing piece behind Q2's note that past "verified live"
  admin claims were code-inspection only.
- checkout-idempotent-click.spec.ts - the frontend-testable half of Q5
  ("repeat webhook and double-click create exactly one order"). The
  webhook-idempotency half is a backend contract
  (PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md §6.3) this suite can't exercise
  without a live backend.

One own test bug fixed en route, not shipped: the idempotency test's first
draft waited on label[for="terms-checkbox"], which does not exist in the
markup (the checkbox and its text share a plain clickable wrapper, no
label/for). checkout-request-shape.spec.ts already had the correct fallback
(dispatchEvent('click') on the input directly) for exactly this reason -
this test just hadn't copied it.

Verified: 237/237 unit tests, arch:check clean, 7/7 E2E, production build
succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:57:09 +04:00
sdarbinyan
90bd05aa98 test: users facade + cart service coverage (F63 complete)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Closes out F63 - every domain named in the delivery plan's Q7 gap now has
facade-level coverage.

admin-users.facade.spec.ts (14 tests): sessions/audit dialog targeting (a
revokeSession must reload the CURRENTLY open user's sessions, not whichever
user happened to be open first), invite() trims and rejects whitespace-only
email, roleName() falls back to the raw id rather than rendering blank for
an unmapped role.

cart.service.spec.ts (23 tests) - this had zero coverage despite feeding
totalWithDelivery and allRequiredDeliveriesSelected directly into
cart.component.ts's checkout gate and the offers/qty payload sent to
POST /api/v2/storefront/checkout, the exact contract this session's F14-F16
rewired. A regression here would silently let checkout proceed with a
missing delivery selection, or compute the wrong total. Covers the delivery-
requirement matrix explicitly (digital items never require selection,
deliverySelectionRequired: false overrides having options present, a
selection satisfies the requirement once made) and the quantity-to-zero ->
line-removal behavior in updateQuantity.

Verified: 237/237 unit tests (37 new since the last commit), arch:check
clean, 5/5 E2E, production build succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:44:08 +04:00
sdarbinyan
f8063b320e test: facade coverage for orders, moderation, transactions, monitoring, products (F63 partial)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
None of these 5 facades had a single test before this - the highest-risk
gap flagged in Q7 of the delivery plan: real business logic (dashboard stat
computation, health scoring, bulk actions, draft save routing) with zero
coverage on domains that just got real backends behind them (Block 3).

Each spec stubs the gateway TOKEN via jasmine.createSpyObj, not a concrete
class - the correct pattern per this session's earlier fix to
admin-analytics.facade.spec.ts / admin-order-watcher.service.spec.ts, so
these don't repeat that same latent bug.

66 new tests, several written specifically to catch a real regression
class rather than pad a count:

- averageRating/averageOrder must be null/0 for an empty set, never NaN
  or a divide-by-zero artifact
- a zero rating must not drag down an average across other real ratings
- a customer with 2+ orders counts as returning; 1 order does not
- moderationHealthPercent is 100 for an empty queue by convention, not 0
- toggleSelection/toggleAll must not produce duplicate ids
- saveDraft must route to createProduct vs updateProduct correctly and
  must do nothing when no draft is loaded - a real prior bug class
  (silently creating a duplicate on a no-op save)
- an events-load failure in AdminMonitoringFacade must not block queues/
  webhooks from loading independently

One own mistake caught before commit, not after: the first updateDraft
test asserted the updatedAt timestamp changed after two synchronous calls
- both landed in the same millisecond, so the ISO string was correctly
identical and the assertion was the bug, not the facade. Replaced with an
assertion that actually matches what's worth testing (a valid timestamp
gets stamped, not that two back-to-back calls differ).

Verified: 205/205 unit tests (66 new), arch:check clean, production build
succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:38:42 +04:00
sdarbinyan
ec949b5a19 feat: order total-formula panel, dashboard metrics, revision-API core (F53/F55/F56)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Checked F49-F56 against current source before writing anything; three
resolved to "already done, nothing to build":

  F49 (ed25519 admin auth flip) - lives entirely in the external
      @marketplaces/auth package plus a live backend; nothing in this repo.
  F50 (HttpOnly session cookie) - HttpOnly can only be set via a
      Set-Cookie response header. No frontend code can ever set one via
      document.cookie. 100% backend, always was.
  F51 (route permission guards) - already deliberately paused by prior
      work with a stated reason ("could lock an admin out without warning"
      across 27 routes, no live backend to verify against). Respected that
      judgment rather than overriding it blind.
  F54 (wire mock requestRefund) - already resolved by the Block 3 gateway
      swap; the facade calls gateway.requestRefund(), which now hits the
      real endpoint. No mock left to wire.

Built:

- AdminOrder gains optional per-line pricing fields (unitPriceMinor,
  lineTotalMinor, priceSnapshotId, discountMinor) plus fxQuoteId and
  routing, per PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md §5.3/§6.5. All
  optional - most orders today have none of it, and absence must render
  as "not available", never a fabricated 0.
- OrderTotalFormulaComponent: pure presentational panel reconstructing
  total = sum(unitPrice*qty) - discounts + delivery, wired into order
  detail. Refuses to show a partial breakdown - every line must carry
  unitPriceMinor or the panel says so plainly instead of half-computing.
- AdminDashboardMetrics gains 8 optional fields per
  PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md §7's target list (GMV, paid
  orders, conversion, payment failure rate, moderation queue, low stock,
  unmatched events, integration health). Status is 'unknown' when a field
  is absent, not 'healthy' - a successful fetch with no field present is
  not the same as a confirmed-healthy metric, same pattern the existing
  'images-without-alt' check already used.
- Marketplace revision gateway core (F52/F55's shared dependency):
  models/marketplace-revision.model.ts, interface, api+local gateways,
  token. Additive only - the 724-line project-editor facade's actual
  localStorage-to-API rewire is NOT done here; that is a separate,
  larger, stateful change (autosave/undo/redo all currently synchronous)
  that deserves its own verified pass, not a rushed retrofit.

One real contract ambiguity surfaced and resolved with a stated
assumption, not silently: §5 describes 4 pipeline stages
(draft/validated/preview/published) but only 3 write endpoints
(validate/publish/rollback). Modeled validate() as moving straight to
'preview' - the state publish() actually requires - treating 'validated'
as a value the caller may never observe. Documented in the model's own
comment for backend to confirm.

Local gateway enforces the pipeline order rather than being a permissive
stub: can't publish a draft, can't re-validate a previewed revision,
rollback only from published, rollback creates a new revision rather
than mutating the old one.

Verified: 152/152 unit tests (13 new), arch:check clean, production
build succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:23:43 +04:00
sdarbinyan
9134a7ff63 docs: master API surface list, all 86 frontend endpoints in one doc
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Generated directly from source (every this.http.get/post/patch/put/delete
call across core/, features/admin/, api.service.ts) rather than written
from memory - a census, not a design doc.

47 already match an existing Phase/Track contract exactly. 24 are inferred
from this codebase's own REST conventions with no contract doc stating them
- each flagged in source at its call site, not just in this doc, so backend
sees the reasoning next to the code. 15 are legacy endpoints
(/category, /cart, /qr, /websession, ...) with no contract anywhere,
still live today.

Biggest concrete gap surfaced: three full admin domains (transactions,
monitoring, moderation) have real UI and real gateways calling
/api/admin/v2/{resource} by convention, with zero backend contract written
for any of them.

One real bug found and fixed while building this, not just flagged:
connector-api.gateway.ts's replay() called
POST /api/admin/v2/integrations/dead-letter/{id}/replay, omitting the
{connectorId} segment the contract's own path requires
(PHASE-4-CONNECTOR-FRAMEWORK-CONTRACT.md §7). Fixed the interface, both
gateway implementations, and the doc entry in the same pass - no callers
existed yet, so this shipped without ever being exercised by a UI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:08:05 +04:00
sdarbinyan
a116c4f592 feat: Partner hierarchy backoffice page (F41-F44 partial)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
New nav entry, route, page. Renders the four-level tree
(Company/Project/Store/PaymentPoint) via AdminPartnerHierarchyFacade on top
of the core built in the previous commit - TEST/LIVE toggle, suspend/activate,
disable behind a confirmation (terminal + cascading, per contract §2), and a
read-only credentials list stating plainly that only the public key is ever
held.

Reload-after-action rather than local patch on suspend/activate/disable:
disable cascades to descendants server-side, so a local patch would leave
children showing a status they no longer have.

Company id is a constant for now (DEFAULT_COMPANY_ID) - company creation is
out of band per contract §4.3, and there is no company-selector UI yet.

Verified: 139/139 unit tests, arch:check clean, production build succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:00:46 +04:00
sdarbinyan
7e7a015ff6 feat: partner hierarchy core - models, gateways, contract invariants
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Foundation for Block 5 (F41-F45): the frontend surface the partner
provisioning API needs and has nothing for today. Core only - no UI yet, so
this is additive and changes no existing behavior.

- models/provisioning-node.model.ts    four fixed levels, node status rules,
                                       PaymentPointConfig, tree helpers
                                       (childLevelOf/ancestorsOf/childrenOf)
- models/partner-credential.model.ts   public-key-only credential shape
- services/*-gateway.interface.ts      hierarchy read/create/status/disable
                                       plus credential register/rotate/revoke
- services/*-api.gateway.ts            /api/partner/v1/... per contract §4, §6
- services/*-local.gateway.ts          in-memory, seeded four-level tree
- services/*-gateway.token.ts          environment.useMockData ? local : api

Two deliberate choices worth stating:

1. PartnerCredential has no private-key field at all, and cannot grow one by
   accident - the partner generates the keypair, we hold only the public
   half (§6.1). looksLikePrivateKey() exists purely so a UI can refuse a
   paste of the wrong half before it reaches a log.

2. The local gateway ENFORCES the §2 invariants rather than being a
   permissive stub: externalReference uniqueness per (companyId,
   environment, level), server-computed path, inherited immutable
   environment/companyId, cascading disable, and disabled-is-terminal. A
   mock that accepts what the real backend rejects would let the UI ship a
   flow the backend then refuses - the invariants are the point, not the
   data.

24 new tests covering exactly those invariants (cascade stops outside the
subtree, disabled cannot be reactivated, same reference allowed at a
different level, lookup scoped by environment, revoked credential cannot
rotate).

Also checked and deliberately NOT done: F40 (delete mock-data.interceptor +
src/assets/mock). Both are still load-bearing. mock-data.interceptor mocks
the LEGACY endpoints (/category, /items, /cart, /qr) still served by
ApiService and untouched by the gateway swap. src/assets/mock feeds
mock-backoffice-data.provider, mock-bootstrap.provider (gated by
useMockBootstrapOnLocal, currently true - it is what makes local dev render
at all), and widget-manifest.service's fallbackManifestUrl, which is not
mock-gated and runs in production. F40 belongs after the legacy-endpoint
retirement (Track N), not here.

Verified: 139/139 unit tests (24 new), arch:check clean, production build
succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 17:10:30 +04:00
sdarbinyan
ffd57f2d18 feat: real API gateways for all remaining local-only domains (F17-F39)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Block 3 of the frontend backlog. 18 gateways were hardcoded to their local
(mock/localStorage) implementation with no seam to a real backend at all -
this closes that gap for everything with a documented contract to build
against.

10 core gateways, token now resolves environment.useMockData ? local : api,
same pattern already proven on fx-quote earlier this session:

  permission, analytics, cart (server-cart), finance, vk-id (identity),
  connector (integrations), marketplace (registry), offer, seller,
  mall-content

8 admin gateways, same pattern:

  orders, products, users, transactions, monitoring, moderation,
  notifications, dashboard-metrics

Endpoints came from the matching contract doc where one exists (Phase 1-10,
Track A, Track S - cited per file). Three domains have no dedicated contract
doc yet (transactions, monitoring, moderation) - those gateways call the
established /api/admin/v2/{resource} convention used throughout the rest of
docs/backend/, flagged in each file's own comment as inferred rather than
specified, for whoever writes that contract to confirm or correct.

One real fix along the way: offer-api.gateway.ts's publish() translates a
422 + details[] response (contract §7's actual failure mode) into the
interface's { ok: false, errors } shape, rather than letting an HTTP error
leak past a caller that expects a value back.

Media repository (mock-media-repository.service.ts) deliberately NOT
swapped - no backend contract exists for it anywhere in docs/backend/, and
inventing endpoint shapes with zero grounding is worse than leaving it mock.

Regression this surfaced, fixed as part of the same change: three spec files
stubbed a gateway's concrete Local class directly via useValue. That worked
by accident while the token unconditionally resolved to the local class; once
the token became conditional on useMockData, those specs silently injected
the real (unmocked) API gateway instead and failed. Fixed by providing the
token instead of the class - the pattern the ADMIN_CATEGORIES_GATEWAY entry
in the same spec file already used correctly, because categories was
already token-swapped before this session:
  - admin-analytics.facade.spec.ts (orders/products/moderation gateways)
  - admin-order-watcher.service.spec.ts (orders gateway, 3 call sites)

Verified: 115/115 unit tests, 5/5 E2E, arch:check clean, production build
succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:29:50 +04:00
sdarbinyan
fc53a3b7f5 feat: server-authoritative checkout, no client-computed amount
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
F14-F16 of the frontend backlog. Contract: PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md §5.2.

The highest-priority change in Phase 1: `POST /cart` sent `amount` computed
client-side (this.convertTotal(this.totalWithDelivery())) and the backend was
asked to trust it. Replaced with two calls:

1. POST /api/v2/storefront/checkout - offer ids + qty only. Returns
   checkoutSessionId and the server-computed total.
2. POST /api/v2/storefront/payments/intents - references checkoutSessionId
   only. Same response shape as before (qrId/qrUrl/bankUrl/qrTTL via the
   existing resolvePaymentQrId/resolvePaymentLink/resolveBankPaymentUrl
   helpers) - this replaces how the charged amount is determined, not the
   QR/card provider polling flow, which Phase 1 does not redesign.

merchantReference (PARTNER-PROVISIONING-API-CONTRACT.md's RoutingContext
field) is sent on the payment intent, generated the same way the old orderId
was - our own correlation id, now with a name that matches what it is.

api.service.ts: CheckoutSessionRequest/Response and PaymentIntentRequest
types added, old CartPaymentRequest/createCartPayment left in place (Phase 7
reconciliation and any other caller may still reference the shape) but no
longer called from checkout.

offerId uses item.itemID: this codebase has no distinct Offer entity yet
(Phase 3, Product/Offer split, not shipped in this model) - itemID is the
same catalog identifier every other endpoint already keys off. Flagged in a
code comment for whoever ships Phase 3 to revisit.

Dead code removed as a consequence, not a separate pass: buildPaymentItems,
getPaymentUserId, getPaymentDescription (no other caller once the old
payload was gone), the ConfigService/TenantResolverService injects that
existed only for getPaymentDescription, and the now-orphaned
cart.paymentDescriptionFallback i18n key in all three locales.

Verification: cart.component.ts has no unit spec (no src/app/pages/cart/
*.spec.ts exists) - this session's E2E suite is the only coverage the
checkout request shape has. Added checkout-request-shape.spec.ts, scoped
narrowly to the request/response contract rather than a full add-to-cart
UI journey: seeds cart state directly into localStorage, fakes the customer
session via cookie + intercepted session-check, intercepts both new
endpoints and asserts on the captured request bodies. Confirms concretely:
no `amount` or `price` field ever leaves the client, offers carry the right
offerId/qty, and the payment intent correctly threads checkoutSessionId
through.

Verified: 5/5 E2E green, 115/115 unit tests green, arch:check clean,
production build succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:14:19 +04:00
sdarbinyan
14467cc6fb feat: FX-quote-backed currency conversion, delete admin rate editor
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
F10-F12 of the frontend backlog. Contract: PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md §3.

Removed the failure mode §5 of that contract exists to close: rates were
typed once by an admin into Settings, persisted to localStorage, seeded from
a hardcoded DEFAULT_RATES table (USD: 0.011, AMD: 4.3) that never updated and
drifted from market. Nothing recorded which rate produced a displayed price
or when.

- currency-rates.service.ts   now fetches through FX_QUOTE_GATEWAY instead of
                              reading admin-typed/localStorage numbers. Stays
                              synchronous at the call site (getRate/convert) -
                              rewriting every consuming template to `| async`
                              is a separate, larger change (F13, not this
                              commit). Before a quote has loaded for a pair,
                              getRate returns 1 rather than a fabricated
                              market rate; isRateReady() lets a caller that
                              cares distinguish the two. ensureFreshQuote()
                              added for checkout to await before charging,
                              per contract §3.2's stale-quote policy.
- language.service.ts        setCurrency() now triggers a quote fetch instead
                              of just flipping the display signal.
- cart.component.ts           openPaymentPopup() awaits ensureFreshQuote()
                              before computing the charged amount.
- admin-settings-page.*        currency-rate editor deleted (F11) - card,
                              component state, and the three orphaned i18n
                              keys it was the only consumer of.

Two real bugs surfaced fixing this, neither cosmetic:

1. fx-quote-local.gateway.ts had CurrencyRatesService.convert() as its rate
   source. That is now circular - CurrencyRatesService depends on
   FX_QUOTE_GATEWAY, and under useMockData:true this gateway IS
   FX_QUOTE_GATEWAY. Would have recursed the moment mock FX data was
   exercised. Fixed by giving the local gateway its own static mock table -
   the correct home for those numbers now: explicitly labelled dev/mock data,
   only wired in behind useMockData, never presented as a live rate.

2. currency-convert.pipe.ts memoized its result on (amount, from, to) alone.
   That was already latently wrong - rates could change via the old
   setRate() without the pipe re-evaluating for an already-rendered price -
   but never surfaced because rates never changed mid-session in practice.
   Async quote loading made it concrete and reproducible: a price rendered
   before its quote arrived stayed wrong forever, because none of the three
   cached inputs ever changed again on their own. Fixed with a ratesVersion
   counter on the service, bumped on every quote arrival, included in the
   pipe's cache key.

Both found and fixed via the E2E suite (docs from the prior commit) actually
exercising the real code path: GET /api/v2/pricing/fx-quote intercepted with
a contract-shaped response rather than flipping the whole app into mock mode,
so the test runs the real FxQuoteApiGateway, not a stand-in for it.

Verified: 3/3 E2E green, 115/115 unit tests green, arch:check clean,
production build succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:03:02 +04:00
sdarbinyan
21443d34a0 feat: stand up E2E harness, fix a real bootstrap bug it found
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Track Q Q1/Q4 (docs/PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md). No E2E existed
before this. Playwright chosen - no existing test runner preference, and it
needs zero extra infra beyond the dev server this repo already has.

- playwright.config.ts, package.json e2e/e2e:ui/e2e:report scripts
- e2e/smoke.spec.ts       app boots, no console errors (network 404s from the
                          absent backend are filtered - expected, not a bug)
- e2e/currency-switch.spec.ts   Track Q Q4: switching currency must change
                          the displayed price VALUE, not just the label next
                          to it. Written specifically so the upcoming
                          checkout money-truth rewrite (frontend backlog
                          F10-F16, which replaces client-side FX math with a
                          server-computed total) has a regression net under
                          it before that rewrite starts.

The first run found a real, current bug: @marketplaces/auth ships plain tsc
output (dist/index.js), not Angular Package Format, so it carries no compiled
Ivy DI metadata. Any class-based provider from it - not just the Ed25519
Noop stub, AuthService itself hit the same failure - forces Angular to
JIT-compile at runtime, which throws immediately when @angular/compiler
isn't loaded. That breaks app bootstrap outright, for real users, not just
this test.

Fixed here with the minimum honest scope:
- src/main.ts: import '@angular/compiler' before bootstrap, so JIT works
  everywhere the package is injected, not just at one call site
- src/app/app.config.ts: useFactory instead of useClass for the Noop
  Ed25519 provider, since it has zero constructor deps and doesn't need
  Angular to derive metadata for it at all
- angular.json: raised the initial-bundle hard-error budget 1.5MB -> 1.8MB,
  because the compiler import made a correct build refuse to complete. A
  build that fails outright is worse than a bundle that's honestly larger
  than it should be.

The real fix belongs in the vitanovaPackages auth repo: publish via
ng-packagr so consumers get Ivy-compiled output and none of this is
necessary. Do not remove the compiler import until that ships - see the
comment left in main.ts.

Also fixed a genuine test defect while getting this to a real green: the
page renders duplicate .currency-option elements (desktop/mobile variants of
the same selector), so the first attempt at this test clicked into a hidden
duplicate and silently no-opped. Scoped the click to .currency-dropdown.open
and added an explicit poll for the DOM to reflect the new currency before
reading it back, rather than trusting a fixed timeout.

Verified: 3/3 E2E green, 115/115 unit tests green, arch:check clean,
production build succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:46:43 +04:00
sdarbinyan
c104f313ce feat: real FX quote gateway, swapped in behind useMockData
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
FxQuoteApiGateway calls GET /api/v2/pricing/fx-quote per
PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md §3.1. Deliberately thin - no caching or
retry here, since the caller decides what an expired quote means (re-fetch vs
block checkout) and baking that into the gateway would hide the decision.

Token now resolves to the real gateway whenever useMockData is false, matching
the existing pattern in runtime-provider-strategy.service.ts.

Additive only: nothing that reads prices today was rewired to this gateway yet.
That rewiring touches checkout's charge-amount computation and is deferred -
see the session summary for why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:28:56 +04:00
sdarbinyan
92e2ee5f49 feat: normalize API errors and handle 429 rate limiting
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Nothing in the frontend parsed the backend error envelope, and nothing
anywhere handled 429 - a rate-limited backend surfaced as a generic failure
with no retry and no user-visible explanation. core/error-handling and
core/interceptors were empty directories.

- api-error.model.ts    typed envelope per BACKEND-API-REFERENCE.md section 5,
                        plus a status-to-code fallback so a response with no
                        envelope still arrives as a usable ApiError
- api-error.mapper.ts   total function: HTML bodies, empty bodies and
                        differently-shaped JSON all produce an ApiError rather
                        than throwing inside the error path
- api-error.interceptor bounded retry on 429 honouring Retry-After (seconds or
                        HTTP-date), idempotent methods only - replaying a POST
                        after a 429 can double-submit, and that call belongs to
                        the caller that knows whether it holds an idempotency key
- rate-limit-notifier   signal-based state so the UI can say "throttled,
                        resumes in N seconds" instead of "something went wrong";
                        self-clearing, because a banner outliving the throttle
                        trains users to ignore it

A 429 carrying no delay hint defaults to a non-zero wait so callers cannot
busy-loop the endpoint that just asked them to stop.

14 mapper tests. Suite 115/115 green, boundaries pass, build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:15:13 +04:00
sdarbinyan
a2204c641b fix: force LF on systemd unit files
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
A CRLF checkout leaves a trailing carriage return in every unit value, which
systemd reads as part of the value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:21:50 +04:00
sdarbinyan
c721120e85 ci: make TLS provisioning dynamic as domains are added
Adding a domain was a manual per-domain script run. With domains arriving
continuously that does not hold, so certificate issuance is now automatic.

HTTP already needed no work: the nginx catch-all serves any Host and the SPA
resolves its tenant from that header. Only TLS needed a name-by-name step.

Two mechanisms:

- setup-wildcard-tls.sh issues one DNS-01 wildcard for *.<apex>, after which a
  new tenant subdomain is live over HTTPS with zero certificate work.
- sync-domains.sh reconciles tenant-owned domains against a desired list on a
  10-minute timer: issues what is missing, skips certificates with >30 days
  left, skips names already covered by the wildcard, waits out unpropagated
  DNS, and caps issuance per run so a bad source cannot burn the weekly ACME
  budget.

Safety properties worth stating: a failed fetch of the desired list aborts the
run rather than reading as "remove every domain"; removing a domain disables
its server block but keeps the certificate, so re-adding is instant; malformed
hostnames are rejected before reaching certbot or an nginx server_name.

The source is pluggable - a file today, the Phase 9 domain registry once it
exists, whose MarketplaceDomain statuses already match what this needs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:21:39 +04:00
sdarbinyan
28861953c8 ci: add frontend CD pipeline, server provisioning, TLS scripts
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
There was no CD: pushing to main deployed nothing, and deploys were a manual
copy onto the server. This adds the missing half.

- .github/workflows/deploy.yml - build, upload to a per-commit release
  directory, swap the symlink atomically, reload nginx, verify over HTTP.
  The swap only happens after the upload is verified to contain index.html,
  so a failed deploy leaves the previous release serving.
- scripts/deploy/server-setup.sh - idempotent one-time provisioning: nginx,
  certbot, ufw, and a key-only deploy user whose sole sudo right is
  "systemctl reload nginx".
- scripts/deploy/add-domain.sh - per-domain server block plus TLS issuance,
  run once a domain's A record resolves to the server.
- docs/DEPLOYMENT.md - setup order, required CI secrets, rollback, limits.

Also adds .gitattributes: the shell scripts were being checked out with CRLF
endings, which makes bash fail on the shebang line on Linux.

Host keys are pinned via DEPLOY_KNOWN_HOSTS rather than trusted on first use.
No credentials are committed; all four deploy secrets are supplied by CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 11:42:06 +04:00
sdarbinyan
71da5a8d80 docs: partner provisioning API contract, routing context, Track P
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
A partner integration request landed for programmatic merchant-hierarchy
management (Company/Project/Store/PaymentPoint). Built the answer generically:
partner-specific behaviour is a PartnerProfile config row, and no partner name
appears in any entity, field, endpoint or status value.

New:
- docs/backend/PARTNER-PROVISIONING-API-CONTRACT.md - hierarchy, idempotency,
  node-scoped public-key credentials, TEST/LIVE partition, routing context
- docs/context/adrs/ADR-0003-generic-partner-provisioning-api.md

Amended, because the schema impact must land before Phase 1 is implemented:
- Phase 1 gains RoutingContext on CheckoutSession/PaymentIntent/Payment,
  frozen at checkout-session creation and immutable after
- Phase 7 gains routing on Refund/ReconciliationRecord, plus the rule that
  seller settlement splits happen after routing, never as a hierarchy level
- Phase 9 gains Company/Project above Marketplace and PaymentPoint below it,
  with a backfill sequence for existing marketplaces
- Track S gains partner credentials: public key only, node-scoped authority,
  rotation with overlap, immediate revoke, audit coverage

Also: Track P (P1-P10) in the delivery plan, and backend ownership closed as
answered across the contract set.

Card payment was checked, not added - qr and card both already ship in
cart.component.ts with separate create paths and status pollers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 11:22:24 +04:00
sdarbinyan
551a22a245 fix: install shared packages over git, unbreaking CI
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
The Verdaccio registry introduced earlier is unreachable from CI (listens on
127.0.0.1:4873 behind a firewall allowing only 80/443/SSH), which broke the
architecture-governance workflow - its npm ci step could no longer resolve
@marketplaces/auth.

Packages are now published to git release branches (release/auth,
release/payment in vitanovaPackages) whose root is the package itself, and
installed with git+<repo>#release/auth. No registry, token, tunnel, or CI
secret - anonymous git read is enough.

- package.json: git dependency; .npmrc removed (no scope mapping needed)
- vitanovaPackages release.yml rebuilt to force-push release branches
- ADR-0001 amended with the distribution change and why the registry lost
- BACKEND-HANDOFF: added the multi-tenancy section (hostname -> tenantKey ->
  per-tenant bootstrap config), corrected the install and deploy notes, and
  recorded that no CD pipeline exists
- PACKAGE-EXTRACTION / PACKAGES-USAGE rewritten for the git-branch flow

Verified: npm ci, arch:check:boundaries, ng build, 103/103 tests, all with
no credentials configured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 02:08:05 +04:00
sdarbinyan
f6045a07b2 docs: backend handoff, package usage guide, finalized CI/CD
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- docs/backend/BACKEND-HANDOFF.md: single entry point for a backend dev -
  reading order, verified infrastructure state (nginx running, Postgres
  inactive, no API on :8080, no TLS, no DNS automation, no CI runner),
  auth surface, and the day-one setup that is still outstanding
- docs/PACKAGES-USAGE.md: install, required DI providers, full exported
  API for both auth mechanisms, and how to ship a package change
- PACKAGE-EXTRACTION.md now covers build/release/infra only and points at
  the usage guide; CI section reflects the two real workflows

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 01:46:58 +04:00
sdarbinyan
2e4bb4ae00 feat: publish @marketplaces/auth to private registry, drop local package copy
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Stood up Verdaccio (Docker, on the dev server) as a private npm registry
since no public registry/NPM_TOKEN exists yet. Published @marketplaces/auth
and @marketplaces/payment there, removed the local packages/ staging copy
from this repo, and switched marketplaces to install @marketplaces/auth
0.1.0 as a real npm dependency through the registry.

- .npmrc scopes @marketplaces to the Verdaccio registry (no token committed;
  each installer/CI supplies its own via npm login or an env-injected token)
- Verified: fresh npm install, ng build, arch:check:boundaries, and full
  test suite (103/103) all pass against the registry-installed package
- Registry is reachable only via SSH tunnel today (firewall allows 80/443/
  SSH only); public/CI access is a follow-up decision, documented in
  docs/PACKAGE-EXTRACTION.md

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 01:32:20 +04:00
sdarbinyan
14c72d1a6a feat: extract auth into @marketplaces/auth package, add backoffice admin provisioning spec
- ADR-0001: decision to extract auth/payment into shared @marketplaces/* packages
- Scaffold packages/auth, packages/payment; @marketplaces/auth now holds the real
  telegram (customer+admin QR/session) and ed25519 (future admin challenge/response)
  auth implementation, pushed to sources.vitanova.network/sdarbinyan/vitanovaPackages
- Rewire ~30 call sites to import from @marketplaces/auth; delete migrated originals
  from core/auth, core/admin-auth, services/, models/
- Replace environment coupling with AUTH_API_URL/TELEGRAM_BOT_USERNAME injection
  tokens and isDevMode(); wired as file:packages/auth pending registry publish
- Add TRACK-S §8: bootstrap per-marketplace admin login + marketplace-scoped
  sub-admin invite/role endpoints
- Build, arch:check:boundaries, and full test suite (103/103) all green

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 01:05:16 +04:00
sdarbinyan
23060261c7 feat: Track S frontend - permission core + Audit & Security section
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
core/permissions (SessionPermissions/AuditEvent models, gateway/token,
requiresScope() CanActivateFn) against docs/backend/
TRACK-S-SECURITY-RBAC-CONTRACT.md §1-3. PermissionLocalGateway grants
PLATFORM_OWNER/'*' unconditionally - this matches TODAY'S REAL behavior
(GAPS-AND-IMPROVEMENTS.md: admin role model is decorative, every
authenticated admin has full access) rather than faking enforcement that
doesn't exist. requiresScope() is correspondingly a no-op against the
mock, by design - it must not create a false sense of security before a
real backend exists.

New features/admin/audit (Audit & Security nav section, missing from
admin nav today) - facade + page, empty state until real audit events
exist.

Scope: deliberately NOT retrofitting requiresScope() onto the 14 existing
live admin routes in this pass - a blanket guard rollout risks locking an
admin out without warning and needs its own verified pass, not a bundled
change alongside nine other phases. This is the single most serious
security gap this session's audit found; closing it for real is Track S's
own dedicated follow-up once a real backend exists to enforce against.

This closes out the full "do all phases" push: 10 phases + 2 tracks, each
with a real mock-gateway-backed swappable seam, several with genuinely new
backoffice UI. Every core/* module here binds via the same DI-token
pattern established for the 9 admin domains at the start of this session -
a real backend is a token swap per module, not a rewrite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 00:08:28 +04:00
sdarbinyan
be167d110e feat: Track A frontend - analytics event pipeline core + real call sites
core/analytics (AnalyticsEvent model, gateway/token/mock, AnalyticsService
wrapper) against docs/backend/TRACK-A-ANALYTICS-CONTRACT.md §1. isSynthetic
is derived from the build environment at the service layer, never
client-settable at a call site - matches the contract's §6 requirement
that synthetic traffic be inseparable-by-accident from production data
once a real backend exists.

Wired into real, live interaction points (additive only, no existing
logic touched): product_view + add_to_cart in
product-details-container.component.ts, checkout_started + payment_started
in pages/cart/cart.component.ts. This is the actual event-firing
infrastructure the plan calls "the single largest remaining backend
effort" (§3.1) - the frontend side (call sites) is real now; the mock
gateway just doesn't persist anywhere yet.

Not wired: search/category_view/seller_view/cart_view/payment_success/
payment_failed/order_created - follow-up call sites once this pattern is
reviewed, to avoid a much larger unreviewed diff in one push.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 00:05:46 +04:00
sdarbinyan
34f79b0303 feat: Phase 10 frontend - Gorbushka-class content module core (models only)
core/content-modules against docs/backend/PHASE-10-CONTENT-MODULES-
CONTRACT.md §1-2: Shop/ShopCategory/MallService/Floor/SchemePin/
RentListing/Lead models + gateway/token, seeded empty.

Scope: models/gateway seam only, no admin UI (mall scheme/floor/pin
editor, rent listing management). This is explicitly the lowest-priority
phase in the delivery plan - only after Commerce Core is real - so it
gets the smallest build in this push, matching that priority rather than
spending equal effort on every phase regardless of sequence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 00:02:14 +04:00
sdarbinyan
5d585ff8ff feat: Phase 9 frontend - Marketplaces registry + Domains & Releases (combined page)
core/marketplace-registry (Marketplace/MarketplaceDomain/
LifecycleAdvanceResult models + gateway/token) derives a single-row
registry from the current tenant's own live bootstrap config and current
hostname, since the platform runs one tenant per deployment today with no
registry anywhere. New features/admin/marketplaces page + nav entry.
Against docs/backend/PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md.

Scope simplification: combined "Marketplaces" and "Domains & Releases"
(two separate backoffice sections in the plan) into one page with two
sections, to keep pace through this push - splitting them into dedicated
routes is a small follow-up once there's real multi-marketplace data to
justify two separate list views. Onboarding wizard (8 steps), Hostinger
DNS automation, and the full lifecycle-advance state machine are not
built - registry/domain/lifecycle read-only display only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 00:01:09 +04:00
sdarbinyan
0e16eecda6 feat: Phase 8 frontend - Customer identity core + standalone VK ID login button
core/identity (Customer/ExternalIdentity/ContactChannel models + VkIdGateway/
token/mock) and a standalone VkIdLoginComponent, per Sprint 0.1's "VK ID
first" decision and v3.1 §14. Against
docs/backend/PHASE-8-IDENTITY-MESSAGING-CONTRACT.md §1-2.

Deliberately not wired into TelegramLoginComponent's dialog - that's the
live, working QR-login surface for both customer and admin auth
(components/telegram-login/), and splicing a second provider into it
needs a real VK OAuth app to test against, not a mock bolt-on next to
nine other phases. The button is a standalone, ready-to-place component;
integrating it into checkout/login flows is follow-up work once VK
credentials exist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:58:48 +04:00
sdarbinyan
a4f44dbb58 feat: Phase 7 frontend - Refund/Reconciliation core + Payments & Finance section
New core/finance (Refund/ReconciliationRecord/Settlement models + gateway/
token, mock backed, seeded empty) and features/admin/finance (facade +
page: reconciliation queue with resolve action, settlements placeholder).
New /backoffice/finance route + nav entry, nav i18n key in all 3 languages.
Against docs/backend/PHASE-7-PAYMENTS-RECONCILIATION-CONTRACT.md.

Note: does not wire AdminOrdersLocalGateway's existing mock
requestRefund(id) method into this new Refund flow yet - that's a small
follow-up once Phase 2's real Order backend exists to refund against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:56:59 +04:00
sdarbinyan
e3a70f5e65 feat: Phase 6 frontend - server-cart core, mock-gateway backed (scoped)
core/cart against docs/backend/PHASE-6-CART-CHECKOUT-CONTRACT.md §2-5:
ServerCart/ServerCartLine/CheckoutSession/DeliveryOption models + gateway/
token, in-memory mock implementation.

Deliberately does not touch pages/cart/cart.component.ts or
services/cart.service.ts (the live localStorage/Telegram-CloudStorage
cart) or features/website/checkout/ (still an empty directory) - same
judgment as Phases 1/3/5: this is real money/payment-adjacent flow and
deserves a dedicated, verified rewiring pass once a real backend exists,
not a bundled swap alongside nine other phases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:54:51 +04:00
sdarbinyan
475d75781f feat: Phase 5 frontend - Seller gateway core, mock-backed (scoped)
core/sellers/services against docs/backend/PHASE-5-SELLER-PORTAL-
CONTRACT.md §3: SellerGateway operating on the EXISTING Seller domain
type (core/sellers/models/seller.model.ts - deliberately not a new
competing shape; GAPS-AND-IMPROVEMENTS.md already flags two rival seller
shapes and a third would make that worse). Adds SellerUser/SellerRole.
Seeded empty - Seller Management has zero real sellers today (flag off
by default).

Scope note: this session's largest deferral. A real Seller Portal is a
separate self-service app surface (/api/seller/v1/*, its own auth, its
own layout) per the contract - building that alongside 9 other phases in
one push risks a shallow, unreviewed seller-facing app. The gateway core
here is the swappable seam; the actual portal deserves its own focused
pass once a real backend and the unified-orders Fulfillment model
(Phase 2) are further along.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:53:24 +04:00
sdarbinyan
6ac52b1c50 feat: Phase 4 frontend - Integrations backoffice section (mock-gateway backed)
New core/integrations (Connector/DeadLetterEntry models + gateway/token)
and features/admin/integrations (facade + page: connector table with
status/lag/errors/backlog/unmatched, pause/resume). Seeded empty per
Sprint 0.1's "no fixed partner list" decision - the section is ready to
populate the moment the first real connector is onboarded against
docs/backend/PHASE-4-CONNECTOR-FRAMEWORK-CONTRACT.md §7. New /backoffice/
integrations route + nav entry, nav i18n key in all 3 languages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:51:38 +04:00
sdarbinyan
5f23c6e5aa feat: Phase 3 frontend - Offer/InventoryRecord core, mock-gateway backed
core/offers module against docs/backend/PHASE-3-CATALOG-OFFER-FULFILLMENT-
CONTRACT.md §2-3, §7. OfferLocalGateway derives one Offer per existing
AdminProduct (sellerId defaults to 'marketplace-owned' when absent - same
convention AdminProduct.sellerId already documents) so the shape is real
without touching the live admin Products domain.

Scope note: this is the core swappable seam only (model + gateway + token),
same judgment as Phase 1's pricing core - deferred is the actual Product/
Offer split UI (multi-seller product page, admin lookup-by-SKU screen),
which is the single largest structural change in the whole programme per
the delivery plan and needs its own dedicated pass against a real backend,
not a bundled mock-data rewire of the working Products admin surface.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:49:24 +04:00
sdarbinyan
580d228484 feat: Phase 2 frontend - Notification Center (mock-gateway backed)
New features/admin/notifications module against
docs/backend/PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md §6: model, gateway
interface + local mock (derives notifications from the existing real
ADMIN_ORDERS_GATEWAY so the shape is genuine), facade, page (unread
filter, event-type filter, mark-read/mark-all-read). New /backoffice/
notifications route + nav entry (nav i18n key added in all 3 languages;
page body copy is plain English - see scope note below).

Existing AdminOrder model/facade/mock-gateway were already solid and
real (just gained a DI token this session) - Order/OrderLine/Fulfillment
canonical-model rework from the Phase 2 contract is deferred; today's
AdminOrder shape is close enough to build the Notification Center against
without a disruptive rewrite of an already-working admin surface.

Scope note (applies going forward for this "do all phases" push): new
page body text uses plain English instead of the full TranslatePipe/
i18n-key system. Multiplying every new string across en/ru/hy + the
Translations type for every phase isn't sustainable at this pace: nav
labels (few, highly visible) still get real i18n keys; page content
does not. Flagged for a follow-up i18n pass before any of this ships.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:47:13 +04:00
sdarbinyan
b19fd77a60 feat: Phase 1 pricing core - Money/FxQuote/PriceSnapshot, mock-gateway backed
First frontend build against docs/backend/PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md:
- Money type (amountMinor + currency, no float math) with add/subtract/
  multiply helpers respecting per-currency minor-unit decimals.
- FxQuote model + FX_QUOTE_GATEWAY token, mirroring the DI-seam pattern
  already used for the 9 admin domains. FxQuoteLocalGateway derives a
  quote from the existing CurrencyRatesService so the shape is real even
  before a backend rate source exists (Sprint 0.1: FX is ours in-house).
  source: 'local-mock' is explicit and distinct from the eventual real
  backend's 'internal' - swapping the token when the real endpoint ships
  requires zero caller changes.
- PriceSnapshot/CheckoutLine/CheckoutResult models per contract §4-5.

Scope note: this does NOT yet rewire the live cart/checkout payment flow
(pages/cart/cart.component.ts) onto this module - that flow handles real
money against a live payment provider, and rewiring it deserves its own
focused pass with explicit verification, not a bundled mega-change. The
core module is ready for that pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:34:00 +04:00
sdarbinyan
2e09369345 docs: Track A/S contracts + backend index tying the full v3.1 contract set together
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- Track A: analytics event pipeline (traffic/catalog/commerce/operational/
  quality events), synthetic-traffic separation enforced server-side by
  environment/token, never a client-settable flag.
- Track S: 17-role/3-scope RBAC enforcement, audit log, secrets, rate
  limiting, step-up auth - closes this session's most serious finding
  (admin role model is decorative today, any authenticated admin has
  full access regardless of assigned role).
- docs/backend/README.md: index of all 12 contract docs (Phases 1-10 +
  2 tracks) in build order, plus what's deliberately excluded (namespace
  migration, per-connector adapters, extra payment providers) and the
  one thing still genuinely open across all of them - backend ownership.
- Cross-linked from BACKEND-API-REFERENCE.md and the delivery plan so the
  index is discoverable from either entry point.

This closes out documentation for every phase/track in
PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md that doesn't require a further business
decision. Nothing left undocumented on our side pending only implementation
and backend-ownership assignment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:09:04 +04:00
sdarbinyan
ec6760ac65 docs: backend contracts for Phases 8-10 (identity/messaging, tenant registry, content modules)
- Phase 8: Customer/ExternalIdentity/ContactChannel, VK ID OAuth 2.1/PKCE
  built first per Sprint 0.1 ("do all after vk"), then OTP, then MAX/
  Telegram bot linking, then the Notification Orchestrator + Delivery
  Conversation State Machine. Hard rule carried through: bots never touch
  financial statuses, only delivery fields via a dedicated Delivery Service.
- Phase 9: Marketplace/MarketplaceDomain/MarketplaceFeatureSet/
  MarketplaceRevision, full Hostinger DNS automation sequence (snapshot
  before change, never touch MX/SPF/DKIM/DMARC/CAA), lifecycle state
  machine that must expose its own blocker on every transition, publish
  model with orders/payments/inventory explicitly excluded from revisions.
- Phase 10: Gorbushka-class content entities (Shop/Service/Floor/
  SchemePin/RentListing/News/Lead/MallSettings), explicitly lowest
  priority and gated on Commerce Core being real first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:05:39 +04:00
sdarbinyan
707db6d43c docs: backend contracts for Phases 5-7 (seller portal, server cart, reconciliation)
- Phase 5: Seller Portal from scratch (zero backend bytes exist today) -
  SellerOrganization/SellerUser/SellerMarketplaceMembership, all endpoints
  scoped server-side to the unified-orders Fulfillment model from Phase 2.
- Phase 6: server-owned Cart/CartLine/CheckoutSession, extending Phase 1's
  server-authoritative-amount contract into the cart itself. Replaces
  localStorage/Telegram-CloudStorage cart persistence.
- Phase 7: Refund and ReconciliationRecord entities, settlement contract.
  Flags additional payment providers (wallets/BNPL) as still an open
  business decision - not blocking, schema is provider-agnostic already.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:04:11 +04:00
sdarbinyan
c91b75c036 docs: backend contracts for Phases 2-4 (orders/notifications, catalog/offer, connectors)
Continues the Phase 1 contract doc with the same wire-contract-only style.
All three build on Sprint 0.1's answered decisions - no further business
input needed to start implementation once backend ownership is confirmed:

- Phase 2: canonical Order/OrderLine/Fulfillment/OrderEvent per the unified
  multi-seller decision (one Order, per-seller Fulfillment groups), event
  bus, Notification Center contract.
- Phase 3: Product/Offer split, InventoryRecord, publish-time executability
  validation (the mechanism behind "no branch may distinguish an inspector
  from a normal buyer").
- Phase 4: generic config-driven connector framework per the "no fixed
  marketplace list" decision - onboarding a new partner is configuration
  against a fixed pipeline, not a bespoke integration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:02:52 +04:00
sdarbinyan
8d9eb97e9e docs: remove superseded super-admin Phase 1 design doc
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
superuser.md (already tracked from prior session) supersedes
2026-08-15-platform-super-admin-design.md. Committing the prior
session's uncommitted rename.
2026-08-17 22:43:09 +04:00
sdarbinyan
821fecf5d3 docs: record Sprint 0.1 decisions across delivery plan, gap analysis, backend contract
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
User answered 8 of 9 Sprint 0.1 blocking decisions (2026-08-17); backend
ownership stays open pending a clearer re-ask. Recorded and propagated:

- Payment chain unfrozen -> BACKEND-API-REFERENCE.md §7 and the Phase 1
  contract doc's status banner both updated; Phase 1/6/7 unblocked.
- No fixed external-marketplace list -> Phase 4's connector framework
  respecified as config-driven/generic; Sprint 4.2 retired as "per named
  marketplace," replaced with a generic onboarding runbook.
- FX rate source: ours, in-house, as the default (not just a fallback) ->
  Phase 1 contract's `source` field can read "internal" as the normal case.
- VK ID before OTP -> Phase 8 sprints resequenced (VK ID now 8.2, OTP 8.3).
- Multi-seller orders: unified -> Phase 3.3, Phase 5.2, and Z16 updated to
  the resolved model (one Order, per-seller Fulfillment groups).
- "Fixed 5-second payment" claim: confirmed non-issue, PAYMENT_POLL_INTERVAL_MS
  is already 5000 (real polling cadence, not an artificial delay).
- API namespace: new endpoints only (/api/v2/...), no forced migration of
  legacy endpoints.
- Document version: v3.1 is canonical.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 22:08:23 +04:00
sdarbinyan
54cd089e80 fix: WCAG AA contrast remediation on border/status colors (Z8, user-authorized)
Darkened --border-color and --success/--warning/--error/--info-color in
all three theme files, hue-preserving, computed against WCAG 2.1 formulas:
- border-color: 1.24-1.42:1 -> >=3.0:1 (non-text/UI-component minimum)
- status colors: 2.15-3.76:1 -> >=4.5:1 (plain-text minimum)
--primary-color/--secondary-color/--accent-color/gradients untouched -
only semantic feedback tokens changed. lavero's success-color now diverges
from primary-color (they only happened to share a hex before; semantic
status vs. brand identity are different concerns).

Also verified/corrected during this pass, doc was stale not code:
- Footer "Contacts" is not a code gap - Footer Builder + static-page CMS
  already resolve any authored page generically via pageKey
- Checkout payment-description fallback already tries brandName -> hostname
  -> i18n-translated fallback (en/ru/hy), not a hardcoded RU string

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 22:04:14 +04:00
sdarbinyan
634e3faf3d docs: correct Z6 finding - Product/Organization JSON-LD already shipped
SeoService.setJsonLd() already injects real application/ld+json for
Product (per-item) and Organization (site default) - the gap doc's
"confirmed absent" claim was stale. Noted the one real remaining gap
(BreadcrumbList/ItemList schema) as future net-new scope rather than
implementing it now. Sitemap generation remains backend-only, unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:50:59 +04:00
sdarbinyan
6ca672987e refactor: extract shared app-breadcrumb component (Z14)
Only breadcrumb logic anywhere in the storefront was a local signal +
inline markup inside catalog-container. Extracted a generic
shared/ui/breadcrumb component (rootLabel/items/ariaLabel inputs,
rootClick/itemClick outputs) and repointed catalog-container onto it,
removing the now-dead inline SCSS block. Future breadcrumb usages
(product detail, admin) have something to reuse instead of duplicating.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:49:54 +04:00
sdarbinyan
bf367fc5fe docs: correct search-model duplication finding - three shapes, not two
Re-verified GAPS-AND-IMPROVEMENTS.md's "duplicate search models" item:
core/search/models/search.model.ts was already a re-export shim (fine),
but core/search/models/search-state.model.ts is a real second copy, and
features/search/facade/search.facade.ts has a third, private
LegacySearchState interface with the same fields again. Documented as its
own scoped task rather than fixed here - reconciling three shapes on the
catalog rendering path needs full consumer tracing first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:38:53 +04:00
sdarbinyan
d24ba38479 docs: mark 7 GAPS-AND-IMPROVEMENTS.md items fixed, verified against current source
Doc was 4 days stale relative to source - each item below was independently
verified against the current file/line during this session's Track Z sweep,
not just marked off the todo list:
- Ed25519 auth-error body-code bug
- Dark mode selector (now actually fixed this session)
- Site Layout selector fallback
- setItemMeta() wiring
- og:locale dynamic locale
- stars.component.scss token usage
- AdminRole duplication
- PRODUCT_DATA_PROVIDER/CATEGORY_REPOSITORY dead mock branch
- sellerId UUID typing

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:37:04 +04:00