All notable changes to the Boundary Framework will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
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 for session-authenticated, state-changing requests (POST/PUT/DELETE/PATCH), replacing a stub that always passed (BOU-43). Enforcement is opt-in — the library default is :enabled? false, so upgrading the framework cannot start rejecting requests from consumers that do not yet emit tokens; each app enables it explicitly after emitting tokens (BOU-56). When enabled, a request is validated — 403 on a missing or invalid token — when the path is not exempt and the request is either session-authenticated (session-token cookie / X-Session-Token header) or a /web route. This protects /web, /web/admin, and any session-authenticated /api route; token-auth API clients that send no session cookie are not CSRF-vulnerable and are not checked. Details:
base64url(nonce).base64url(HMAC-SHA256(secret, nonce ‖ binding))); authenticated requests bind to the session, unauthenticated /web flows (login, register, MFA) bind to a SameSite=Strict csrf-session cookie minted on the page GET.(boundary.platform.core.csrf/hx-headers) merged onto an element (e.g. <body>, inherited by all hx-* requests) or from the shared page layout's <meta name="csrf-token"> tag plus a global htmx:configRequest listener that attaches X-CSRF-Token to every HTMX request. Plain <form method=post> forms include a hidden field via (boundary.platform.core.csrf/hidden-field).:boundary/http :security :csrf {:enabled? :secret :exempt-paths}; the library default is opt-in (:enabled? false). The bundled app enables it explicitly in dev/prod/acc (prod/acc require JWT_SECRET from the environment); the secret otherwise falls back to JWT_SECRET. List webhooks/callbacks (which cannot carry a token) under :exempt-paths (a trailing /* matches by path-segment prefix). Startup fails loud: if CSRF is enabled with a blank secret the system wiring throws and the app refuses to boot, rather than starting with the interceptor failing open (running unvalidated). BREAKING (BOU-56): this replaces the previous warn-and-continue behavior — an app that set :enabled? true but left JWT_SECRET/:secret unset used to boot (CSRF silently disabled) and will now fail to start. Set the secret in the environment before upgrading.boundary-audience: New audience segmentation library (libs/audience/) with declarative, rule-based segment definitions. Features include:
defaudience macro for code-defined segments with seven built-in filter types (demographics, location, role, account-tenure, last-active, behavior, feature-usage)audience_memberships table with configurable per-segment TTLfilter->sql and filter->predicate multimethodsIAudienceResolver, IAudienceRepository, and IAudienceCache componentsboundary-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 (libs/push/) with multi-platform delivery via FCM (Firebase Cloud Messaging) and APNs (Apple Push Notification service). Features include:
defpush macro for declarative notification definitions with i18n locale maps, deep links, priority, TTL, collapse keys, and retry configurationIFCMProvider, IAPNsProvider) behind unified IPushService orchestratorsendAsync + CompletableFuture for both FCM and APNsboundary-jobs/api/push/devices), callback (/api/push/callback), stats (/api/push/stats/:id)push_device_tokens, push_send_log, push_analytics_events with multi-tenant supportboundary-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. All rights reserved.
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 |