All notable changes to the Wagoe Framework will be documented in this file.
Note: entries below the "Renamed" section predate the Boundary → Wagoe rename and keep their original
boundary-*names,:boundary/*keys, andorg.boundary-appcoordinates on purpose — they describe releases that shipped under those names.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning
from 1.0.0 onwards. Until then, breaking changes are permitted between beta
releases — see the Stability & Versioning policy
for what is public API, what is internal, and how deprecations are announced.
Note — the version scheme changed, and the number went down. Releases were
1.0.1-alpha-Nup to1.0.1-alpha-42(2026-07-19); they are1.0.0-beta-Nfrom1.0.0-beta-1(2026-07-23). The old scheme read as a patch release of a shipped1.0, which had never happened.Because Maven sorts
1.0.0below1.0.1, every beta compares as older than the last alpha. Anything resolving "newest" will pick1.0.1-alpha-42over the current release — pin exact versions. The1.0.1-alpha-*line is discontinued and receives no fixes.There is no separate
1.0.0-beta-1entry below. Its changes were still in[Unreleased]when1.0.0-beta-2shipped eight days later, so they are recorded under the1.0.0-beta-2heading.
A generated project works end to end. 1.0.0-beta-5 could create one, but the
first commands it told you to run did not: (status) was undefined, the module
bb quickstart scaffolded answered 404, devtools reached no classpath, and
bb scaffold ai died resolving wagoe-ai. Most of the 42 fixes are on that path.
Upgrade if you use JWT auth — any Authorization: Bearer value authenticated
(BOU-374). Two breaking changes besides: modules emit Reitit route data, and a
module's web routes mount under /web. The Changed section says what moves.
examples/shop — a generated application you can read and run (BOU-300).
Regenerated by bb example:regen, booted and asserted in CI, so it cannot drift.
wagoe doctor (BOU-324). One diagnostic front door — environment, config,
commands, setup — ending in a single next action.
bb check:error-shape (BOU-323). Fails a boundary throw with no :type, and
a {:success? false} whose :error is not {:type … :message …}.
bb check:isolation, and an isolated build per library in CI (BOU-304).
Proves "independently publishable libraries" rather than asserting it.
bb bump <version> (BOU-316). Rewrites exactly the locations check:versions
finds, prints a diff, and verifies the result. The README snippet rewrote nothing.
com.wagoe/wagoe-config (BOU-306). Config loading and the typed accessors as
a library, so libraries stop resolving wagoe.config at runtime. Makes it 31.
bb scaffold integrate writes the config key, and --dry-run works (BOU-310).
That key is the whole registration — no require to add by hand.
Scaffolded modules are discovered, and an unknown module key fails the boot (BOU-311). A misspelled key used to look exactly like a working one.
The scaffolder emits shell/module_wiring.clj (BOU-309). The one file its own
integrate step required and it did not write.
:wagoe/router settings now reach the router (BOU-357). :adapter is gone,
and an unrecognised :coercion fails the boot instead of silently giving you Malli.
Two routes that can both match a request now fail the boot (BOU-356).
:wagoe/router {:conflicts nil} restores the old behaviour if you must ship first.
Platform has a database port (BOU-303). Require wagoe.platform.database or
wagoe.platform.ports.database; function names and arities are unchanged.
Feature flags take the environment as a parameter (BOU-302). Breaking:
1-arity calls move to wagoe.platform.shell.feature-flags.
Modules emit Reitit route data; the normalized format is gone (BOU-331,
ADR-037). Breaking: a route map becomes ["/users" {:get {:handler ns/fn}}].
A library can serve HTTP without platform knowing it exists (BOU-330). Routes
arrive as one collection, and a module names its own :web-prefix.
A scaffolded module's web routes mount under /web (BOU-330). Their URLs
move, from /<path> to /web/<path> — the documented contract, finally kept.
A generated project's own code lives under its own namespace (BOU-360).
Existing projects keep booting: discovery falls back to wagoe.<module>.
A generated config.clj is 63 lines instead of 404 (BOU-326). Each module
describes its own graph. Nothing to do for an existing project.
A healthy project produces no diagnostic noise (BOU-324). Four checks warned about things that are not problems, which teaches readers to skim past the rest.
bb check:fcis reads code rather than lines, and its requires are an allowlist
(BOU-301). A newline between ( and throw no longer hides the call.
check:ports catches any reach into another module's shell (BOU-307), not two
named suffixes. Composition roots stay exempt, since wiring must name adapters.
check:versions covers documentation as well as source (BOU-317). 96 locations
rather than 59 — the ungated half was the half users copy from.
check:branch-protection also verifies that CI can run at all (BOU-315).
A missing pull_request: trigger, or a release path that routes around CI.
The monorepo's wagoe.config is now wagoe.system-config (BOU-306). Two
namespaces of that name on one classpath shadow each other.
One clock read per library instead of eleven (BOU-302). All private now; every call site was already in a shell namespace, so no behaviour changes.
wagoe.platform.shell.interfaces.http.middleware (BOU-372). A parallel
correlation-id / logging / exception stack no application ran. Use the interceptors.
wagoe.platform.shell.interfaces.http.routes (BOU-358). A second, documented
route builder nothing wired — and it applied no security stack. Use module contributions.
realtime's UserJWTAdapter (BOU-305). It reached into wagoe.user.shell.auth
undeclared and threw from Clojars. Supply your own IJWTVerifier.
bb scaffold ai works in a generated project (BOU-401). It never resolved
wagoe-ai and died on the classpath; bb setup ai failed the same way, silently.
An application using both the admin and the search module starts again
(BOU-392). Their routes overlap — /web/admin/search/:index-id against
admin's generic /web/admin/:entity/:id — and the boot-time conflict check
refused the pair, so the whole application failed to start. Reitit settles
those at search, taking the literal segment before the parameter one; the
check now decides a pair at the first segment that separates them instead of
requiring every segment to. Genuinely ambiguous pairs and catch-alls still
fail the boot.
Admin tables reliably format date and timestamp values (BOU-382), including
date-only/zone-less shapes. Set :date-time-format; invalid patterns fall back.
Static assets revalidate instead of caching blind (BOU-389). No asset sent
Cache-Control, so a deployed fix could go unseen for days.
Form validation errors reach the screen (BOU-381). htmx discarded the 400 that carried them, so pressing Create or Save did nothing at all.
…and they arrive as one kind of message (BOU-393). On the create-user and registration forms a password error rendered in the form-level panel while the email error beside it was a line of inline text, so a single submission produced two unrelated styles of complaint. The create-user form is also centred rather than pinned to the left edge of the page, and no longer repeats the page header's title beneath it.
Two tests bound a port they had already released (BOU-377), failing the
full suite intermittently. bb test:all now names what exited non-zero.
The dev dashboard's busy-port fallback never ran (BOU-377). A busy 9999 was a hard failure instead of the documented scan to 10009.
(status) points at the admin UI that is running, and closes its box
(BOU-394). In a generated project it named no admin URL at all; in this
repository's REPL it advertised /admin, which 404s — the path was read from
a config key that never exists, so the fallback was the only route it ever
took. Both now read the prefix the router actually mounted, and say nothing
when the admin module is not running. The panel's title row was also three
characters narrower than the rows beneath it.
(status) and (modules) list modules, not Integrant keys (BOU-399).
They filtered the system's keys against a blocklist and reported the rest by
raw name, so one module arrived as several of its parts —
admin-routes, admin-schema-provider, … rather than admin. That is the
shape BOU-319 replaced elsewhere; this copy never got it, and now shares the
one implementation. Devtools' own components no longer count as modules
either: you did not add the tool you are asking through.
A security test rejected one run in a hundred (BOU-377). It searched the
response for "120", which the random correlation-id UUID sometimes contains.
The brand logo is Wagoe's (BOU-379). The four assets behind brand-logo
were renamed to wagoe-* but still drew the Boundary mark and wordmark. The
dark variant was not lighter than the light one, and :variant :icon was
byte-identical to the full lockup. bb check:no-boundary cannot see artwork.
The dev dashboard names your database instead of its Java class (BOU-396).
The Environment panel read class wagoe.platform.shell.adapters.database. …
PostgreSQLAdapter @ localhost, over three lines. It now reads
PostgreSQL @ localhost.
The dev dashboard sees the system you actually started (BOU-400). It read
the running system from integrant.repl.state/system, which only a REPL
(go) fills, so every ordinary server start — clojure -M:run server,
bb quickstart, a generated project — left it looking at the three
components it could reach through its own wiring. It reported those as
"3 components · all healthy" while forty more ran unseen. wagoe.main now
starts through wiring/start!, which records the system, and the dashboard
reads that when there is no REPL. Its module list comes from the same place
as (status)'s, so the two no longer disagree.
bb scaffold field/endpoint/adapter fail when the module is not there
(BOU-364). field left a migration behind for a column its schema would reject.
bb scaffold --output-dir reads the namespace from the project it edits
(BOU-364), not the one you run it from.
bb scaffold generate no longer overwrites a module without asking (BOU-308).
--force was declared, threaded, and read by nothing.
:tenant-membership was nil on every request (BOU-373), so require-tenant-member
refused all of them. Authentication now runs ahead of the middleware that needs it.
A malformed request answered 500 (BOU-321). It is a 400 naming the fields, and
in dev it carries the BND code — which had no name for :validation-error.
"Your code" in an error means your code (BOU-395). The stack-trace filter recognised the framework by listing four of its libraries, so the rest of them were reported to you as yours — while your own namespaces, matching nothing, were folded away under "Framework (N frames)". An error thrown in an application's own code answered "No user code frames found". The framework is now the side that gets enumerated, and anything else is yours.
58 exceptions thrown at boundaries say what kind of failure they are (BOU-323). An untyped throw on a request path is a 500 that could have been a 404 or a 400.
The user library's authentication and MFA results carry a typed error (BOU-323). Direct callers of the two shell namespaces are affected; see UPGRADING.md.
The error-shape allowlist is empty (BOU-323). It shipped with 81 findings on 18 August; what the gate protects from here is a new violation, not a backlog.
One validation API instead of three (BOU-323). One of them chose its return shape at runtime from an environment variable; that fork is deleted.
wagoe add workflow created a module whose tables did not exist (BOU-326).
Two hand-maintained copies of a graph the module already described.
A module can no longer ship a component nothing wires (BOU-326). Seven exemptions remain, three of them the same defect elsewhere; emptying it is BOU-346.
The monorepo ran the tenant module whether or not it was configured (BOU-326),
and wired neither :wagoe/settings nor the AI service it had switched on.
Two admin helpers had never worked (BOU-326). They built their graph against
three keys no application wires. Removed; the module's real graph is ig-config.
The e2e suite runs in CI again, and a red e2e test blocks the merge (BOU-297).
52 tests sat behind if: false; turning them on found real flakiness, now fixed.
A pull request from a fork ran no tests at all (BOU-315), leaving the one required context pending rather than failing. Pending is not red.
A tag on an untested commit could publish 30 immutable artifacts (BOU-314).
Publishing now requires a green All Tests Passed on the tagged commit.
A scaffolded module booted but served nothing in a generated project (BOU-312). The first-run smoke now curls the module it scaffolded and requires a 2xx.
devtools reached generated projects nowhere, and wagoe add ai wrote nothing
(BOU-318). wagoe add searched deps.edn as text and matched the :mcp alias.
The first thing bb quickstart tells you to run did not exist (BOU-319).
A generated dev/user.clj now has (status), (modules), (routes), (fix!).
The MCP server advertised seven resources and could serve none in a project (BOU-320). The knowledge base ships in the jar and is read from the classpath.
The quickstart's last step 404'd (BOU-328). It ended on a path in a module a generated project does not enable, at a prefix admin does not use.
The getting-started tutorial showed code the scaffolder does not write (BOU-327). A test now calls the generators and checks every name the page shows exists.
Two guides were unreachable, and 29 ADRs did not say what they were (BOU-329). Both are in the nav; every ADR states a status and names its implementation.
Stale install instructions on the first page users copy from (BOU-313). It pinned a discontinued line that Maven sorts newer than every beta.
A generated project no longer ships bb deploy (BOU-325). It published
Wagoe's libraries to Clojars, from a user's project.
bb check:deps was a green row that could not fail — or worse (BOU-325). In a
project with its own libs/, it reported the project's namespaces as violations.
The documentation version scanner read one match per line (BOU-317 follow-up),
so a second coordinate on the same line stayed stale through a verified bb bump.
MFA secrets and backup codes no longer reach handlers (BOU-373). Session
authentication put the whole user record on :user; only :password-hash was stripped.
Any Authorization: Bearer value authenticated (BOU-374). Upgrade if you use
JWT auth.
Running as more than one process. 1.0.0-beta-4 could serve an application from
a single JVM and little else: cross-module calls had a protocol seam but nothing
that crossed a network, the prod profile could not boot, and the deployment
documentation described topologies with no reference to run them from.
This release makes the seam real. A module can be served over HTTP and called through the protocol its callers already use, one or several modules can be booted as their own service, and modules can tell each other things asynchronously through a new event bus. A circuit breaker keeps a service that is down from taking its callers with it, and the state behind all of it lives where every replica can see it rather than in one JVM's memory.
The other half is what looking closely turned up. Holding the cache and
job-queue adapters to a shared contract found twenty-one places where they
disagreed — including three different answers to what order jobs come off a
queue in, two of them newest-first. A failed production boot logged the database
password. install.sh accepted a JDK too old to run any of this. Those were all
shipped behaviour, and none of it was visible from the suite that was supposed
to be watching.
Three HikariCP pool keys that no build has ever applied (BOU-89). The pool
schema is :closed, so a config setting any of them failed the boot.
Integrant config for four libraries that register none (BOU-284, BOU-286).
wagoe add jobs|calendar|reports|ui-style wrote a key nothing reads.
bb scaffold new / wagoe scaffolder new (BOU-259). A second project
generator, drifted until it could not boot, test or build. Use wagoe new.
A module can run in another process without its callers knowing (BOU-90).
wagoe.platform.shell.rpc serves any port over HTTP; call sites do not change.
service launch mode (BOU-91). java -jar wagoe.jar service payments boots
only the modules named, plus the platform they need.
Reference deployment topologies (BOU-89). Compose files for replicas and for per-service, a Kubernetes manifest, and a page on when each applies.
libs/events — an asynchronous event bus (BOU-93). In-memory and Redis
Streams, with consumer groups, at-least-once delivery and a dead-letter stream.
A circuit breaker for the remote-port adapter (BOU-285). State lives in the
cache port, so replicas share one breaker. Opt-in: no :cache, no breaker.
wagoe new --no-user (BOU-234). Authentication and its four tables are now a
choice rather than the only option.
Realtime topics accept in-process subscribers (BOU-233), so server-side code no longer needs a second pub/sub beside this one.
Adapter contract suites for cache and jobs (BOU-288, BOU-289). One sweep per library against every adapter; they found twenty-one divergences.
bb check:changelog — a branch changing shipped src/ must add an entry.
Thirty PRs had merged in eleven days without one between them.
Gates — required checks verified against the job names CI emits (BOU-277), and every third-party namespace a library requires must be declared (BOU-273, BOU-276).
Nightly first-run matrix, and three more cells (BOU-232). Ubuntu, Fedora and Arch, plus an old-JDK machine and one with no network.
Four dev-workflow Claude Code skills (BOU-235) — wagoe-doctor,
wagoe-migrate, wagoe-scaffold, wagoe-debug. Closes BOU-237, BOU-238, BOU-240, BOU-242.
Job dispatch is FIFO within a priority, on every backend (BOU-289). Behaviour change: the three backends had three different orders.
The cache adapters agree about expiry, batch reads and patterns (BOU-288). Behaviour change: an expired key now reads as absent from every operation.
Heavy test dependencies moved out of the shared :test alias (BOU-260). They
live in :test/pg, :test/otel, :test/http; :test/all composes them.
platform no longer requires any module's wiring (BOU-131). A consumer ships only the jars it uses; the layer that emits a key registers it.
install.sh accepted any JDK, including ones too old to run Wagoe (BOU-232).
It reads the major version now and verifies what it installed.
The prod and acc profiles could not boot (BOU-89). :port arrived as a string
against a pos-int? schema, on top of the three pool keys above.
A deleted job stayed on the queue (BOU-289), still counting towards
queue-size, and work behind it waited for a poll that might not come.
The in-memory cache lost concurrent writes while reclaiming expired entries
(BOU-288). delete-key! and expire! are one swap-vals! now.
A BigInteger beyond 64 bits was silently lost by the Redis cache (BOU-288). The write reported success and the read reported a miss.
bb create-admin could not create a user at all (BOU-266). Without an admin
user the admin UI redirects to a login nobody can pass.
H2 is now file-backed in dev (BOU-265). In-memory H2 is private to one JVM, so migrations, the admin user and the app each got their own empty database.
Scaffolded modules now pass bb check (BOU-267). Two of the 36 warnings were
real: a protocol method declared twice, and a service calling a method that is not there.
bb check reported failures a user could not act on (BOU-264). Five checks are
framework-only; they are skipped outside this repo, and named when skipped.
bb check's Config doctor gate could never fail (BOU-270). It invoked
bb doctor without --ci, which prints errors and exits 0.
Every bb ai subcommand failed in a generated project (BOU-272). The
dependency is injected via -Sdeps now, as bb scaffold already did.
bb migrate create threw a ClassCastException (BOU-271), so the documented way
to add a migration failed for everyone; it also wrote to a shadowed directory (BOU-274).
bb scaffold field listed a file it never opened (BOU-275), and generated
projects lacked the check:ports task bb check shells out to (BOU-80).
The AI CLI discarded unknown options and swallowed the failures it was built to report (BOU-279, BOU-280). An exhausted balance was reported as a rate limit.
wagoe new into a directory you cannot write surfaced a stack trace (BOU-232)
instead of a permissions message.
The documented ways to run the app now run the app (BOU-243), and the documented
way to run wagoe-mcp no longer corrupts the protocol (BOU-105).
bb ai gen-tests emitted tests that did not compile (BOU-239), and bb i18n:scan
— a required CI job — could not report anything (BOU-241).
The scaling and deployment documentation described a system from several tickets ago. Two entries told a reader to do something that breaks.
Five quality items, each with a gate behind it (BOU-92, BOU-151, BOU-61, BOU-253, BOU-245). The dependency allowlist is empty.
A failed production boot no longer logs the database password (BOU-244). Integrant's ex-data carries the config map for the key that failed.
js-yaml and brace-expansion advisories cleared in the docs build (GHSA-5p4m-2wfm-xmqj, GHSA-mh99-v99m-4gvg, GHSA-rgw5-rvv9-x895). Dev-only.
Everything a new project touches. 1.0.0-beta-3 shipped a scaffolder whose
migrations were never applied, so the sample module bb quickstart creates had
no database table — and reported success while doing it.
Scaffolded migrations are now applied (BOU-256). The scaffolder emitted a
filename migratus does not discover, so bb quickstart reported 8/8 over zero migrations.
bb doctor no longer passes configs the application cannot load. An
unparseable config.edn left every check inspecting an empty map and passing.
install.sh supports Fedora and the RHEL family, and checks for which,
which babashka's installer calls and minimal Fedora images do not ship.
The admin UI is reachable at /web/admin without the trailing slash, and
wagoe add admin now says that bb create-admin is needed to log in.
bb db:seed. Previously advertised and printed "not yet implemented". EDN
seed files, one transaction, refused outside development unless --force.
A production build path for generated projects — main.clj, :run and
:build aliases, a Dockerfile, and graceful shutdown on container stop.
A first-run smoke test in CI walking install → wagoe new → bb quickstart →
serving app in a bare container, asserting on HTTP rather than exit codes.
clojure -M:repl-clj and export
WAG_ENV="development". Neither exists in a generated project.The release that makes the documented install path true. 1.0.0-beta-2
advertised a first-run flow that did not work on a machine with nothing
installed; every failure below was measured in a clean ubuntu:24.04
container, not inferred.
New projects default to SQLite (previously H2). It needs no server and, unlike in-memory H2, the data survives a restart.
bb setup defaults to SQLite on all three entry points — interactive menu,
bb setup ai, and flag invocations.
bb quickstart no longer runs the configuration wizard over a config wagoe new
has just written. Pass --preset <name> to reconfigure deliberately.
bb quickstart's banner says it will verify rather than start; it never
started the app, and claiming otherwise sent people hunting a failure that had not happened.
install.sh failed three separate ways on clean Linux (BOU-226): no
prerequisite check, an unbound variable under set -u, and an assumed sudo.
A new project could not complete its own quickstart (BOU-228). The wizard defaulted to PostgreSQL, which was not installed and not on the classpath.
The generated config used :database-path where the platform's reader looks for
:db, yielding a Malli validation abort at migration time.
/admin returns 404 in a new project — com.wagoe/wagoe-admin ships in
deps.edn but :wagoe/admin is not wired into the generated config (BOU-229).:run alias, -main, or :build alias, so the app
can only be started from a REPL via (go) (BOU-254).First release under the Wagoe name, on the com.wagoe Clojars group.
Clojars coordinates are now com.wagoe/wagoe-<lib> (previously
org.boundary-app/boundary-<lib>). org.wagoe was never claimable.
Website moved to framework.wagoe.com, and subsequently to wagoe.org with the
older hostnames kept as permanent redirects.
pom.xml carrying a vulnerable pin was removed.The framework is renamed from Boundary to Wagoe ahead of the first
public release, and versioning moves to plain SemVer starting 1.0.0-beta-1.
This is a hard rename with no compatibility shims: nothing had been published
under a stable version, so no deprecation window is provided.
Migration — mechanical replacements, in this order:
| Kind | Before | After |
|---|---|---|
| Namespaces | boundary.<seg>.… | wagoe.<seg>.… |
| Integrant / config keys | :boundary/http-server | :wagoe/http-server |
| Maven / Clojars coords | org.boundary-app/boundary-<lib> | com.wagoe/wagoe-<lib> |
| deps.edn local aliases | boundary/<lib> | wagoe/<lib> |
| Environment variables | BND_*, BOUNDARY_* | WAG_* |
| CLI binary | boundary <cmd> | wagoe <cmd> |
| Resource paths | boundary/i18n/translations | wagoe/i18n/translations |
| MCP resource URIs | boundary://… | wagoe://… |
MCP server name (.mcp.json) | "boundary" | "wagoe" |
AGENTS.md region markers | <!-- boundary:installed-modules --> | <!-- wagoe:installed-modules --> |
| Redis pub/sub channel | boundary:realtime:bus | wagoe:realtime:bus |
| Logger name (logback) | boundary | wagoe |
| Repositories | thijs-creemers/boundary{,-examples} | wagoebv/wagoe{,-examples} |
| Sites | boundary-app.org, get.boundary-app.org | wagoe.org, get.wagoe.org |
docs.boundary-app.org is retired; the documentation is folded into
wagoe.org/docs. The GitHub repository transfers preserve history
and leave redirects in place, so existing clones keep working until you update
the remote.
Not renamed, on purpose. "Boundary" is also an architecture term in this
codebase, and those uses are unchanged: the FC/IS boundary rules (ADR-021),
the persistence / HTTP / API / DB boundary, the boundary-check step,
boundary conditions and boundary testing, and "System Boundary" in the PRD.
A bb check:no-boundary gate guards the renamed token families and treats the
prose word as report-only for exactly this reason.
Framework Quality (Phase 0–2, 2026-07) security hardening:
boundary-user: JWT algorithm pinned and startup fails fast on a weak secret — a JWT_SECRET of ≥32 chars is now required, with a separate CSRF secret (BOU-163, #253).boundary-user: MFA TOTP secrets are encrypted at rest and backup codes are hashed (BOU-162, #252).boundary-user: IDOR closed on the user API routes (/users, /users/:id) — a caller can no longer read or modify another user by id (BOU-190, #280).boundary-user: sessions are rotated on password and role change, defeating fixation and stale-privilege reuse (BOU-191, #281).boundary-user: user-management web routes are mounted behind authz guards and the web user-detail page is admin-only (BOU-197, #286, #287).boundary-platform: 5xx responses no longer leak exception internals, and the admin error flash no longer echoes raw exception messages (BOU-161, BOU-182, #250, #260).boundary-platform: hardened security response headers and session-cookie attributes; brute-force lockout and session-fixation coverage; a dedicated authz negative-path suite (RBAC / IDOR / cross-tenant) (BOU-168, #272, #274, #275).boundary-jobs: reliable Redis dequeue — jobs are no longer lost when a worker crashes mid-dequeue (BOU-160, #248).boundary-cli: boundary agents update (+ generated bb agents:update task) refreshes the framework-owned sections of a project's AGENTS.md after a Boundary upgrade. The marker-delimited blocks (gen:fc-is, gen:naming, gen:pitfalls, boundary:available-modules) are re-rendered from the installed CLI's template and spliced in place; everything outside the markers — team notes, custom sections — is left untouched, the installed-modules block is treated as project state, and installed modules stay removed from the refreshed available table (mirroring boundary add). --check exits 1 when the file is stale, for CI. Idempotent (#236).clojure -M:bench hotpaths, dev/boundary/bench/hotpaths.clj) measuring current-vs-proposed implementations for each performance-assessment finding: raw vs compiled Malli validation, reflective vs protocol logger dispatch, DB result-set case-conversion passes, and i18n marker resolution. Notable negative result recorded: memoizing case-conversion keys is slower than plain str/replace — the DB-layer fix targets pass elimination, not caching (#232).boundary-cli: The generated AGENTS.md template now opens the "Adding new functionality" workflow with Step -1 — check existing Boundary modules FIRST: run boundary list modules and prefer boundary add <module> + its ports before writing custom code. Coding agents working in bootstrapped projects were reimplementing functionality that existing modules (auth, storage, jobs, email, cache, search, payments, …) already provide (#232).Framework Quality (Phase 0–2, 2026-07):
boundary-observability: a Prometheus metrics adapter with a GET /metrics scrape endpoint; a backend-agnostic tracing port (ITracer + the with-span macro) with no-op and logging adapters; and an OpenTelemetry OTLP exporter for both traces and metrics, plus automatic per-request HTTP spans and per-job worker spans. One vendor-neutral OTLP/HTTP exporter feeds any OTel backend (SigNoz, Grafana Tempo, Jaeger, Datadog-via-OTel) (BOU-174, #304, #305, #306).boundary-storage: a Google Cloud Storage adapter (V4 signed URLs); a :boundary/storage Integrant key dispatching :local / :s3 / :gcs; and real HMAC-signed, expiring URLs for the local adapter (BOU-206, #308).boundary-email: an in-memory EmailQueueProtocol implementation (bounded retry); :boundary/email + :boundary/email-queue Integrant keys; and user welcome mail routed through the email lib (BOU-206, #309).boundary-jobs: a DB-backed job queue adapter (durable background jobs without Redis) with transactional outbox enqueue (BOU-181, #299, #300).worker run mode (no HTTP listener), and a production configuration guard (BOU-173, #298).boundary-scaffolder: module namespace + path parameterization via --base-ns, and an app-first bb scaffold integrate flow (BOU-205, #302, #303).check:poms (published-POM boundary-dep completeness) with a publishable boundary-shared-ui (BOU-202, #289); a check:test-tags gate with test-pyramid tag backfill across all libs (BOU-166, #262–#266); a check:test-meta gate (BOU-184, #255); and a full-system boot test against embedded PostgreSQL (BOU-183, #261).examples/todo) with CI smoke, 7 missing library READMEs, expanded module AGENTS.md guides, and multi-tenancy usage docs relocated to the tenant README (BOU-175, BOU-201, #295, #296, #297, #292).Performance, tier 1 (#232): Malli validators compiled once (8.6–9.9× on 121 call sites), and reflective logger interop replaced with protocol calls (~90×).
Performance, tier 2 (#233): snake→kebab conversion moved into the JDBC builder, admin metadata cached, i18n postwalk shares structure, N+1 writes batched.
Performance, tier 3: CSRF fast paths, cheaper correlation ids, rseq in
interceptor leave phases, JWT secret resolved once, throttled jobs heartbeat.
Framework Quality (Phase 0–2, 2026-07) — architecture & FC/IS:
core/ into the shell; core/ may no longer throw or hold mutable state, enforced by check:fcis. Follow-ups: audience filter/composition validation moved to the shell (BOU-185, #256), the validation rule registry split into a pure core + stateful shell (BOU-188, #258), and legitimate core exemptions reclassified inline (BOU-186, #257).boundary-shared-ui, dissolving admin↔user (BOU-193/194, #283); tenant HTTP middleware relocated out of platform and its wiring moved to the app layer (BOU-198/200, #284, #291); parallel search stacks merged into libs/search (BOU-169, #276).user/ports (BOU-170, #277); dead admin http/support helpers removed (#293); admin core/ui and shell/http split into focused namespaces behind a facade (BOU-195, #288).boundary-observability: HTTP request metrics now emit through the real IMetricsEmitter (request count, error count, latency histogram) instead of a no-op stub, so they reach any active provider (BOU-208, #307).boundary-platform: PostgreSQL session settings (statement_timeout, TimeZone=UTC, ApplicationName) now reach every pooled connection via JDBC URL properties — the previous SET statements only affected the single pooled connection that happened to execute them, leaving the statement-timeout guard effectively unset on the rest of the pool. reWriteBatchedInserts=true enabled while at it (#232).build.clj hardcoded 9 source dirs; the list now derives from deps.edn :paths, so new libs are packaged automatically. Also enables -Dclojure.compiler.direct-linking=true and resolves a LICENSE file-vs-directory merge conflict between dependency jars (#232).boundary-email / boundary-external: Attachment schemas used :bytes, which is not a schema in Malli's default registry — valid-email?, valid-email-input? and explain-email-errors threw :malli.core/invalid-schema on every call (latent: zero callers; surfaced by compiling validators at namespace load). Fixed to bytes? with regression tests (#232).boundary-user: find-active-users-by-role, find-users-created-since and find-users-by-email-domain ran SELECT … ORDER BY created_at DESC with no LIMIT — unbounded memory/latency as data grows. All three now default to the platform's max-pagination-limit (1000) via build-pagination; new {:limit :offset} options arities added to IUserRepository (base arities delegate), nil limit/offset guarded at the SQL assembly point.Framework Quality (Phase 0–2, 2026-07):
boundary-observability: Prometheus metric/label names that collide after sanitization (e.g. :http.requests and :http-requests both → http_requests) are handled deterministically — a later colliding metric registration is logged and ignored (first wins), and colliding label keys within a series are de-duplicated — instead of rendering invalid exposition (BOU-207, #310).boundary-audience: the account-tenure :neq filter now maps to SQL <> (BOU-189, #259).boundary-platform: enum CHECK constraints are now idempotent across H2 reconnects (#273).boundary-payments: Stripe checkout 400 on an invalid success_url (BOU-148, BOU-149). create-checkout-session POSTed whatever stripe-checkout-params produced. A broken upstream return-URL config (e.g. an unset PUBLIC_BASE_URL) reached Stripe two ways: an empty string when the :redirect-url fallback was also blank (400 parameter_invalid_empty, BOU-148), or a scheme-less relative path like /web/license/payment/return?… when the configured URL was left relative (400 url_invalid, BOU-149). The adapter now validates the resolved success_url/cancel_url before the Stripe call: anything that is not an absolute http(s) URL throws a :config-error ex-info naming the offending param, its value, and the fix (provide an absolute return URL / set PUBLIC_BASE_URL in acc/prod), so the misconfiguration is actionable instead of surfacing as an opaque provider error.boundary-payments: Stripe webhook 500 on unmapped event types (BOU-147). process-webhook threw an :internal-error ex-info for any Stripe event whose type is not one of the four generic payment_intent.* mappings — including checkout.session.completed (the primary paid-flow event), checkout.session.expired, and charge.dispute.created. In the billing webhook handler that throw is uncaught and returns HTTP 500, so a freshly-connected Stripe endpoint 500s on every delivery Stripe fires by default and Stripe retries for days. The consumer's event-action was already designed to route these by payload type (or ignore them), but the throw fired first. process-webhook now returns a result with :event-type nil and the full payload for any unmapped-but-parseable event instead of throwing, so the handler acknowledges with 200 and the billing layer routes/ignores by payload type. Only the four payment_intent.* types still resolve to a framework event-type.boundary-platform: Rate limiting wired into the default route pipeline (BOU-87). New config-driven http-rate-limit-protection interceptor runs in the default HTTP stack and reads its policy from :boundary/http :rate-limit {:enabled? :limit :window-ms} (env HTTP_RATE_LIMIT, HTTP_RATE_LIMIT_WINDOW_MS); the wiring injects the :boundary/cache so an active Redis cache yields a fixed-window limit shared across replicas. Enforcement is opt-in (default off) so an upgrade cannot start 429-ing existing consumers — enabled only in the bundled dev config (single-node, 1000/min); the prod/acc configs ship it disabled (enable together with an active Redis cache), and test leaves it off. Caveat: with no active cache the limiter falls back to a per-process counter — correct on a single node only; across N replicas the effective global limit is limit × N, and the wiring logs a warning at startup. The existing fixed-arg http-rate-limit form remains for explicit per-route use.
boundary-jobs: Multi-instance hardening (BOU-88). (1) A dequeued job whose type has no handler on the local worker is now re-enqueued for another instance instead of being silently dead-lettered; it is failed terminally with a NoHandlerError only after it has gone unhandled for :max-requeue-age-ms (default 300000 / 5 min). The re-enqueue is delayed (parked in the scheduled set :requeue-delay-ms ahead, default 1000) so a handlerless worker cannot reacquire the job on its next immediate poll and spin. Give-up is age-based, not attempt-based (tracked via [:metadata :first-missing-at]): a wall-clock window is independent of fleet size and load, so wrong-worker misses can't drop a job a slow handler-owning worker hadn't polled yet (:max-requeues, default 10000, remains only as a runaway backstop). A worker created with an empty handler registry logs a loud warning at startup. (2) Scheduled-job promotion is now an atomic claim so concurrent workers can't both move (and run) the same due job: the Redis adapter uses ZREM as the claim (only the worker whose ZREM returns 1 enqueues) and the in-memory adapter uses swap-vals!. Both process-scheduled-jobs! implementations now return the count actually promoted.
boundary-tenant: Per-tenant provisioning of the compliance/vbar/import entity tables plus a background-job schema-iteration helper (ZZP-86). Four new tables (compliance_snapshots, compliance_changes, vbar_assessments, import_batches) join tenant-scoped-tables, so provision-tenant! now copies them into each new tenant_<slug> schema. New sync-tenant-schemas! is the idempotent upgrade/sync path: because provision-tenant! only copies tenant-scoped tables when creating a schema and returns early for existing ones, previously-provisioned tenants would otherwise miss tables added in a later release. sync-tenant-schemas! re-copies every tenant-scoped table into every existing tenant schema via CREATE TABLE IF NOT EXISTS (existing tables untouched, only missing ones created — safe to re-run); no-op on non-PostgreSQL. Run it on deploy after extending tenant-scoped-tables. New boundary.tenant.shell.tenant-iteration/for-each-tenant-schema is the background-job analogue of the HTTP wrap-tenant-schema middleware: it runs a 1-arg fn once per provisioned tenant schema with that tenant's search_path pinned via with-tenant-schema, so jobs touching tenant-scoped tables get the same connection-pinned isolation as the request path (without it a job runs under search_path = public and sees only the empty public tables). Per-tenant failures are isolated and counted — one tenant's error never aborts the others — returning {:processed n :failed n :results [...]}.
boundary-platform: Graceful connection draining on shutdown (BOU-86). The :boundary/http-server component configures Jetty's GracefulHandler and setStopTimeout so that on stop the server stops accepting new connections, rejects new requests with 503, and lets in-flight requests finish before halting — eliminating cut requests during rolling restarts. New config knob :boundary/http :drain-timeout-ms (env HTTP_DRAIN_TIMEOUT_MS): default 30000 ms in prod/acc, 5000 in dev, 1000 in test; 0 or nil disables draining. Set the window above the load balancer's deregistration delay for zero-downtime rollouts.
boundary-platform: The in-memory rate-limit fallback is now heap-bounded and the bundled prod/acc configs ship rate limiting disabled (BOU-87 follow-up). Previously check-rate-limit-memory only pruned a client's timestamp vector when that same client returned and never evicted old client keys, so high-cardinality client ids (rotating API keys, many remote addresses) could grow the process-global state map without limit on a long-running node — and prod/acc enabled rate limiting by default while :boundary/cache was inactive, silently selecting that fallback in production. The fallback is now bounded by a hard cap (max-tracked-clients = 10000): before recording a new client at the cap it sweeps clients with no in-window requests and, if the map is still full (every client in-window), evicts the least-recently-active client — so even a sustained stream of fresh client ids can't grow it past the cap. The read-modify-write is atomic inside one swap!. prod/acc default to :enabled? false and wire HTTP_RATE_LIMIT_ENABLED (via aero #boolean) so operators can enable it with an env var once an active Redis cache is in place, rather than editing EDN.
boundary-platform: The http-rate-limit interceptor now actually blocks over-limit requests (BOU-87). Its rejection set :response but not :halt?, so run-pipeline continued to the downstream ring-handler, which overwrote the 429 with the handler's 200 — the limit was counted but never enforced. The rejection now sets :halt? true (matching http-csrf-protection), short-circuiting the pipeline.
boundary-payments: Stripe Checkout Session creation is now diagnosable and blank-safe (BOU-127, #216). create-checkout-session logs the Stripe error reason (type/code/param/message) on any non-2xx response instead of only status=%d id=%s, so a 400 is no longer opaque. stripe-checkout-params builds success_url/cancel_url blank-safely — an empty/whitespace override (e.g. an unset PUBLIC_BASE_URL upstream) no longer wins over redirect-url via (or "" redirect-url) and produces an empty success_url that Stripe rejects with a 400.
boundary-push: Comprehensive developer documentation for the push notification library (BOU-44). AGENTS.md now covers all five protocols (IPushService, IFCMProvider, IAPNsProvider, IDeviceTokenStore, IPushAnalyticsStore), Integrant wiring keys, HTTP routes, job handler arg shapes, HMAC callback flow, DB table overview, and REPL smoke checks. README.md corrects the Integrant configuration (actual ig/init-key dispatch keys), fixes API function names (register-device! / unregister-device! / get-user-devices), and adds the missing push_analytics_events migration to the DDL reference.
boundary-devtools: Unified error-code catalogue — bb guide error BND-201 and bb guide error BND-601 now return title, cause, and fix instead of "Unknown error code" (BOU-49). error_catalog.edn (libs/devtools/resources/) is the single source of truth consumed by both the JVM runtime (boundary.devtools.error-codes, moved out of core/ so I/O is permitted) and the Babashka CLI (bb guide). BND-0xx tooling codes are retired in favour of the BND-1xx..7xx range scheme: BND-1xx configuration, BND-2xx validation, BND-3xx persistence, BND-4xx auth, BND-5xx interceptor, BND-6xx FC/IS violations, BND-7xx tooling/build (new — circular deps BND-701, admin entity config BND-702, module not wired BND-703, migration version conflict BND-305). bb guide error (no code) lists all codes grouped by category in numerical range order.
boundary-platform: discover-migration-dirs now scans each resolved migration directory after discovery and emits a WARN log for any subdirectory that contains .sql files (e.g. migrations/tenant/). Catches the class of misconfiguration where tenant-scoped migrations are placed inside the public migration root and silently applied to the wrong schema; the warning fires on the first clojure -M:migrate up run rather than producing a hard-to-diagnose data error later.
boundary-admin: Proportional list-view column widths derived from field :type plus a field-name heuristic, replacing the previous even distribution where a boolean column got the same width as a name or description column (BOU-46). Weights: boolean=1, enum/numeric/uuid/json=2, date/instant=3, text=6; string columns default to 3, with name-like fields (name, title, email, …) widened to 4 and long-form fields (description, address, comment, …) to 6. An optional :width key on a field config (a positive integer weight) overrides the computed default. Widths are emitted as proportional width:N% on the table <colgroup> and resolved deterministically at render time — no runtime AI.
boundary-ai: bb ai admin-entity now suggests :width values in generated admin entity EDN configs (BOU-48, layer 3 of the column-width system). The AI is taught the same type/name weight table used by the runtime heuristic and only emits :width for fields whose semantics differ from the default — e.g. :sku, :code, :ref, :barcode get {:width 1} (narrow identifiers) while :description and :name are left without :width because the heuristic already assigns them correct weights. The result is a static :width in the generated EDN with no AI involvement on the hot render path.
boundary-platform: The default HTTP interceptor stack is no longer skipped for :no-doc routes. Interceptor application is now controlled by an explicit per-route :skip-interceptors? flag (set only on genuinely-internal endpoints such as health checks); :no-doc once again means only "exclude from the Swagger spec". As a result every /web route now runs the full stack — request logging, metrics, error reporting, correlation header, CSRF, and security headers — where previously it ran none. The most visible effect is that HTML pages now carry the security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, …) they were silently missing. The shipped CSP allows 'unsafe-inline'/'unsafe-eval' for HTMX/Alpine and all UI assets are self-hosted, so rendering is unaffected (BOU-43).boundary-platform: Replaced the ring/ring-anti-forgery dependency with buddy/buddy-core. CSRF tokens are generated and verified directly (HMAC-SHA256, constant-time) rather than via Ring's session-backed anti-forgery middleware, which did not fit the framework's cookie/header session model (BOU-43).boundary-cache: The Redis adapter now serializes values with Nippy instead of JSON, fixing class java.lang.String cannot be cast to class java.time.temporal.Temporal for cached java.time values (BOU-47). JSON is lossy — Temporal values became ISO-8601 strings, keywords became strings, and sets became vectors — and the loss surfaced only against Redis since the in-memory adapter stores values by reference. Nippy round-trips keywords, sets, ratios and java.time/Temporal values intact, matching the in-memory adapter.boundary-cache: The Redis adapter treats unreadable entries (values written by the previous JSON format, or otherwise non-Nippy bytes) as a cache miss instead of throwing, so the cache self-heals on rollout. Note: the on-the-wire format changes from JSON to Nippy; flushing the cache namespace on deploy is still recommended to avoid log noise from stale reads (BOU-47).boundary-platform: Real CSRF protection, replacing a stub that always
passed (BOU-43). Opt-in. BREAKING (BOU-56): enabled with a blank secret now
refuses to boot rather than failing open.boundary-audience: New audience segmentation library — defaudience,
seven filter types, a hybrid SQL + predicate pipeline, and a cached membership table.
boundary-realtime: Optional :on-open callback for websocket-handler — (fn [connection-id]) invoked after a successful connect, for subscribing connections to topics based on the authenticated user's roles. Exceptions thrown by the callback are logged and swallowed, so they do not abort the connection.
boundary-push: New push notification library — FCM and APNs behind one
IPushService, device token management, and job-based reliable delivery.
boundary-user: Welcome email on admin user creation — optional send-welcome checkbox triggers email via ISmtpProvider with graceful failure handling.
boundary-user: Dashboard extensibility via :dashboard-extra-cards config for injecting custom Hiccup cards into the user dashboard.
boundary-ui-style: Cross-page toast notification system via X-Toast response header + sessionStorage, works across all page layouts (base, pilot, admin-pilot).
boundary-cache: Deterministic LRU eviction — replaced timestamp-based ordering with monotonic access counter. Fixes non-deterministic eviction when entries are created within the same millisecond.boundary-user: XSS in create-user-htmx-handler inline <script> — added escape-js-string to sanitize return-to URL, toast JSON, and user name before interpolation. Prevents quote-breaking and </script> tag injection.boundary-admin: Toast JSON injection via entity labels in delete/bulk-delete handlers — added escape-json-string to sanitize label values in X-Toast and HX-Trigger headers.boundary-admin: Split-table soft-delete now correctly writes deleted_at to both primary and secondary tables in a transaction, fixing column "deleted_at" does not exist errors.boundary-admin: Added config validation for split-table entities missing :create-redirect-url, failing early with a clear error instead of a StreamableResponseBody crash.boundary-admin: Added log/error to create-entity exception handler (previously swallowed silently).boundary-admin: Added missing deleted_at column to users test DDL for embedded PostgreSQL integration tests, fixing 12 pre-existing test errors.boundary-user: Restored 500 status code for server errors in create-user-htmx-handler (was incorrectly returning 200).boundary-user: Fixed arity mismatch in create-user-htmx-handler test calls — handler signature changed to [user-service email-sender config] but tests were not updated.boundary-ui-style: Removed duplicate XHR monkey-patch from admin-ux.js — components.js already handles X-Toast capture for all bundles.boundary-ui-style: Increased horizontal padding on table pagination for better alignment.ci: Replaced :local/root dep in bb.edn with direct :paths entry for libs/tools/src, preventing deps.clj from triggering a Clojure tools download that times out on CI runners.boundary-admin: Auto-introspect secondary table fields when :split-table-update is configured, so split-table entities no longer require manual field definitions (#158).boundary-admin: Auto-expand SELECT columns for join queries in split-table setups, ensuring all fields from both tables are fetched (#158).boundary-admin: Auto-hide tsvector generated columns from entity forms and list views (#158).boundary-admin: Skip required validation for boolean fields, which default to false rather than NULL (#158).boundary-admin: Fixed swapped primary/secondary table alias mapping in resolve-query-config, which caused wrong SQL column qualifiers for split-table entities (#158).boundary-admin: Fixed snake_case→kebab-case mismatch in SELECT deduplication that caused duplicate columns in split-table join queries (#158).boundary-admin: Fixed split-table SELECT auto-expansion assigning columns to wrong table alias when :secondary-table maps to the :from table in query-overrides (e.g., a.tenant_id instead of u.tenant_id). Alias is now resolved by matching :secondary-table against :from/:join table names (#158).boundary-admin: Embedded PostgreSQL test infrastructure (io.zonky.test/embedded-postgres) for admin-user-operations-test. Split-table tests with tenant_id and other PG-specific columns now run against a real PostgreSQL instance instead of H2, fixing 8 pre-existing test errors.boundary-admin: New test helper namespace boundary.admin.test.embedded-pg with start!/stop!/db-context/with-embedded-pg for reusable embedded PG lifecycle in tests.ci: Removed non-existent :db/h2 alias from all CI test commands. H2 and embedded PostgreSQL deps are already in the :test alias; the phantom alias was silently ignored but produced warnings.cheshire version in boundary-cli from 5.12.0 to 6.2.0 (matches rest of monorepo).org.clojure/clojure in :build alias from 1.12.3 to 1.12.4.boundary-admin: schema_repository/get-entity-config now uses :table-name from manual entity config when fetching table metadata, so entities whose key differs from their table name (e.g. :users → auth_users) resolve correctly (BOU-28).boundary-admin: bulk-delete-entities now targets :soft-delete-table instead of :table-name when soft-deleting, fixing bulk deletes in split-table setups (BOU-28).boundary-admin: update-entity and update-entity-field now use execute-update! for DML statements instead of execute-one!, fixing UPDATE execution in both split-table and single-table paths (BOU-28).boundary-admin: Added :soft-delete true to the default users admin entity config so soft-delete is enabled out of the box (BOU-28).boundary-tools: bb create-admin now works in freshly generated projects (BOU-27). The command previously shelled out to clojure -M:cli:db which requires boundary.cli — a monorepo-only namespace never included in published libraries. Replaced with clojure -M:user-cli which calls boundary.user.shell.cli-entry/run-cli! directly via -e eval, requiring no unpublished code.boundary-cli: Generated deps.edn now includes a :user-cli alias with all four JDBC drivers (SQLite, PostgreSQL, H2, MySQL) so bb create-admin works regardless of which database adapter the project is configured to use.boundary-cli: Generated config.clj now defines user-validation-config, which boundary.user.shell.cli-entry resolves at runtime via requiring-resolve.boundary-tools: bb create-admin passes the target environment via BND_ENV environment variable instead of -J-Denv=, matching how boundary.config/load-config actually reads the active profile.boundary-user: MFA QR code is now generated locally using the ZXing library (com.google.zxing/core and com.google.zxing/javase 3.5.3) instead of calling the external api.qrserver.com service. generate-qr-code-data-url returns a data:image/png;base64,… URL that works in <img src> without any network dependency (#148).build (all 25 libraries): Each library JAR now embeds a cljdoc.edn file containing {:cljdoc/root "libs/<name>"}. Without this hint cljdoc defaulted to the repo root and could not find source files located under libs/{name}/src, breaking all Clojars cljdoc links (BOU-26, #149).1.0.1-alpha-21 to re-align lockstep versioning.boundary-cli: boundary new now generates a full boundary-tools task suite in bb.edn instead of a minimal 3-task config. The old template used broken (clojure ["-M:repl-clj"]) syntax that caused FileNotFoundException: [-M:repl-clj] on bb repl.boundary-cli: Generated deps.edn now uses :repl alias (consistent with generated-project convention; monorepo uses :repl-clj).1.0.1-alpha-20 to re-align lockstep versioning.boundary-tools: bb scaffold generate now works in projects created from boundary-starter — scaffolder is injected via -Sdeps instead of requiring it on the classpath.boundary-tools: bb smoke-check no longer fails in generated projects — removed monorepo-only :docs-lint alias from required checks.boundary-tools: bb check linting no longer includes libs/*/src libs/*/test paths when not in the monorepo.boundary-tools: bb install-hooks gives a friendly message instead of a Java exception when run outside a git repository.boundary-tools: AI CLI (bb ai) falls back to environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, OLLAMA_URL) when config has :provider :no-op or no AI config is present.boundary-ai: OpenAI-compatible base URLs with a trailing /v1 suffix no longer produce double /v1/v1/chat/completions paths.boundary-scaffolder: Generated deps.edn now includes :clj-kondo and :migrate aliases with all four database drivers (SQLite, PostgreSQL, H2, MySQL).boundary-devtools — DX Vision: 6-phase developer experience overhaulBND-*) with structured messages, ADRs for devtools guidance engine, REPL command center, dev dashboard, error experience, and progressive learning (ADR-024 through ADR-029).boundary.devtools.core.introspection), schema exploration (schema-tools), documentation lookup (documentation), and guidance engine (guidance). REPL namespace (boundary.devtools.shell.repl) with unified API.boundary.devtools.core.auto_fix), stacktrace parser, FC/IS checker, HTTP error middleware, and REPL error handler.localhost:9090) with pages for system overview, routes, schemas, database, errors, requests, and docs. Hiccup-rendered with custom CSS, served via Ring.boundary.devtools.core.recording), route testing (router), and rapid prototyping (prototype). Shell adapters for file-based recording persistence and route simulation.boundary-ai — REPL AI integration (Phase 6)explain-code, suggest-refactor, generate-docs in boundary.ai.shell.repl.boundary.ai.core.prompts.boundary-cache — LRU eviction bug (#137)boundary.cache.shell.adapters.in_memory to correctly identify the least-recently-used entry when multiple entries share the same timestamp.boundary-tools — BOU-15 deprecated wrapper usage scanner (BOU-15):refer'd deprecated symbols: normalize-require-spec now extracts :refer [sym ...] vectors alongside :as aliases. A new extract-referred-symbols function maps directly referred symbol names to their source namespace. find-qualified-call-sites runs a second regex pass for bare (symbol ...) call sites, so usage like (:require [boundary.search.core.index :refer [build-document]]) is no longer silently missed.find-qualified-call-sites now unconditionally searches for (namespace/symbol ...) patterns regardless of whether the file has an alias or :refer entry. Calls like (boundary.search.core.index/build-document ...) are now correctly reported.ci — E2E job disablede2e CI job with if: false to reduce pipeline run time. Tests can be run manually when needed.boundary-tools into libs/tools/ to follow monorepo convention. Removed redundant top-level boundary-tools/ directory.boundary-e2e — Admin UI end-to-end test suite (BOU-10)boundary.e2e.helpers.admin) with login-as-admin!, login-as-user!, two-phase HTMX settle waiting (install-htmx-settle-listener! / await-htmx-settle!), and table/form query utilities.with-fresh-seed fixture for isolated H2 state per test.platform — Compile-time PostgreSQL class references(:import [org.postgresql.util PGobject]) and (instance? org.postgresql.util.PSQLException ...) from boundary.user.shell.service, boundary.tenant.shell.persistence, and boundary.tenant.shell.invite-persistence. Replaced with runtime class name checks so the REPL starts without the :db alias on the classpath.admin — Table view UX improvementsadmin — Compact table view layoutadmin — Collapsible sidebaradmin-ux.js to comply with Content Security Policy.admin-ux.js now loads before alpine.min.js so the sidebar store is registered before Alpine initializes.boundary-e2e — end-to-end test suite for login sequence/web/login, /web/register, and /api/v1/auth/* — browser automation + API testing via spel (Playwright Java wrapper). No Node.js/npm/TypeScript introduced.libs/e2e/ with com.blockether/spel dependency, isolated behind opt-in :e2e alias — normal clojure -M:test runs are unaffected.bb e2e: orchestrator task that starts the app in :test profile on port 3100, runs the kaocha :e2e suite, and tears down the server.bb run-e2e-server: standalone task for manual debugging against the test-profile server.POST /test/reset endpoint (behind :test/reset-endpoint-enabled? config flag) that truncates H2 and re-seeds baseline tenant/users via production services. Guarded by startup assertion (throws in prod/acc) and bb doctor check.:e2e suite in tests.e2e.edn with ^:e2e metadata filtering.e2e job in .github/workflows/ci.yml with Playwright browser cache.boundary-user — 5 auth/session bugs discovered by e2e tests[:session :user :id] instead of [:user :id] from the request — all 4 MFA endpoints (setup, enable, disable, status) always returned 500. Fixed by reading (:user request) directly.login-submit-handler checked for "on" but the ui/checkbox component submitted "true" — remember-me never activated. Fixed by accepting any truthy form value.string->instant in boundary.core.utils.type-conversion did not handle java.time.OffsetDateTime (returned by H2 for TIMESTAMP WITH TIME ZONE columns) — caused NPE in is-session-valid?, bouncing users back to login after successful authentication. Fixed by adding OffsetDateTime handling.should-allow-login-attempt? and calculate-failed-login-consequences existed in the core layer but were never called from the service layer. Fixed by adding a service-level lockout gate that checks the threshold before delegating to authenticate-user. Only true lockout (:retry-after present) short-circuits — deactivated/deleted accounts fall through to the normal auth flow to preserve their own error semantics.+, /, = characters that caused Jetty 400 errors on GET/DELETE /api/v1/sessions/:token. Fixed by switching generate-session-token to URL-safe base64 (Base64/getUrlEncoder without padding). Breaking change: existing sessions with old-format tokens will fail validation — users must re-login after deploy.boundary-tools — 4 new developer helper toolsbb doctor — Config Doctor (rule-based)config.edn and project files — no AI required.env-refs (error): detects #env VAR references in the :active section without #or fallback that are not set in the environment.providers (error): validates :provider values against known sets (logging, metrics, error-reporting, payments, AI, cache).jwt-secret (error): verifies JWT_SECRET is set when the user module is active.admin-parity (warn): checks that admin entity EDN files exist in both dev/admin/ and test/admin/.prod-placeholders (error): flags placeholder values (company.com, example.com, TODO, CHANGEME) in prod/acc configs.wiring-requires (warn): verifies active Integrant modules have their module-wiring require in wiring.clj.bb doctor [--env dev|prod|acc|all] [--ci]. --ci exits non-zero on any error for CI pipelines.boundary.tools.doctor.bb setup — Config Setup Wizard (templates + optional AI)--database postgresql --payment stripe), or AI-powered natural language (bb setup ai "PostgreSQL with Stripe").resources/conf/dev/config.edn, resources/conf/test/config.edn, and .env.example from template fragments.boundary.ai.shell.cli-entry setup-parse; falls back to interactive if no AI provider is available.boundary.tools.setup.bb scaffold integrate — Module Integration (rule-based)bb scaffold generate: patches deps.edn (source/test paths), tests.edn (per-library suite), and wiring.clj (module-wiring require).--dry-run mode previews all changes without writing files.bb scaffold integrate <module> and bb scaffold:integrate <module>.boundary.tools.integrate.bb ai admin-entity — Admin Entity Generator (AI-powered)bb ai admin-entity "products with name, price, status").resources/conf/dev/admin/ and includes them as examples in the prompt for style consistency.--yes flag for non-interactive use.resources/conf/dev/admin/<entity>.edn and resources/conf/test/admin/<entity>.edn.#include directive).boundary.tools.admin_entity. Clojure-side additions:
boundary.ai.core.prompts: admin-entity-messages, setup-parse-messages prompt builders.boundary.ai.shell.service: generate-admin-entity, parse-setup-description orchestration functions.boundary.ai.shell.cli-entry: cmd-admin-entity, cmd-setup-parse subcommands.bb.edn: 3 new tasks registered (doctor, setup, scaffold:integrate) + 3 new requires (boundary.tools.doctor, boundary.tools.setup, boundary.tools.integrate).bb ai help text and dispatch updated with admin-entity and setup-parse subcommands.bb scaffold help text and dispatch updated with integrate subcommand.AGENTS.md (root): new tools added to Quick Reference and namespace table.CLAUDE.md: new commands added to Scripting section.boundary-tools/AGENTS.md: comprehensive documentation for all 4 tools with examples, tables, and workflow guides.libs/ai/AGENTS.md: features list updated to 7, service API examples added, 3 new pitfalls documented (#9–#11).boundary-realtime — Ring WebSocket handlerboundary.realtime.shell.handlers.ring-websocket): bridges Ring's map-based ::ring.websocket/listener response to the existing IRealtimeService connect/disconnect lifecycle. JWT authentication via token query parameter; on-open creates adapter and registers connection, on-close/on-error triggers disconnect cleanup.websocket-handler accepts keyword options: :token-param (default "token") and :on-message for optional client→server bidirectional messaging.ring/ring-core 1.15.3 added to libs/realtime/deps.edn.boundary-payments — new libraryIPaymentProvider protocol in boundary.payments.ports with create-checkout-session, get-payment-status, process-webhook, and verify-webhook-signature methods. Implementations: StripePaymentProvider, MolliePaymentProvider, MockPaymentProvider (development/tests).CheckoutRequest, CheckoutResult, PaymentStatusResult (:pending/:paid/:failed/:cancelled), WebhookResult (:payment.paid/:payment.failed/:payment.cancelled/:payment.authorized).boundary.payments.core.provider): cents->euro, normalize-event-type, mollie-status->event-type, mollie-status->payment-status, stripe-event->event-type.payment_intent_data[metadata][checkout_id] for webhook correlation, HMAC-SHA256 signature verification with constant-time comparison, 300s timestamp tolerance, graceful handling of malformed Stripe-Signature headers.get-payment-status, form-POST webhook processing with payment fetch-back verification.:boundary/payment-provider with :provider (:mock/:mollie/:stripe), :api-key, :webhook-secret, :webhook-base-url.payments-module-config in boundary.config, boundary.payments.shell.module-wiring loaded via platform wiring, boundary/payments dependency added to libs/platform/deps.edn.^:unit + ^:integration).libs/payments/deps.edn: standalone library with clj-http, cheshire, malli, integrant, tools.logging.boundary-ai — new library (Phase 19 of Boundary Roadmap)IAIProvider protocol in boundary.ai.ports with complete, complete-json, and provider-name methods. Implementations: OllamaProvider (offline-first, no API key), AnthropicProvider, OpenAIProvider, NoOpProvider (test stub).:fallback provider in :boundary/ai-service; if the primary fails, the fallback is used transparently.bb scaffold ai "<description>" [--yes]): parses a natural language module description into a validated ModuleGenerationRequest spec and delegates to the existing scaffolder pipeline. Preview + confirm by default; use --yes for non-interactive generation.bb ai explain, (ai/explain *e)): reads a Clojure/Boundary stack trace, extracts referenced source files, and returns a structured root-cause + fix-suggestion using framework-specific system prompts.bb ai gen-tests <file>): reads a source file, detects test type (:unit for core/, :contract for adapters/, :integration otherwise), and generates a complete Kaocha-compatible test namespace.bb ai sql "<description>", (ai/sql "...")): translates a natural language query description into HoneySQL map + explanation + raw SQL preview. Auto-discovers schema context from schema.clj files.bb ai docs --module <path> --type agents|openapi|readme): generates AGENTS.md developer guides, OpenAPI 3.x YAML, or README.md from source files.boundary.ai.shell.repl): (ai/explain *e), (ai/sql "..."), (ai/gen-tests "path/to/file.clj") — bind service once with (ai/set-service! system-service).:boundary/ai-service with :provider, :model, :base-url/:api-key, and optional :fallback sub-config.Message, AIRequest, AIResponse, ProviderConfig, AIConfig.boundary.ai.core.*): prompts.clj (system + user prompt builders for all 5 features), context.clj (module name extraction, stack trace parsing, function signature discovery, schema context), parsing.clj (JSON response parser, module spec → CLI args converter, SQL + test code extractors).^:unit + ^:integration).libs/ai/AGENTS.md: 7-section developer guide covering provider setup, REPL usage, CLI reference, common pitfalls (8 patterns), testing commands.libs/ai/deps.edn: standalone library with clj-http, cheshire, malli, integrant, tools.logging..github/workflows/ci.yml: test-ai job added (needs: lint); libs/ai/src added to the lint step; test-ai wired into test-summary..github/workflows/publish.yml: boundary-ai added to Layer 4 (standalone, no inter-library dependencies); updated release body and step summary.scripts/ai.clj: new Babashka script — bb ai explain, bb ai gen-tests, bb ai sql, bb ai docs.scripts/scaffold.clj: bb scaffold ai "<description>" subcommand added.bb.edn: ai task added.AGENTS.md and CLAUDE.md: ai added to library listing, test command reference, Babashka commands, and Library-Specific Guides table. Version bumped to 3.5.0.resources/conf/dev/config.edn: :boundary/ai-service added (Ollama primary, Anthropic fallback).resources/conf/test/config.edn: :boundary/ai-service {:provider :no-op} for test isolation.boundary-calendar — new library (Phase 2 / Q3 2026 roadmap)defevent macro and in-process registry (atom-backed, same pattern as defreport in boundary-reports): register named event type schemas at load time; get-event-type, list-event-types, clear-registry!.boundary.calendar.schema: Malli schemas — EventData, EventDef, OccurrenceResult, ConflictResult; helpers valid-event?, explain-event, valid-event-def?.boundary.calendar.core.event: pure helpers — duration, all-day?, within-range?.boundary.calendar.core.recurrence: DST-aware RRULE expansion via ical4j 4.x Recur with ZonedDateTime seeds; recurring?, occurrences, next-occurrence, expand-event.boundary.calendar.core.conflict: pairwise conflict detection — overlaps?, conflicts?, find-conflicts (returns ConflictResult maps with :overlap-start/:overlap-end).boundary.calendar.core.ui: pure Hiccup calendar views — event-badge, day-cell, month-view, week-view, mini-calendar.boundary.calendar.ports: CalendarAdapterProtocol (export-ical, import-ical).boundary.calendar.shell.adapters.ical: ICalAdapter backed by org.mnode.ical4j/ical4j 4.0.3; TZID extracted via regex from property text (ical4j 4.x creates synthetic zone IDs internally).boundary.calendar.shell.service: public API — export-ical, import-ical, ical-feed-response (returns Ring response with Content-Type: text/calendar; charset=utf-8).^:unit + ^:integration round-trip).libs/calendar/AGENTS.md: 11-section developer guide covering DST pitfalls, RRULE examples, ical4j 4.x API notes, registry pollution warning, REPL smoke check.docs-site/content/guides/calendar.adoc (weight 68): user-facing how-to guide.docs-site/content/api/calendar.adoc (weight 50): complete function API reference.dev-docs/adr/ADR-011-calendar-library.adoc: architecture decision record (7 decisions, alternatives considered).boundary-reports — added to CI (was missing)test-reports job added to .github/workflows/ci.yml; libs/reports/src added to the lint step..github/workflows/ci.yml: test-calendar and test-reports jobs added (both needs: lint; standalone, no inter-library dependencies). Both wired into test-summary.AGENTS.md and CLAUDE.md: reports and calendar added to library listing, test command reference, and Library-Specific Guides table. New "Adding a New Library to CI" checklist section in AGENTS.md.boundary-workflow — new library (Phase 2 / Q3 2026 roadmap)defworkflow macro and in-process registry: declare state machine definitions as data; get-workflow, list-workflows, clear-registry!.boundary.workflow.schema: Malli schemas — WorkflowDefinition, WorkflowInstance, TransitionDef, AuditEntry; state/transition validation at definition time.boundary.workflow.core.machine: pure state machine logic — can-transition?, find-transition, permission checks against :required-permissions, guard evaluation.boundary.workflow.core.transitions: available-transitions-with-status — returns all candidate transitions with :enabled?, :label, :reason for a given state and actor-roles.boundary.workflow.core.audit: pure audit entry constructors.boundary.workflow.ports: IWorkflowStore, IWorkflowEngine, IWorkflowRegistry protocols.boundary.workflow.shell.persistence: DB persistence via next.jdbc + HoneySQL (IWorkflowStore implementation).boundary.workflow.shell.service: orchestration — load → validate → persist → side-effects; create-workflow-service factory accepts optional job-queue and guard-registry.:context map; return boolean.TransitionDef (:side-effects [:notify-user]); enqueued via boundary-jobs after successful transition; silently skipped if no job queue configured.boundary.workflow.shell.http: REST API — POST /workflow/instances (start), POST /workflow/instances/:id/transition, GET /workflow/instances/:id (state + availableTransitions), GET /workflow/instances/:id/audit.boundary.workflow.shell.module-wiring: Integrant :boundary/workflow key; depends on :boundary/database-context (required) and :boundary/job-queue (optional).libs/workflow/AGENTS.md: developer guide covering defworkflow syntax, guards, side effects, auto-transitions, hooks, and Integrant wiring.docs-site/content/guides/workflow.adoc: user-facing how-to guide.boundary-workflow — lifecycle hooks, auto-transitions, available-transitions:hooks map on WorkflowDefinition: supports :on-enter-<state>, :on-exit-<state>, and :on-any-transition keys. Hooks receive the updated WorkflowInstance and fire synchronously after each successful transition (after the audit entry is saved). Exceptions are caught and logged; they do not roll back the transition.:auto? true on TransitionDef: marks a transition as system-initiated. process-auto-transitions! port method fires all eligible auto-transitions for a given workflow; uses [:system] actor-roles (no user permission check). Returns {:attempted :processed :failed} counts.available-transitions port method: returns candidate transitions with :enabled?, :label, and :reason fields for the current state and actor-roles. Exposed on the GET /api/workflow/instances/:id HTTP response as availableTransitions.:label on TransitionDef and :state-config map on WorkflowDefinition for human-readable display names.available-transitions-with-status pure function in boundary.workflow.core.transitions.boundary-search — filter support:filters key on SearchDefinition: declares filterable keyword dimensions (e.g. [:tenant-id :category-id]).:filter-values opt in index-document! and build-document: stores filter data as compact JSON in a new filters TEXT column.d.filters::jsonb->>'key' = ?; H2/SQLite uses INSTR(filters, '"key":"val"') > 0 (H2 2.4.x has no JDBC JSON function support).filter-key->json-key utility in boundary.search.core.index (kebab → snake conversion for JSON storage).resources/migrations/20260312000000-search-filters.{up,down}.sql.boundary-admin — Admin UI Frontend Redesign ("Refined Editorial")/fonts/ for CSP compliance (font-src 'self'); no external CDN dependency.fonts.css with variable-weight @font-face declarations (DM Sans 300–700, JetBrains Mono 400–600).boundary-tokens.css: --font-sans, --font-display, --font-mono.--shadow-sm through --shadow-2xl) for modern depth.--radius-sm 6px, --radius-md 8px, --radius-lg 12px, --radius-xl 16px).--transition-fast, --transition-normal, --transition-slow, --transition-bounce).--shadow-card-hover, --shadow-inner-glow, --tracking-tight, --tracking-tighter, --topbar-backdrop.backdrop-filter: blur(12px) saturate(180%).translateY(-2px)), icon color inversion on hover..entity-card-link, .entity-card-icon, .entity-card-title, .entity-card-count, .entity-card-description classes.translateY(-1px)).fadeInUp keyframe for page content entry with staggered delays.tableRowReveal keyframe for HTMX-loaded table rows (staggered first 10 rows).::after pseudo-element with scaleX transform.@media (prefers-reduced-motion: reduce) disables all animations.#0c0f17 base).rgba(255,255,255,0.08).border-radius: var(--radius-full).boundary-admin — UX Enhancements (6 features)htmx:beforeRequest / htmx:afterRequest / htmx:responseError events.HX-Trigger: {"showToast": {...}} response header or window.AdminUX.showToast() JS API..alert-success, .alert-error, etc.) auto-converted to toasts on page load.escapeHtml() prevents XSS in toast title/message content.data-href attribute).a, button, input, select, textarea, .actions-cell, .checkbox-cell, and td.editable are ignored (preserves inline editing).<thead>.w-full, w-3-4, w-1-2, w-1-3, w-1-4) for visual variety.window.confirm() for all delete operations.htmx:confirm event on elements with hx-delete or .danger class.data-confirm-title, data-confirm-cancel, data-confirm-label attributes (server-rendered via [:t ...] i18n markers); falls back to English.removeAfterAnimation() helper checks prefers-reduced-motion: reduce and removes DOM elements immediately instead of waiting for animationend (which never fires when animation: none).@media (prefers-reduced-motion: reduce).boundary-admin — Delete Flow ImprovementsHX-Redirect instead of empty response with HX-Trigger.return_to query parameter preserved through the delete flow for context-aware redirect.return_to validated to start with /web/admin/; invalid values fall back to entity list.boundary-admin — Pagination Enhancementspage-window algorithm improved: single-page gaps show the actual page number instead of an ellipsis (e.g. 1 2 3 ... 8 instead of 1 ... 3 ... 8 when page 2 is the only gap).[:t ...] markers (8 new i18n keys).boundary-i18n — New Translation Keysen.edn and nl.edn:
:admin/pagination-showing, :admin/pagination-of, :admin/pagination-label, :admin/pagination-first-page, :admin/pagination-previous-page, :admin/pagination-next-page, :admin/pagination-last-page, :admin/pagination-page.:admin/modal-button-cancel, :admin/modal-button-delete.boundary-admin — tenant entity + dashboard statsresources/conf/{dev,test}/admin/tenants.edn — list/search fields, status enum filter (active/suspended/deleted), field groups (Identity, State, Settings), readonly system fields.admin-home-handler now calls count-entities for each registered entity and passes the stats map to admin-home, so entity tiles show real counts instead of always displaying "0".#{:users :tenants}).boundary-tenant — convenience functions and protocol extensiontenant-provisioned? public function in boundary.tenant.shell.provisioning: checks if a tenant's schema exists in PostgreSQL; returns false for non-PostgreSQL databases; throws on missing :schema-name.list-tenant-schemas public function in boundary.tenant.shell.provisioning: lists all tenant_* schemas in PostgreSQL; returns empty vector for non-PostgreSQL databases.ITenantSchemaProvider protocol extended with tenant-provisioned? and list-tenant-schemas methods; TenantSchemaProvider record updated to implement both.dev-docs/adr/ADR-020-tenant-database-scope.adoc: decision to keep tenant provisioning PostgreSQL-only; MySQL/SQLite version promises removed from README.boundary-admin UI theme evolved from "Cyberpunk Professionalism" (Geist + Indigo/Lime) to "Refined Editorial" (DM Sans + JetBrains Mono, warmer surfaces, layered shadows, spring-eased transitions). Dark mode refined with blue-tinted surfaces and colored shadow glows.boundary-admin entity card markup restructured: icon standalone on its own line, then title, description, and count as metadata.boundary-admin delete handler: returns HX-Redirect header instead of empty body with HX-Trigger: entityDeleted.boundary-admin event listeners: all 7 document.body.addEventListener calls changed to document.addEventListener to survive HTMX body swaps (hx-target="body" hx-swap="outerHTML").boundary-ui-style CSS bundle: fonts.css added as first entry in admin-pilot-css; admin-ux.js added to admin-pilot-js.boundary-ui-style keyboard.js: confirm modal escape handling added; debug mode disabled.boundary-tenant promoted from "Active" to "Stable" in PROJECT_STATUS.adoc. All convenience functions documented in README are now implemented; 70 tests, 474 assertions, 0 failures.boundary-tenant README: fixed middleware naming (wrap-tenant-resolver → wrap-tenant-resolution), removed non-existent wrap-require-tenant (use :require-tenant? true option instead), clarified middleware locations (platform lib vs tenant lib), replaced MySQL/SQLite roadmap promises with ADR-020 reference.boundary-tenant integration tests: removed stale "DEFERRED" comment — tests pass with mock observability services and H2 in-memory DB.boundary-external promoted from "In Development" to "Active" (Twilio, SMTP/IMAP adapters production-capable). Stripe moved to boundary-payments.AGENTS.md updated: workflow and search added to library structure, test commands, and Library-Specific Guides table. Version bumped to 3.3.0.libs/workflow/AGENTS.md and libs/search/AGENTS.md updated to document all new features.docs-site/content/guides/workflow.adoc and docs-site/content/guides/search.adoc updated with new API examples, filter DDL, migration notes, and hook/auto-transition reference.libs/ui-style/resources/public/js/admin-ux.js — Central JS for all 5 UX features (~340 lines).libs/ui-style/resources/public/css/fonts.css — Self-hosted @font-face declarations.libs/ui-style/resources/public/fonts/dm-sans-latin.woff2 (63 KB).libs/ui-style/resources/public/fonts/dm-sans-italic-latin.woff2 (76 KB).libs/ui-style/resources/public/fonts/jetbrains-mono-latin.woff2 (31 KB).clojure -M:test:db/h2).clojure -M:test:db/h2 :admin).workflow.core.transitions-test (available-transitions-with-status), workflow.shell.service-test (hooks, auto-transitions), search.core.query-test (filter SQL), search.shell.persistence-test (filter round-trip).The first production-ready release of the Boundary Framework - a batteries-included web framework for Clojure that brings Django's productivity and Rails' conventions with functional programming rigor.
core/ namespaces (no side effects)shell/ namespacesports.clj for dependency injectionboundary-core (0.1.0)Foundation library with essential utilities:
boundary-observability (0.1.0)Multi-provider observability infrastructure:
boundary-platform (0.1.0)HTTP and database infrastructure:
boundary-user (0.1.0)Authentication and authorization:
boundary-admin (0.1.0)Auto-generated CRUD admin interface (Django Admin for Clojure):
deleted_at columnsboundary-storage (0.1.0)File storage abstraction:
boundary-scaffolder (0.1.0)Production-ready module generator:
boundary-cache (0.1.0)Distributed caching:
boundary-jobs (0.1.0)Background job processing:
run-at timestampboundary-realtime (0.1.0)WebSocket-based real-time communication:
boundary-tenant (0.1.0)Multi-tenancy infrastructure:
boundary-email (0.1.0)Email infrastructure:
boundary-external (0.1.0) - In DevelopmentExternal service adapters:
:field-order:field-groups/api/auth/mfa/setup, /api/auth/mfa/enable, /api/auth/mfa/verify:enter (request), :leave (response), :error (exception){:path "/api/admin"
:methods {:post {:handler 'handlers/create-resource
:interceptors ['auth/require-admin 'audit/log-action]
:summary "Create admin resource"}}}
(defn create-user [this user-data]
(service-interceptors/execute-service-operation
:create-user
{:user-data user-data}
(fn [{:keys [params]}]
;; Business logic here - observability automatic
(let [user (user-core/prepare-user (:user-data params))]
(.create-user repository user)))))
limit and offset parametersfirst, prev, next, last relationsdev, test, prod)#include support: Modular config files per moduleBND_ENVresources/conf/{env}/admin/{module}.edn:test alias)clojure -M:migrate uphttps://thijs-creemers.github.io/boundary/hugo server in docs-site/ directorydocs/cheatsheet.html with client-side search, copy-to-clipboard:password-hash, :created-atpassword_hash, created_atpasswordHash, createdAtsnake-case->kebab-case-map, kebab-case->snake-case-mapWhy: Recent bug caused authentication failures because service layer used :password_hash but entities had :password-hash. This convention prevents such mismatches.
:unit metadata):integration metadata):contract metadata)clojure -M:test:db/h2 # All tests
clojure -M:test:db/h2 :core # Core library
clojure -M:test:db/h2 --focus-meta :unit # Unit tests only
clojure -M:test:db/h2 --watch :core # Watch mode
clojure -M:repl-clj <<'EOF'
(require '[boundary.shared.tools.validation.repl :as v])
(spit "build/validation-user.dot" (v/rules->dot {:modules #{:user}}))
(System/exit 0)
EOF
dot -Tpng build/validation-user.dot -o docs/diagrams/validation-user.png
resources/public/css/tokens-openprops.css).github/workflows/publish.yml (304 lines)v*io.github.thijs-creemersthijs-creemers (password via GitHub Secrets)boundary-core → io.github.thijs-creemers/boundary-coreboundary-observability → io.github.thijs-creemers/boundary-observabilityboundary-platform → io.github.thijs-creemers/boundary-platformboundary-user → io.github.thijs-creemers/boundary-userboundary-admin → io.github.thijs-creemers/boundary-adminboundary-storage → io.github.thijs-creemers/boundary-storageboundary-scaffolder → io.github.thijs-creemers/boundary-scaffolderboundary-cache → io.github.thijs-creemers/boundary-cacheboundary-jobs → io.github.thijs-creemers/boundary-jobsboundary-tenant → io.github.thijs-creemers/boundary-tenantboundary-email → io.github.thijs-creemers/boundary-emailboundary-external → io.github.thijs-creemers/boundary-external (skeleton, not production-ready)Use the boundary-starter template:
git clone https://github.com/thijs-creemers/boundary-starter
cd boundary-starter
export JWT_SECRET="change-me-dev-secret-min-32-chars"
export BND_ENV="development"
clojure -M:repl-clj
In REPL:
(require '[integrant.repl :as ig-repl])
(ig-repl/go) ;; Visit http://localhost:3000
What you get:
;; deps.edn
{:deps {io.github.thijs-creemers/boundary-core {:mvn/version "1.0.0"}
io.github.thijs-creemers/boundary-platform {:mvn/version "1.0.0"}
io.github.thijs-creemers/boundary-user {:mvn/version "1.0.0"}
io.github.thijs-creemers/boundary-admin {:mvn/version "1.0.0"}}}
clojure -T:build clean && clojure -T:build uber
java -jar target/boundary-*.jar server
Use provided Dockerfile in boundary-starter template.
export JWT_SECRET="production-secret-min-32-chars"
export BND_ENV="production"
export DB_PASSWORD="secure_password"
export DATABASE_URL="jdbc:postgresql://localhost:5432/boundary"
tx (15 occurrences)tx-ctx (5 occurrences)These are false positives from clj-kondo's static analysis and do not affect runtime behavior.
let expressions: 3 warnings in test files (cosmetic issue)This is the initial 1.0.0 release. No migration from previous versions.
Copyright 2024-2025 Thijs Creemers.
Distributed under the Eclipse Public License 2.0.
boundary new bb.edn template — full boundary-tools task suite, version re-alignmentCan you improve this documentation? These fine people already did:
Thijs Creemers & thijscreemersEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |