Back to blog
2026-06-14 Thijs Creemers

Release: v1.0.1-alpha-32

We just tagged v1.0.1-alpha-32. This release hardens the payments library (idempotent off-session charges, boot-time credential validation, webhook correlation), adds tooling and scaffolding that keep the ports convention from eroding, fixes a boundary-tools packaging bug, and ships a new sizing & scaling guide.

Added: idempotency keys for off-session payments

IPaymentProvider/create-off-session-payment now accepts an optional :idempotency-key — the stable business identity of a charge, e.g. "incasso-<subscription>-<period>" (BOU-82). A retry after a crash or lost response returns the original charge instead of double-charging.

  • OffSessionPaymentRequest schema gains the optional :idempotency-key.

  • The Stripe adapter sends it as the Idempotency-Key header on POST /v1/payment_intents.

  • The mock provider caches results per key (per instance) and replays the first result for a repeated key; a new make-mock-provider factory is used by wiring and tests.

  • Mollie is unchanged — off-session still throws :not-implemented.

Changed: fail-fast on missing payment credentials

ig/init-key :boundary/payment-provider previously constructed adapters without checking credentials (BOU-77). With STRIPE_API_KEY / STRIPE_WEBHOOK_SECRET (or MOLLIE_API_KEY) unset, Aero #env resolved to nil and the system booted fine — the failure only surfaced at runtime: the first charge hit Stripe with Bearer null (401), and the first webhook verified HMAC against a nil secret and silently rejected events. A forgotten env var shipped a payment system that took no money.

Credentials are now validated at boot: :stripe requires :api-key + :webhook-secret, :mollie requires :api-key, :mock requires nothing. nil and blank both count as missing, and all missing keys are reported together via ex-info {:type :config-error …} naming each key and env var.

Note
This is a behaviour change — a deploy that was previously booting with missing payment credentials (and failing only on first use) will now refuse to start. Set the credentials before upgrading.

Fixed: webhooks can be correlated to their checkout

process-webhook surfaced the adapter-internal UUID as :provider-checkout-id, but that UUID was never returned at creation — consumers stored the cs_/tr_ session id and could never match an incoming webhook to it (BOU-78). The Mollie adapter had the same defect.

The fix is port-level and provider-agnostic: CheckoutResult gains a required :correlation-id and WebhookResult an optional one. Adapters echo the internal UUID (already embedded in PSP payment metadata) on both creation and webhook, so consumers correlate every webhook to a stored checkout using only CheckoutResult fields — no extra API calls. Webhook :provider-checkout-id now means the genuine session id only (absent for Stripe payment_intent.* events) instead of aliasing the UUID.

Added: bb check:ports hexagonal boundary gate

Documentation alone won’t keep the ports convention intact — boundary-license proved it erodes silently (BOU-79). check:fcis only guards the pure-core side; nothing verified the hexagonal side. The new check:ports gate (static ns-form analysis, BOU-81) enforces three rules:

  1. Module completeness — a directory with both core/ and shell/ must define a ports.clj with at least one defprotocol.

  2. No cross-module shell coupling — a boundary.X.shell.* namespace must not require another module’s shell.persistence / shell.service; cross-module access goes through boundary.Y.ports.

  3. Web/HTTP layers never require .shell.persistence directly.*

Legitimate exceptions use ^:boundary/allow-direct ns metadata or a .boundary/check-ports.edn allowlist. It’s wired into bb check (incl. --quick), CI, and the pre-commit hook. Validated against boundary-license, it reproduces the BOU-79 audit exactly (28 violations); the monorepo passes clean (21 modules, 0 violations).

Added: project template mandating ports.clj

New Boundary projects had no agent-guidance file, so the ports convention was invisible to developers and coding agents (BOU-80). generate-project now emits an AGENTS.md documenting the FC/IS + ports architecture with ports.clj marked REQUIRED, plus a thin CLAUDE.md pointing at it. The generated bb.edn now also wires the check:ports task, so the gate the template mandates is actually runnable in a fresh project. bb ai docs --type agents learned a matching "Ports & Protocols" section.

Fixed: boundary-tools jar shipped without its error catalogue

boundary.tools.help realized the error catalogue at namespace-load time and threw when boundary/devtools/error_catalog.edn was absent (BOU-76). Because bb.edn :requires loads help eagerly, every bb task died at startup in consumer projects that depend on boundary-tools without boundary-devtools. The catalogue read is now pure (nil → {}, never throws) and lazy (a delay), help-error degrades to a "catalogue not available" message, and build.clj copies error_catalog.edn into the boundary-tools jar so consumers get it.

Fixed: pom scm tag aligned with git tag for cljdoc

libs/*/build.clj wrote <scm><tag>v<version> while git tags were bare semver, so cljdoc re-clones pointed at a non-existent ref (BOU-75). All build files now emit the bare version as the scm tag, matching the published git tags — alpha-32 poms are bare.

Docs: sizing & scaling guide

A new architecture guide covers three scaling axes — vertical (config knobs), horizontal (N replicas), and functional decomposition (slicing modules into independent services) — with a horizontal-readiness matrix, deployment topologies, per-module sliceability, and a production checklist (BOU-84). Read it under Architecture → Sizing & Scaling.

Version alignment

All 25 libraries bumped to v1.0.1-alpha-32 to maintain lockstep versioning.

Upgrade

Re-run the installer to pick up the latest release:

curl -fsSL https://get.boundary-app.org | bash

The payments schema additions (:idempotency-key, :correlation-id) are additive. The one behaviour change to watch is boot-time credential validation: if a deploy was running with missing payment credentials, set them before upgrading or the system will refuse to start.

Feedback and issues welcome on GitHub.