The seat headline read a default, not a ceiling
The card printed "Up to 5 users" from plan.maxSeats, directly above its own feature list reading "Users (25)" and a comparison column reading 25 - the same card...
Catalog rows that made the pricing page contradict itself
COMMERCE_POS is ServiceType.POS(21), whose real name is "Lines of Business" - the umbrella switch for what an org sells, rents, services or lends. It was labell...
A CCD preview is not a disclosure
Previewing a summary recorded a disclosure naming the recipient — so a patient asking who has their record under §164.528 would be told it went to somebody who ...
Phase 0 infrastructure gate
- TURN over TLS on 5349 with a real cert (turn-kamocrm-com-cert). Without turns:, a customer behind a 443-only egress firewall has no media path. Verified e...
Rebuild for SubscriptionPlanFeatureDTO.unit
The catalog stores a unit per feature and the DTO now carries it, so the pricing page can render "15 GB per user" instead of a bare 15. Shared-lib reaches a ser...
Three catalog rows that made the pricing page contradict itself
Seat counts. The plan card headline reads max_seats ("Up to 5 users") while the feature list beneath it and the comparison table read the USERS feature row, whi...
Carry a plan feature's unit to the buying surfaces
The catalog has stored a unit per feature all along - "GB/user", "users", "contacts" - and the plan-feature DTO dropped it, so every surface rendering a measure...
The page contradicted itself, and the calculator billed email per user
Included-in badge named the wrong plan. All four per-seat modules are included in Business and Enterprise, and the badge picked the FIRST id in includedInPlanId...
FreePBX is deprecated (Decision 4 revised)
No migration and no permanent coexistence. KamoPBX stays on k3m1 whose host netns is empty, so it needs none of FreePBX's ports and no shutdown is required. Des...
Plan SW6a, and split SW6 into three
The spec calls SW6 'wire the finished UI', which assumed a backend existed to wire to. ServiceJob, ServiceVisit and ServiceLineItem's new columns have zero runt...
Pin the two form rules that fail silently
Both decide what NOT to send, which is the half of a form with no visible symptom when it goes wrong. A blank remittance line must not become a zero. Every lin...
One request-body parser, not three
The three clinical write controllers each grew their own string/uuid/integer/ date helpers within a week of each other — identical except for which ones each fi...
KamoPBX implementation plans (programme + 9 phases)
Decomposed into one plan per phase; a single plan would not produce working software until the end. Full bite-sized TDD detail: Phase 0 infrastructure gate ...
Rebuild against the storage dedup fix
Picks up kamo-shared-library d21e01ce. uploadDocument — the path behind member documents, mortgage disclosures, picture frames and media streams here — created ...
Rebuild against the document upload fix
Picks up kamo-shared-library d21e01ce, which landed minutes after the previous build. The chat-attachment path already has f848f394; this carries the second hal...
Rebuild against the storage dedup fix
Picks up kamo-shared-library d21e01ce. uploadDocument created its ImgDat with isMissing=false BEFORE sending the bytes, and its @Transactional did not roll back...
Rebuild against the storage dedup fix
Picks up kamo-shared-library d21e01ce. uploadDocument created its ImgDat with isMissing=false BEFORE sending the bytes, and its @Transactional did not roll back...
Record KamoPBX decisions — KAZOO confirmed, k3m1 placement, ACDc, Tier 3 rebrand
Six open questions answered: 1. KAZOO is the route; hardware owned, so cost framing removed entirely. 2. One node/cluster now, three later — CouchDB q=8 from da...
The document upload publishes its row only once the bytes land
The buffered uploadDocument was the one store that wrote its ImgDat with isMissing=false BEFORE sending anything, and merely re-set it to false after a successf...
Rebuild against the storage dedup fix
Picks up kamo-shared-library f848f394. A chat attachment whose upload failed left an ImgDat row that later uploads of the same file deduplicated against, so the...
A failed upload must not become a dedup match
A member sent an 11 MB .wav to a chat, got "Cannot invoke ObjectWriteResponse.etag() because response is null", sent it again, and the second one delivered inst...
Drop dead rate.loans and priceturbo.com from static provisioning
Both have had ACME challenges stuck pending for 47-54 days. The ratestack estate they proxy to has been scaled to 0 since 2026-08-04, so nothing can answer HTTP...
Bound the Go heap with GOMEMLIMIT and declare a real memory request
Root cause of the crash-loop was not a leak. The 485Mi steady state that kept tripping the old 512Mi limit was two normal things stacked: ~350Mi Go heap at ...
Close out SW5
Six departures. The sharpest: the task-book proxy could not be copied from the quotes proxy, which exports PATCH and no PUT — a verbatim copy would have made ev...
Swap the inbound greeting to KamoCRM-Main-Intro-2
Uploaded as a new file rather than overwriting the old one, so rolling back is a one-line change to KAMO_ATTENDANT_INTRO plus a dialplan reload — no re-upload n...
Rebuild against kamo-shared-library for the SW5 task book
An empty commit. ServiceTaskBookService and the four task-book entities live in the shared library; ServiceTaskBookController cannot resolve them until Security...
Author a flat-rate task book, priced by the minute
Replaces the SW5 placeholder pane with the real authoring surface: three sub-views on one tab — books, categories, entries — following the SubView switcher Mark...
BaseLosController's unauthorized()/forbidden() do exist
A task brief told an implementer those helpers were invented and did not exist. requireOrgId genuinely does not; those two do, declared protected static on Base...
Right-gated task book API
SW5 Task 3, controller half. CRUD for books, categories, tasks and options, plus GET /{bookUid}/tasks/by-code/{code} - the lookup a quote or work order performs...
Task book service with org-scoped CRUD and code resolution
SW5 Task 3, library half. ServiceTaskBookService exposes books, categories, tasks and options behind an org-scoped API, plus resolveTaskByCode - the lookup a qu...
Un-gate pricing, tax and tiers for service markets
A SERVICE market got exactly three tabs — Overview, Vendors, Agreements. Customers, Tax & Shipping and Discounts & Pricing were all gated on commerceType === 'R...
Apply a remittance to a claim
"Unapplied money" is money the payer has sent that nobody attached to a claim, and the queue could only be looked at. POST_INSURANCE_PAYMENTS gated an operation...
Find a claim by the number a payer quotes
Posting a remittance needs the claim it settles, and a remittance carries no internal id — only the number the practice sent. Without this lookup, applying mone...
An inbound call reads properly and respects the status pill
Three things wrong with the softphone tool window's incoming-call overlay. The number was painted exactly as the INVITE carried it, so a caller arrived as "+15...
Four things that could be looked at and not done
Each of these had a working endpoint, a client function, and no way for a person to reach it. Closing an order loop. The open-loops queue exists because the or...
Nothing returned an account id, so nothing could be withdrawn
Withdrawing portal access and granting a proxy both take an account id, and no endpoint ever returned one. A practice could invite a patient to the portal and t...
Record the enum-ordering trap the PBX rollout hit
Seeding a catalog row whose pricing_model is a new enum value makes that row unreadable to any service still on an older shared-lib, and getCatalog loads every ...
Rebuild against shared-lib PricingModel.PER_EXTENSION
The running image predates the enum value, so reading the two seeded PBX catalog rows (pricing_model = 'PER_EXTENSION') throws on enum parse - and because Subsc...
Add the flat-rate task book
A priced task catalogue whose entries carry labourMinutes — the number that both prices a line and sizes a visit. ServiceLineItem.taskCode, landed in SW3, joins...
Restore SW4's close-out, lost to a stale working-tree edit
The previous commit edited the on-disk copy of this plan, which predated the Shipped section — that section was appended to a scratchpad copy and landed from th...
Raise memory limit to 1Gi and relax liveness probe timeout
Traefik sat at ~485Mi against a 512Mi limit (95%). The cgroup recorded 47836 memory.max hits with oom_kill 0 — never OOM-killed, because enough of the footprint...
Applying DDL opens a 40001 window — watch it, do not restart
Bumping a catalog version leaves pooled JDBC connections holding the old one and the next query through each fails SQLState 40001, which Hibernate does not retr...
Price-list entry read and update spoke to the wrong endpoint
Two silent client/server mismatches made the retail price-list entry screen's write path dead: - the entries list was unwrapped with the key "entries", but C...
Per-extension PBX billing schema, backfill and catalog
PbxBillingSchemaMigration is @Order(0) because it adds a column. The shared-library entity maps phone_cost_mode from the moment the build starts, so any runner ...
Bill phone service by the extension
PricingModel.PER_EXTENSION, its own reserved quantity key, an allocator, a nightly reconciler, a removal gate and the summary API - the phone twin of the per-ma...
Give the phone add-ons a phone icon
PER_EXTENSION already renders as "extension/month" - addOnUnitLabel handles it and needs no change. The icon map does: it is keyed on the lowercased catalog cod...
Show what extensions cost, where extensions are managed
ExtensionUsagePanel on the stats tab, beside the mailbox and storage panels: count by class, the catalog rate, the monthly total, and the rule in plain language...
Queue timeout and "press 0" both fall through to voicemail
Replaces the hand-rolled Dial()+Goto queue loop with app_queue. The deciding factor is DTMF: digits are NOT matched against the dialplan while Dial() is running...
Count the extensions a third-party server does not charge for
Queues, IVRs, ring groups and spares are the extensions nobody is assigned to, and under the third-party rule they are free - twelve of one production org's sev...
The extension sweep no longer erases every member assignment
MemberVoipConfigController.put writes an assignment to two places - MEMBER_VOIP_CONFIG and **************** - and **************** then cleared the second on it...
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.
