# Local development Task [#42](https://git.sendico.io/tech/sxchnge/issues/42), [PR #43](https://git.sendico.io/tech/sxchnge/pulls/43), branch `dev-env-42`. ## Build and start Requires Make, a POSIX shell and a local Docker engine with Compose v2 and BuildKit. No host Python, Go, Flutter, jq or curl is required. Make runs bootstrap tooling in Docker with the local Docker socket and workspace mounted. Remote Docker engines are unsupported. ```sh make build && make up ``` The build resolves the latest stable releases and pins their digests in ignored `.local/dev/images.env`. Prereleases are excluded, resolution failures stop the build, and previous pins are retained on failure. Flutter is pinned to the stable release commit. Tooling uses a freshly pulled stable Docker CLI digest. Generated protobuf bindings and Flutter output are built locally/in Docker and never tracked. Compose is at `infra/local/compose.yml`; Make supplies the repository root as its project directory. Dockerfiles are in `infra/images/`. Runtime and startup files are grouped by service under `infra/local/`. The stack consists of Vault, Mongo, NATS, notifications, BFF and Caddy serving the frontend. On first startup, bootstrap initializes/unseals persistent Vault, provisions AppRoles and secrets, initializes Mongo replica set `rs0`, configures Vault's Mongo plugin, and starts authenticated NATS JetStream. Product startup fails explicitly until Resend credentials exist. Infrastructure remains available for provisioning. ## Vault layout Paths below are inside the KV v2 mount `kv`; the API inserts `data/` after `kv/`. ```text kv/ ├── services/ │ ├── bff/jwt # jwt │ └── notifications/ │ └── email/resend # api_key only └── infrastructure/ ├── nats/auth # token; read by NATS and notifications └── mongo/bootstrap # local administrator and replica-set bootstrap material ``` Every secret has an owning service or infrastructure component. Service policies permit only the exact paths they consume. Applications cannot read bootstrap credentials. Mongo application users are dynamic secrets under `database/creds/bff` and `database/creds/notifications`, not KV entries. They have `readWrite` only on their respective `sxchnge` / `sxchnge_notifications` databases. Vault uses `mongodb-database-plugin` and its own `vault-manager` account, whose password is rotated immediately after initial configuration. There are no frontend certificates, notification certificates or generic `tls` entries in KV. Browser certificates belong to Caddy; internal gRPC is plaintext on the trusted private Docker network without caller authentication. ## Resend Open [Vault](http://127.0.0.1:18200) and edit `kv/services/notifications/email/resend`: | Field | Value | | --- | --- | | `api_key` | Your Resend API key | The driver and sender settings belong in `infra/local/notifications/runtime.yml`: ```yaml email: driver: resend settings: api_key_env: EMAIL_API_KEY sender_address: "your-verified-sender@example.com" sender_name: "SXCHNGE" ``` Set your verified address in that file and rebuild. It is intentionally empty by default, so notifications cannot start with an unconfigured sender. Vault Agent injects only the API key; it does not generate email configuration. Local administrator login material is in private `.local/dev/admin/init.json`. Product containers cannot access it. Alternatively use the hidden terminal prompt: ```sh make email make build && make up ``` No placeholder key or delivery fallback is installed. Startup validates presence; it does not send an email or validate domain ownership with Resend. Updating the API key causes Agent to restart notifications. Driver/sender changes require an image rebuild. Existing API keys survive bootstrap. Migration removes legacy driver/sender fields from the current Vault secret with a version-checked write; the preceding version is preserved in Vault history and the private archive. ## Browser HTTPS and internal connections Caddy serves Flutter files and proxies `/api/*` to BFF over HTTP. Open [https://localhost:5126](https://localhost:5126). HTTPS preserves Secure/HttpOnly cookies and same-origin API access. Caddy uses `tls internal` and manages its local CA, certificates and renewal in the `caddy-data` volume. The `caddy-config` volume stores Caddy runtime configuration. Neither volume nor Caddy has Vault access. After frontend starts, Make exports only the public Caddy CA to `.local/dev/ca.pem`. Trust it in your browser/OS to avoid certificate errors. This is a new CA when migrating from Vault-issued browser certificates; the old CA will not trust it. Trust installation is not automated. A CLI check can use: ```sh curl --cacert .local/dev/ca.pem https://localhost:5126/api/v1/health ``` BFF calls notifications over plaintext gRPC without caller authentication. Mongo and NATS also stay within the private Compose network; no internal service publishes a host port. Only frontend and Vault bind host loopback ports. TLS remains optional in the reusable notification client/server for deployments that explicitly configure it; local runtime configuration contains no certificate settings. Caddy behavior follows its [internal TLS](https://caddyserver.com/docs/caddyfile/directives/tls) and [automatic HTTPS](https://caddyserver.com/docs/automatic-https) documentation. ## Mongo transactions and credential rotation Mongo is an authenticated single-node replica set (`rs0`), supporting multi-document transactions. It has no failover or redundant data copy. A Mongo restart interrupts access even though application credential replacement does not. The shared database wrapper authenticates with each service's AppRole and obtains an independent token/lease for each pool. At half the shorter token/lease lifetime, it verifies a replacement pool before publishing it. Active operations, cursors and full transactions retain the old pool until they finish, then it closes and its token is revoked. Nested stores and commit retries keep the same pool. Failed refreshes retain valid credentials and retry with bounded backoff. After expiry, database operations fail closed until automatic recovery. Administrative revocation, infrastructure outages and operations longer than their lease can still interrupt work. Repository consumers use `source.Source` operation scopes; `source.Fixed` adapts externally owned static/test connections. ## Migration and persistence Bootstrap migrates the old flat `bff`, `email` and `nats` paths into the hierarchy above, and moves `bootstrap/mongo` to `infrastructure/mongo/bootstrap`. It copies with CAS protection, compares values, and refuses to overwrite conflicting destinations. After consumers use the new paths, it removes old KV metadata and obsolete `tls`, `frontend-tls`, `notifications-tls` entries. Readable old version history and metadata are archived privately in `.local/dev/migrations/organized-v1/` before removal. That archive is administrative recovery material; applications do not mount it. Old deleted/destroyed versions are not recreated. The old PKI engine is not used or provisioned by this stack; an existing PKI mount is retained to avoid destroying its non-exportable signing key and history as part of a KV reorganization. Vault, Mongo, JetStream and Caddy data use project-scoped named volumes. Private bootstrap material lives in `.local/dev/`. `make down` preserves both. Vault's local unseal file allows automatic unsealing on restart. This local convenience and loopback HTTP Vault endpoint are for development. Keep bootstrap files paired with their matching volumes; do not discard one side independently. ## Verification ```sh make ps make logs make down make test-dev-tooling # shell analysis, failure paths, migration conflict checks make test-dev-infra # policy isolation, real Mongo transactions and lease lifecycle make test-dev-rotation # temporarily deploy BFF/frontend and exercise short leases make test-dev # requires all six services; verified frontend/API HTTPS ``` Rotation validation temporarily shortens the BFF role TTL, recreates BFF/frontend, and requires three replacements with unchanged BFF PID and zero unexpected transaction-backed HTTP responses. It restores TTLs and removes services it started. No verification command sends email. Backend validation remains `make test` and `make lint-backend`, using the host SDKs documented in module guides. Bootstrap archives and removes obsolete `caller` and `services/notifications/callers/bff` KV entries after updating Agent policies/templates. NATS authentication remains enabled.