Maintain commit_logs.translation_count at write time
DDL: **************** (column + partial-friendly index, applied to production already) and **************** (one-time catch-up, already run — 19,373 rows, all l...
Use the lean LeadVendorProduct reads, not the 8-way eager findById
Caller half of the kamo-shared-library fix: switches every findById() call on LeadVendorProductRepository in securityservice to whichever lean method matches wh...
List only the Getting Started steps an organization is asked
An organization whose security model fixes every Compliance answer (no product a member may switch, one age range for each member type) was still counted agains...
Read the public changelog's commit counts from a ledger, not COUNT(*)
pg_stat_statements: `SELECT COUNT(c) FROM CommitLog c WHERE c.project IN (:~33 public projects) [AND c.commitTypeId = :t]` ran ~140K times a day at 100-200 ms e...
Gift cards take the Discounts tab's pricing rights
Listing gift cards, issuing one and editing one (including its balance, which the shared library sets again as of the companion kamo-shared-library commit) chec...
A member's official title and status need member security too, on their own record
Department and job title already needed MANAGE_MEMBER_SECURITY (or an open god window) on /member-security, your own record included. The rest of the Position c...
CommerceMarketController's retail sub-resources are org-scoped
CommerceMarketController's get-one/update/delete handlers under /retail/... called RetailService methods that took no orgId (bare findById/deleteById) - a same-...
Drop 4 newly-guarded handlers from the unguarded-endpoints ratchet
**************** caught the previous commit: **************** and **************** now resolve a session (getCachedOrganizationId, in each handler's own body), ...
Scope roles, member access and profile writes to the caller's org
Five gaps let a signed-in member reach outside their own organization, or reach a colleague's account, with no right check: - **************** resolved no sess...
An entry page reads three rows, not the whole public changelog
pg_stat_statements put the public changelog's single-entry reads at the top of the database by a distance: the slug lookup and the two neighbour lookups ran ~90...
Forward the actor when relaying a commission line
addLine/updateLine relayed memberId (normalized to Long in 789c544) but never who was making the call — unlike openDraft, send, voidLine and every Stripe-config...
Forward subjectMemberId and role so TimecardService can verify punch ownership
**************** resolved the caller's role against a CLIENT-SUPPLIED subjectMemberId (resolveRole -> EMPLOYEE whenever actor==subject) but never forwarded that...
Adding people and member security need their rights
POST /members/create, /create-team-member and /bulk-create, and GET /members/lookup-user, now require MANAGE_MEMBERS or an open god window. They checked only fo...
Scrub copies of live credentials from config and dumps
A sweep of every repo for the values of the cluster's live Secrets (2026-09-23) found copies here: the JWT signing secret as a `${JWT_SECRET:<literal>}` default...
No live credentials in application.yml defaults
The local-development defaults carried the database OWNER's password (${DB_PASSWORD:<literal>}), and apiservice/securityservice also the live changelog webhook ...
Gate the API on the app, not only the screen
The settings page and the nav option both check organization.isHostedComputers, and until now that was the whole gate. AppAvailabilityInterceptor never mapped /...
Human/AI members, phonetic names, role requirements; fix position saves wiping security
Members - Create, create-team-member and bulk-create accept `intelligenceType`, `namePhonetic` (new accounts only, like the name), and for AI accounts `aiSupe...
The browser-facing surface, and the second Guacamole gateway
SecurityService is the trust boundary for Hosted Computers. It holds the session, the rights and the org chart; ComputeService holds the Kubernetes credentials ...
Intake endpoints show exact received/imported counts from the intake ledger
The Lead Intake settings list showed total_received / total_processed / last_received_at straight off the endpoint row, where they were bumped once per payload ...
Manage-Credits and Leads-Available read the lead ledgers once instead of counting per row
/leads/credits (GET /api/security/credits/manage) counted the lead pool once per allotment row — ~8 s apiece against one org's 796k-lead pool — then once more g...
Normalize memberId when relaying line add/update requests
The browser keeps member ids as strings to avoid IEEE-754 rounding on CockroachDB unique_rowids. Normalize to Long before relaying so the credit lands on the in...
The account view names its primary member
GET /customers/{uid} sent the primary member only as primaryMemberId, while the account view reads a primaryMember object — so its Primary member row read "---"...
The member card carries the wallpaper their desktop shows
GET /members/{id}/card gains "wallpaper" for the card's header, resolved the way the console backdrop and the profile hero resolve a member's desktop: none when...
An account note names the member who wrote it, not "System"
GET /customers/{uid}/notes sent each note's authorMemberId but no authorName, and the account view shows a note with no name as written by the system — so every...
One rule for what a member may do with another, served per member and per roster row
The same five actions — chat, call, email, meet, view profile — were offered from three places in kamo-internal (the Interaction Center row, the chat hex-head p...
The demos booked on a lead, on the lead
GET **************** The lead page's Webinars section has called /api/webinar/leads/{id} since it was written and nothing ever served it — the live API answers ...
Short one-time codes can no longer be guessed without limit
Every short code the platform mails or texts was checked with no count: - POST /api/recover/email/verify-code took the 8-hex-character reset code ALONE and mat...
Securityservice never stores a password as typed
Two writers put plaintext into USERS.PASSWORD. Both called User.encodePassword(), which needs a static encoder that only initializerservice ever sets **********...
Retail store connections are reachable only by their own org's commerce settings managers
The retail provider-config handlers on CommerceMarketController resolved the session's org and then used whatever market and config uid the path named. Update, ...
The System User acts only inside the organization it was entered into
There is one System User for the whole platform. Platform operators, and support staff holding a grant scoped to a single ticket's organization, enter a tenant ...
Only an org's owner, its CONFIGURE_SYSTEM members or active god mode can change its settings
PUT /api/security/org/{id} took no request at all. ResourceServerConfig permits every request and APIService relays /api/security/**, so one anonymous request c...
An OAuth app save keeps its client secret unless a new one is typed; owners' credentials stay out of org JSON
PUT /api/oauth-config/{id} stored whatever arrived under clientSecret. Responses mask the secret, and the edit form sends a blank back while promising "Leave bl...
A search term reads the lead's email
Walks the grid specification's LIKE branches on the Hibernate this service runs (the HQL rendering leaves function arguments out, so it cannot show which column...
Stop GET /org/{id} shipping the GoDaddy credentials, and lock it to this service's mapper
The fix is in kamo-shared-library 096feaa1: Organization.godaddyApiKey and godaddyApiSecret are now @JsonIgnore. That change reaches production only when a serv...
Lifestyle logo uploads, config.json keys, and the LIFESTYLE watermark
The lifestyle logo is a fourth logo per brand (company and white-label), the decorative rendition the background watermark now draws by default. Its fallback is...
An imported lead is recorded as imported
LeadImportController created every row with the two-argument createLead, so a lead's history said source API with nobody acting, exactly like one sent in throug...
Retry public reads the database aborted with 40001
Every DDL bumps the YSQL catalog version, and a transaction that began on the old one fails with "catalog snapshot ... invalidated: MISMATCHED_SCHEMA" (SQLSTATE...
Release idle Hikari connections now that YSQL pooling is shared
The connection manager no longer pins sessions **************** so idle app connections no longer each hold a database backend. Keep the pool maximum, but stop ...
Take kamo-meet and kamo-analytics off the public changelog
Both names leave changelog.public-projects. Every public read goes through that one list, so the marketing site's /changelog stops showing them everywhere at on...
The Email Campaigns widget arrives on for everyone who can open the campaigns
EMAIL_CAMPAIGNS_WIDGET and its ADDED, OPTIONAL and MOVABLE policies seed from VIEW_BULK_EMAIL on the boot that introduces them, so the pane lands on the launchp...
Lead opt-out panel passes personAsked to the ledger
The lead page's opt-out update takes an optional personAsked flag and hands it to the ledger, which now refuses to lift a person's own unsubscribe, complaint or...
The organizations each member created, on the member lists
GET **************** and /team-members-contractors now carry createdOrgs per person: the names of the organizations they own (orgs.user_id_owner, set once by PO...
An organization's and a member's own page transition
The page transition is now chosen at three levels, resolved member, then organization, then platform — for the choice of transition and, one transition at a tim...
Tune each page transition, tile size and colour to start
GET and PUT /api/security/page-transition answer options for every catalogue id, normalised from PAGE_TRANSITION_OPTIONS: a missing, unreadable or out-of-range ...
Every tab reads the page transition, and an operator chooses it
GET /api/security/page-transition answers any signed-in session, since every tab needs it to uncover a page; an unset or unknown value reads as TILE_DISSOLVE. T...
The id resolver never hands a campaign an address the member can't read
Resolving lead ids read each address through the lead masking DTO and skipped only blank ones. Masking now obfuscates instead of blanking ("s*****@gmail.com"), ...
A lead's opt-out change refreshes open screens live
The ledger here tells OptOutChangePublisher after the change commits, which publishes rt.org.{orgId}.email-opt-outs on core NATS (this service's JetStream strea...
Issue personal single-use codes from a template promotion
The kamocrm.com site hunt hands each solver a code of their own, and only the first N solvers ever get one. A shared code could not do that: the first solver to...
A new organization's folder holds none of the platform's backgrounds, stylesheet or config
Provisioning copied the whole base theme into every new organization's folder, KamoCRM's three sign-in photographs included. They sat at img/bg/1-3.webp under t...
Like what you see shipping?
All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.
