Mabadiliko
Nini kipya.
Kila kuboresha jukwaa, kama ni meli.
· SecurityService
All-networks follows the System User right
Seeing every organization and being able to enter one are the same decision — the list exists so an operator can find the organization they are about to support — so /all-networks now asks for SYSTEM_USER instead of ownership of the top-level org. A platform administrator granted System User access could previously enter any organization while being unable to see the list of them. resolveFromSession supplies the rest of the rule and replaces the hand-rolled version this handler carried: the caller must be in the top-level organization, the System User's own sessions are refused, and an owner or an open god-mode window still qualifies without an explicit grant.
· kamo-internal
Platform Admins console gates every platform surface
Renames the System User tab to Platform Admins and turns it into what it was always standing in for: a list of the people who may operate the platform, each holding a named set of rights. Adding somebody uses the same member picker the manager field and the chat and e-sign pickers use — staff only, already-added people marked rather than hidden — so the one search that knows how to page a five-figure directory is the one used here too. Manage Rights grants and revokes; Remove takes them off the list entirely. They land holding nothing. Adding somebody and granting them capabilities are two decisions, and defaulting the second is how a person ends up with the System User because whoever added them accepted a pre-ticked box. PLATFORM_TAB_RIGHT is the single mapping of screen to right: the tab strip, the settings sub-menu, the settings landing cards and the page guard all read it, so a screen cannot be offered by one surface and refused by another. A test fails the build on a tab rendered without its check and on a menu link whose right disagrees with its tab's. The universe routes stop deciding for themselves. worldgenAuth read GD — which means "may activate god mode", not "has" — so every god-eligible operator held the universe console permanently while believing they were an ordinary user. It now asks SecurityService for MANAGE_UNIVERSE, and the session-diagnostics route behind System Debug asks for SYSTEM_DEBUG. None of this is the security. Every endpoint enforces the same right independently; hiding a screen keeps an operator off one that would 403 every request it made.
· InitializerService
Stand up the platform administrator list
Backfills it from the retired system_user_access grants so nobody who could operate the System User before this deploy loses it: direct member grants map one-to-one, department and job-title grants expand into the team members they covered. That expansion is the one deliberate behaviour change — somebody later moved into a granted department no longer inherits the access, which is the point of a list you edit rather than one that grows on its own. Grants Sage and Erik every platform right, matched on their account email inside the top-level org rather than by member id, so a fresh environment does the right thing too. A miss is a warning, not a throw: refusing to start the platform over a missing seed account turns a convenience into an outage. system_user_access is left in place and untouched. Dropping it in the same release that starts reading its replacement would leave no way back if the backfill turned out wrong. Every statement is NOT EXISTS-guarded, so a second run inserts nothing.
· BillingService
Decide platform access with the shared resolver
@PlatformAdminOnly now names the platform right it requires (defaulting to MANAGE_ORGANIZATIONS, which is what this service's platform surface is about) and the verdict comes from PlatformRightsResolver — the same rules SecurityService enforces. It used to decide for itself and admitted owners and god mode only, so a platform administrator granted MANAGE_ORGANIZATIONS could open the Organizations tab and be refused by every request it made. Identity resolution stays here, where the caller arrives as forwarded headers rather than a *** session; only the verdict moved. Also stops treating User.isGod alone as half the god-mode test: eligibility means "may activate", and the header says whether they have.
· SecurityService
Enforce platform rights on every platform surface
@RequirePlatformRight + PlatformRightInterceptor replace three hand-rolled "is the caller a top-level-org owner" gates that were copies of each other, one per platform controller, each cross-referencing the others in its own comment. They were also wrong in the same way: owner-only, so a platform administrator granted a console's right could open its tab and be refused by every request it made. Ownership still qualifies — it resolves to every platform right. The annotation, not a path list, is what marks an endpoint platform-only; the interceptor is registered for /** so there is no second list to forget. A declared-but-empty annotation refuses rather than admits. system_user_access is retired as a read. EnterAsController, DomainController and the /network capability probe now ask for the SYSTEM_USER platform right, so the capability sits beside the rest of the platform's rights instead of in a table only those three endpoints knew about. The configuration singleton is edited from two tabs by two audiences, so the endpoint admits either right and then checks each field group against its own — holding MANAGE_VOIP_SMS must not let you retarget the extraction model. Platform sounds move to MANAGE_PLATFORM_SOUNDS; the org and member scopes stay on the tenant rights system, which is the correct spine for them. PlatformRightCoverageTest fails the build on a right that guards nothing, a platform controller with no guard, and any revival of the retired owner check. The unauthenticated-endpoint ratchet learns that an annotation enforced before the handler runs is a guard — its body-only scan could not see one.
· InitializerService
Seed the meet rights before the gate can lock anyone out
MediaService now requires START_MEETING to create a meeting and VIEW_MEETINGS to resolve one. Measured on production before writing this: 42 security roles across 14 organizations, three per org, and only the single "Administrator (Full Access)" role in each holds them — the other 28 roles carry a row with setting NULL. 33 of 69 members had START_MEETING applied. Shipping the gate alone would have taken meeting creation away from the other 36. So both rights are granted to every existing role. Roles are the right level: every member has at least one, so the grant reaches all of them and keeps reaching members added later. Departments, job titles and member profiles hold a row only where somebody set one and hold none for these two, so there is nothing to seed there and inserting rows would invent overrides nobody asked for. Deliberately one-shot, unlike **************** That runner enforces an invariant the write paths maintain forever, so re-running it is a no-op. "Every role grants START_MEETING" is a cutover state instead — the whole point of the gate is that an organization can now revoke it — and re-running would silently re-grant what an administrator had deliberately taken away. A small platform_data_migrations ledger makes it fire exactly once; the clusters still ahead of this one need the same thing. The gate fails the run unless afterwards every role grants both rights, no department, job title or member denies either, and no security-model template FORCE_NUKEs either. That last check queries by enum NAME, because that table stores the name while the four rights tables store the integer id — an id-shaped predicate against it matches nothing and reports success. MANAGE_MEETINGS is not seeded. Nothing in the platform manages a meeting, so it stays decorative; granting it would hand every role a right that gates nothing.
· MediaService
Creating and resolving a meeting take the rights that name them
/api/meet/create checked only that you had a session, so every authenticated member of every organization could create a meeting with the org's provider — START_MEETING has been grantable and decorative for as long as it has existed. /api/meet/join-info was the same, and it hands back the meeting's passcode and provider join URL, so any member holding a meeting id could read the credentials for any meeting in the org. Both now go through MeetSessionResolver, beside the canManageMeetSettings check the settings endpoints already use. START_MEETING deliberately does not re-check VIEW_MEETINGS, its parent: hierarchy closure revokes a child whose ancestor chain is not granted, so a session carrying the child carries the parent by construction, and hand-rolling the pair here is the requireAll duplication the hierarchy exists to remove. The checks fail closed on a session with no rights array. That is the same reasoning that kept the client's "type unknown -> allow" fallbacks out of the server-side share checks: a legacy-session exemption becomes a permanent bypass. This locks out anyone who does not hold the right — 36 of 69 members on production today — so it must not ship without MeetRightsSeedMigration in KamoInitializerService, which grants both rights to every existing role.
· kamo-shared-library
Platform rights as a spine of their own
Who may operate the platform has been three different answers in three different places — a system_user_access grant table, an owner check copied into each platform controller, and a god-mode flag read as though eligibility were activation. This is one model for all of it: PlatformRightType names each capability, platform_administrator says who holds any, and platform_administrator_right says which, with a timestamp and an author per grant so "who gave this person System User access" has an answer. Deliberately NOT a RoleRightType. Tenant rights say what someone may do inside their organization and are editable from that organization's own settings; these say what someone may do to the platform every organization runs on. The two share a member id and nothing else. PlatformRightsResolver carries the rules — the top-level-org precondition, the owner and god-mode floors — because enforcement lives in more than one service and each identifies its caller differently. Identity stays per service; the verdict does not, so SecurityService and BillingService cannot drift into disagreeing about who runs the platform. Both FK columnDefinitions are pinned so they do not inherit their parents' **************** defaults and orphan every existing row.
· kamo-internal
Drop the Reset Filters button from the leads list
· kamo-internal
Move the callback marker beside the contact name, add a Callback filter
The marker sits with the name because that is who the call is to, rather than in the id column where it pushed the ids out of line. The filter offers All / Scheduled / None and defaults to All, so it never hides work unasked. Scheduled and None read the same summary map the marker draws from, so the two together always account for every row All shows.
· kamo-internal
Upload your own sound effects, at all three layers
The system could only ever play the thirty-nine tones it shipped with. Now any layer can add its own file — the platform's catalogue, an organization's, a member's — and assign it to any event exactly like a built-in tone. ONE ID SPACE. An assigned sound is a single string, `custom:<uuid>` for an upload and a tone id otherwise, so the three layers merge it the same way and only the player has to know the difference. Custom ids are accepted on SHAPE, not on existence: the library loads asynchronously, and rejecting them until it lands would flip every assigned upload to the platform default for the first second of every page load — which reads as the setting having been lost. A genuinely retired sound fails softly at play time and falls back to the layer beneath. PLAYING HAS TO BE SYNCHRONOUS TO BE ON TIME. An event that awaits a fetch before making a noise arrives after the thing it was announcing, so the first play of an undecoded upload is silent by construction. The provider therefore primes the decode from the resolved settings — by the time a message arrives, the sound assigned to it is already an AudioBuffer. Previews await, because there the member pressed a button and is expecting the delay. TWO TABS, because they answer different questions. Assigned Sounds is about EVENTS ("what should a new message sound like"); Sound Effect Library is about FILES ("what have we got, who added it, is anything using it"). One screen meant fifty event rows each carrying an upload control, and no way at all to see a sound nobody had assigned yet. The library says what each file is in use by, because the one thing someone about to delete a file needs to know is what will go quiet. Waveforms for uploads are computed from the DECODED AUDIO, not faked. A plausible-looking shape would be a picture claiming to be this file and is not, and telling two sounds apart before playing them is the entire reason the waveform is there. The description is asked for with the file, not afterwards: a library of "recording_final_2.wav" is one nobody can use six months later, and the description is the only part of a sound file a person can search. Gates: tsc clean, 1894 tests pass (6 new for custom ids), next build compiles, i18n guard clean. Dictionary pushed first, as always.
· SecurityService
List, rename, retire and stream uploaded sound effects
Bytes arrive through ConversionService (which owns ffmpeg and the scanner); everything afterwards is here, where the session and the scoping rules already live. All three shelves come back in one response for the same reason the profiles do — a member picking a tone is offered the platform's, their organization's and their own together. Managing and hearing are deliberately different permissions. A member must be able to HEAR the organization sound their administrator assigned to them without being able to rename or delete it, so mayHear is wider than assertMayManage. Deletes are soft and the object is left in place. Something may still be assigned to that sound — possibly in a layer this caller cannot see — and a hard delete would turn that assignment into a 404 at the moment the event fires. Resolution treats an unknown sound as "inherit", so a retired sound degrades to the layer beneath it rather than to silence. Audio is streamed, not presigned: a presigned URL is a bearer token for the object that outlives the request and cannot be withdrawn when someone leaves an organization. These are a few kilobytes. Cached immutably, because the bytes at an id never change — a re-upload is a new asset — and a cached buffer is what lets an assigned sound play the instant an event fires rather than after a round trip. 620 tests pass.
· ConversionService
Transcode uploaded sound effects to Opus
Extends the ffmpeg wrapper the VoIP recording pipeline already had — this image installs ffmpeg with libopus for it, so putting a second ffmpeg in another image to avoid one hop would have been the wrong trade. The VoIP methods make a phone call small: mono, 32 kbit, `-application voip`, which is tuned for speech and audibly wrong for a chime. Sound effects get their own path: stereo preserved where it exists, 48 kHz (Opus's native rate, so no resampling), `-application audio`, and Ogg rather than WebM — `.opus` IS the Ogg-contained form, it is what every tool that says "Opus file" produces, and it is what the browser's decodeAudioData is handed. probeAudio answers two questions in one ffprobe: what codec arrived, and is there any audio at all. A renamed PDF reaches the endpoint, and "convert it anyway" spends twenty seconds of ffmpeg to produce an empty file. A file that is ALREADY Opus is stored byte-for-byte. Re-encoding Opus to Opus is generation loss for nothing — unless the uploader asked for loudness normalisation, which by definition requires a re-encode. That is off by default: it changes audio somebody chose, and doing that silently is not ours to decide. Over-long uploads are trimmed, not refused: twenty seconds is generous for a custom jingle, and the member gets the front of what they picked, which is the part they meant. Scan first, always. A decoder is exactly the kind of thing a malformed media file is written to attack, so UploadScanGuard runs ahead of ffmpeg, not after it. Note: ImageResizeServiceTest fails at HEAD (PNG expected, WebP produced) and is unrelated to this change — the Docker build runs -DskipTests, so it does not block the image.
· kamo-shared-library
Uploaded sound effects, and the storage domain that counts them
EVENT_SOUND_ASSETS: a sound file somebody uploaded, ready to be assigned. Scope mirrors EVENT_SOUND_PROFILES exactly — same three layers, same SCOPE_ID convention — so "which sounds may I choose from" and "which layer am I editing" are one model asked of two tables. DELIBERATELY NOT an imgs/img_dats row. The imaging pipeline is built for documents: content-addressed dedup, thumbnails, folders, doc types, per-assoc delete and overwrite rules. A sound effect is a two-second file with a name and a description and needs none of it. More to the point, a fifteenth ImageAssocType obliges EVERY imaging service to redeploy in lockstep — they resolve associations by values()[ordinal] and a stale enum throws ArrayIndexOutOfBounds — and a sound effect should not be able to break document uploads by existing. What it keeps from that pipeline is what matters: the bytes go through ConversionService, which malware-scans them and transcodes to Opus, and they land in the same MinIO bucket under their own prefix. ORG_ID is separate from SCOPE_ID because a MEMBER-scoped sound has the member's id in the scope and is still bytes on that organization's shelf. Platform sounds carry a null org and are charged to nobody — that catalogue is Kamo's. Hence the EVENT_SOUNDS storage domain rather than a line inside DOCUMENTS: these are a distinct product an organization can be surprised by, and burying them would repeat the mistake that hid 5.08 GB of chat traffic inside a "Documents" figure. Schema applied via KamoInitializer and verified in Yugabyte before this push. 1418 tests pass, StorageDomainCoverageTest included.
· kamo-internal
Let the sender edit the meeting invite email subject
The invitation subject was fixed at "<host> from <org> invited you to a meeting" and built inside the send route, so nobody could say what the meeting was actually about. The dialog now pre-fills an editable subject field with that same default — resolved by the route so what is shown is exactly what would be sent — and passes the result through to the send path. The route sanitises what comes back: CR/LF fold to spaces (a newline in a subject is header injection) and a blank field falls back to the default rather than sending an empty subject line.
· kamo-internal
Gate the personal-loan proxy on the PLOS rights
KamoLOS enforces nothing — no rights check anywhere in its source — so this proxy is the authorization boundary for the module, the position denyUnless occupies inside SecurityService. Without it any authenticated org member reaches every loan endpoint. Done NOW because KamoLOS has no deployment and therefore no users: gating today costs nobody access, while the identical change after the module ships is a seeded migration with a real lockout blast radius. The cheap moment is before it goes live. Reads take PLOS_VIEW_PIPELINE; state-changing paths take the right that names the act, so an org can let someone take applications without also letting them fund or deny a loan — the separation the six rights were split for. Unrecognised paths fall through to the WRITE default: a new KamoLOS endpoint arrives locked rather than inheriting read access because nobody updated the table. ksemSessionShared gains rightsFromKsem/ksemHasRight, making this the first proxy route in the app to check a right. It FAILS CLOSED on a session with no rights array — a cookie predating the field is refused, not waved through, or anyone holding an old cookie has a permanent bypass. A test caught a real defect while writing this: String(null) is the truthy string 'null', which survived filter(Boolean) and put a junk name in the rights list. Still outstanding inside KamoLOS for whoever deploys it: LOS_PROXY_HMAC_ENABLED is unset so ProxyHmacFilter is off and LoanController trusts an unsigned X-Org-Id, and LoanController.get(uid) returns any loan with no org check. The proxy gate reduces exposure but substitutes for neither.
· kamo-shared-library
A kind for the meeting that is about to start
MEETING_REMINDER, so MediaService can raise the start-time nudge as a notification instead of a popup only it knows about. Its own kind rather than CALENDAR_EVENT because a meeting room is not a calendar entry, and the kind is the unit a member mutes: someone who has silenced calendar reminders has not asked to stop being told that a call they are expected on is starting. No migration. member_notifications.KIND is @Enumerated(STRING) into a VARCHAR(50) with no CHECK constraint (verified against the live table), so a new constant needs nothing but this.
· kamo-internal
Subscribe to the course count instead of asking for it every minute
NavTop sums two counts into one obligation badge, and only the legal half was live. A member assigned a course — or excused from one by HR — kept the old number until the 60-second poll happened to fire. The provider now subscribes to **************** over the EXISTING chat STOMP client, exactly as its legal sibling does. No new connection: the app already stands up more than a dozen, and that client's CONNECT has already stamped the member id MediaService's SUBSCRIBE guard reads. The poll stays, and is still load-bearing. The subject is core NATS with no stream behind it, so a frame published while this tab's socket was down is gone with nothing to replay — hence a seed on mount, another on every reconnect, and the 60-second floor under staleness. What changes is that the badge now moves in the second rather than the minute. Both paths go through one `apply`, because the sound rule is about the number and not about how it arrived: a rise plays, a value does not, and the first reading for a member is their standing backlog rather than news. That also makes a push and the seed that follows it idempotent — they carry the same number, and only the first of them is a rise. The provider's own comment explaining why it was poll-only is replaced rather than deleted: the reasoning was correct when written (DocsService had no `nats:` block), and the note now records what changed and when, so the next person does not re-derive it.
· MediaService
Relay the outstanding-course count onto the member's own topic
The twin of the legal relay, because NavTop sums the two counts into one badge and a pair of halves that behaved differently would give the member chrome that was live for signatures and stale for courses — the worst of the three possible states, since it looks live. DocsService publishes **************** on core NATS; this puts it on **************** for the subscription kamo-internal holds over the existing chat connection. No JetStream and no stream to provision: a badge is a live signal, not a durable log, and the client seeds over REST for exactly the frames this loses. The destination is guarded on SEND as well as SUBSCRIBE. The simple broker routes by destination without caring which command carried the frame, so a SUBSCRIBE-only guard still lets any authenticated member publish a forged **************** to a colleague's topic — the payload names the victim, so their client's "is this addressed to me?" check passes and the mark saying they still owe a course goes out.
· DocsService
The outstanding-course badge moves when the course does, not a minute later
The NavTop obligation badge sums two counts — packages to sign and courses to take — and only one of them was live. The legal half has pushed since its NATS block landed; the training half was a 60-second poll, so a member assigned a course, or excused from one by HR, kept the old number on their chrome until the next tick happened to fire. That was the right call when it was made: DocsService had no `nats:` block, so a subscription would have been three moving parts maintaining a channel with no traffic. The block is there now, and pinned by an arch test, so the channel carries traffic. DocsService publishes **************** on core NATS, in the shape the legal publisher already uses, from the three services that can cross the OPEN boundary: assign, finish, and HR's cancel/waive/retake. The count is read inside the caller's transaction — where a count query flushes the rows they just wrote — and sent after the commit, because an exception in afterCommit propagates and would turn a Finish that has already written an append-only attestation into a 500 telling the member they failed. TrainingPlayerService is deliberately left out and asserted negatively: its only status write is ASSIGNED -> IN_PROGRESS on every open of the player, which is open to open. Publishing there would put a frame on the wire, logged at INFO with its full payload, every time somebody clicked into a course, for a number that did not change. FAILED_RETAKE_ALLOWED and AWAITING_GRADING are the two that look like completions and are not — both still owe the member work, so neither moves the badge.
· EmailService
Publish every change of state, not only arrivals
A member has this open in more than one tab about as often as not, and often on a phone as well. Each of those holds its own socket and draws its own bell, and until now the only thing that reached them was a new notification — so reading or clearing the centre in one window left every other one counting notifications that were already gone, with nothing but a page reload to correct it. Reads, mark-all-reads, dismissals, snoozes and deletions now go out on the same per-member topic arrivals use. The tab that caused the change gets its own echo like everybody else; the browser applies them idempotently. Quiet hours and do-not-disturb were holding the notification rather than the toast. Nothing at all was published inside the window, so the badge could not move until the page was reloaded — the member who had configured the feature was the only one whose count could not be trusted. A held notification is now published with `silent: true`: it belongs in the centre and in the count, and it is the sound, the card and the desktop pop that quiet hours exists to suppress. It still does not touch deliveredAt, so it can never come back as a toast once the window has passed. The topic's `type` strings are constants now rather than literals scattered across six methods, since the browser switches on them and a typo is a frame that falls through to "an arrival".
· kamo-internal
The bell tells the truth in every tab, not just the one you clicked in
The nav-top bell was live for arrivals and nothing else. Reads, dismissals, snoozes and deletions were applied to whichever tab the member clicked in and announced to nobody, so clearing the centre in one window left every other one — and their phone — counting notifications that no longer existed, with no path back to the truth short of a reload. EmailService now publishes every change of state on the same per-member topic it already publishes arrivals on, and the browser applies all of them idempotently: the tab that caused a change receives its own echo like everybody else, so each transition has to be a no-op the second time it lands. Three fixes fall out of that: - Quiet hours and do-not-disturb held the whole notification, not just the toast. The badge stayed frozen for the length of the window, which made the member who had configured the feature the only one whose count could not be trusted. A held notification is now published `silent`: it counts, it just does not interrupt. - The unread count is moved through a ref-backed ledger rather than four copies of "and take one off the badge". Every action is now applied twice by design, and two copies of that arithmetic is exactly how the second application subtracts something the first already did. The server's total still seeds it, so a member with more unread than the centre holds still sees the true number. - A socket that has been away — a sleeping laptop, a redeployed pod — refetches on reconnect, and the pile the server hands over on connect can now re-toast a notification the history load has already listed. That pile was being discarded as duplicates, so a member who was offline came back to cards they were never shown. The wire contract lives in a pure `readNotificationEvent` with a test that spells out each `type` string, because a drift on either side does not fail loudly — it falls through to "an arrival" and puts a dismissal back on screen as a toast.
· kamo-internal
Drop the three chat-channel rights from the frontend
Mirrors the shared-lib deletion: the enum mirror, its registry array, both hierarchy edges (ACCESS_CHAT -> CREATE_CHANNELS -> MANAGE_CHANNELS and ACCESS_CHAT -> MODERATE_CHAT), the unenforced list and the committed audit. 132 parent edges now, and ACCESS_CHAT is standalone. The parity test made this safe to do quickly — it reads RoleRightType.java and compares edge for edge, so a mirror left holding an edge Java no longer has fails the build rather than silently making the editor cascade to a right that does not exist.
· InitializerService
Purge stored rows for the deleted chat-channel rights
Ids 74/75/76 no longer resolve. RoleRightsSyncService would sweep them on some future SecurityService boot anyway — silently, at an unpredictable moment, with no record of how many rows or which orgs. Doing it here makes the removal deliberate, counted and logged in the run that ships the enum change. TWO ENCODINGS, and getting this wrong is silent. The four rights tables store the integer id via RoleRightTypeIdConverter; **************** stores the enum NAME (@Enumerated(EnumType.STRING), length 64). A single DELETE ... WHERE right_type IN (74,75,76) would match nothing on the template table and report success, leaving template rows for rights that no longer resolve being read into the force-rule map on every recalculation. Split accordingly. Non-fatal per table: these rows are inert by definition, so aborting the whole initializer — and skipping every runner after it — would be far worse than leaving three dead ids for one more deploy. No seeding and no verification gate, unlike the hierarchy backfill: this cannot remove access because nothing ever read these rights.
· kamo-shared-library
Delete the three chat-channel rights — 196
CREATE_CHANNELS (74), MANAGE_CHANNELS (75) and MODERATE_CHAT (76) gated nothing and could not be implemented as written: chat is real but the platform has no channel concept at all. ChatService holds a single MeetController with no channel code and no chat component mentions channels. An earlier grep that appeared to find channel files was matching MLOS loan-origination channel — retail/wholesale/correspondent — an unrelated use of the word. Deleting a right cannot remove access when nothing ever read it, which is what makes these the safely removable ones. Taxonomy 199/135/34/30 -> 196/132/33/31. Three numbers move rather than one because ACCESS_CHAT loses its entire subtree and becomes standalone; the root count is unchanged at 64. Ids 74-76 are BURNED and the enum says so. RoleRightTypeIdConverter persists the id, so a future right reusing one would silently inherit whatever those rows meant in orgs that had them switched on.
· InitializerService
Move notes from account ownership onto membership
Adds notes.member_id and attaches each note to its owner's earliest membership — the organisation the account has belonged to longest, which in practice is where the notes were written. A note therefore lands in exactly one organisation rather than being duplicated into all of them; the count of accounts for which that choice was material is logged, because for them the notes stop being visible from their other orgs. @Order(0) for the reason every column-add runner is: once the shared-lib entity maps MEMBER_ID, any runner that loads a Note emits the column in its SELECT, and a migration ordered late would never be reached. The column arrives nullable — notes is populated and YugabyteDB rejects ADD COLUMN ... NOT NULL on such a table — and its default is dropped explicitly. An inherited unique_rowid() would stamp every existing note with a member id matching no row, which the backfill would then skip as already placed, hiding every note in the system at once. Run against production: column added, 3 note(s) placed, none unplaced.
· kamo-internal
One event-sound system, three layers, wired to what actually fires
There were two sound systems and neither knew about the other: four mp3s behind useChatSounds, and four synthesised tones behind notificationSound with a five-option picker on the notifications page. Between them they covered chat and notifications. Everything else in the application — a lead being offered, a call connecting, an upload failing, a session about to expire — was silent, and there was nowhere to say otherwise. Both are deleted. In their place: one catalogue of 46 events, one palette of 39 synthesised tones, and three layers of settings that resolve field by field. WHY THE MERGE IS PER FIELD. A member who changes one event's volume keeps their organization's tone for it and the platform's for everything else. The obvious alternative — the top layer holding a full copy — freezes a member's sounds at whatever shipped the day they first opened the page, and nothing an administrator changes afterwards ever reaches them. WHY THE TONES ARE SYNTHESISED. A sound file has to be fetched, so the first time an event fires is the one time it is silent, on exactly the slow connection where the visual cue was also missed. These are oscillators: nothing to ship, nothing to cache, the same size in 22 locales, re-tunable without a binary. Every one is more than one voice or one voice that moves — a static beep is indistinguishable from every other noise a computer makes, and a test enforces it. WHY A CATALOGUE RATHER THAN CALL SITES. An undeclared key does not compile, and a declared event that nothing fires fails a test that greps the whole application for it. That guard already earned its place: it caught ui.action.success shipping as a settings row that did nothing, and it is why the catalogue holds no meetings, e-sign or ticket-resolved events — kamo-internal has no producer for them, and a row that does nothing is what the old system's problem looked like. Storage is one table for all three scopes (EVENT_SOUND_PROFILES, applied and verified in Yugabyte before the shared-lib push). Device routing deliberately stays in localStorage with the microphone and camera: the headset on one desk is not the headset on another. Only the choice of tone roams with the account. UI. The signature is the inheritance spine — three segments per row, PLT/ORG/YOU, filled where a layer has an opinion and ringed where it won. A single "inherited" badge cannot express "my organization chose the tone and I changed only the volume", which is the state members actually end up in. Waveforms are sampled from the real spec, so the picker shows the shape of a sound before you commit to hearing it fifty times a day, and the sweep is timed to the actual playback. Settings live where the thing they configure lives: platform defaults on **************** (added to the settings menu), the organization's layer with its labels and colours on Behavior & Labeling, and the member's own beside their speaker in Audio & Video. Wired at 30 call sites across chat, AI, notifications, mail, SMS, social, calendar, telephony, leads, support, uploads, conversion, campaigns, timecard, training, documents and connectivity. Bursty sources are throttled per event — a socket firehose played faithfully is a fault, not a notification. Gates: tsc clean, 1851 tests pass (25 new), next build compiles, i18n guard clean across 2330 files. Dictionary pushed first, since kamo-internal CI clones it.
· SecurityService
Read and write the three layers of the event-sound stack
GET returns all three layers in one round trip plus what the caller may edit — every settings page needs the whole stack, because an org administrator editing their layer has to see the platform's to know what "inherited" means. The reads are gated on authentication and nothing more. Every signed-in member needs the platform and org layers to know what to play, and a 403 there would mean silence rather than a visible failure. Writes: platform is top-level-org owners, org is CONFIGURE_SYSTEM, member is the caller for themselves. The org id comes from the session and never from a path segment — an org id a client can name is an org id a client can change. There is deliberately no path to write another member's sounds; an administrator choosing what someone's laptop chimes at is the org layer's job, and that layer exists. A reset soft-deletes, so the row keeps occupying UK_EVENT_SOUND_SCOPE. Writers resolve by natural key regardless of active state and revive the dead row rather than inserting a second one into the constraint.
· kamo-shared-library
One table for the three layers of the event-sound stack
Platform defaults, an organization's overrides and a member's own are the same shape, so they are the same table: a scope, the id that scope is about, and the settings document. Splitting them across SYSTEM_CONFIGURATION, a column on ORGANIZATIONS and a MEMBER_UI_PREFERENCES key would have made three readers, three writers and three shapes for one idea. SCOPE_ID is 0 rather than null for the platform row, so UK_EVENT_SOUND_SCOPE actually binds — SQL uniqueness does not constrain NULLs, and two platform rows is not a state anything downstream can resolve. The blob stays opaque to Java. Which events exist and which tones the palette offers is a browser concern; a service that validated the key set would need a redeploy every time an event was added, and would reject a member's saved settings the moment it fell behind. Schema applied via KamoInitializer and verified in Yugabyte before this push.
· kamo-internal
Give the deck its slide back, without the lurch
Dropping `layout` to stop the card lurching onto the edge of the screen also took the sibling slide with it: dismissing a card left the rest snapping into its place. Both are reachable, because the two are not the same event. `layoutDependency` is the gate. The deck's composition — the ids of the cards currently in it — is handed to each card, and framer re-measures only when that value changes. So a neighbour leaving animates, and a render caused by anything else does not: not the countdown ticking every second, and not the options panel opening, which is the one that caused the lurch. Left to re-measure every render, framer read that panel's growth as a position change, and since the deck is anchored to the bottom of the viewport it animated the card up from where it used to sit, which is lower down. `layout="position"` rather than `layout`, so size is never projected. The panel animates its own height; a size projection would animate the same growth a second time, as a scale distortion of the card's contents.

Ulimwengu wako unasubiri.
Kuanza bure katika dakika. Ongeza bidhaa wakati wowote wewe ni tayari - hakuna uhamiaji, hakuna logins mpya.
Bure milele - hakuna kadi ya mkopo inahitajika