791 lines
49 KiB
Markdown
791 lines
49 KiB
Markdown
# 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).
|