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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>