Skip to content
KAMOUniverse

Changelog

What's new.

Every improvement to the platform, as it ships.

  1. · MediaService

    Verify routing token, persist leadUid, reassign chat on lead move

    - createSupportSession takes a signed routingToken (not preferredAgentMemberId); verified via ChatRoutingTokenCodec + cluster internal.auth.secret, enforces token.orgId == org, routes only when integration.assignmentMode == SPECIFIC_AGENT. Hard cut of the bare browser-supplied member-id path. - persist leadUid on SupportTicket + PublicChatVisitor (durable borrower-lead link) - ChatRoutingConsumer: on security.chat.routing.>, reassign the borrower's open ticket(s) by leadUid to the new officer (reuses assignToPreferredAgent) - lo-presence org-scoped: cross-org/unknown member -> online:false (no leak) - WebChatIntegration CRUD/bootstrap carry assignmentMode; store publicSiteKey for SPECIFIC_AGENT integrations

  2. · kamo-shared-library

    Signed agent-routing token + lead-reassignment propagation

    Foundation for making the reusable public-chat widget handle the KamoMLOS borrower->loan-officer variant securely, and for propagating lead reassignment into an open chat. - WebChatAssignmentMode (TOPIC_QUEUE default / SPECIFIC_AGENT); WebChatIntegration gains assignmentMode + publicSiteKey (platform-managed key for host-resolved MLOS) - ChatRoutingTokenCodec: short-lived HMAC token over the cluster secret proving the target agent; replaces the browser-supplied preferredAgentMemberId - SupportTicket.leadUid + PublicChatVisitor.leadUid (durable borrower-lead link) + **************** - **************** -> security.chat.routing.{orgId}, emitted from lead reassign (LeadService.updateLead) and pool re-claim (acceptNextLead) - **************** managed-integration finder

  3. · SecurityService

    Expose vision capability so the console can explain a missing provider

    PDF rate sheets are read by the org's configured vision provider. When none is configured the AI call fails the same way a model error does, so the console had no way to distinguish "nothing is set up to read this" from "the sheet was blank". GET **************** proxies AIService's capability probe under the existing steward gate, org server-stamped, so the UI can name the actual problem and point at where to fix it.

  4. · AIService

    Inline image vision + tier fallback that prefers stronger over weaker

    Two limits made org-configured vision unusable for whole classes of caller. 1. Vision was URL-ONLY. Every caller had to stand up public, provider-fetchable serving infrastructure — a signer, an unauthenticated serve endpoint, an ingress. A ClusterIP service could not use vision at all no matter what the org had configured. Adapters now is strict — a malformed data: URI falls back to URL handling, so an ordinary URL can never be turned into a broken inline part. Callers with a signed public URL are unaffected. 2. Tier selection only distinguished "exact match" from "everything else", so a LIGHT or untiered provider could outrank a configured HEAVY purely on priorityOrder — a MEDIUM request could quietly read a document with a model too weak for it. Ordering is now requested tier -> stronger tiers (strongest first) -> untiered -> weaker tiers. Under-serving is the failure that produces wrong answers, so over-serving is preferred to it. Tier stays a preference, never a filter, and priorityOrder still breaks ties. Shared by both vision and text selection, which had duplicated the old comparator. Also adds GET **************** resolved through the real routing path: MlosAiClient collapses every failure into an empty Optional, so "no provider configured" is otherwise indistinguishable from "the model erred" and nothing can tell a member which. This is what lets a UI say so.

  5. · kamo-internal

    Split Devices into "Phone System(s) Devices" + "Mobile Devices"

    The Devices tab now has two sub-tabs. The existing PBX/softphone hardware table moves under "Phone System(s) Devices" unchanged; the new "Mobile Devices" tab Mobile Devices lets an admin set: - the ORGANIZATION DEFAULT policy (personal devices allowed vs company-managed required), saved through the existing org field patch, and - a PER-MEMBER override (or "use org default"), with the resulting effective policy shown per member, alongside the devices each member has enrolled (name, platform, last used, last IP). A member on COMPANY_MANAGED_REQUIRED can only reach the org from a company-managed (Android Device Owner enrolled) phone — the app hides the org entirely on any other device; managed devices also update silently and stay running. The sub-selection uses its own `?deviceView=` param so it never collides with SettingsView's `?tab=`. Backed by the new **************** endpoints. Note: the new labels are literal strings — the next-intl message catalog lives in the separate translation dictionary, so keys should be added there in a follow-up.

  6. · SecurityService

    Org-admin endpoints for mobile device policy + enrolled devices

    Backs the new "Mobile Devices" sub-tab under Settings → Features → Phone → Devices (its sibling "Phone System(s) Devices" stays on VOIPService and covers - GET **************** org default policy + one row per ACTIVE member: their override, the effective policy, and the devices they currently have enrolled (non-revoked UserDeviceTokens: name, platform, last used, last IP, expiry). - PATCH **************** sets or clears the per-member override; null inherits the org default. Gated on MANAGE_VOIP_SETTINGS (the right that already opens the Phone settings page) plus same-org, or god mode. The org-wide default itself is written through the existing PUT /api/security/org/{id} field patch.

  7. · kamo-internal

    Ingestion panel accepts spreadsheets, not just CSV

    The extractor now reads .xlsx/.xls and sniffs the format from the file itself, so the picker no longer restricts to CSV and the copy says plainly what is readable — including that PDFs are not, rather than letting a steward upload one and get a confusing failure.

  8. · kamo-internal

    Steward Ingestion tab + the Catalog/Identifier proxy routes that were missing

    Rate-sheet ingestion existed end-to-end in the backend and none of it was reachable. This adds the one surface that walks the whole chain: upload an investor's sheet -> ingest it -> see exactly what the extractor read, cell by cell with its provenance -> approve/reject -> publish the approved rows as a priceable rate-sheet version. The panel is honest about what it will actually do: it previews how many approved rows have BOTH a rate and a price (only those publish) and says plainly that a half row is skipped rather than guessed. Nothing reaches the catalog before approval. Also adds the /api/security/mlos/catalog and /api/security/mlos/identifiers proxy routes. Both panels shipped without one, so every call from them 404'd at the Next layer and never reached SecurityService at all.

  9. · kamo-shared-library

    CATALOG_SOURCE assoc type for MLOS catalog source documents

    Investor rate sheets / LLPA + eligibility matrices / lock policies need somewhere to live: the ingest extracts from the blob, and the published rate-sheet version points back at it as provenance. No existing assoc type fits — MY_DOCS is a person's own area and SYSTEM_WIDE's bucket is e-sign templates. CATALOG_SOURCE is org-scoped and deliberately NOT deletable or overwritable: a live price cites this file, so replacing or removing it would silently rewrite the evidence behind a published version. Appended last, so no existing ordinal shifts.

  10. · SecurityService

    Gateway for candidate review + rate-sheet promotion

    Exposes the two new catalog promotion writes to the steward console under the existing MLOS steward gate, with organizationId server-stamped from the operator session: POST **************** POST ****************

  11. · kamo-shared-library

    MobileDevicePolicy — org default + per-member override

    personal phone or must be on a company-managed (Android Device Owner enrolled) device: - MobileDevicePolicy enum: PERSONAL_ALLOWED | COMPANY_MANAGED_REQUIRED. - Organization.mobileDevicePolicy — the org-wide default (NOT NULL, defaults to PERSONAL_ALLOWED so existing orgs are unaffected). - Member.mobileDevicePolicy — nullable per-member override; NULL inherits the org default, which is how the policy is configured "per member". The EFFECTIVE value per membership is what the mobile app gates on: an org whose effective policy is COMPANY_MANAGED_REQUIRED is hidden from the org switcher on a device that isn't enrolled as company-managed. Columns applied via KamoInitializer.

  12. · SecurityService

    Steward gateway for ingestion (intake + staging area were unreachable)

    MLOSIngestionService had no gateway presence, so no tenant could hand a rate sheet to the extractor and the candidates it stages had no reader on any reachable surface. Adds **************** at /api/security/mlos/ingestion: multipart rate-sheet ingest relay plus the two staging reads (a source document's candidates, an investor's intake history). organizationId is SERVER-STAMPED from the operator session on every call, so a steward only ever touches their own tenant's staging area. Read timeout is 120s — a parse is CPU work, not a lookup. imgDatId stays required: it is the content-addressed provenance anchor and the intake-dedup key, so the blob is stored through the imaging pipeline first.

  13. · kamo-internal

    Steward Identifiers tab — resolve an external number to its loan

    New Identifiers tab in the mortgage steward console: paste an external number (loan number, MERS MIN, DU/LP/GUS case file, e-sign envelope...) and resolve it back to its loan — the same gate the inbound vendor webhook router uses, so a steward can chase an unrouted callback — plus a per-loan identifier ledger and lifecycle view. Fail-closed: unknown, ambiguous, or another tenant's number all read UNRESOLVED. Wires the new /api/security/mlos/identifiers proxy. Plain-string labels (i18n follow-up).

  14. · SecurityService

    Identifier gateway — steward can resolve an external number to its loan

    MLOSIdentifierService had no gateway, so the typed identifier plane (the routing gate every inbound vendor/agency/AUS/e-sign callback uses) was unreachable from the console. New MlosIdentifierClient + MlosIdentifierController **************** resolve an external number, and read a loan's identifier ledger + lifecycle events. Tenant isolation: the per-loan reads pass the operator's own org as the doc-11 §6.5 cross-org guard; /resolve carries no org downstream, so a resolution landing in ANOTHER org is reported as UNRESOLVED here rather than leaking that the number exists. god-eligible steward gate. configmap: mlos.identifier.url.

  15. · kamo-shared-library

    Value-keyed AUS case-file + e-sign envelope finders for identifier owner-delegation

    Completes the owner-column lookups the MLOS identifier plane needs to route inbound callbacks: **************** (DU/LP/GUS run handle) and **************** Both org-agnostic and LIST-returning so a re-run collapses and an ambiguous cross-loan collision fails closed rather than mis-routing. Additive @Query finders; no schema.

  16. · kamo-shared-library

    Value-keyed loan-identity finders for identifier owner-delegation

    The MLOS identifier plane resolves an inbound vendor/agency callback back to a loan; for OWNED_ELSEWHERE types the authoritative value lives on the OWNER's column. Adds value-keyed (org-agnostic) finders returning a LIST (so an ambiguous cross-org collision fails closed rather than mis-routing): **************** and **************** / findByLenderLoanNumber / findByMersMin. Additive @Query finders on the donotmodify repos; no schema change.

  17. · kamo-shared-library

    CONDITION_ADDED / CONDITION_CLEARED canonical email templates

    The borrower-facing half of the conditions loop: two generic checklist templates for the new condition notification consumer. Added to CANONICAL_KEYS with their json+html resources; EmailService's seedForOrg self-heals them into any org on first send (idempotent), and it auto-injects orgName/logoUrl. Copy is deliberately generic (a new item / a completed item — open your portal) — the email never embeds a condition's specifics; the portal is the source of truth.

  18. · SecurityService

    Public pre-auth trial-status endpoint for login/register pill

    GET **************** resolves the org by hostname (alias-fallback) and reports whether its account is in an active native trial, reusing **************** (now public) so the pill matches the in-app trial banner. Fails closed to inTrial:false for unknown domains.

  19. · kamo-register

    Show TRIAL USE ONLY pill above Powered-by for trial orgs

    Refactors PoweredByKamo into a fixed column stack and self-fetches /api/trial-status/{host} (proxy to SecurityService public trial-status endpoint), rendering an amber pill above the Powered-by pill when the org's account is in an active native trial. Fails open to hidden.

  20. · kamo-login

    Show TRIAL USE ONLY pill above Powered-by for trial orgs

    Self-fetches /api/trial-status (proxy to SecurityService public trial-status endpoint, resolved by login host) and renders an amber pill above the Powered-by pill when the org's account is in an active native trial. Fails open to hidden.

  21. · kamo-internal

    View third-party (loan) documents in the steward requirements review

    A steward reviewing a THIRD_PARTY requirement (appraisal/title/flood) can now open the satisfying document. New binary Next.js route streams the per-loan authz'd content endpoint; imagingApi.viewLoanDocument blob-fetches + opens inline/downloads; AppRequirementView surfaces the (already-projected) satisfierImgId + loanFileUid; DocumentsSection shows a View button on third-party rows with a satisfier. Backs the new SecurityService per-loan LOAN-doc content endpoint. Plain-string label (i18n follow-up).

  22. · SecurityService

    Per-loan third-party (LOAN) document viewer — appraisal/title/flood

    Completes the doc-review byte path started with the vault viewer. LOAN-collection imgs (appraisal/title/flood) are refused by the generic imaging endpoints (per-loan PII lockdown), so a steward reviewing a third-party satisfier could not open it. New GET **************** gated on an LOS operator (LOS_VIEW_PIPELINE) OR a member who is a party to this loan's application, and confused-deputy anchored by LoanDocLoader's assocObjectId==loanFileUid check (a LOAN img carries its owning loan in assocObjectId). Bytes stream in-process from the imaging-loans bucket, so the loan-file gate IS the compartment (not the CONFIDENTIAL clearance). ?download=true attaches; default inline. Closes the LOAN-side residual of ****************

  23. · kamo-internal

    View/Download vault documents in the account docs tab

    Turns the conditions/docs stage into a real review loop. The member-documents proxy route now passes through non-JSON (binary) upstream responses instead of text()-decoding them; memberDocsApi.viewDocument fetches the per-party /content bytes with the *** session and opens the blob inline (or downloads); AccountDocumentsTab gets View + Download actions per vault document. Backs the new SecurityService per-party content endpoint. Plain-string labels (i18n follow-up).

  24. · SecurityService

    Per-party vault document viewer — bytes were write-only, now review-able

    Uploaded vault documents (pay stub / Gov ID / bank statement) were write-only: the generic imaging byte endpoints refuse ACCOUNT_MEMBER_VAULT (per-party PII lockdown), so a steward accepted/rejected a requirement without ever opening the doc, and a borrower couldn't re-view a submission. New authz'd byte endpoint GET **************** mirrors the extract guard: requirePartyAccess (owning member OR an LOS right) + a document-belongs-to-this-party confused-deputy check, then serves bytes IN-PROCESS via VaultDocLoader (MinIO) — so the per-party gate IS the PII compartment, not the CONFIDENTIAL clearance level (which would fail even for the owner). ?download=true attaches; default inline. Closes the residual noted in ****************

  25. · kamo-internal

    Steward Catalog tab — seed a priceable bundle + inspect the catalog

    New Catalog tab in the mortgage steward console: seed a tenant's first priceable bundle with REAL investor + product identities (unblocks pricing), and inspect the org's investors -> products -> rate-sheet versions. Wires the new /api/security/mlos/catalog proxy via catalogApi. Closes the audit's #1 gap — a real tenant's catalog was unseedable/unreachable, so nothing downstream could price. Plain-string UI (i18n follow-up).

  26. · SecurityService

    Catalog gateway — the missing proxy that blocked tenant pricing

    Catalog was the ONLY MLOS bounded context with no SecurityService gateway, so a tenant's catalog could never be authored/seeded from the console and BorrowerQuoteService returned NO_CATALOG for every real org (pricing stage dead). New MlosCatalogClient + MlosCatalogController (/api/security/mlos/catalog) proxy the catalog reads **************** + the version-spine writers (priceable-bundle seed, **************** publish, bulletin). org is SERVER-STAMPED onto every write from the operator session (a client-supplied org is overwritten); god-eligible steward gate. configmap: mlos.catalog.url.

  27. · kamo-internal

    Loan-sale desk UI — the terminal capital-markets steward screen

    LoanSaleDeskPanel in the Capital & Risk Command Center: list investor delivery commitments (status filter), open a commitment **************** and per-commitment allocate locked loans, deliver, purchase (funds the loans CLOSED -> FUNDED), board, and pair off allocations. Wires the shipped **************** proxy via capitalMarketsApi. Replaces the roadmap gap noted in the audit as the single biggest missing steward screen.

  28. · DaemonService

    Durable consumer forwards investor purchase to loan-file FUNDED

    MlosLoanFundedConsumer subscribes **************** (durable daemon-mlos-loan-funded) and forwards each purchase to SecurityService's /funded pipeline endpoint via SecurityPipelineClient, advancing the loan CLOSED -> FUNDED (terminal origination stage). Reuses the shared IdempotentEventProcessor core (dedup + nak-retry + DLQ) exactly as MlosClosingCompletedConsumer; idempotent server-side (already FUNDED = 200 noop). SecurityPipelineClient generalized (closingCompleted + funded, body key closingPackageId -> context). configmap: daemon.jobs.mlos-loan-funded enabled, max-attempts 5.

  29. · SecurityService

    Loan-sale desk proxy + CLOSED->FUNDED pipeline seam

    - **************** generalized the eClose seam to a shared advance() + added POST /funded (CLOSED -> FUNDED on investor purchase). Same idempotent/at-least-once contract (already-target noop, wrong-precoder skip, blocker held, transient 5xx). Request field closingPackageId -> generic context. - **************** org-scoped commitment desk endpoints (open/list/get/allocate/ **************** org SERVER-STAMPED from the session; opener stamped as createdByMemberId.

  30. · kamo-shared-library

    Commitment boardedAt + loan-funded event type (loan-sale desk)

    - MlosCommitment.boardedAt: in-system marker for a PURCHASED commitment's loans boarded to servicing (the external servicing-system handoff is the blocked leg). - MlosEventTypes.LOAN_FUNDED **************** emitted per allocation on investor purchase; the pipeline seam consumes it to advance the loan CLOSED -> FUNDED (terminal origination stage). KamoInitializer run completed — mlos_commitment (+ boarded_at) and mlos_commitment_allocation are live.

Your universe is waiting.

Start free in minutes. Add products whenever you are ready — no migration, no new logins.

Free forever — no credit card required