docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
# Fork Harvest — TODO
**Branch:** `improvements/fork-harvest` (from `B2B` @ `92f1c88` )
**Design:** [2026-08-21-fork-harvest-design.md ](superpowers/specs/2026-08-21-fork-harvest-design.md )
**Source analysis:** [FORK-ANALYSIS-2026-08-21.md ](FORK-ANALYSIS-2026-08-21.md )
Improvements only. Nothing here regresses our Angular version, test count, or architecture governance.
**Effort:** S ≤ half a day · M ≤ 2 days · L > 2 days
**Lane:** A frontend · B backend contract · C `@marketplaces/auth` package · D infra/ops · E process
---
## Wave 0 — Decide first (blocks Wave 4)
- [ ] **FH-0.1 — Decide the central identity host ** · L · Lane C · * blocker *
VK ID and Yandex ID both validate `redirect_uri` against an exact registered list. We cannot register one per tenant domain, and we cannot let tenants supply their own.
**Decision needed: ** single central callback host (e.g. `id.<platform-domain>` ) as the only registered URI, tenant carried inside signed `state` , 302 back to the tenant domain with a short-lived signed handoff token the tenant API exchanges for a session cookie.
**Also decide: ** is one VK account across two of our storefronts one `Customer` or two? Their platform says two; our `Customer.marketplaceId` already implies two.
**Done when: ** an ADR exists in `docs/context/adrs/` and both questions have a recorded answer.
- [ ] **FH-0.2 — Confirm the server-priced checkout path covers every live flow ** · S · Lane A · * blocks FH-1.3 *
`api.service.ts` already has a server-priced checkout session method. Confirm no production flow still depends on `createPayment(payload, headers)` before deleting the header path.
**Done when: ** every caller of the legacy header path is enumerated and has a replacement.
---
## Wave 1 — Live defects with a security benefit (Lane A, this sprint)
2026-08-21 10:30:27 +04:00
- [x] **FH-1.1 — Kill the plaintext third-party geo call ** · S · Lane A · **done 2026-08-21 **
Now `GET {tenantApiBase}/geo/resolve` , same base as `/regions` . Server reads the client IP; nothing leaves our infrastructure. Endpoint specified in [BACKEND-API-REFERENCE.md ](../BACKEND-API-REFERENCE.md ) §6 — **not built yet ** , and until it is the client falls back to the manual picker, which is what production has effectively had all along. Covered by `src/app/services/location.service.spec.ts` (4 tests, one of which fails the build on any off-origin or plaintext request from this service).
2026-08-21 10:37:10 +04:00
* Was: * `location.service.ts:75` called `http://ip-api.com/json/?fields=…` from an HTTPS origin. Mixed active content is blocked, so `detectLocation()` only ever took its error branch — auto-detect was dead in production, not merely insecure — and the attempt still leaked every visitor's IP to a third party.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
- [ ] **FH-1.2 — Stop blindly trusting the bank redirect URL ** · M · Lane A
`src/app/pages/cart/cart.component.ts:485` — `bypassSecurityTrustResourceUrl(bankUrl)` with no validation, rendered into a popup iframe. Most acquirer 3-D Secure pages send `X-Frame-Options: DENY` , so the popup is blank for those banks. Their spec: card checkout navigates the current tab, no intermediate popup.
**Do: ** accept only an `https:` URL whose origin the backend returned in the payment response (backend allowlist, per their `safeHttpsUrl()` ); navigate the current tab instead of framing.
**Done when: ** a non-https or non-allowlisted URL is refused with a visible payment error; a test covers both the accepted and the refused case.
2026-08-21 10:30:27 +04:00
- [x] **FH-1.3 — Remove provider credentials from the browser ** · M · Lane A · **landed via the `@marketplaces/payment` migration **
The legacy payment surface on `ApiService` was deleted wholesale in that work. `grep -ri "authorization-key\|userid-value\|web-97ec" src/` now returns nothing. Keep FH-3.5 (bundle secret scan) to stop it coming back.
2026-08-21 10:37:10 +04:00
* Was: * `api.service.ts:675` set `authorization-key` and `userid-value` headers client-side, and `api.service.ts:143` shipped a partner ID literal in the bundle. Their audit's most serious finding, and it was correct.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
- [ ] **FH-1.4 — Send `Idempotency-Key` on payment creation ** · S · Lane A
Zero `idempot*` anywhere in our codebase. Their API requires the header and rejects a key reused across a different order.
**Do: ** generate one key per checkout attempt, stable across retries and across a double-click, sent on payment creation.
**Done when: ** the existing `checkout-idempotent-click.spec.ts` asserts both requests carry the * same * key.
---
## Wave 2 — Contract hardening (Lane B, parallel with Wave 1)
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
Each item is normative text plus an acceptance scenario in `backend/BACKEND-INTEGRATION.md` , so it becomes a delivery gate rather than a wish.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
- [x] **FH-2.1 — Conditional-UPDATE stock reservation ** · S · `backend/BACKEND-INTEGRATION.md`
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Written 2026-08-21: ** PHASE-3 §3.1 — the conditional `UPDATE … WHERE (available - reserved) >= qty RETURNING id` , 409 on zero rows, whole-cart rollback, 15 min TTL.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
`UPDATE … SET reserved = reserved + $qty WHERE (onHand - reserved) >= $qty RETURNING id` ; empty result → `409` . Reservation TTL 15 min. Price read only from the server-side snapshot, never from the request.
**Acceptance: ** two concurrent purchases of the last unit produce exactly one payable order.
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
- [x] **FH-2.2 — Idempotency as unique constraints ** · S · `backend/BACKEND-INTEGRATION.md`
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Written 2026-08-21: ** PHASE-7 §5 — unique constraints on `payment.idempotency_key` and `(provider, event_key)` , insert-first webhook handling, `sha256(rawBody)` fallback key, signature over the raw body, 24 h poll as reconciliation.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
`Payment.idempotencyKey UNIQUE` ; a key reused against a different order/marketplace → `409` . `PaymentWebhookEvent @@unique([provider, eventKey])` ; duplicate insert → `{accepted: true, duplicate: true}` . `eventKey` falls back to `sha256(rawBody)` . Signature verified against the **raw ** body. Status poll as a 24-hour reconciliation fallback.
**Acceptance: ** a replayed webhook neither completes the order twice nor moves stock twice.
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
- [x] **FH-2.3 — Session and credential model ** · M · `backend/BACKEND-INTEGRATION.md`
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Written 2026-08-21: ** TRACK-S §2.1 — 32 random bytes stored as SHA-256 only, HttpOnly/Secure/SameSite, one cookie per contour, Argon2id params, mandatory TOTP with a single-use enrolment token, password change revokes all sessions in-transaction.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
Server-stored sessions; random 32 bytes; **stored as SHA-256 hash only ** ; HttpOnly + Secure + SameSite; revocable; a distinct cookie per contour (`bo_session` / `manager_session` / `marketplace_session` ). Argon2id `memoryCost 65536, timeCost 3, parallelism 1` . TOTP mandatory, gated by a signed 10-minute setup token. Password change ≥16 chars and revokes every live session in the same transaction. Role weights `ORDER_MANAGER 0 < VIEWER 1 < CONTENT_MANAGER 2 < ADMIN 3 < OWNER 4` , checked together with marketplace scope.
**Acceptance: ** a CONTENT_MANAGER cannot read an unassigned marketplace through a direct API call.
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
- [x] **FH-2.4 — Origin allowlist for admin mutations ** · S · `backend/BACKEND-INTEGRATION.md`
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Written 2026-08-21: ** TRACK-S §2.2 — origin allowlist ahead of routing on every admin/platform/manager mutation, same list for CORS.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
Global hook: any non-GET on an admin/manager path whose `Origin` is not in the configured allowlist → `403` . CORS uses the same allowlist with `credentials: true` .
**Acceptance: ** a cross-origin POST with a valid session cookie is refused.
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
- [x] **FH-2.5 — Tenant by verified Host only ** · S · `backend/BACKEND-INTEGRATION.md`
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Written 2026-08-21: ** PHASE-9 §6 — normalization specified, `verifiedAt` required, cache with explicit invalidation, proxy header trust, no public endpoint accepts `marketplaceId` .
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
Normalize host (lowercase, strip trailing dot, strip port) → unique `hostname` row → require `verifiedAt` and `ACTIVE` . Short cache with explicit invalidation. Unknown host → `404` , never a fallback tenant. The public API never accepts a `marketplaceId` from the browser.
**Acceptance: ** an unknown Host returns 404 and leaks no other tenant's data.
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-2.6 — Signed preview token, read-only preview ** · S · `PHASE-9-…`
**Written 2026-08-21: ** PHASE-9 §5.3 — HMAC preview token, 15 min, HttpOnly cookie, every non-GET 404s while preview is active, `noindex` .
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
HMAC-signed token carrying `{marketplaceId, expiresAt, nonce}` , 15-minute TTL, `storefront_preview` cookie. Global hook returns `404 Preview mode is read-only` for any non-GET while that cookie is present. Preview is not indexable.
**Acceptance: ** a mutation attempted in preview mode is refused.
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-2.7 — Immutable revisions, rollback, clone ** · M · new section, `PHASE-9-…`
**Written 2026-08-21: ** PHASE-9 §5.1– 5.2 — `version = max+1` unique per marketplace, materialized snapshot, pointer flipped in-transaction, rollback as a new revision, clone carry/no-carry list, inventory to zero, topological category walk.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
`version = max(version) + 1` , immutable snapshot row, `publishedRevision` pointer flipped in the same transaction. Rollback creates a new revision; history is never rewritten. Clone copies design + catalog assignments, **forces inventory to 0 ** , never copies domains/customers/orders/secrets, and walks the category tree topologically with explicit cycle detection.
**Acceptance: ** rollback restores the chosen revision and leaves live inventory untouched.
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
- [x] **FH-2.8 — Append-only inventory journal ** · S · `backend/BACKEND-INTEGRATION.md`
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Written 2026-08-21: ** PHASE-3 §3.2 — `InventoryMovement` append-only with reason, reference, actor, and `resultingAvailable` written at the time.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
Every stock change writes `reason` , `referenceType` , `referenceId` , `actorId` , resulting balance. Direct answer to the v3.1 "we cannot explain your numbers" complaint.
**Acceptance: ** any current quantity is reconstructible from the journal alone.
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-2.9 — Per-tenant encrypted credentials ** · S · `PHASE-1` / `PHASE-7`
**Written 2026-08-21: ** TRACK-S §4.2 — `v1.iv.tag.ciphertext` AES-256-GCM envelope, per-value IV, decrypt only in-service, HMAC fingerprints for display, backend-built allowlisted redirect URLs.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
AES-256-GCM, versioned envelope `v1.iv.tag.ciphertext` (base64url), 32-byte key from the environment. Decrypted only inside the service; never serialized into any response. Redirect/callback URLs built backend-side and allowlisted.
**Acceptance: ** no credential appears in any API response, JS bundle, or browser storage.
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
- [x] **FH-2.10 — Server-side storefront config validation ** · M · `backend/BACKEND-INTEGRATION.md`
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Written 2026-08-21: ** PHASE-10 §3a — server re-runs the editor rules, clamp-and-fallback ergonomics, structural violations 400, limits published as one schema, referential checks as publish blockers.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
The server re-runs our editor's validation. Clamp-and-fallback ergonomics: clamp out-of-range numbers rather than rejecting; blank a URL that is not local `/path` or `https://` rather than erroring; fall back an invalid colour. Cap sections per page and IDs per list.
**Acceptance: ** a hand-crafted API call cannot store a config the editor would have refused.
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-2.11 — Digital goods ** · M · `PHASE-3-…`
**Written 2026-08-21: ** PHASE-3 §6a — `FulfillmentMode` , `DigitalCode` states, `valueHash` unique per (marketplace, offer), codes revealed only when paid.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
`FulfillmentMode: MANUAL | CODE_POOL` . `DigitalCode` pool with `AVAILABLE/RESERVED/ASSIGNED/REVOKED` , encrypted value, `valueHash` unique per `(marketplace, variant)` . Codes revealed only when the order is `PAID` /`PROCESSING` /`FULFILLED` .
**Acceptance: ** an unpaid order never returns a code.
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [~] **FH-2.12 — Marketplace status machine ** · **rejected 2026-08-21 — ours is better **
Theirs is `DRAFT → DOMAIN_PENDING → READY → ACTIVE → SUSPENDED` . PHASE-9 §2 already carries `draft → configured → content_ready → domains_planned → staging_live → qa_passed → production_ready → live → paused/archived` , plus a lifecycle endpoint that must name the specific blocker preventing the next transition. Adopting theirs would be a downgrade. Recorded so it does not get raised again.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
- [x] **FH-2.13 — Order public token, not sequential IDs ** · S · `backend/BACKEND-INTEGRATION.md`
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Written 2026-08-21: ** PHASE-2 §3.1 — `publicToken` ≥24 random bytes for every customer-facing route, tenant-scoped lookup, snapshot completeness, snapshots never updated in place.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
Orders are addressed publicly by a random `base64url` token. Order line items carry an immutable snapshot of name, SKU, price, currency, delivery, and contact data at purchase time.
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-2.14 — Order-manager as a separate contour ** · M · `TRACK-S-…`
**Written 2026-08-21: ** TRACK-S §8a — separate URL, shell, login and cookie; scope from membership rows not configuration; endpoints refuse rather than hide; PII masking and audited reveal.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
Separate URL, shell, cookie, and login; scoped to assigned marketplaces via **membership rows, not an environment variable ** (their env-pinned slug is the one part not to copy). No visibility into catalog, design, domains, payment settings, or platform users. PII masked in lists, revealed in detail only with permission, and both export and reveal are logged.
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-2.15 — Bulk import: idempotency and rollback ** · S · **done 2026-08-21 **
The validate-then-apply half already existed — PHASE-3 §6 has the preview of validation errors and a separate apply step, which is equivalent to their `dryRun` . What was missing and is now written: the import is **idempotent by SKU/external key ** so re-running a file updates rather than duplicates, a row-level error never publishes a partial result, and an applied import is rollback-able only while none of its products have appeared on a paid order.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
---
## Wave 3 — Proof (Lane A)
- [ ] **FH-3.1 — E2E: concurrent purchase of the last unit ** · M · `e2e/`
Their §22 scenario 3. Two sessions race for the final unit; exactly one payable order results, the other gets a clean out-of-stock state.
- [ ] **FH-3.2 — E2E: replayed webhook ** · M · `e2e/`
Their §22 scenario 10. The same provider event delivered twice does not complete the order twice or move stock twice.
ci: ratchet the bundle budget and scan builds for credentials (FH-3.3, FH-3.5)
Bundle budget was warning-only: initial warning 700 kB, error 1.8 MB.
Measured today the initial bundle is 1.55 MB raw / 324.58 kB transfer -
up from the 1.15 MB measured on 11 August, so it had been growing with
nothing to stop it.
Lowers maximumError to 1.6 MB. That is a ratchet, not a target: just
above today's size so the bundle cannot grow, with the 700 kB warning
left in place as the goal. Lower it each time the number comes down.
Adds scripts/ci/scan-bundle.sh (npm run scan:bundle), run in CI after
the build. Seven patterns: both provider auth headers, the partner ID
shape, client_secret, private key blocks, AWS keys, Telegram bot
tokens. The legacy payment code that put credentials in the browser is
already deleted; this is what stops it coming back. Verified in both
directions - clean against the real dist, exit 1 against a planted
credential.
Measurement also corrected two assumptions recorded in the harvest
TODO: admin and editor code is already lazy-loaded, so the initial
bundle is main alone rather than a deployable-split problem; and mock
gateway fixtures do reach production chunks, which is now filed as
FH-E.6 with the cause identified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:36:37 +04:00
- [x] **FH-3.3 — Bundle budget as a blocking CI check ** · S · **done 2026-08-21 **
perf(build): drop the JIT compiler from production, 1.55 MB -> 1.04 MB (FH-3.4)
Built with --stats-json and read the esbuild metafile instead of guessing.
@angular/compiler was 495,831 bytes of a 1.5 MB main chunk - the JIT
compiler, in an AOT production build, 33% of everything an anonymous
visitor downloads.
src/main.ts imported it deliberately, with a comment explaining that
@marketplaces/auth shipped plain tsc output carrying no Ivy metadata, so
Angular JIT-compiled its classes at runtime and bootstrap threw without
it.
That comment was stale. The package is 0.2.0, built with ng-packagr,
module: dist/fesm2022/marketplaces-auth.mjs - proper Angular Package
Format, partial-compiled (ɵɵngDeclareInjectable), linked at consumer
build time. Nothing needs JIT.
Verified against the production bundle served statically, not just a
green build: the failure mode this guarded was a runtime throw, so a
successful compile proves nothing. Angular 22.0.8 bootstrapped, the
router resolved /ru, and the app rendered its own "server unavailable"
screen - meaning DI, HttpClient and the full interceptor chain ran. That
chain injects AuthService from @marketplaces/auth, the exact class the
old comment named. Zero JIT or compiler errors; the only console output
was the expected 404s from having no backend behind a static server.
Initial bundle 1.55 MB -> 1.04 MB raw, 323.58 kB -> 215.45 kB transfer.
Budget ratchet lowered 1.6MB -> 1.1MB, which is now also what stops the
import being re-added.
Next lever, deliberately not taken here: i18n/ru.ts is 290 kB, eager,
while en/hy are already lazy. TranslateService has the loader plumbing
and languageGuard already awaits a preload, so it is mechanically small -
but it adds a round-trip before first paint for the majority language,
which is a product tradeoff, not a cleanup. Roughly a 750 kB bundle once
someone decides.
256 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 16:10:34 +04:00
`maximumError` on the initial bundle lowered `1.8MB → 1.6MB` in `angular.json` , and again to `1.1MB` once FH-3.4 landed. Measured today: **1.55 MB raw / 324.58 kB transfer ** — worse than the 1.15 MB they measured on 11 Aug, so this had been growing unwatched. The threshold is a **ratchet ** , not the target: set just above today's size so the bundle cannot grow, with the 700 kB warning left in place as the goal. Lower it every time the number comes down. CI now runs the production build (`npm run build` already defaults to production).
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
perf(build): drop the JIT compiler from production, 1.55 MB -> 1.04 MB (FH-3.4)
Built with --stats-json and read the esbuild metafile instead of guessing.
@angular/compiler was 495,831 bytes of a 1.5 MB main chunk - the JIT
compiler, in an AOT production build, 33% of everything an anonymous
visitor downloads.
src/main.ts imported it deliberately, with a comment explaining that
@marketplaces/auth shipped plain tsc output carrying no Ivy metadata, so
Angular JIT-compiled its classes at runtime and bootstrap threw without
it.
That comment was stale. The package is 0.2.0, built with ng-packagr,
module: dist/fesm2022/marketplaces-auth.mjs - proper Angular Package
Format, partial-compiled (ɵɵngDeclareInjectable), linked at consumer
build time. Nothing needs JIT.
Verified against the production bundle served statically, not just a
green build: the failure mode this guarded was a runtime throw, so a
successful compile proves nothing. Angular 22.0.8 bootstrapped, the
router resolved /ru, and the app rendered its own "server unavailable"
screen - meaning DI, HttpClient and the full interceptor chain ran. That
chain injects AuthService from @marketplaces/auth, the exact class the
old comment named. Zero JIT or compiler errors; the only console output
was the expected 404s from having no backend behind a static server.
Initial bundle 1.55 MB -> 1.04 MB raw, 323.58 kB -> 215.45 kB transfer.
Budget ratchet lowered 1.6MB -> 1.1MB, which is now also what stops the
import being re-added.
Next lever, deliberately not taken here: i18n/ru.ts is 290 kB, eager,
while en/hy are already lazy. TranslateService has the loader plumbing
and languageGuard already awaits a preload, so it is mechanically small -
but it adds a round-trip before first paint for the majority language,
which is a product tradeoff, not a cleanup. Roughly a 750 kB bundle once
someone decides.
256 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 16:10:34 +04:00
- [~] **FH-3.4 — Get the initial bundle down ** · L · **1.55 MB → 1.04 MB on 2026-08-21; not yet at target **
**Premise corrected after measuring. ** Admin, editor, catalog, cart and the `en` /`hy` locales are * already * lazy chunks — nothing admin-shaped ships to an anonymous visitor. The whole initial bundle is `main` alone, so this was never a "split the deployables" job.
Built with `--stats-json` and read the esbuild metafile rather than guessing. Composition of the 1.5 MB `main` :
| bytes | what |
|---:|---|
| 495,831 | `@angular/compiler` |
| 290,589 | `src/app/i18n/ru.ts` |
| 162,915 | `@angular/core` |
| 82,209 | `@angular/router` |
| 53,354 | `@angular/common` |
* * `@angular/compiler` — 33% of the bundle — was the JIT compiler, in an AOT production build.** `src/main.ts` imported it explicitly, with a comment explaining that `@marketplaces/auth` shipped plain `tsc` output carrying no Ivy metadata, so Angular JIT-compiled its classes at runtime and bootstrap threw without it.
That comment was **stale ** . The package is `0.2.0` , built with ng-packagr, `module: dist/fesm2022/marketplaces-auth.mjs` — proper Angular Package Format, partial-compiled (`ɵɵngDeclareInjectable` ), linked at consumer build time. No JIT needed.
Removed the import. Verified against the **production ** bundle served statically, not just a successful build — the failure mode it guarded was a runtime throw, so a green build proves nothing. Angular 22.0.8 bootstrapped, the router resolved `/ru` , and the app rendered its own "server unavailable" screen, which means DI, HttpClient and the whole interceptor chain ran. That chain injects `AuthService` from `@marketplaces/auth` — the exact class named in the old comment. Zero JIT/compiler errors; the only console output was the expected 404s from having no backend.
**Result: 1.55 MB → 1.04 MB raw, 323.58 kB → 215.45 kB transfer. ** Ratchet lowered `1.6MB → 1.1MB` , which is now also the guard against the import being re-added.
**Remaining path to the 700 kB target. ** The next lever is `i18n/ru.ts` at 290 kB — the default locale, eager, while `en` /`hy` are lazy. `TranslateService` already has the loader plumbing and `languageGuard` already awaits a preload before route activation, so making `ru` lazy is mechanically small. It is deliberately **not ** done here: it adds a round-trip before first paint for the majority language, which is a product tradeoff rather than a cleanup. Needs a decision, then it is roughly a 750 kB bundle.
Also outstanding: `qrcode` (23.7 kB), pulled in by `@marketplaces/auth` , is not ESM and causes an optimizer bailout — a fix for the package repo.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
ci: ratchet the bundle budget and scan builds for credentials (FH-3.3, FH-3.5)
Bundle budget was warning-only: initial warning 700 kB, error 1.8 MB.
Measured today the initial bundle is 1.55 MB raw / 324.58 kB transfer -
up from the 1.15 MB measured on 11 August, so it had been growing with
nothing to stop it.
Lowers maximumError to 1.6 MB. That is a ratchet, not a target: just
above today's size so the bundle cannot grow, with the 700 kB warning
left in place as the goal. Lower it each time the number comes down.
Adds scripts/ci/scan-bundle.sh (npm run scan:bundle), run in CI after
the build. Seven patterns: both provider auth headers, the partner ID
shape, client_secret, private key blocks, AWS keys, Telegram bot
tokens. The legacy payment code that put credentials in the browser is
already deleted; this is what stops it coming back. Verified in both
directions - clean against the real dist, exit 1 against a planted
credential.
Measurement also corrected two assumptions recorded in the harvest
TODO: admin and editor code is already lazy-loaded, so the initial
bundle is main alone rather than a deployable-split problem; and mock
gateway fixtures do reach production chunks, which is now filed as
FH-E.6 with the cause identified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:36:37 +04:00
- [x] **FH-3.5 — Bundle secret scan in CI ** · S · **done 2026-08-21 **
`scripts/ci/scan-bundle.sh` , wired as `npm run scan:bundle` and a CI step after Build. Seven patterns: both provider auth headers, the partner ID shape, `client_secret` , private key blocks, AWS keys, Telegram bot tokens. Verified in both directions — clean against the real `dist/` , and fails with exit 1 against a planted credential.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
---
## Wave 4 — Identity: VK ID + Yandex ID (Lane C, `@marketplaces/auth`)
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
Nothing to copy from the archive — it has zero VK/Yandex/OAuth code. We take the session-issuing shape of their Telegram flow and terminate both providers into it.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
The **client half and the contract are done ** (2026-08-21). What remains is backend implementation, and registering the OAuth applications — which is what FH-0.1 gates.
- [x] **FH-4.1 — Provider-agnostic social identity surface ** · M · **done 2026-08-21 **
Landed as `social-identity-gateway.interface.ts` / `-api.gateway.ts` / `-local.gateway.ts` / `-gateway.token.ts` under `src/app/core/identity/services/` , with the four `vk-id-*` files deleted and `vk-id-login` replaced by `social-login-button` taking a `provider` input. `'yandex_id'` added to `ExternalIdentityProvider` . Covered by `social-identity-gateway.spec.ts` (5 tests), which asserts the request carries no `code_verifier` or `client_secret` — so re-adding a browser-held verifier fails the build rather than passing review.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
Collapse `VkIdGateway` into `SocialIdentityGateway` :
```ts
export type SocialProvider = 'vk' | 'yandex';
export interface SocialIdentityGateway {
getAuthorizeUrl(provider: SocialProvider, returnTo?: string): Observable<string>;
listIdentities(): Observable<ExternalIdentity[]>;
unlink(provider: SocialProvider): Observable<void>;
}
```
Touches: `src/app/core/identity/services/vk-id-gateway.interface.ts` , `vk-id-api.gateway.ts` , `vk-id-local.gateway.ts` , `vk-id-gateway.token.ts` , `src/app/components/vk-id-login/` → `social-login-button` . Add `'yandex_id'` to `ExternalIdentityProvider` in `core/identity/models/customer-identity.model.ts` .
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
- [x] **FH-4.2 — Move PKCE ownership to the backend ** · S · **done 2026-08-21 **
`completeCallback()` is gone from the frontend entirely. PHASE-8 §2.1– 2.2 rewritten: `/authorize` mints and stores `{state, codeVerifier, marketplaceId, returnTo, expiresAt}` single-use for 10 minutes, `/callback` is a backend GET that exchanges, links, issues the session cookie and redirects. `returnTo` validated against the tenant's own origin.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
Today `completeCallback(code, codeVerifier)` forces the browser to generate and hold the verifier. We are a confidential client. Backend generates `state` + `code_verifier` , stores them single-use for 10 minutes, handles the callback, and redirects. `completeCallback()` leaves the frontend entirely.
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
Contract endpoints: `GET /api/identity/v1/{provider}/authorize` , `GET /api/identity/v1/{provider}/callback` , `POST /{provider}/unlink` , `GET /me/identities` . Update `backend/BACKEND-INTEGRATION.md` §2.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
- [~] **FH-4.3 — `ExternalIdentity` model ** · S · Lane B · **contract written 2026-08-21, awaiting backend **
PHASE-8 §1 and §2.3: `UNIQUE (provider, providerUserId)` , conflict routes to controlled resolution rather than rebinding, optional email/phone/displayName, per-tenant OAuth app config under the Track S §4.2 envelope.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
```prisma
@@unique ([provider, providerUserId]) // one provider account -> one customer
```
Conflict is **not ** an upsert: a `providerUserId` already bound to a different `Customer` routes to controlled resolution. The unique index makes the database refuse a silent rebind. Per-tenant OAuth app config stored encrypted (same envelope as FH-2.9): `{ clientId, clientSecret, scopes[], redirectUri }` .
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
- [~] **FH-4.4 — VK ID ** · M · **contract written 2026-08-21, awaiting backend **
PHASE-8 §2.5 carries the full endpoint set and the `device_id` trap.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
OAuth 2.1, PKCE mandatory (S256). Authorize `https://id.vk.com/authorize` ; token `POST https://id.vk.com/oauth2/auth` ; profile `POST https://id.vk.com/oauth2/user_info` ; logout `https://id.vk.com/oauth2/logout` on unlink.
**Trap to write into the contract: ** the callback returns `device_id` alongside `code` , and the token exchange fails without it. This is the most common VK ID integration bug.
VK often does not return an email — email must stay optional on `Customer` .
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
- [~] **FH-4.5 — Yandex ID ** · S · **contract written 2026-08-21, awaiting backend **
PHASE-8 §2.5. On the client it is a `provider` input, not new code.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
OAuth 2.0 with PKCE. Authorize `https://oauth.yandex.ru/authorize` ; token `POST https://oauth.yandex.ru/token` with HTTP Basic `client_id:client_secret` ; profile `GET https://login.yandex.ru/info?format=json` with header `Authorization: OAuth <token>` → `id` , `login` , `default_email` , `default_phone` , `psuid` .
A second strategy object against the same surface — roughly a day once VK works.
* Confirm exact parameter and scope names against live provider docs; both providers revised their flows recently. *
feat(identity): account-linking UI + Telegram-as-identity surface (FH-4.7, FH-4.6, FH-4.8)
FH-4.7 - AccountIdentitiesComponent under
features/website/account/identities/. Lists linked identities from
GET /me/identities, offers attach buttons only for OAuth providers not
already linked (reusing SocialLoginButtonComponent), detaches through
unlink(). Refuses to detach the last remaining identity - it is the only
way back in - with the control disabled and an explanatory title, matching
the backend's last-identity 409. Loading / error / ready states; a load
failure surfaces an error rather than rendering an empty account, and a
slot carries the identity-conflict message from PHASE-8 §2.3. 6 unit tests.
Not wired into a route: the storefront has no customer account area yet
and no live OAuth application to authorize against (FH-0.1). This is the
surface both depend on, buildable and tested now.
FH-4.6 (client + contract) - the gateway now separates the two provider
sets. SocialProvider (vk | yandex) is what has an OAuth authorize
redirect; ExternalIdentityProvider (adds telegram | max) is what can be
listed and unlinked. unlink() widened to the latter so Telegram detaches
through the same path as VK, with no second code path. The dev local
gateway seeds a Telegram identity so the linking screen is exercisable
before any real provider exists.
PHASE-8 §2.6 specifies the backend migration: a Telegram login writes an
ExternalIdentity row under the same uniqueness and identity-conflict rule
as VK, appears in /me/identities, is removable subject to the
last-identity 409, and keeps customer (marketplace_session) and admin
(bo_session) sessions as distinct cookies - closing the shared
customer/admin Telegram session the audit flagged. The identity row and
the messaging BotConversationBinding stay separate records.
FH-4.8 - PHASE-8 §3 now states email/phone OTP's position explicitly:
recovery when a linked messenger is unreachable and an addable second
factor, never the primary login, and one more identity on the same
customer rather than a parallel account.
262 tests pass. Build green, boundaries and cycles green, bundle scan clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 22:36:36 +04:00
- [~] **FH-4.6 — Migrate Telegram onto `ExternalIdentity` ** · M · **client + contract done 2026-08-21; backend write path pending **
Client: the gateway now separates the two provider sets — `SocialProvider` (`vk` /`yandex` , has an OAuth authorize) vs `ExternalIdentityProvider` (adds `telegram` /`max` , listable and unlinkable). `unlink()` takes the wider type, so the linking UI detaches Telegram through the same path as VK. The dev local gateway seeds a Telegram identity so the surface is exercisable now.
Contract: PHASE-8 §2.6 — Telegram login writes an `ExternalIdentity` row under the same uniqueness/conflict rule as VK, appears in `/me/identities` , is removable subject to the last-identity `409` , and — the audit finding — keeps customer (`marketplace_session` ) and admin (`bo_session` ) sessions as separate cookies so a Telegram customer never satisfies an admin guard. Identity row vs messaging `BotConversationBinding` kept distinct.
Backend still owns: the actual write-on-login and the session split enforcement. Telegram login itself lives in `@marketplaces/auth` .
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
feat(identity): account-linking UI + Telegram-as-identity surface (FH-4.7, FH-4.6, FH-4.8)
FH-4.7 - AccountIdentitiesComponent under
features/website/account/identities/. Lists linked identities from
GET /me/identities, offers attach buttons only for OAuth providers not
already linked (reusing SocialLoginButtonComponent), detaches through
unlink(). Refuses to detach the last remaining identity - it is the only
way back in - with the control disabled and an explanatory title, matching
the backend's last-identity 409. Loading / error / ready states; a load
failure surfaces an error rather than rendering an empty account, and a
slot carries the identity-conflict message from PHASE-8 §2.3. 6 unit tests.
Not wired into a route: the storefront has no customer account area yet
and no live OAuth application to authorize against (FH-0.1). This is the
surface both depend on, buildable and tested now.
FH-4.6 (client + contract) - the gateway now separates the two provider
sets. SocialProvider (vk | yandex) is what has an OAuth authorize
redirect; ExternalIdentityProvider (adds telegram | max) is what can be
listed and unlinked. unlink() widened to the latter so Telegram detaches
through the same path as VK, with no second code path. The dev local
gateway seeds a Telegram identity so the linking screen is exercisable
before any real provider exists.
PHASE-8 §2.6 specifies the backend migration: a Telegram login writes an
ExternalIdentity row under the same uniqueness and identity-conflict rule
as VK, appears in /me/identities, is removable subject to the
last-identity 409, and keeps customer (marketplace_session) and admin
(bo_session) sessions as distinct cookies - closing the shared
customer/admin Telegram session the audit flagged. The identity row and
the messaging BotConversationBinding stay separate records.
FH-4.8 - PHASE-8 §3 now states email/phone OTP's position explicitly:
recovery when a linked messenger is unreachable and an addable second
factor, never the primary login, and one more identity on the same
customer rather than a parallel account.
262 tests pass. Build green, boundaries and cycles green, bundle scan clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 22:36:36 +04:00
- [x] **FH-4.7 — Account linking UI ** · M · **done 2026-08-21 **
`AccountIdentitiesComponent` (`src/app/features/website/account/identities/` ): lists linked identities from `GET /me/identities` , offers attach buttons only for OAuth providers not yet linked (reusing `SocialLoginButtonComponent` ), detaches through `unlink()` , disables the detach control on the last remaining identity with an explanatory title, and has a slot for the §2.3 conflict message. Loading / error / ready states, error surfaced rather than shown as an empty account. 6 unit tests. Not yet wired into a route — the storefront has no customer account area and no live OAuth app (FH-0.1) — but fully built and tested behind that.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
feat(identity): account-linking UI + Telegram-as-identity surface (FH-4.7, FH-4.6, FH-4.8)
FH-4.7 - AccountIdentitiesComponent under
features/website/account/identities/. Lists linked identities from
GET /me/identities, offers attach buttons only for OAuth providers not
already linked (reusing SocialLoginButtonComponent), detaches through
unlink(). Refuses to detach the last remaining identity - it is the only
way back in - with the control disabled and an explanatory title, matching
the backend's last-identity 409. Loading / error / ready states; a load
failure surfaces an error rather than rendering an empty account, and a
slot carries the identity-conflict message from PHASE-8 §2.3. 6 unit tests.
Not wired into a route: the storefront has no customer account area yet
and no live OAuth application to authorize against (FH-0.1). This is the
surface both depend on, buildable and tested now.
FH-4.6 (client + contract) - the gateway now separates the two provider
sets. SocialProvider (vk | yandex) is what has an OAuth authorize
redirect; ExternalIdentityProvider (adds telegram | max) is what can be
listed and unlinked. unlink() widened to the latter so Telegram detaches
through the same path as VK, with no second code path. The dev local
gateway seeds a Telegram identity so the linking screen is exercisable
before any real provider exists.
PHASE-8 §2.6 specifies the backend migration: a Telegram login writes an
ExternalIdentity row under the same uniqueness and identity-conflict rule
as VK, appears in /me/identities, is removable subject to the
last-identity 409, and keeps customer (marketplace_session) and admin
(bo_session) sessions as distinct cookies - closing the shared
customer/admin Telegram session the audit flagged. The identity row and
the messaging BotConversationBinding stay separate records.
FH-4.8 - PHASE-8 §3 now states email/phone OTP's position explicitly:
recovery when a linked messenger is unreachable and an addable second
factor, never the primary login, and one more identity on the same
customer rather than a parallel account.
262 tests pass. Build green, boundaries and cycles green, bundle scan clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 22:36:36 +04:00
- [x] **FH-4.8 — Email/phone OTP repositioned as recovery ** · S · **done 2026-08-21 **
PHASE-8 §3 now states it explicitly: OTP is a way back in when a linked messenger is unreachable and a second factor a customer may add, never the front-and-centre first login option, and one more `ExternalIdentity` /`ContactMethod` on the same customer rather than a parallel account. The [email/phone spec ](superpowers/specs/2026-08-15-email-phone-login-design.md ) stays valid; only its priority relative to VK ID moves.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
---
## Continuous — Ops (Lane D)
- [ ] **FH-D.1 — Proven restore drill ** · M
We have deploy automation and no proven restore. Add a restore-check script and schedule it. Their `restore-check.sh` + WAL archiving (`wal_level=replica` , `archive_mode=on` , `archive_timeout=300` ) is the model.
**Done when: ** a restore into a clean environment has been executed and its result recorded.
- [ ] **FH-D.2 — Database unreachable from the internet, structurally ** · S · Lane B/D
Data network `internal: true` ; API bound to loopback only; `no-new-privileges` on every service. Makes it a property of the topology rather than a firewall promise.
feat(deploy): host hardening on the frontend server (FH-D.3)
server-setup.sh configured ufw and stopped there, which leaves SSH open
to unlimited password guessing and the kernel on defaults that are wrong
for an internet-facing host.
Adds three drop-in files, so a re-run replaces its own config and never
edits a distro file in place:
/etc/ssh/sshd_config.d/10-marketplaces-hardening.conf
password and keyboard-interactive auth off, root key-only,
no agent/X11 forwarding, MaxAuthTries 3, 30s login grace
/etc/fail2ban/jail.d/marketplaces.local
sshd, nginx-http-auth, nginx-bad-request; 5 in 10m, 1h ban
/etc/sysctl.d/99-marketplaces-hardening.conf
no redirects or source routing, rp_filter, SYN cookies,
forwarding off, restricted kernel pointers and dmesg
Both accounts on the host are key-only by construction - the deploy user
is created with no password at all - so disabling password auth cannot
lock anyone out. It only closes guessing against a credential nobody
intended to exist.
The sshd block runs `sshd -t` first and removes its own drop-in if the
test fails. A bad sshd config that takes effect on a remote box is how
people lock themselves out permanently.
DEPLOYMENT.md §3.2 documents all three plus the post-provision checks.
Not copied from the reference implementation: its hardcoded server IP.
Kept as-is because ours is already better: add-domain.sh pre-checks the
DNS A record and runs nginx -t before and after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:17:02 +04:00
- [x] **FH-D.3 — Host hardening we lack ** · M · **done 2026-08-21 **
Three drop-in files in `scripts/deploy/server-setup.sh` , documented in [DEPLOYMENT.md ](DEPLOYMENT.md ) §3.2: sshd hardening (password and keyboard-interactive auth off, root key-only, `MaxAuthTries 3` , 30 s grace, no forwarding), fail2ban (`sshd` , `nginx-http-auth` , `nginx-bad-request` ; 5 failures in 10 min, 1 h ban), and sysctl (no redirects or source routing, rp_filter, SYN cookies, forwarding off, restricted kernel pointers and dmesg). The script runs `sshd -t` before reloading and removes its own drop-in if the test fails — a bad sshd config taking effect remotely is how people lock themselves out permanently.
Scoped sudoers was already in place. * Kept ours where ours is better: * `add-domain.sh` pre-checks the DNS A record and runs `nginx -t` before and after; ufw was already configured. Their hardcoded server IP deliberately not copied.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
---
## Continuous — Process (Lane E)
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-E.1 — Adopt the nine invariants as an acceptance gate ** · S · **done 2026-08-21 **
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
Now `backend/BACKEND-INTEGRATION.md` §0, ahead of everything else, each one cross-referenced to the contract section that specifies it. Framed as a release gate: violate one and it does not ship, regardless of what else is finished.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-E.2 — PR policy ** · S · **done 2026-08-21 **
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
`backend/BACKEND-INTEGRATION.md` §0a, with the expand/contract migration rule alongside it.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-E.3 — Release discipline ** · S · **done 2026-08-21 **
docs(backend): consolidate all backend contracts into one file
Collapses the entire docs/backend/ set - Phase 1-10, Track A/S, the
partner API, the two handoffs, the frontend surface inventory, and the
harvest requirements - into a single source of truth,
docs/backend/BACKEND-INTEGRATION.md.
Every contract's entities, endpoints, and invariants are preserved,
reorganised by domain rather than by sprint. The nine release
invariants, the FH-* harvest mechanisms, the RBAC/audit/secrets
cross-cutting rules, the tenant-routing infra contract, the 15
acceptance tests, build order, dev setup, and open decisions are all in
the one file, with a change log (§14) at the bottom.
The file opens with the maintenance rule: any new backend need, contract
change, or shipped item updates this file in the same change - the
affected section and the change log. No new backend .md files.
Inbound links from BACKEND-API-REFERENCE, the ADRs, the fork docs,
DEPLOYMENT, PACKAGES-USAGE, the delivery plan, and e2e/README are
repointed at the single doc (section anchors collapse to the file; the
prose section refs remain as context). Also recorded the rule in the
repo CLAUDE.md.
17 backend docs removed, 1 added. No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 16:15:58 +04:00
`backend/BACKEND-INTEGRATION.md` §0a. A release records version, migrations, healthcheck, smoke, dependency audit, and the rollback path actually available.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
- [x] **FH-E.4 — ADR for the harvest ** · S · **done 2026-08-21 **
[ADR-0006 ](context/adrs/ADR-0006-harvest-mechanisms-from-the-parallel-platform.md ). Records what we take, what we reject, what we keep because ours is better, and the one organizational question it deliberately does not settle.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
perf(build): drop the JIT compiler from production, 1.55 MB -> 1.04 MB (FH-3.4)
Built with --stats-json and read the esbuild metafile instead of guessing.
@angular/compiler was 495,831 bytes of a 1.5 MB main chunk - the JIT
compiler, in an AOT production build, 33% of everything an anonymous
visitor downloads.
src/main.ts imported it deliberately, with a comment explaining that
@marketplaces/auth shipped plain tsc output carrying no Ivy metadata, so
Angular JIT-compiled its classes at runtime and bootstrap threw without
it.
That comment was stale. The package is 0.2.0, built with ng-packagr,
module: dist/fesm2022/marketplaces-auth.mjs - proper Angular Package
Format, partial-compiled (ɵɵngDeclareInjectable), linked at consumer
build time. Nothing needs JIT.
Verified against the production bundle served statically, not just a
green build: the failure mode this guarded was a runtime throw, so a
successful compile proves nothing. Angular 22.0.8 bootstrapped, the
router resolved /ru, and the app rendered its own "server unavailable"
screen - meaning DI, HttpClient and the full interceptor chain ran. That
chain injects AuthService from @marketplaces/auth, the exact class the
old comment named. Zero JIT or compiler errors; the only console output
was the expected 404s from having no backend behind a static server.
Initial bundle 1.55 MB -> 1.04 MB raw, 323.58 kB -> 215.45 kB transfer.
Budget ratchet lowered 1.6MB -> 1.1MB, which is now also what stops the
import being re-added.
Next lever, deliberately not taken here: i18n/ru.ts is 290 kB, eager,
while en/hy are already lazy. TranslateService has the loader plumbing
and languageGuard already awaits a preload, so it is mechanically small -
but it adds a round-trip before first paint for the majority language,
which is a product tradeoff, not a cleanup. Roughly a 750 kB bundle once
someone decides.
256 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 16:10:34 +04:00
- [x] **FH-E.6 — Keep mock gateways out of production builds ** · M · **done 2026-08-21 **
21 token factories now `inject(XApiGateway)` unconditionally; mock overrides moved to `src/app/mock-gateway.providers.ts` , swapped for a production copy that imports nothing via `fileReplacements` . Zero `*LocalGateway` classes and zero fixtures in the production bundle, down from 21 classes and 75 kB of source. `scan-bundle.sh` gained two patterns so it cannot return, verified in both directions. Dev behaviour unchanged — flip `useMockData` in `environment.ts` as before.
Worth noting for whoever picks up FH-E.5: `useMockData` is `false` in **both ** environment files, so none of these mocks were ever the selected implementation. They were pure weight.
**Not fixed here: ** `MediaRepository` is still bound to `MockMediaRepository` unconditionally in `app.config.ts` . That one cannot simply be deleted — no real implementation exists — so it is a missing API gateway, not dead weight.
ci: ratchet the bundle budget and scan builds for credentials (FH-3.3, FH-3.5)
Bundle budget was warning-only: initial warning 700 kB, error 1.8 MB.
Measured today the initial bundle is 1.55 MB raw / 324.58 kB transfer -
up from the 1.15 MB measured on 11 August, so it had been growing with
nothing to stop it.
Lowers maximumError to 1.6 MB. That is a ratchet, not a target: just
above today's size so the bundle cannot grow, with the 700 kB warning
left in place as the goal. Lower it each time the number comes down.
Adds scripts/ci/scan-bundle.sh (npm run scan:bundle), run in CI after
the build. Seven patterns: both provider auth headers, the partner ID
shape, client_secret, private key blocks, AWS keys, Telegram bot
tokens. The legacy payment code that put credentials in the browser is
already deleted; this is what stops it coming back. Verified in both
directions - clean against the real dist, exit 1 against a planted
credential.
Measurement also corrected two assumptions recorded in the harvest
TODO: admin and editor code is already lazy-loaded, so the initial
bundle is main alone rather than a deployable-split problem; and mock
gateway fixtures do reach production chunks, which is now filed as
FH-E.6 with the cause identified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:36:37 +04:00
Measured 2026-08-21: mock seed data reaches the production bundle. `ptr_local` , a fixture literal from `partner-hierarchy-local.gateway.ts` , is present in a built lazy chunk. Cause: 21 DI tokens use `factory: () => (environment.useMockData ? inject(XLocalGateway) : inject(XApiGateway))` , and referencing both branches keeps both classes reachable, so the optimizer cannot drop the mock. 75 kB of local-gateway source, plus its fixtures, ships to users.
This is the concrete form of their strongest objection — "mock repositories as production implementation" — and it is mechanical to fix. The pattern to copy is already in this repo: `mock-data.interceptor.production.ts` swapped in via `fileReplacements` .
**Done when: ** `scan-bundle.sh` can gate on mock fixture markers and pass.
docs(editor): mark editor publish as backend-blocked local-only (FH-E.5)
Audited all 21 localStorage users against the "localStorage as source of
truth" objection. It was already false almost everywhere:
- Every admin facade (products, categories, orders, moderation,
dashboard) uses localStorage only for view preferences - viewMode,
density, visibleColumns, expandedIds, sort. Entity CRUD goes through
the API gateways.
- currency-rates.service already removed its localStorage-typed rates.
- language, location region, search history, the anonymous session id,
admin preferences - all legitimate cache/preference.
- The editor already shows an "unsaved local draft restored" banner
(draftRestored -> save bar), which is the recovery-cache indicator this
item called for.
One real gap remains and it is backend-blocked: project-editor
publish() applies config to the in-memory runtime and saves the draft to
localStorage, then declares itself published - no server round-trip,
because the PHASE-9 §5 revision API does not exist yet. Marked precisely
in publish() with the required behaviour (await the server, only then
mark published) and cross-referenced to the contract.
Nothing to rip out: the codebase was already at the target state
everywhere the backend exists to support it.
262 tests pass. Boundaries green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 22:41:14 +04:00
- [x] **FH-E.5 — Reduce `localStorage` to cache, never truth ** · M · **audited + gap marked 2026-08-21 **
Audited all 21 `localStorage` users. The premise — "localStorage is your source of truth" — turned out **already false ** across the app:
- Every admin facade (products, categories, orders, moderation, dashboard) uses `localStorage` for **view preferences only ** — `viewMode` , `density` , `visibleColumns` , `expandedIds` , `sort` . Entity CRUD goes through the API gateways. That is cache, not truth.
- `currency-rates.service.ts` already removed its localStorage-typed rates (its own comment records it).
- `language` , `location` region, `search-history` , `api-headers` anonymous session id, `admin-preferences` — all legitimate preference/cache.
- The editor already surfaces an * * "unsaved local draft restored"** banner (`draftRestored` → save bar), which is the recovery-cache indicator this item asked for.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
docs(editor): mark editor publish as backend-blocked local-only (FH-E.5)
Audited all 21 localStorage users against the "localStorage as source of
truth" objection. It was already false almost everywhere:
- Every admin facade (products, categories, orders, moderation,
dashboard) uses localStorage only for view preferences - viewMode,
density, visibleColumns, expandedIds, sort. Entity CRUD goes through
the API gateways.
- currency-rates.service already removed its localStorage-typed rates.
- language, location region, search history, the anonymous session id,
admin preferences - all legitimate cache/preference.
- The editor already shows an "unsaved local draft restored" banner
(draftRestored -> save bar), which is the recovery-cache indicator this
item called for.
One real gap remains and it is backend-blocked: project-editor
publish() applies config to the in-memory runtime and saves the draft to
localStorage, then declares itself published - no server round-trip,
because the PHASE-9 §5 revision API does not exist yet. Marked precisely
in publish() with the required behaviour (await the server, only then
mark published) and cross-referenced to the contract.
Nothing to rip out: the codebase was already at the target state
everywhere the backend exists to support it.
262 tests pass. Boundaries green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 22:41:14 +04:00
**One real gap, and it is backend-blocked: ** the project editor's `publish()` applies config to the in-memory runtime and saves the draft to localStorage, then declares itself published — no server round-trip, because the PHASE-9 §5 revision API does not exist yet. Marked precisely in `publish()` with the required behaviour (await the server, only then mark published) and cross-referenced to the contract. Cannot be finished on the frontend alone; the contract for the fix is already written.
Net: nothing to rip out — the codebase was already at the target state everywhere the backend exists to support it.
docs: fork analysis + improvement harvest spec and todo
Deep analysis of marketplaces-main.zip (hub.numus.cc/numus/marketplaces).
Findings:
- Not a fork of us. Separate platform monorepo (NestJS + Postgres +
2 Angular apps + infra) sharing an older dexarmarket ancestor.
- Our repo is vendored inside it as reference/parallel-frontend/,
SHA-256 pinned, dated 11 Aug 2026, classified "reference, not production".
- Zero VK/Yandex/OAuth code anywhere in their source. Their only
customer login is Telegram, proxied to an external service.
- They lead on backend truth and ops; we lead on frontend depth,
tests, e2e, and framework currency.
Three of their audit findings are still live in our code and are
defects, not just posture: plaintext ip-api.com call from an HTTPS
origin (mixed content, region detect silently dead), unvalidated
bypassSecurityTrustResourceUrl on a bank URL rendered in an iframe,
and provider credentials plus a partner ID literal in the bundle.
Adds:
- docs/FORK-ANALYSIS-2026-08-21.md - full comparison, their audit of
us assessed line by line, and a VK ID + Yandex ID design.
- docs/superpowers/specs/2026-08-21-fork-harvest-design.md - working
brief, five lanes, four waves, scope and rejection rules.
- docs/FORK-HARVEST-TODO.md - 42 items with effort, dependencies and
acceptance criteria. Improvements only; nothing regresses our
Angular version, test count, or architecture governance.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:31:54 +04:00
---
## Scoreboard
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
| Wave | Done | Contract written, awaiting backend | Open | Blocked by |
|---|---:|---:|---:|---|
| 0 — Decide | 0 | — | 2 | needs a person, not a session |
| 1 — Live defects | 2 | — | 2 | FH-1.2 / FH-1.4 sit in files another session owns |
| 2 — Contracts | 14 | — | 0 | 1 rejected (FH-2.12) |
perf(build): drop the JIT compiler from production, 1.55 MB -> 1.04 MB (FH-3.4)
Built with --stats-json and read the esbuild metafile instead of guessing.
@angular/compiler was 495,831 bytes of a 1.5 MB main chunk - the JIT
compiler, in an AOT production build, 33% of everything an anonymous
visitor downloads.
src/main.ts imported it deliberately, with a comment explaining that
@marketplaces/auth shipped plain tsc output carrying no Ivy metadata, so
Angular JIT-compiled its classes at runtime and bootstrap threw without
it.
That comment was stale. The package is 0.2.0, built with ng-packagr,
module: dist/fesm2022/marketplaces-auth.mjs - proper Angular Package
Format, partial-compiled (ɵɵngDeclareInjectable), linked at consumer
build time. Nothing needs JIT.
Verified against the production bundle served statically, not just a
green build: the failure mode this guarded was a runtime throw, so a
successful compile proves nothing. Angular 22.0.8 bootstrapped, the
router resolved /ru, and the app rendered its own "server unavailable"
screen - meaning DI, HttpClient and the full interceptor chain ran. That
chain injects AuthService from @marketplaces/auth, the exact class the
old comment named. Zero JIT or compiler errors; the only console output
was the expected 404s from having no backend behind a static server.
Initial bundle 1.55 MB -> 1.04 MB raw, 323.58 kB -> 215.45 kB transfer.
Budget ratchet lowered 1.6MB -> 1.1MB, which is now also what stops the
import being re-added.
Next lever, deliberately not taken here: i18n/ru.ts is 290 kB, eager,
while en/hy are already lazy. TranslateService has the loader plumbing
and languageGuard already awaits a preload, so it is mechanically small -
but it adds a round-trip before first paint for the majority language,
which is a product tradeoff, not a cleanup. Roughly a 750 kB bundle once
someone decides.
256 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 16:10:34 +04:00
| 3 — Proof | 3 | 1 | 1 | see note below |
feat(identity): account-linking UI + Telegram-as-identity surface (FH-4.7, FH-4.6, FH-4.8)
FH-4.7 - AccountIdentitiesComponent under
features/website/account/identities/. Lists linked identities from
GET /me/identities, offers attach buttons only for OAuth providers not
already linked (reusing SocialLoginButtonComponent), detaches through
unlink(). Refuses to detach the last remaining identity - it is the only
way back in - with the control disabled and an explanatory title, matching
the backend's last-identity 409. Loading / error / ready states; a load
failure surfaces an error rather than rendering an empty account, and a
slot carries the identity-conflict message from PHASE-8 §2.3. 6 unit tests.
Not wired into a route: the storefront has no customer account area yet
and no live OAuth application to authorize against (FH-0.1). This is the
surface both depend on, buildable and tested now.
FH-4.6 (client + contract) - the gateway now separates the two provider
sets. SocialProvider (vk | yandex) is what has an OAuth authorize
redirect; ExternalIdentityProvider (adds telegram | max) is what can be
listed and unlinked. unlink() widened to the latter so Telegram detaches
through the same path as VK, with no second code path. The dev local
gateway seeds a Telegram identity so the linking screen is exercisable
before any real provider exists.
PHASE-8 §2.6 specifies the backend migration: a Telegram login writes an
ExternalIdentity row under the same uniqueness and identity-conflict rule
as VK, appears in /me/identities, is removable subject to the
last-identity 409, and keeps customer (marketplace_session) and admin
(bo_session) sessions as distinct cookies - closing the shared
customer/admin Telegram session the audit flagged. The identity row and
the messaging BotConversationBinding stay separate records.
FH-4.8 - PHASE-8 §3 now states email/phone OTP's position explicitly:
recovery when a linked messenger is unreachable and an addable second
factor, never the primary login, and one more identity on the same
customer rather than a parallel account.
262 tests pass. Build green, boundaries and cycles green, bundle scan clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 22:36:36 +04:00
| 4 — Identity | 4 | 3 | 1 | OAuth apps, which FH-0.1 gates |
feat(deploy): host hardening on the frontend server (FH-D.3)
server-setup.sh configured ufw and stopped there, which leaves SSH open
to unlimited password guessing and the kernel on defaults that are wrong
for an internet-facing host.
Adds three drop-in files, so a re-run replaces its own config and never
edits a distro file in place:
/etc/ssh/sshd_config.d/10-marketplaces-hardening.conf
password and keyboard-interactive auth off, root key-only,
no agent/X11 forwarding, MaxAuthTries 3, 30s login grace
/etc/fail2ban/jail.d/marketplaces.local
sshd, nginx-http-auth, nginx-bad-request; 5 in 10m, 1h ban
/etc/sysctl.d/99-marketplaces-hardening.conf
no redirects or source routing, rp_filter, SYN cookies,
forwarding off, restricted kernel pointers and dmesg
Both accounts on the host are key-only by construction - the deploy user
is created with no password at all - so disabling password auth cannot
lock anyone out. It only closes guessing against a credential nobody
intended to exist.
The sshd block runs `sshd -t` first and removes its own drop-in if the
test fails. A bad sshd config that takes effect on a remote box is how
people lock themselves out permanently.
DEPLOYMENT.md §3.2 documents all three plus the post-provision checks.
Not copied from the reference implementation: its hardcoded server IP.
Kept as-is because ours is already better: add-domain.sh pre-checks the
DNS A record and runs nginx -t before and after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:17:02 +04:00
| Ops | 1 | — | 2 | — |
docs(editor): mark editor publish as backend-blocked local-only (FH-E.5)
Audited all 21 localStorage users against the "localStorage as source of
truth" objection. It was already false almost everywhere:
- Every admin facade (products, categories, orders, moderation,
dashboard) uses localStorage only for view preferences - viewMode,
density, visibleColumns, expandedIds, sort. Entity CRUD goes through
the API gateways.
- currency-rates.service already removed its localStorage-typed rates.
- language, location region, search history, the anonymous session id,
admin preferences - all legitimate cache/preference.
- The editor already shows an "unsaved local draft restored" banner
(draftRestored -> save bar), which is the recovery-cache indicator this
item called for.
One real gap remains and it is backend-blocked: project-editor
publish() applies config to the in-memory runtime and saves the draft to
localStorage, then declares itself published - no server round-trip,
because the PHASE-9 §5 revision API does not exist yet. Marked precisely
in publish() with the required behaviour (await the server, only then
mark published) and cross-referenced to the contract.
Nothing to rip out: the codebase was already at the target state
everywhere the backend exists to support it.
262 tests pass. Boundaries green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 22:41:14 +04:00
| Process | 6 | — | 0 | — |
| **Total ** | **30 ** | **4 ** | **8 ** | 1 rejected |
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Landed 2026-08-21**
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
- **Wave 1** — FH-1.1 (geo off `ip-api.com` , 4 new tests), FH-1.3 (credentials out of the browser, via the `@marketplaces/payment` migration).
- **Wave 2** — all 14 remaining contract items written into `docs/backend/` , tagged `FH-*` and dated so each traces back to the analysis. FH-2.12 rejected on the merits: our lifecycle state machine is richer than theirs.
- **Wave 3** — FH-3.3 (bundle budget ratcheted to a blocking error at 1.6 MB, measured 1.55 MB), FH-3.5 (`scripts/ci/scan-bundle.sh` , in CI, verified in both directions).
- **Wave 4** — FH-4.1 and FH-4.2 complete on the client and in the contract; FH-4.3– 4.5 specified and waiting on backend plus registered OAuth applications.
- **Process** — FH-E.1– E.4, including [ADR-0006 ](context/adrs/ADR-0006-harvest-mechanisms-from-the-parallel-platform.md ).
perf(build): drop the JIT compiler from production, 1.55 MB -> 1.04 MB (FH-3.4)
Built with --stats-json and read the esbuild metafile instead of guessing.
@angular/compiler was 495,831 bytes of a 1.5 MB main chunk - the JIT
compiler, in an AOT production build, 33% of everything an anonymous
visitor downloads.
src/main.ts imported it deliberately, with a comment explaining that
@marketplaces/auth shipped plain tsc output carrying no Ivy metadata, so
Angular JIT-compiled its classes at runtime and bootstrap threw without
it.
That comment was stale. The package is 0.2.0, built with ng-packagr,
module: dist/fesm2022/marketplaces-auth.mjs - proper Angular Package
Format, partial-compiled (ɵɵngDeclareInjectable), linked at consumer
build time. Nothing needs JIT.
Verified against the production bundle served statically, not just a
green build: the failure mode this guarded was a runtime throw, so a
successful compile proves nothing. Angular 22.0.8 bootstrapped, the
router resolved /ru, and the app rendered its own "server unavailable"
screen - meaning DI, HttpClient and the full interceptor chain ran. That
chain injects AuthService from @marketplaces/auth, the exact class the
old comment named. Zero JIT or compiler errors; the only console output
was the expected 404s from having no backend behind a static server.
Initial bundle 1.55 MB -> 1.04 MB raw, 323.58 kB -> 215.45 kB transfer.
Budget ratchet lowered 1.6MB -> 1.1MB, which is now also what stops the
import being re-added.
Next lever, deliberately not taken here: i18n/ru.ts is 290 kB, eager,
while en/hy are already lazy. TranslateService has the loader plumbing
and languageGuard already awaits a preload, so it is mechanically small -
but it adds a round-trip before first paint for the majority language,
which is a product tradeoff, not a cleanup. Roughly a 750 kB bundle once
someone decides.
256 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 16:10:34 +04:00
Test count over the session: 247 -> 256 (+4 geo, +5 social identity). Initial bundle 1.55 MB -> 1.04 MB. Build green, boundary and cycle checks green.
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
**On FH-3.1 / FH-3.2 — reclassified, not skipped**
Both are backend races: two transactions competing for the last unit, and the same provider event arriving twice. Playwright against mocked routes cannot prove either — a test that mocks both sides of a race proves only that the mock behaved. `checkout-idempotent-click.spec.ts` already says this in its own header and covers the genuinely frontend-testable half.
So the acceptance criteria now live where they bind, as normative text in PHASE-3 §3.1 and PHASE-7 §5, and the e2e work they imply is **backend integration testing ** , not frontend e2e. What * is * worth doing on our side first: the existing checkout e2e specs have a known-failing session setup (documented in-file, dated 2026-08-21) — a green suite is the prerequisite for anything built on top of it.
docs(backend): harvest platform mechanisms into the contracts (Wave 2, FH-E.1-E.4)
Writes the 14 harvested mechanisms from FORK-ANALYSIS-2026-08-21.md into
the backend contracts. Each section is dated 2026-08-21 and tagged FH-*
so any wording traces back to why it is worded that way.
The through-line: several contracts stated correctness as behaviour
("the webhook must be idempotent"). Behaviour written as an if-statement
gets deleted by a refactor and the failure mode is a double charge. These
sections restate it as schema and mechanism.
PHASE-3 3.1 conditional-write reservation, 409 on zero rows, cart-wide
rollback, 15 min TTL
3.2 InventoryMovement append-only journal with resultingAvailable
6 bulk import idempotent by SKU, rollback while unsold
6a digital code pools, revealed only when paid
PHASE-7 5 unique constraints for payment idempotency and webhook
replay, insert-first handling, signature over raw body,
24h poll as reconciliation not primary
TRACK-S 2.1 session model - 32 bytes stored as SHA-256 only, HttpOnly,
one cookie per contour, Argon2id params, mandatory TOTP
2.2 origin allowlist ahead of routing on every cookie mutation
4.2 AES-256-GCM envelope for stored secrets, HMAC fingerprints
8a order manager as a separate contour, scoped by membership
rows rather than by configuration
PHASE-9 5.1 revision immutability, version = max+1, pointer flipped
in-transaction, operational state does not travel
5.2 clone carry / no-carry list, inventory to zero
5.3 signed read-only preview, non-GET 404s while previewing
6 host normalization, verifiedAt required, cache invalidation
PHASE-10 3a server re-runs the editor's validation, clamp-and-fallback
PHASE-2 3.1 order publicToken, snapshot completeness, never updated
FH-2.12 rejected on the merits: our marketplace lifecycle state machine
is richer than theirs, adopting it would be a downgrade. Recorded in the
TODO so it is not raised again.
Also adds BACKEND-HANDOFF.md sections 0 and 0a - nine falsifiable
invariants as a release gate, each cross-referenced to the contract that
specifies it, plus PR and release discipline. And ADR-0006 recording what
we take, what we reject, what we keep because ours is better, and the
organizational question it deliberately does not settle.
No implementation changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:12:05 +04:00
**Next**
feat(identity): provider-agnostic social login, VK ID + Yandex ID (FH-4.1, FH-4.2)
The VK-only scaffolding had a shape problem worth fixing before anything
was built on it: completeCallback(code, codeVerifier) took the PKCE
verifier from the client, which forces the browser to generate and hold
it. We are a confidential client - a browser-held verifier buys nothing
and adds a place to steal it from.
Replaces the four vk-id-* files with a provider-agnostic surface:
getAuthorizeUrl(provider, returnTo?)
listIdentities()
unlink(provider)
completeCallback is gone entirely. The backend mints and stores state and
code_verifier single-use for 10 minutes, handles the provider's callback
itself, issues the session cookie and redirects. VK and Yandex differ
only in a path segment, because everything that actually differs between
them - PKCE handling, VK's device_id, Yandex's Basic-auth exchange -
lives backend-side.
vk-id-login becomes social-login-button with a provider input; adding
Yandex to the UI is an input value, not new code. Adds yandex_id to
ExternalIdentityProvider, plus optional email/phone/displayName since VK
frequently returns no email.
social-identity-gateway.spec.ts (5 tests) asserts the requests carry no
code_verifier and no client_secret, so reintroducing a browser-held
verifier fails the build rather than passing review.
PHASE-8 §2 rewritten to match: the four endpoints, backend-owned state
and verifier, UNIQUE (provider, providerUserId) with conflict routed to
controlled resolution rather than a silent rebind, per-tenant OAuth app
config under the Track S §4.2 envelope, and both providers' full endpoint
sets. Two things recorded there because they are expensive to discover
later: VK's callback returns device_id alongside code and the token
exchange fails without it, and both providers validate redirect_uri
against an exact registered list - which a multi-tenant platform cannot
satisfy without a central identity host (FH-0.1, still undecided).
256 tests pass. Build green, boundaries and cycles green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:15:26 +04:00
1. **FH-0.1 ** — the central identity host, and the one-customer-or-two question. One-way door, gates the remaining Wave 4 work, needs a decision from a person.
2. **FH-1.2 / FH-1.4 ** — bank URL validation and `Idempotency-Key` . Both live in `cart.component.ts` / the payment package; pick up once that work settles.
3. **FH-E.6 ** — mock fixtures reach production chunks. The fix is mechanical but touches 21 DI token files plus `app.config.ts` , which another session currently owns — deliberately deferred rather than merged into a busy tree. Plan: move mock selection out of the token factories into one dev-only provider array swapped by `fileReplacements` , the same mechanism `mock-data.interceptor.production.ts` already uses.
4. **Fix the e2e session setup ** , then revisit what proof is worth adding.