---
title: "Platform Updates — May 2026"
slug: "platform-update-2026-05-31"
date: "2026-05-31"
tag: "Application"
summary: "65 production image releases (1.7.3 → 1.25.0): field-level credential encryption, a full CRM domain, tiered chat-privacy controls, LiveKit voice call agents, an LLM-as-Judge evaluation surface, and Microsoft SSO in the provisioning CLI."
author: "ibl.ai Engineering"
---

Production image releases `1.7.3` → `1.25.0` (65 releases, May 2026).

| service | image / pin | version span |
|---|---|---|
| dm | `iblai-dm-pro` | `4.243.2` → `4.266.0-ai` |
| os | `iblai-os-spa` | `0.58.1` → `0.70.0` |
| lms | `iblai-lms-spa` | `0.20.0` → `0.35.0` |
| edx | `iblai-edx-pro` | `sumac.2.16.2` → `sumac.2.30.13` |
| cli | `iblai/infra-cli` | `1.5.4` → `1.10.1` |

---

## `iblai-dm-pro` — `4.243.2` → `4.266.0-ai`

**Credential encryption and sensitivity-aware access.** The Data Manager gained field-level encryption for every sensitive credential model — `LLMCredential`, `GlobalCredential`, `IntegrationCredential`, `ConnectedService`, `MCPServer`, and `MCPServerConnection` now carry encrypted shadow fields backed by new `EncryptedTextField`, `EncryptedJSONField`, and `EncryptedFieldsMixin` primitives in `dl-core-app` (Fernet symmetric encryption). Transparent `secure_*` properties and updated `get_cred()`/`aget_cred()` utilities read through the encryption, and lifecycle management commands (`backfill_encrypted_fields`, `verify_encrypted_fields`, `cleanup_plaintext_fields`) handle migration. Django admin now masks both plaintext and encrypted credential fields, gated by a new `MASK_CREDENTIALS` GlobalConfiguration setting (default `True`).

**Integration-credential schema V2.** Integration credentials moved to a sensitivity-aware model: students see only `drive`/`dropbox`/`onedrive` credentials with sensitive fields stripped, while admins see all credentials with sensitive values masked (e.g. `G*****`). A new FK from `IntegrationCredential` to `IntegrationCredentialSchema` drives per-field sensitivity via a V2 schema format carrying `is_sensitive` metadata, with V1 backward-compatibility retained. Phase 2 cleanup (`0321_convert_credential_schema_to_v2_and_backfill_fk`) rewrites every schema row to the V2 shape and makes sensitivity fail-closed — when the FK is missing or the schema isn't V2, every field is treated as sensitive. The legacy V1 schema endpoint now returns `Deprecation: true` and an RFC 8594 `Link: …; rel="successor-version"` header.

**Voice and LiveKit call agents.** `voice_provider`, `google_voice`, and `openai_voice` became read/write on the agent settings endpoints (`POST /mentor-with-settings/`, `PUT /mentor/<mentor>/settings/`), accepting `openai` or `google`. LiveKit call agents now receive the agent's configured Langchain tools through a new `CallAgentToolFetcher` that reuses the chat-side `BaseAgentToolFetcher` pipeline (tool-matrix gating, output sanitization, MCP OAuth + retries, rate limits) for both inference (separate STT/LLM/TTS) and realtime sessions; clients subscribe to the `"tool"` data-channel topic to receive OAuth prompts and rate-limit notices during a call. The deprecated `gemini-2.0-flash-exp` was retired from call paths in favor of `gemini-2.5-flash-native-audio-preview-12-2025` (realtime) and `gemini-2.5-flash` (inference), alongside a `livekit-agents` 1.3.10 → 1.5.11 dependency bump.

**CRM domain, RBAC, and notifications.** A full CRM domain landed in `ibl-dm-crm-app` — Platform-scoped `Pipeline`, `PipelineStage`, `LeadSource`, `Deal`, `Activity`, and `Tag` models with a row-locked deal state machine (`move_deal_to_stage`, `mark_won`, `mark_lost`) that serializes concurrent transitions under `select_for_update()`, writes an audit `Activity` per move, and dispatches `deal_stage_changed`/`deal_won`/`deal_lost` events. `Deal.status` and `Deal.closed_at` are service-managed (read-only via the API, derived from stage `is_won`/`is_lost` flags), and REST endpoints cover `pipelines/`, nested `stages/`, `lead-sources/`, `deals/`, `deals/{id}/move-stage/`, and `activities/`. A new `Ibl.CRM/*` RBAC namespace seeds four policies per platform (`CRM Viewer`, `CRM User`, `CRM Manager`, `CRM Inviter`), and `Person`/`Organization` endpoints resolve platform scope from the auth token via a shared `BaseCRMMixin`. Three notification types (`CRM_PERSON_CREATED`, `CRM_DEAL_STAGE_CHANGED`, `CRM_PERSON_LINKED_TO_USER`) ship with a new `RecipientsConfig` routing dataclass whose modes (`platform_admins_only`, `object_owner_only`, `platform_admins_and_object_owner`, `custom`) are filtered by active `UserPlatformLink` so notifications can't leak cross-tenant.

**Tiered chat-privacy controls.** A strict four-tier privacy hierarchy (agent → tenant gate → session → user → default) is now enforced by a single pure resolver in `ibl_ai_mentor/services/chat_privacy.py`, where each lower tier can only further restrict history. A new `Session.disable_chathistory` field lets sessions be created already-private; a one-way mid-session toggle endpoint (`POST …/sessions/{session_id}/disable-chathistory/`) nulls `Session.student` when flipped on and rejects re-enabling with `400`; and `GET …/chat-privacy-effective/` returns `{ mode, source, is_locked }`. In `DISABLED` mode no rows are written or surfaced for LLM context. A separate fix to `IBLChatMessageHistory.preprocess_message` replaced the substring page-content stripper with a non-greedy `re.DOTALL` regex per envelope and added context-metadata (`METADATA_PREFIX`/`METADATA_SUFFIX`) stripping, closing a customer-visible leak where the metadata envelope was persisted verbatim into chat history.

**Evaluation, observability, and the LLM registry.** Evaluation API responses became frontend-grid-friendly — dataset items expose `trace_input`/`trace_output`, experiment detail embeds `input`/`expected_output`/`actual_output`/`trace_id`/`scores` per run item, and a new local `EvaluationTaskRecord` table surfaces pending/in-progress Celery tasks with a full status lifecycle. New LLM-as-Judge list endpoints (`…/evaluate/`, `…/judges/`) were added, and read paths now hit Langfuse's Postgres and ClickHouse directly via `managed=False` reflection models, cutting experiment detail from N ClickHouse round-trips to a constant 1 PG + 2 CH queries. A new `ChatRunTracker` model records per-request timing breakdowns, LLM provider info, fallback usage, and failure categories, linked to `ChatMessageHistory.generation`. `LLMModelRegistry` gained a per-model `kwargs` JSONField for init overrides (`temperature`, `streaming`, `reasoning`, `use_responses_api`) editable from Django admin, a `check_model_parameters` command that runs a one-shot chat against every active model, and registrations for Anthropic `claude-opus-4-7` and OpenAI `gpt-5.5`.

**Monetization and billing.** A new `Platform.enable_monetization` boolean separates the merchant-of-record/Stripe-Connect role from `show_paywall` enforcement, provisioned alongside `show_paywall` and backfilled from it for existing platforms. Auto-recharge is enabled by default for all credit accounts (configurable default $10) with a payment-method guard to prevent failed charges. `ReconciliationService` now sums managed-OpenClaw LLM usage (fetched via WebSocket RPC) alongside ClickHouse and self-hosted usage so previously-invisible managed-claw spend is billed, with a `provision_mode` parameter to scope reconciliation to managed or self-hosted instances. An anonymous platform-config endpoint was added for catalog gating.

**Notifications and course signals.** Course schedule-change notifications now email enrolled learners when a course's start date, pacing, or certificate-availability date changes, and grade-posted notifications alert learners when a subsection grade is posted or updated (with debounced or first-only dedup). Notification templates gained a three-dimension taxonomy (content / audience / action) stored as prefixed strings in the existing `tags` JSONField, with a controlled vocabulary in `notification_consumer/taxonomy.py` and Content/Audience/Action admin filters.

**Search, catalog, and OpenClaw client hardening.** Agent search fixed an N+1 (`prefetch_related("categories")`), added a paginated `GET /api/catalog/resources/search/` endpoint, and made the `mentor_include_community_mentors` opt-out a hard cross-tenant gate on `/api/ai-search/mentors/`; anonymous `?platform_key=` and `?tenant=` now return identical results after a fix to `V2GlobalMentorSearchView` that was clobbering the requested tenant to `None`. The OpenClaw client now negotiates a protocol-version range (default v3–v4) instead of pinning v3, tolerates a trailing slash on `server_url`, provisions device-identity keys with fingerprint display on the `ClawInstance` admin, and honors the `ClawMentorConfig.auto_push` flag on agent-config edits.

**Security hardening and fixes.** All Langchain tool results are now run through a new `ToolOutputSanitizer` (HTML stripped, content wrapped with prompt-injection markers) with a per-tool `clean_output` toggle; a follow-up fixed a `RecursionError` when sanitized tools ran under LangGraph's `ToolNode` by patching tool methods in-place. Additional fixes closed a SQL injection in the `create_ai_user` command, added authentication/authorization to the `GuidedPrompts` and `UserChatFeedback` endpoints, removed a deprecated GitHub PR-review webhook, upgraded `starlette` to `1.1.0` for CVE-2026-48710, and corrected multimodal `.docx` parsing by moving to `markitdown[all]`. The default AI agent was renamed `mentorAI` → `agentAI` on the main tenant and new tenants in a backward-compatible way (`name__in=("agentAI", "mentorAI")` lookups keep rolling deploys and existing tenants working), and the `Mentor` model gained `show_catalogue`/`show_reasoning` booleans plus a seeded `twenty` (Twenty CRM) integration credential schema.

## `iblai-os-spa` — `0.58.1` → `0.70.0`

**Credits and commerce.** A credit-coin / credit-balance component was integrated into the header navbar alongside an ecommerce update, and the `is_enterprise` flag was replaced with `show_paywall` for gating.

**Chat and accessibility.** The chat surface added a new-chat post-message event for iframe handlers, forwarding of the `?prompt=` URL param into `useAdvancedChat` as an initial prompt, Markdown rendering for suggested prompts in the welcome and advanced-chat views, a labeled chat-input textarea, `aria-label`s on icon-only composer buttons, and a narrow-viewport composer reflow with a skip link. Voice autoplay of AI messages was added, driven by a voice-event-completed signal.

**Agent editing and tenancy.** The edit-agent experience gained OpenClaw tabs, a privacy tab, and an "Enhance Document Retrieval" toggle in settings. Cross-tab tenant switching now syncs via a `BroadcastChannel` and an `ibl_tenant_switching` cookie set during the switch.

**Embedding.** The embed tab added a "Show Catalogue" toggle, gated sidebar-logo navigation on `show_catalogue` in embed mode, validated the Website URL input as origin-only (no trailing slash), and hid the Close "X" when the chat is not iframed.

**Desktop (Tauri) and security.** The macOS desktop build removed the `network.server` entitlement per App Store review and patched Cargo alerts. Security fixes restricted and sanitized `postMessage` target origins to prevent XSS, looped HTML-tag stripping to prevent incomplete sanitization, patched 32 (plus 6 follow-up) Dependabot alerts, pinned `qs` (CVE-2026-8723) and `uuid` (CVE-2026-41907), and replaced `xlsx` with `write-excel-file` to close a prototype-pollution vector. UI labels were normalized — "Learner/Instructor" became "User/Admin", settings-tab labels were unified to nouns, and the Agent AI banner copy was updated.

## `iblai-lms-spa` — `0.20.0` → `0.35.0`

**Monetization.** The bulk of the LMS work was a programs-and-courses monetization feature — a paywall surface for programs and courses, a programs detail page, and a migration of the gating flag from `show_paywall` to `enable_monetization` (with a matching `iblai-js` bump).

**Agent mode and assessment.** Agent mode now fires a proactive agent message on initial course load, and an agent-autoplay feature drives voice autoplay off the Studio `agent_autoplay` flag and the `enable_course_voice_autoplay` tenant metadata. An assessment feature was completed, with agent-xblock CSS that hides all other UI except the agent in assessment mode. A new-chat button triggers a fresh agent session over `postMessage`.

**Authoring, memory, and tenancy.** An authoring tab was added to course pages, memory was enabled on the profile component, and a direct course link from another tenant now auto-switches the user to the correct tenant instead of showing a 403. The app also gained an overall suspense loader and a set of env-flag-gated UI updates. The Tauri desktop build uses an `iblai-skills://` deep link as its auth redirect URL.

## `iblai-edx-pro` — `sumac.2.16.2` → `sumac.2.30.13`

**edX signal relay expansion.** The `ibl-edx-signal-relay-app` now relays five additional upstream edX signals to the Data Manager via `core/signals/edx/` — `SUBSECTION_SCORE_CHANGED`, `SUBSECTION_OVERRIDE_CHANGED`, `COURSE_START_DATE_CHANGED`, `COURSE_PACING_CHANGED`, and `COURSE_CERT_DATE_CHANGE` — with the DM endpoint accepting unknown `signal_type` values as a no-op so it can deploy ahead of consumers. Operator kill-switches at the `emit_remote_signal` boundary (`IBL_RELAY_ENABLED`, `IBL_RELAY_DISABLED_SIGNAL_TYPES`) fail safe, and the relay gained per-type log tagging and warning-on-non-2xx observability.

**Security hardening pass.** A run of theme and XBlock security fixes landed: the LMSX theme `header.html` was rewritten to emit user-controlled values through a JSON data island (`json_script`-style `<`/`>`/`&` escaping) instead of per-field JS-string interpolation (XSS-LMSX-THEME-01); `ibl-edx-scorm-xblock` `1.2.1` replaced its regex "sanitizer" with `bleach.clean` and a scheme allowlist to close stored XSS in the SCORM navigation menu; `ibl-edx-custom-emails-app` `1.4.1` introduced a restricted `SafeEmailFormatter` to fix `str.format` server-side template injection; and `ibl-edx-third-party-auth` `2.4.1` added Phase-1 nOAuth account-takeover hardening on the Keycloak, Google, and Entra ID backends (opt-in `reject_unverified_email()` plus audit logging on every email-keyed lookup, gated by `IBL_TPA_REQUIRE_EMAIL_VERIFIED`, default `False`). A dead `OAUTH_ENFORCE_SECURE = False` setting flagged as a false positive was also removed.

**Dependency and CVE remediation.** The Sumac chain moved Django `4.2.26` → `4.2.30` (closing 15 CVEs, the terminal 4.2 LTS release) and pinned `urllib3==2.7.0` (6 CVEs) via runtime pins, without rebuilding the base image and without touching the Olive Python 3.8/Django 3.2 chain. Both shared themes bumped `axios` `0.21.4` → `0.32.0` plus `overrides` for `lodash`/`fast-uri`/`follow-redirects`/`picomatch`/`minimatch`, and the CMS theme also pinned `postcss` `8.5.15`. The theme-build Node was upgraded `16.17.1` → `20.20.2` (fixing a latent nvm-PATH bug where builds silently ran on the base image's Node), unlocking `serialize-javascript@7.0.5` and taking `npm audit` to zero.

**Magic-link login and Studio agent config.** `ibl-edx-magic-link-login` `1.8.0` added long-lived allowlisted test emails and an idempotent re-request path — when both flags are on and a valid unexpired token exists, `request_magic_link` reuses it instead of minting a new one (critical for the Apple App Review workflow), preserving the single-active-token invariant under `SELECT FOR UPDATE`. In Studio, `ibl-edx-mentor-xblock` `4.0.0` renamed the rendered web-component tag from `<mentor-ai>` to `<agent-ai>` (a breaking element-name change; attributes unchanged), `4.0.1` exposed `enable_chat_action_popup` as an editable Studio field, and an "Agent Autoplay" checkbox (`agent_autoplay`) was added to the "Agent & Content Mode" section so the SPAs can speak agent responses aloud automatically.

**Build-infrastructure fix.** A Sumac-only typo — the lmsx-theme lib-build guard checking `vendor-manifest.json` instead of the emitted `vendor.manifest.json` — was silently skipping `npm run lib` on every Sumac image, so `iblxlib.js` was never produced and course iframes (HTML-XBlock completion tracking, ORA grading, JWT bootstrap) degraded with a MIME/`ReferenceError`. Fixing the guard (and bumping `ibl-edx-lmsx-addon-theme` `0.1.4` → `0.1.5` as a release marker) restored the working bundle.

## `iblai/infra-cli` — `1.5.4` → `1.10.1`

The infrastructure CLI shipped five May releases. `1.7.0` added optional Microsoft (Azure AD) SSO setup via a new `microsoft_sso_config` ansible role that patches `IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND`, creates the `azuread-oauth2` `OAuth2ProviderConfig` row idempotently, and introduced a top-level `SetupConfig.platform_name` plus `--platform-name`/`--microsoft-sso-*` launch flags. `1.9.0` added `.env`-driven provisioning — `iblai infra provision-env -f .env` (fresh single-server Terraform provision, no AMI) and `iblai infra setup-env` (non-interactive, idempotent Ansible bootstrap) — with `.env.provision.example`/`.env.setup.example` templates and a fix for an `ansible-core` 2.19 parser error that had blocked six roles. `1.10.0` added an `ibl_tenant_platform` role that launches a tenant `Platform` through the full `run_launch_steps` state machine (not a raw create) when `PLATFORM_NAME != main`, made Microsoft SSO also patch `IBL_SPA.AUTH`, added an `INSTANCE_RAM_GB` 32 GB warning and a 100 GB minimum root-volume gate, and — a behavior change — now writes `IBL_SPA.MENTOR.STRIPE_ENABLED=false` unconditionally so Stripe deployments must explicitly re-enable billing UI post-setup. `1.10.1` raised the SPA-ready wait budget from 150 s to 450 s, and `1.5.4` pinned direct runtime dependencies to the running freeze.

The companion `iblai-cli-ops` pin advanced across the month from `5.9.0` → `5.23.0`, carrying the field-encryption `FIELD_ENCRYPTION_KEY` auto-generation, nginx upstream/server blocks for `agent.{BASE_DOMAIN}` and `skillsai.{BASE_DOMAIN}` (gated by `RUN_AGENT_SPA`/`RUN_SKILLSAI_SPA`), the surfacing of `agent_content_mode`/`course_content_mode`/`agent_autoplay` through the DM `COURSE_METADATA_KEYS`/`COURSE_METADATA_PUBLIC_KEYS` defaults, and the `hq` → `os` SPA-image rename (reading `images.spa.os`/`courseai`/`videoai` from the catalog).
