Files
sxchnge/docs/implementation.md
T

930 lines
58 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Product implementation record
## Reusable UX guidance — task #10
[Task #10](https://git.sendico.io/tech/sxchnge/issues/10), branch `skills-10`,
delivered in [PR #11](https://git.sendico.io/tech/sxchnge/pulls/11) into `main`,
adds a concise root UX policy linking two independent skills, now consolidated under `.agents/skills/`:
UX Design covers usable interfaces and its design review; UX Observability covers
purposeful journey measurement and its observability review. Both preserve product
conventions and avoid prescribing frameworks, vendors, or visual styles.
Validation: both skills passed `quick_validate.py` from the skill-creator skill;
root skill links resolve, scaffold checks and `git diff --check` pass. Manual
review checked the requested principles and checklist coverage, duplication,
contradictions, and actionable wording. The initial validator attempt lacked
PyYAML; rerunning in a temporary virtual environment with PyYAML passed.
This is documentation-only guidance, not an implementation or verification of
product usability or journey instrumentation. Runtime tests and security scans
were not run. No application behavior or analytics events changed.
## Scope
The public Astro website now lives in `website/`, preserving its source, assets,
brand references and hosting identity. `backend/` describes the proposed BFF
boundary; no backend service is implemented.
The frontend includes:
- `sxchnge_theme`: reusable Flutter theme, Onest font/license, light/dark themes,
and a consumer example. UI uses ColorScheme and TextTheme roles.
- `sxchnge_auth`: shared login, signup, and email-verification contracts,
models, providers, and deterministic dummy services, reusable on mobile.
- `app`: responsive login/signup, EN/RU localization and saved locale,
email-sent and verified screens, expired/invalid/internal-error outcomes,
resend/retry, and centralized go_router navigation.
## Review boundaries and task mapping
The accumulated work was initially left uncommitted across task branches.
Delivery groups the actual final code into three stacked contributions, without
claiming that the earlier intermediate implementations were committed:
1. `theme-1` into `main`: website relocation, repository scaffolding and theme.
Related task: [#1](https://git.sendico.io/tech/sxchnge/issues/1).
2. `auth-4` into `theme-1`: shared auth and verification package.
Related tasks: [#4](https://git.sendico.io/tech/sxchnge/issues/4),
[#5](https://git.sendico.io/tech/sxchnge/issues/5).
3. `routing-6` into `auth-4`: integrated application, root test entry point,
self-contained frontend guidelines/workflow skills and this delivery record.
Related tasks: [#2](https://git.sendico.io/tech/sxchnge/issues/2),
[#3](https://git.sendico.io/tech/sxchnge/issues/3),
[#5](https://git.sendico.io/tech/sxchnge/issues/5),
[#6](https://git.sendico.io/tech/sxchnge/issues/6).
Review and merge in that order. Preserve commit ancestry with merge commits,
then retarget the next PR to `main` after its dependency merges. With squash
merges, rebase the remaining stack before merging. PR links are recorded below
once created. Earlier empty task branches are retained for traceability.
## Validation
The frontend suite covers 56 tests: 20 shared auth, 3 theme, 3 theme consumer,
and 30 app tests. Coverage includes validation, localized layouts at 200% text
scale, provider lifecycle/race handling, signup through email confirmation,
resend/retry, token changes, transient history extras and URL route updates.
Commands used for delivery:
- Root: `make test-frontend`.
- Flutter packages: formatting checks and `flutter analyze`.
- App: `flutter build web --no-wasm-dry-run`.
- Website: `npm run test`, `npm run check`, `npm run build`.
The localhost preview was manually checked for confirmation deep-link reload,
return to login, and browser back/forward. Tests and builds are local validation;
no remote CI outcome is implied. Build outputs and dependency caches are ignored.
## Current limitations
Authentication and verification are explicitly simulated. No real account,
session, email delivery, backend token validation or KYC access guard exists.
The three app routes are public; backend authorization remains future work.
`demo-valid`, `demo-expired` and `demo-internal` are preview-only confirmation
values. Other/missing tokens display invalid confirmation. Signup email is
transient and excluded from serialized browser history. Only locale preference
is persisted by the application.
The website and Flutter app are separate applications. The Flutter app currently
has a web runner; the shared packages can be consumed by a future mobile app.
## Published PRs
| Order | PR | Branch → base | Implementation commit |
| --- | --- | --- | --- |
| 1 | [#7: Product layout and theme](https://git.sendico.io/tech/sxchnge/pulls/7) | `theme-1` → `main` | `d931805` |
| 2 | [#8: Shared auth and verification](https://git.sendico.io/tech/sxchnge/pulls/8) | `auth-4` → `theme-1` | `27424ed` |
| 3 | [#9: Localized app and routing](https://git.sendico.io/tech/sxchnge/pulls/9) | `routing-6` → `auth-4` | `10a278c` |
All three implementation commits are pushed. PRs are open, assigned to `tech`,
labeled `frontend`, and associated with the SXCHNGE project. Follow-up documentation
commits are included in PR #9. Task references point to the delivering branches;
older empty branches are historical only. No PR has been merged during delivery.
Delivery validation passed: 56 frontend tests; formatting and all four Flutter
analyses; 3 website tests, Astro check (zero errors/warnings), and website build.
The app web build and browser checks passed during the immediately preceding
routing task; delivery changed documentation and Git organization only.
## Onboarding UX fixes and analytics preparation
Task [#12](https://git.sendico.io/tech/sxchnge/issues/12) corrects the four UX
review findings: measurable submitted journeys, accurate sent recipient state,
primary retry on confirmation errors, and prominent/truthful simulated outcomes.
The new shared `sxchnge_analytics` package supplies typed events, a bounded dummy
sink and a journey recorder; auth service decorators keep collection independent
of widgets. A PostHog adapter can replace the sink later; no actual SDK or data
transmission is included. The [journey contract](ux/auth-journeys.md) records event
semantics, metrics, privacy constraints, windows, validation and limitations.
Delivery: [PR #13](https://git.sendico.io/tech/sxchnge/pulls/13), `onboarding-12`.
Implementation commit `f1cbf64`; 68 tests and affected-package analysis/formatting
plus web build passed. Final browser verification confirms accessible recipient
text and keyboard recovery. No merge or actual PostHog collection was performed.
## Consolidated skill location
Task [#16](https://git.sendico.io/tech/sxchnge/issues/16) consolidates all four
repository skills under `.agents/skills/`. UX Design and UX Observability moved
without content changes; root policy links and this record now use that location.
The old root `skills/` directory is removed. Validation compares moved content
byte-for-byte, checks all four skill paths and updated UX links, and runs
`git diff --check`. No runtime tests are needed for this move.
Delivery: [PR #17](https://git.sendico.io/tech/sxchnge/pulls/17), branch `skills-16`.
## Editor configuration cleanup
Task [#14](https://git.sendico.io/tech/sxchnge/issues/14) removes the root VS Code
extension recommendations and launch configuration. Root `.vscode/` is ignored
so personal editor configuration stays local. Development uses the CLI commands
in the README. Validation: no tracked root `.vscode` files, ignore rule verified,
and diff whitespace check passed. Runtime tests are unnecessary for this change.
Delivery: [PR #15](https://git.sendico.io/tech/sxchnge/pulls/15), branch `editor-14`.
## Root README refresh
Task [#18](https://git.sendico.io/tech/sxchnge/issues/18), delivered in
[PR #19](https://git.sendico.io/tech/sxchnge/pulls/19) on `docs-18`, documents
current packages, app/website commands, verification previews, analytics limits,
skill location and contribution workflow. Local links and diff checks pass;
commands and prerequisites were checked against repository configuration.
Documentation only; no runtime tests were run.
## Exchange workspace UX plan — task #24
- Plan-only scope confirmed by the user; no application code changed.
- [Exchange workspace plan](ux/exchange-workspace.md) defines accounts, top-up,
exchange, payout, Activity, Limits and Settings; account-owned language replaces
repeated page selectors in the proposed interface.
- Includes authoritative quotes/status/limits, idempotent recovery, account-profile
language persistence, accessible/responsive behavior and semantic funnel metrics.
- Standalone documentation branch `exchange-shell-24` from main. Future interface
implementation should incorporate KYC PR #21 and usage PR #23.
- Validation: documentation links and diff checks; no runtime changes or tests.
- Delivery: [PR #25](https://git.sendico.io/tech/sxchnge/pulls/25), committed and pushed; not merged.
## Optional identity verification and backend-owned limits — task #20
- Branch: `kyc-20`, based on `61ccfc1` (main after README PR #19).
- Planned the UX before implementation in [docs/ux/kyc.md](ux/kyc.md), including
optional basic access, two-photo preparation/review, pending and decision states,
errors, recovery, privacy and measurement definitions.
- Added the mobile-reusable `sxchnge_kyc` package, injectable identity/photo/limits
services, transport mappers and providers. Followed Sendico's visible-limits
ownership boundary; current tier/caps/remaining amounts come from a service.
- Added themed EN/RU `/identity` screens, login/email-confirmation entry points,
route exit guards, review/replace/discard controls, safe error messages and
demo-only backoffice decision controls. No real documents are transmitted.
- Reviewed full frontend scope and corrected web image-metadata validation,
stale-state messaging, cancelled-picker analytics, step focus/scroll behavior,
duplicate submissions and sensitive image cache cleanup.
- Validation: format check for all 27 changed/new Dart files; analyzer passes for
app, sxchnge_kyc and sxchnge_analytics; `make test-frontend` passes **86 tests**
(10 KYC, 7 analytics, 20 auth, 3 theme, 3 consumer example, 43 app). Web build
passes. Actual browser selection of generated PNG samples reached pending
review with unchanged basic limits. EN/RU, 320px/1.6 text scale, accessibility
labels/tap targets, route guards and all decision paths have widget coverage.
- Limits are illustrative backend-stub fixtures. Real authenticated uploads,
private storage, staff review, support, authoritative limits enforcement and
server-side review analytics remain integration work. Native mobile and
VoiceOver/TalkBack checks remain release QA. Security scans were not requested.
- Delivery: [PR #21](https://git.sendico.io/tech/sxchnge/pulls/21), pushed on `kyc-20`; not merged. Final browser review also preserved heading semantics when moving keyboard focus.
## Limits usage display — task #22
- Branch `limits-22`, stacked on `kyc-20` / PR #21 (merge #21 first).
- Added explicit used/committed and reserved fields through DTO, mapper, service
and model. Remaining is independently supplied by the backend stand-in.
- Daily/monthly cards show used, reserved, available, cap and upgrade amounts,
with a labeled theme-based occupied-capacity indicator and exhausted text.
Dummy fixtures contain nonzero activity; usage survives tier increases.
- UX review: EN/RU labels explain pending holds, no color-only information,
320px/1.6-scale widget checks, actual browser visual verification, zero and
over-cap ratio handling. Existing KYC telemetry is sufficient; no usage amounts
are sent to analytics.
- Validation passed: all 91 frontend tests, analyze app and sxchnge_kyc,
format check of all 9 affected Dart files, web build and diff checks.
- Delivery: [PR #23](https://git.sendico.io/tech/sxchnge/pulls/23), unmerged. Merge #21 first and retarget #23 to main.
## UX simplicity preference
Task [#26](https://git.sendico.io/tech/sxchnge/issues/26), delivered in
[PR #27](https://git.sendico.io/tech/sxchnge/pulls/27) on `skills-26`, makes
simplicity an explicit preference in the UX Design skill and its review checklist.
Prefer fewer concepts, choices, and controls when they serve the same need; retain
essential information, accessibility, and recovery. Documentation only.
Validation: skill-creator `quick_validate.py` passed in an isolated environment
with PyYAML (the system Python lacked it); `git diff --check` passed. No runtime
tests were needed. The PR is not merged.
## Exchange workspace — task #28
- Branch `workspace-28`, stacked on `limits-22` / PR #23. Merge KYC #21 first,
then limits #23, then retarget this workspace contribution to main. The approved
plan from #24/#25 is included and updated for the user's available-only direction.
- Implemented Accounts, Activity/detail, Limits and Settings in a responsive
go_router shell, with top-up, exchange and payout input/review/status flows.
One action group follows the selected currency; all colors use theme roles.
Only available balances are displayed. Status explains credit timing, outgoing
amount plus fee, completion and full return after failure.
- Shared `sxchnge_wallet` service/providers own coherent demo balances, quotes,
idempotent submissions, reconciliation, history, account language and live
snapshot refresh. The limits adapter reuses the KYC DTO/mapper and refreshes
usage when operations change. Widgets consume provider models.
- Language is in account settings, with failed-save feedback and retry. Public
auth and verified-email screens enter the workspace. KYC remains optional;
the demo service enforces its basic/verified outgoing caps at submission.
- UX review: EN/RU at 320px with 1.6 text scaling; available-only presentation;
field preservation after errors; explicit draft discard; quote expiry;
loading/stale/empty states; operation outcome explanations; accessible labels,
semantic status announcements and focus after quote/errors. Browser walkthrough
verifies quote amounts and service-confirmed credit with the exchange handoff.
Native mobile and dedicated VoiceOver/TalkBack testing remain release QA.
- Measurement review: typed draft/quote/submission/settlement steps with scoped
journey IDs, safe failure enums, simulated flag and deduplicated terminal
outcomes. No financial amounts, recipients or account IDs in events. Pending
app closure is not draft abandonment. Real PostHog delivery and cross-session
server outcomes remain integration work.
- Validation passed: `make test-frontend` (115 tests), `flutter analyze` in app,
sxchnge_wallet, sxchnge_analytics and sxchnge_kyc; format check of all 36 changed
Dart files; `flutter build web`; `git diff --check`. Review fixes included
narrow Russian layouts, limit-provider typing, automatic pending refresh,
single-use quotes and ambiguous-response recovery. No security scan requested.
- Demo limits: opening EUR 75, fixed rates/fees and three currencies, no real
payment methods/bank validation/storage/auth enforcement. Account preferences,
history and balances reset on reload. No receipt export, date pagination,
notification preferences or security settings beyond an honest capability note.
Backend adapters must supply these before production use; see the wallet README.
- Delivery: [PR #29](https://git.sendico.io/tech/sxchnge/pulls/29), pushed and
unmerged. Task and PR have frontend label, tech owner and SXCHNGE project.
Browser demo walkthrough also completed exchange and payout, confirming source
currency handoffs and exact fee/debit/receipt values on the result screen.
### Workspace copy cleanup — follow-up to #28
At the user's request, removed repeated demo/preview banners from the workspace,
transaction forms, auth, email confirmation, identity and limits. Confirmation
and result copy now uses ordinary product wording in EN/RU; the mobile wordmark
remains without the extra note. Removed unused localization entries and duplicate
email-state copy. Existing journey measurement, simulation flags, service behavior,
funds explanations and recovery controls are unchanged. Development constraints
belong in this record and package README, not repeated throughout the product UI.
Delivery continues in [PR #29](https://git.sendico.io/tech/sxchnge/pulls/29) on
`workspace-28`. This copy change does not connect real authentication, email,
payment, support or backoffice services.
Validation: all 115 frontend tests, app analysis, app Dart formatting, web build
and diff checks pass. Updated existing copy expectations; telemetry tests still
verify simulated events never count as real business success. No new click events
were needed for this presentation-only cleanup.
## Data model and provider boundaries — task #30
- `architecture-30`, based on workspace PR #29. Root and frontend agent rules,
plus README, now explicitly preserve DTO → mapper → service → data model →
provider → UI across app features, shared packages, stubs and future clients.
Models cannot be replaced by providers or reused as transport DTOs merely
because their fields currently match.
- Checked application service access: widgets compose providers with service
dependencies; they do not parse DTOs or invoke backend operations directly.
KYC and wallet transaction services already map transport data to models.
Auth success responses now pass through explicit response DTOs and mappers;
wallet profile conversion now accepts a ProfileDTO instead of a raw string.
- Existing models, provider state, asynchronous/error behavior and public service
contracts are preserved. DTOs/mappers remain service implementation details.
No UI, telemetry semantics or backend integration changed.
- Frontend review and validation: formatting of all 6 changed Dart files passed;
analysis of sxchnge_auth and sxchnge_wallet passed; all 115 frontend tests
passed; diff check passed. Existing service, provider and integrated tests
exercise the mapping paths. No additional behavioral test was needed for these
direct field projections. No security scan requested.
- Stack order: KYC #21 → limits #23 → workspace #29 → this contribution;
retarget each dependent PR to main after its base is merged.
- Delivery: [PR #31](https://git.sendico.io/tech/sxchnge/pulls/31), pushed and
unmerged. Task and PR metadata verified: frontend label, tech owner, SXCHNGE
project. Retarget after workspace #29 merges.
## Authorized integration — 2026-09-12
The user requested merging all open PRs. Skills #17, KYC #21, limits/workspace #23
and UX plan #25 have merged into main. Workspace #29 had already merged into
limits-22 and reached main through #23. Architecture #31 is the final integration
PR and carries this record.
Resolved documentation conflicts by preserving both contribution histories.
Preserved the latest implemented workspace plan rather than restoring the older
held-funds design. Skill moves retain the newer simplicity guidance, and root
AGENTS.md points to .agents/skills while retaining the architecture requirements.
No frontend source was changed by conflict resolution. Older unmerged/stack notes
above describe delivery-time state and are superseded by this integration record.
Validation of the combined tree: all 115 frontend tests and app analysis passed;
diff checks passed. Branches were retained. No checks were bypassed.
## Reusable two-factor verification — task #32
Branch `two-factor-32` adds a SXCHNGE-designed, theme-based code page reused by
login, top-up, exchange, payout and verified profile enable/disable. EN/RU copy,
one accessible input, keyboard/paste support, resend cooldown, invalid/expired/
locked/unavailable recovery and cancellation preserve context. Transaction
verification retains its reviewed funds movement and fees. No Sendico UI or code
was copied for this feature.
Shared auth owns explicit DTOs, mappers, domain models, service contracts and
providers. Method-aware contracts distinguish email and authenticator challenges;
the initial email fixture uses code `123456`, sends no email, defaults off and
resets on reload. Real TOTP enrollment, method selection, delivery, recovery and
backend enforcement remain integration work. Mobile can reuse the page and
providers with its own router/localization and service adapters.
Login stays pending until approval is consumed. The dummy wallet consumes a
purpose/quote-bound, expiring, one-use approval before execution. Concurrent
idempotent retries debit once; expired quotes require fresh review; uncertain
outcomes retain reconciliation. Timers stop on route/app disposal and late
responses are ignored. Settings update only after service confirmation. Analytics
retains parent journey correlation and distinguishes verification from actual
login/settings/settlement outcomes; codes, grants and targets never enter events.
Frontend and UX review covered the complete task diff. Validation passed:
`dart format --output=none --set-exit-if-changed` for all 34 changed Dart files;
`flutter analyze` in app, sxchnge_auth, sxchnge_wallet and sxchnge_analytics;
`make test-frontend` (147 tests); `flutter build web` (including Wasm dry run);
`git diff --check`. Browser checks at 1280×720 and 390×844 verified themed layout,
invalid-code recovery, keyboard entry and confirmed settings. Widget tests cover
Russian at 320px/1.6× text scale and all money entry points. No security scan was
requested or run. Earlier validation failures were corrected, including ARB
metadata handling, disabled-policy latency, route timer disposal and lint findings.
See [the UX and measurement contract](ux/two-factor.md) and
[shared auth integration guide](../frontend/packages/sxchnge_auth/README.md).
Delivery: [PR #33](https://git.sendico.io/tech/sxchnge/pulls/33) targets `main`;
committed and pushed, unmerged. Issue #32 and PR #33 metadata verified: frontend
label, tech owner and SXCHNGE project. No stacked dependencies.
## Minimal customer BFF and public Sendico contracts — task #34
[Task #34](https://git.sendico.io/tech/sxchnge/issues/34), branch `bff-34`,
[PR #35](https://git.sendico.io/tech/sxchnge/pulls/35) into `main`.
The initial copy at `7062672` incorrectly retained Sendico's local tenancy model.
The correction removes organization membership, roles, Casbin, policy storage and
seeding, invitations and their models/repositories/tests. It also removes unused
financial models/errors, ledger/wallet provisioning metadata, platform service
names, S3 configuration and WebSocket scaffolding. No default tenant or bypassing
authorizer replaces them.
Retained Sendico source is pinned to `8bb20dcd622dd8eb92d289b6b34411e7eccab083`:
HTTP runtime, local customer/password service and Mongo repository, customer
sessions and verification storage, generic repository/transaction code, errors,
logging and health. [Provenance](../backend/PROVENANCE.json) covers 232 retained
Go files at that cleanup stage; every adaptation is flagged. The later module
reorganization below removes the unused public OpenAPI snapshot.
The Sendico checkout remains unchanged.
Customer creation accepts a raw password, validates/hashes it and commits the
customer and activation token together. Token transactions join an active outer
customer transaction. Deletion revokes only the selected customer's sessions and
deletes its account atomically, including when it is the final local customer.
Customer lifecycle and login use the same configured auth collections. Pending,
active and blocked customer state and session/client binding remain local.
The sole Sendico organization reference belongs to the future public HTTP adapter
configuration. [Architecture and capabilities](architecture/bff.md) document
server-side customer/resource ownership and the public external-customer contract
gaps. No internal service, shared database or provider resource creation is used.
Signup/profile HTTP routes, verification delivery/completion, application client
setup and live Sendico integration remain subsequent wiring work.
Backend completion review covered the retained copied runtime and the full
extraction diff. Inactive-customer checks and refresh client/account binding were
added after review; explicit response DTOs keep storage fields out of HTTP output.
Validation passed: `make test` (race-enabled backend suite, including fresh Mongo
replica-set customer lifecycle/runtime tests, plus all frontend suites),
`make lint-backend` (architecture, formatting and vet), and
`go test -tags integration -run '^$' ./...` from `backend` (compilation of the
additional tagged suites; this command does not execute those tests).
Provenance/source blobs, public contract references and `git diff --check` passed.
Earlier extraction compile errors were corrected. The initial fresh-database test
attempt used an unavailable image tag and then hit Docker Desktop replica-set
address discovery; it now uses `mongo:8` and a direct connection to the disposable
single-node replica set. No security scan was requested or run.
PR #35 remains unmerged. There is no deployed-data migration: this bootstrap has
not been deployed. Existing experimental databases may retain unused tenancy
collections; this change does not delete external database records. Customer HTTP
responses now expose only ID, name, last name, login, locale and optional avatar.
The frontend still uses dummy services and is unchanged.
### Go module organization correction
`backend/` now groups two implemented Go modules: the runnable `product` BFF
(`main.go`, `go.mod`, configuration and feature packages) and the `shared` library
(its own `go.mod`, generic storage, errors, logging and HTTP helpers). Product uses
`replace git.sendico.io/tech/sxchnge/shared => ../shared`. A future backoffice BFF
belongs in its own `backend/backoffice` executable module; it is not implemented
or silently combined with product in this task.
Customer, session and verification models/storage now live with their features.
Shared database code no longer knows about customer repositories. Auth and
verification configurations belong to their feature packages. The inherited
`cmd`, `edge`, `internal`, `pkg`, `scripts` and `contracts` top-level folders are
removed. All 112 unused contract files are removed; future HTTP adapters will
own only consumed public wire definitions. Provenance retains the pinned upstream
source paths while recording the new local module paths.
Root Make targets validate both modules independently and run product with
`cd backend/product && go run .`. Source behavior remains the previously validated
local customer/auth/session bootstrap; frontend and Sendico source are untouched.
Validation passed after the module moves: `make test` (both Go modules with the
race detector and all frontend suites), `make lint-backend`, product executable
build and `-version` smoke check, and `go test -tags integration -run '^$' ./...`
in each Go module (compile-only for the additional tagged suites). All 233
provenance source blobs match the pinned revision. `git diff --check` passed.
No security scans were requested or run. The correction is delivered on `bff-34`
in [PR #35](https://git.sendico.io/tech/sxchnge/pulls/35), which remains unmerged.
## Notifications service (#36)
- Added independent `backend/notifications` executable with a separate
`interface` Go module. Canonical protobuf source:
`backend/notifications/interface/proto/v1/interface.proto`. Go bindings are
ignored; Make generates them before validation and packages source/client
archives without committing generated code.
- Email-first Send/Get contract, caller-scoped durable idempotency, strict
template parameters and HTML escaping, immutable rendered snapshots, expiry,
and sanitized per-delivery status. Unsupported channels remain explicit in
the contract and fail before enqueueing.
- SendGrid adapts Sendico's driver to the new rendered-email interface; Resend is
a new implementation behind the same shared DriverConfig pattern. No provider
managed template or Sendico business-runtime dependency is introduced.
- Mongo persistence uses shared DBImp/query/patch/index/connection wrappers.
The notification document is the atomic outbox. Shared messaging contains the
selected Sendico NATS durable adapters, and the executable consumes their
Transport/Subscription interfaces for publication, delivery and acknowledgement.
- Tests cover both provider payloads, authentication, parameters, concurrent
idempotency/claims, outbox publication failure, lease recovery, expiry and
retries, with disposable MongoDB and NATS containers. Final `make test` and
`make lint-backend` passed. `make package-notifications-interface` passed;
the extracted client archive passed standalone `go test ./...`. No live
email or optional security scan was run.
- Limitations: email only; no delivery receipts. Ambiguous sends require
reconciliation, rather than automatic replay. Initial NATS provisioning uses
one replica. Caller services own recipient authorization/preferences. Drain
queued work before changing driver/sender configuration.
- Delivery is stacked on BFF PR #35 (`bff-34`); merge #35 first, then retarget
the notifications PR to main. Do not merge without user authorization.
- Task: <https://git.sendico.io/tech/sxchnge/issues/36>.
PR: <https://git.sendico.io/tech/sxchnge/pulls/37>, branch `notifications-36`.
Merge order: [#35](https://git.sendico.io/tech/sxchnge/pulls/35), then #37.
### Notifications completion follow-up (#36 / PR #37)
- Queue configuration now composes shared stream/provisioning settings. NATS
replication, retention and app/ops/hybrid provisioning are configurable;
production drift is rejected. Shared provisioning calls honor cancellation.
- Recovery expires queued and retrying notifications and clears rendered content
without requiring a working NATS connection, including future-scheduled retries.
Expiry queries have a dedicated index. Lost leases are logged with counts.
- Extended the real Mongo/NATS integration test through authenticated TLS gRPC
Send/Get, including caller isolation, idempotency and parameter propagation to
the email driver. Added queue configuration, cancellation and retention-drift
checks; gRPC now preserves canceled/deadline-exceeded outcomes.
- Validation: `make test`, `make lint-backend` and interface packaging passed in
an isolated clean checkout containing this contribution. Shared-workspace runs
initially failed because concurrent product/auth work had missing module
dependencies and unformatted files; that work was excluded and left untouched.
## Signup and login notifications (#38)
Task [#38](https://git.sendico.io/tech/sxchnge/issues/38), branch `auth-38`.
Added signup/activation and login-code routes using the local customer,
verification and session stores, with a TLS-authenticated adapter to the
notifications interface module. English/Russian email templates and Flutter
DTO/mapper/service adapters preserve provider/UI boundaries. Backend mode hides
the preview shortcut and requires confirmed login for workspace navigation;
unverified accounts go to email recovery.
Review fixed real Mongo empty-target matching for cooldown/invalidation/replay,
replaced predictable automatic verification keys with cryptographic randomness,
removed token/hash/key previews from verification logging, bounded resend expiry
by the pending JWT, preserved password whitespace and prevented consumed-code
redelivery. Structured flow outcomes and latency metrics supplement existing
journey instrumentation. Generated localization files are now ignored/untracked;
`make test-frontend` regenerates them from ARB sources before tests.
Validation passed: `make test` (Go race tests and all frontend suites),
`make lint-backend`, `flutter analyze` in both `frontend/packages/sxchnge_auth`
and `frontend/app`, and Dart format verification for all 17 changed hand-written
Dart files. Tests include a real Mongo signup/activation/login journey, caller
TLS authentication, unavailable delivery, invalid/single-use codes, immutable
resend payloads, Flutter adapter/session mapping, unverified-account recovery,
and backend-mode route gating. Initial failures in query-test OR support,
localized-copy expectations and formatting were fixed and the checks rerun.
No live email or optional security scan was run.
See [configuration, contracts, UX evidence and recovery limits](auth-notifications.md).
Queue acceptance is not inbox delivery. Product-side crashes before Send require
user resend; initial signup/login retries are not exactly-once operations.
Session response failures require restarting login. Session restore/refresh,
production analytics export and wallet/settings backend adapters remain outside
this contribution. Configure notification credentials/TLS/templates and the
frontend API URL/public client before enabling real auth.
Delivered in [PR #39](https://git.sendico.io/tech/sxchnge/pulls/39). Merge order: #35 → #37 → #39. The auth branch incorporates notification completion commit `4b14649`; only the delivery-log append conflicted, and both task records are preserved.
## Dedicated second-factor service (#40)
Task [#40](https://git.sendico.io/tech/sxchnge/issues/40), branch `twofactor-40`,
based on main after merged PR #39. Primary module: twofactor; secondary modules:
auth, verification purpose definitions, notification templates and Flutter auth.
`product/twofactor` now owns challenge issuance/delivery, verified destinations,
preferences, attempt/cooldown limits and single-use proofs. It reuses the
Sendico-derived verification store at the existing pinned revision. No new
Sendico handler or private messaging code was copied. Auth owns credentials,
pending JWTs and session creation; signup activation remains in `auth/flow`.
The service's `Complete` transaction commits code verification, proof consumption
and the owner's session write together. Review fixed rejected-attempt rollback,
ensured failed session writes preserve retryability, retained activation replay
key compatibility and removed the unused verification-store injection from auth
HTTP. A profile write serializes consumption against preference changes.
Authenticated APIs enroll additional verified emails and change the preferred
factor only after verifying the current factor. Unsupported channels fail closed.
Flutter's shared DTO/mapper/service adapter sends the server challenge ID with
the pending JWT, preserves resend keys after failure and rejects unknown channels.
No widgets or provider/analytics boundaries changed. Existing login journey
measurement and error/recovery UI remain in use; settings UI integration is not
part of this separation.
Validation passed: `make test` (all Go race suites and `make test-frontend`),
`make lint-backend`, `flutter analyze` in `frontend/packages/sxchnge_auth`, and
Dart format verification for all four changed Dart files. New real Mongo tests
cover single-use proofs, customer/purpose/context isolation, delivery retries,
committed failed guesses, expiry, inactive accounts, operation rollback,
concurrent completion, verified enrollment, preference selection and old-proof
invalidation. The updated signup/login test covers the full auth boundary;
transport and architecture tests cover scope injection and domain dependencies.
Frontend tests cover challenge binding, resend recovery and unsupported channels.
No live email or optional security scan was run.
See [ownership, Sendico provenance, HTTP contract and rollout](architecture/two-factor.md).
Deploy backend and client together, load `twofactor.code` revision 1, and restart
previously pending logins after rollout. Email is the implemented channel.
Existing settings screens still use preview adapters; SMS/push/TOTP/passkeys,
factor removal/recovery, payout integration and a separate process are not
implemented. Successful-commit response loss still requires login restart;
notification queue acceptance is not inbox delivery.
Delivered in [PR #41](https://git.sendico.io/tech/sxchnge/pulls/41) against `main`.
Branch `twofactor-40` is pushed. The PR is left open for review; no merge was performed.
## Vault-backed local development (#42)
Task [#42](https://git.sendico.io/tech/sxchnge/issues/42),
[PR #43](https://git.sendico.io/tech/sxchnge/pulls/43), branch `dev-env-42`.
Primary module: dev environment; secondary scope: container packaging, root build
commands, current-stable backend test images and repository container policy.
- Added a dedicated `sxchnge-local` Compose project with persistent Vault, an
authenticated MongoDB replica set, NATS JetStream and build definitions for
notifications, BFF and Flutter/Nginx. Existing Sendico containers and volumes
remain independent.
- `make build` resolves current stable releases and pins manifest digests in
ignored local state. Flutter is built from its stable source tag/commit. All
protobuf bindings and frontend output are generated within the build, never
committed. Root `AGENTS.md` now records the stable-container policy.
- Bootstrap configures Vault KV/PKI/AppRole and `mongodb-database-plugin`, rotates
its Mongo manager credential and issues separate database roles for the BFF
and notifications. The shared database wrapper replaces credential pools in
process and drains transactions/cursors before revoking retired credentials.
Agent handles non-database secrets. Shared messaging paths remain intact.
- Resend is the default. The user explicitly chose to provision its API key and
verified sender manually after bootstrap. `make up` leaves healthy infrastructure
running and intentionally exits nonzero before product startup while those
fields are empty. No placeholder credential or disabled driver was retained.
- Frontend HTTPS and notification TLS use Vault PKI. The frontend proxies the BFF
at the same origin and builds with the existing real auth adapter and a seeded
public browser client. No UI or business service code changed.
Validation: `make build` built all product images; `make test` and
`make lint-backend` passed. Backend tests pull fresh Mongo/NATS stable images and
use `-count=1` to prevent Go's test cache from hiding an image update.
`make test-dev-infra` passed its five setup regression tests and live checks for
AppRole restrictions, database issuance, cross-database denial, renewal and
revocation. `docker compose ... config --quiet` and `git diff --check` passed.
The initial image build exposed missing protobuf headers; installing
`protobuf-dev` in the build stage fixed it.
Additional live checks passed: a down/up cycle preserved a Mongo marker and Vault
secrets and automatically unsealed Vault; changing/restoring the NATS token
restarted the NATS child; the initial restart-based database credential lifecycle was superseded by
the uninterrupted pool rotation implementation described below.
Temporary BFF/frontend containers served the web bundle and proxied BFF health
successfully over HTTPS verified with the generated CA; those test containers
were removed. The notifications image rendered its Agent configuration and exited
with the expected missing email API key error. No real email was sent.
The final `make build && make up` is expected to exit at the Resend provisioning
check until the user populates `kv/email`. Full six-service readiness and a live
Resend signup/login were not asserted without that credential. Vault, Mongo and
NATS remain running. No optional security scan or merge was performed. See the
[local development guide](local-development.md) for exact provisioning fields,
certificate trust, restart behavior, local bootstrap-material handling and
verification commands.
### Uninterrupted Mongo credential rotation
BFF and notifications now acquire database operation scopes from the shared
wrapper. A replacement is issued at half the effective token/lease lifetime,
validated before publication, and independently revoked after draining. Whole
transactions and cursors retain their original pool. Failed refreshes retry while
the current credential remains valid. Expiry rejects new operations until recovery;
there is no scheduled service restart. Non-database Agent template changes still
restart their child process. This is planned-rotation continuity, not infrastructure
high availability or a guarantee beyond Vault credential expiry.
The repository and store constructor contract now uses `source.Source` rather
than a concrete Mongo database. Static test integrations use `source.Fixed`.
Application code continues through shared repositories and transaction factories.
The new `make test-dev-rotation` command proves real Vault plugin replacements
under BFF transaction traffic and checks process identity. It restores shortened
role TTLs and removes temporary product containers afterward.
Validation of the revision: `make build`, `make test`, `make lint-backend`,
`make test-dev-infra` (six setup tests and live checks), and
`make test-dev-rotation` passed. The live rotation check observed three replacements
with an unchanged BFF PID and 1,155 expected invalid-token HTTP responses, zero
failures. Product integration-tag packages also compiled. The race-enabled
rotation suite covers active transactions/cursors, rejected credentials, transient
and extended outages, automatic recovery, rollback and shutdown; issuer tests
cover effective lifetime, credential escaping and failed-issuance cleanup.
An additional `go test -race -tags integration ./database/internal/repository/...
./database/vault/...` run in `backend/shared` found two existing test issues: the
partial-index test expects nested BSON maps instead of ordered documents, and the
patch timestamp assertion can require a strict increase within the same Mongo
millisecond. The BSON mismatch reproduces against `origin/main`; five isolated baseline
repetitions of the timestamp test pass, consistent with its timing-dependent
assertion. Neither assertion nor the timestamp update logic changes in this task;
the supplementary failures are recorded and are not suppressed. The required
normal test and lint commands pass. No optional security scan or email delivery
was performed. Resend remains intentionally unconfigured; infrastructure stays up
and product startup awaits manual `kv/email` provisioning.
### Make and shell development tooling
The local development tooling now runs POSIX-style shell scripts in a Docker
image containing Docker CLI, curl, jq, Git, OpenSSL, flock and ShellCheck. All eight
Python scripts were removed. Make remains the entry point; `make email` replaces
the Python credential prompt. Host requirements are Make, shell and local Docker
with Compose/BuildKit. The tool image is built from a freshly pulled stable Docker
CLI digest; application/infrastructure stable resolution and atomic local pins
remain intact. Tooling mounts the local Docker socket and workspace at its host
path; remote Docker engines are unsupported.
Static Agent HCL sources replace configuration generation and contain only Vault
template expressions. Bootstrap preserves existing Vault state and credentials,
protects its private files, and fails when Resend credentials are absent. The shell
infrastructure check now explicitly validates real Mongo transaction commit and
rollback with each application's Vault-issued identity. The rotation command
retains its unchanged-process and expected-response checks and TTL restoration.
Validation of the shell replacement passed: `make build`, `make test-dev-tooling`,
`make test-dev-infra`, Compose configuration validation and `git diff --check`.
An isolated `sxchnge-shell-check` project passed first bootstrap, live transaction
and lease checks, and repeated bootstrap; its containers, volumes and temporary
state were removed. `make test-dev-rotation` passed three real Vault replacements
with unchanged BFF PID, 1,048 expected HTTP responses and zero failures; role TTLs
were restored and temporary BFF/frontend containers removed. `make up` exits at
the intentional missing-Resend check, with infrastructure healthy. Backend and
frontend code did not change in this revision; their earlier test/lint results
and supplementary legacy test limitations above still apply. No email was sent.
### Organized infrastructure and Vault ownership
This revision supersedes the earlier local Nginx/Vault-certificate arrangement.
Local Compose, runtime configuration, startup scripts and verification now live
under `infra/local/`, grouped by service and responsibility. Dockerfiles live in
`infra/images/`; the root Make targets remain the entry point. Product source
stays in backend/frontend. Generated state remains in ignored `.local/dev/`.
Vault KV now contains `services/bff/jwt`,
`services/notifications/callers/bff`, `services/notifications/email/resend`,
`infrastructure/nats/auth` and `bootstrap/mongo`. Dynamic database credentials
remain in the database engine. Migration copies with CAS protection, compares
existing values, archives readable old versions and metadata privately, and then
removes old flat/TLS KV entries. Conflicting destinations stop migration without
removing sources. Policies and Agent templates reference only the new paths.
The unused existing PKI mount is retained to avoid destroying its signing key;
new bootstrap neither creates nor uses it.
Caddy replaces Nginx, serving Flutter and proxying `/api` to BFF. Caddy owns browser
certificates and its CA in persistent volumes; only its public CA is exported to
`.local/dev/ca.pem`. Existing browser trust must be updated to that new CA. Local
BFF-to-notifications uses plaintext authenticated gRPC. Optional explicitly
configured TLS remains available in the reusable client/server; incomplete TLS
configuration is rejected. No local service consumes TLS secrets from Vault.
Validation passed: `make build`, `make test`, `make lint-backend`,
`make test-dev-tooling`, `make test-dev-infra`, and Compose/diff checks. Tests cover
plaintext caller-token transport, optional verified TLS, invalid certificate
configuration and secret migration conflicts. The existing Vault was migrated
and canonical paths/absence of old entries verified. An isolated fresh project
passed transaction/lease checks and repeated bootstrap; disposable volumes and
state were removed. The final rotation check also verified Caddy static serving,
SPA fallback and API health, then three pool replacements with unchanged BFF PID
and 1,083 expected HTTP responses, zero failures. Role TTLs were restored and
temporary BFF/frontend containers removed. `make up` intentionally stops at the
missing Resend credential check; no real email, optional security scan or merge
was performed. Earlier supplementary legacy test limitations remain recorded.
Mongo bootstrap credentials now live at `kv/infrastructure/mongo/bootstrap`,
consistent with component ownership. The live `kv/bootstrap/mongo` value was
copied, compared and archived before removing the old path. Bootstrap and admin
checks read the new path; application identities are denied both paths. Shell
checks and live infrastructure validation (including Mongo transactions, lease
renewal/revocation and obsolete-path absence) passed. `make up` still stops at the
expected missing-Resend check. No backend code or product behavior changed.
Email configuration is now separated from credentials. The current Vault secret
`kv/services/notifications/email/resend` contains only `api_key`; `driver`,
`sender_address` and `sender_name` live in
`infra/local/notifications/runtime.yml`. Sender address is empty until configured.
Agent injects only `EMAIL_API_KEY`, and notifications loads its runtime YAML
directly. `make email` prompts only for the API key. The migration preserves the
key with a CAS-checked update, archives the prior readable version, and refuses
unknown fields rather than discarding them. The live Vault entry was normalized.
Validation passed: image build, shell/migration tests, live infrastructure checks
(including key-only secret shape and Mongo transactions/leases), and diff checks.
`make up` intentionally fails for the still-missing API key. Verified sender must
also be configured in runtime YAML before rebuilding and starting notifications.
No backend source changed and no email was sent.
### Task #42: remove internal notification caller authentication
Removed BFF bearer metadata, token configuration/validation, and the notifications
authentication interceptor. The private gRPC endpoint uses the existing `product`
storage namespace, preserving notification receipts and idempotency without a
database migration. NATS authentication and application/user authentication remain.
Removed the caller secret from bootstrap, Vault Agent templates and policies.
Bootstrap archives and retires both `caller` and
`services/notifications/callers/bff`; live Vault no longer contains either path.
Service policies deny both paths. The retirement regression fixture verifies
repeatability and preservation of the NATS authentication secret.
Validation: `make build`, `make test`, `make lint-backend`,
`make test-dev-tooling`, `make test-dev-infra`, and `git diff --check`.
Tests cover credential-free Send/Get, idempotent replay through the real service,
and absence of outgoing bearer metadata over plaintext and optional TLS.
`make up` applied the live migration and intentionally stopped at the missing
Resend API key. No email was sent; full product deployment still awaits the API
key and configured sender. Delivery continues on `dev-env-42` in
[PR #43](https://git.sendico.io/tech/sxchnge/pulls/43).
## Independently installed product API versions — task #44
[Task #44](https://git.sendico.io/tech/sxchnge/issues/44), branch
`product-api-44`, is delivered in
[PR #45](https://git.sendico.io/tech/sxchnge/pulls/45). The PR targets
`dev-env-42`: merge [PR #43](https://git.sendico.io/tech/sxchnge/pulls/43) into
`main` first, then retarget #45 to `main` before merging. Owner: `tech`; project:
SXCHNGE; label: backend.
The product registry explicitly installs v1 and supports independently selected
future versions through YAML. Runtime owns shared stores, customer/auth/factor
services and the notification connection; middleware owns HTTP concerns.
Version-owned transports retain their existing contracts and operate on scoped
routers with explicit cookie paths and URL providers. Unknown versions and
invalid, duplicate or overlapping mounts fail startup. Failed installation does
not publish application routes, and cleanup runs once on failure or shutdown.
An architecture test prevents generic runtime imports of concrete transports.
Backend review covered the entire task contribution, including transport moves,
service reuse, DTO/model separation, project errors and structured lifecycle
logging. Initial focused checks exposed compile references after renaming and a
missing leading slash in relative route registration; these were fixed. Review
also tightened mounts to characters safe in both HTTP and cookie paths, validated
origins, preserved cleanup errors and avoided duplicate shutdown error logging.
Final validation passed: `make test` (backend race tests and all frontend suites),
`make lint-backend` and `git diff --check`. Tests cover independently behaving
versions, dependency identity, disabled routes, YAML selection, mount rejection,
scoped URLs and cookie set/delete, bootstrap/installation failure cleanup and
concurrent/repeated shutdown. The Mongo-backed default v1 startup and existing
moved auth/factor contract tests pass. Implementation commit: `d04c8b1`.
Only v1 ships; simultaneous versions are demonstrated with distinct test
installers. Default v1 paths, JSON and operational URLs remain compatible. No
database migration is needed. Internal Go composition APIs changed, and moving an
existing mount requires reauthentication at its new cookie path. Notification
activation URLs remain configured frontend URLs. No live deployment, optional
security scans or merge were performed. The unrelated local notifications sender
configuration remains uncommitted. See [API version installation](api-versions.md).
## Sign-up Facts intake — task #46
[Task #46](https://git.sendico.io/tech/sxchnge/issues/46), branch
`identity-facts-46`, connects the product sign-up service to Sendico's public
individual registry and immutable Facts intake through optional `api.sendico`
configuration. Owner: `tech`; project: SXCHNGE; labels: backend and frontend.
Delivered in [PR #47](https://git.sendico.io/tech/sxchnge/pulls/47), targeting
`main`. Implementation commit: `a584c81`.
The adapter sends full name, normalized email and the local customer reference.
The BFF persists a customer-owned subject binding, immutable facts snapshot and
confirmed receipt before activation email or sign-up acceptance. Activation and
resend enforce the handoff. The shared database source can require majority
writes and primary majority reads without bypassing its connection/transaction
lifecycle. Initial account/token creation remains atomic and uses a secret-derived
activation key, allowing same-key recovery after an intake outage without an
extra cooldown for an unsent token. Archived customers cannot resume sign-up.
Backend and frontend completion reviews covered the full task contribution,
including the public contracts, customer ownership, stable replay namespace,
concurrent claims, error classification, credential handling, logging, metrics and
DTO/mapper/service/model/provider separation. UX review preserved input, loading
suppression and confirmation-based success. Typed support guidance reaches both
sign-up and verification resend/confirmation through the shared auth package;
EN/RU feedback preserves live-region semantics and existing responsive controls.
The new localized widget fixture initially double-disposed the app-owned locale
provider; the fixture ownership was corrected. Focused integration tests also
exposed the initial-token cooldown issue, which was fixed in the creation flow.
Individual registration is not idempotent upstream. A durable claim prevents
speculative repeat registration after an ambiguous result; reconciliation uses
the saved request reference and authoritative Sendico records. Intake replay is
safe after receipt loss and retains original assertions. Actual provider IDs,
the endpoint and a same-account Vault-rendered access token must be provisioned
before enabling this integration. Tests use simulated HTTP and disposable MongoDB;
no live provider resources, live email, deployment, merge or optional security
scan is part of this delivery. The unrelated notifications runtime configuration
is excluded. Configuration, rollout, telemetry and recovery details are in
[Sign-up Facts intake](signup-facts.md).
Final validation passed: `GOFLAGS=-p=2 make test` (all backend race tests and
`make test-frontend` suites), `make lint-backend`,
`dart format --output=none --set-exit-if-changed` for all 11 changed Dart files,
and `flutter analyze` in `frontend/app` and `frontend/packages/sxchnge_auth`.
Focused Go race suites and Flutter backend/provider/widget tests also passed;
the diagnostic public API run
`go -C backend/product test -race -v ./auth/http/v1/public -count=1 -timeout=90s`
passed. The initial unrestricted `make test` failed when Testcontainers could
not start its reaper before the Mongo fixture deadline. Lower Go package
parallelism resolved that infrastructure contention. The next full run exposed
the new locale fixture disposal error noted above; the corrected final run
passed, including EN/RU support states and verification recovery. No checks were
weakened or unrelated work changed.
## Customer identity verification — task #48
[Implementation and rollout](identity-verification.md) adds optional verification
on authenticated onboarding and later from the account: legal profile, ID photo
and selfie holding the same ID. Real adapters replace demo KYC/limits in backend
mode. Sendico's existing backoffice owns decisions and the reviewed Limits profile;
SXCHNGE has no local tenancy, roles or policy engine. Provider-subject uniqueness,
private bounded uploads, stable retries, exact decimal policy projections and
explicit upgrade-pending feedback are included. The unrelated notifications
runtime configuration is excluded.
Delivered in [PR #49](https://git.sendico.io/tech/sxchnge/pulls/49), branch
`identity-verification-48`, implementation commit `1354551`. It is stacked on
[sign-up Facts PR #47](https://git.sendico.io/tech/sxchnge/pulls/47), which must
merge first, and requires
[Sendico PR #2886](https://git.sendico.io/tech/sendico/pulls/2886) to be deployed
before enabling the new flow. Tracking tasks:
[SXCHNGE #48](https://git.sendico.io/tech/sxchnge/issues/48) and
[Sendico #2885](https://git.sendico.io/tech/sendico/issues/2885).
Completion review covered the full backend and frontend contribution, including
subject ownership, private uploads, idempotent retries, session changes, exact
limits, DTO/mapper/service/model/provider boundaries, and the UX/measurement
checklists. Review fixes included duplicate-binding error classification,
upgrade-pending feedback, bounded response timeouts and stale-session protection.
Final validation passed: `GOFLAGS=-p=1 make test` (all backend race tests and all
frontend suites; 79 app tests), `make lint-backend`, changed-Dart formatting,
and `flutter analyze` in the app and shared KYC package. Focused KYC adapter and
onboarding tests passed (20 tests), as did focused Go identity/provider/HTTP
checks. An initial parallel run hit the existing Testcontainers reaper deadline;
serial Go package execution resolved local Docker contention without weakening
checks. No optional security scan, live customer proof, email, production
approval, deployment or merge was performed. Generated outputs remain ignored.
Reconciliation follow-up: the shared Limits assignment implementation and its
persisted-state validation are owned by Sendico PR #2886. The subsequent
[Sendico Facts review PR #2887](https://git.sendico.io/tech/sendico/pulls/2887)
retains both Product providers, Backoffice services and KYC destinations, and
routes proof cases to Identity proof review while blocking generic login linking
and assignment. Apply #2886 before #2887 and deploy the combined Sendico result
before this SXCHNGE flow. The public proof/limits contract is unchanged; this
SXCHNGE follow-up changes documentation only and requires consistency/diff checks.