Mabadiliko
Nini kipya.
Kila kuboresha jukwaa, kama ni meli.
· SecurityService
Record lead reads and refuse un-auditable exports
HIPAA § 164.312(b) kwa ajili ya huduma ya usalama. Orodha ya viongozi wote Kila kitu kinabadilika na hakuna mabadiliko. Soma yote, kwa hivyo "Nani aliyeangalia mkopaji huyu" - swali la kwanza § 164.402 Ukiukaji wa tathmini ya madai – ilikuwa haiwezekani. PhiAccessAuditor hujenga matukio kutoka kwenye ramani ya kikao cha OTK / ** Mwisho wa pointi tayari kutatuliwa, hivyo utambulisho katika mstari wa ukaguzi ni sawa Taarifa ambayo iliidhinisha ombi hilo. IP ya wateja ni kutoka X-For-For; nyuma ya Traefik, remoteAddr inarekodi pod ya ingress na Ni mbaya kwa ajili ya utambuzi. Washtakiwa walikana mashtaka hayo na kusema kuwa Kugundua inaonekana kwa - na caller isiyo na kikao ardhi kwenye sentinel org badala ya kuwa chini. Kusoma: moja risasi kuchota = VIEW, gridi ya taifa = tukio moja LIST kwa risasi Kumwagilia hufanyika kwa njia ya utumbo, na kwa njia ya utumbo. ViEW UNASSIGNED LEADS kamwe inaonekana kama moja ambayo ilikuwa kusoma. Kifungu cha 401.14 Kila kitu ni kukataa. Mauzo ya nje kushindwa kufungwa - upatikanaji-log CSV, HMDA LAR, QC mafunzo corpus na vifaa vya uchimbaji wa madini. Kila tukio lina tukio moja. Rekodi kabla ya faili kujengwa, na kushindwa ukaguzi kuandika anarudi 503: "Tulitoa data lakini tulipoteza rekodi" ni jibu moja kwamba Haiwezi kurudi nyuma. Kwa hiyo, kila upande wa shilingi, kwa hiyo Kila rekodi inahusishwa na org ambayo inamiliki, sio ya operator. Vitambulisho tu ni watumiaji, kamwe maudhui. [TD="width: 456"] [FONT=&](2)[/FONT][FONT=&]Bila kuathiri masharti ya kifungu kidogo (1) ili waweze kujua ni nani aliye ndani yao.
· kamo-signer-monorepo
Public branded quote/estimate document at /{locale}/q/{token}
The customer-facing surface for the commerce quote system. Server-rendered so the org's branding is in the first paint — an unbranded flash on a commercial document is exactly what this must not do, and it is why the page emits brand custom properties inline via QuoteBrandVars rather than swapping them after hydration. Lives in kamo-signer because that app already has the HostRegexp ingress, autocert coverage, DNS aliases and the theme package with hostnameOverride — and because quote -> accept -> e-sign is then one hop, since EsignGenericEnvelope binds any record via (context_type, context_id). Reusing it was only viable once the /{locale}/ i18n work landed; all 31 strings are translation keys sourced from the dictionary. Money and dates format through next-intl using the quote's own currency and the server-resolved timezone (recipient member -> org -> UTC), never the viewer's device. An expiry rendered in the wrong zone can name a date on which the offer is already dead. Adds the platform's first real @media print block. "Print Invoice" in kamo-internal has always printed the whole app chrome because no @media print rule existed anywhere; here the same markup prints as a clean Letter document with the brand accent preserved (print-color-adjust: exact — a colourless quote reads as a draft), the action bar suppressed, and page-break-inside: avoid on rows and totals. Terminal states are deliberately asymmetric: EXPIRED gets an honest branded page naming the date and the org's contact details, safe because the caller already proved possession of a 256-bit token; unknown/revoked/wrong-org all collapse to one generic page so the surface cannot be probed. The client whitelists the reason before rendering. The token never travels onward in a URL — the server-side resolve posts it in a body, and the BFF forwards the real client IP explicitly because APIService stamps X-Forwarded-For from its own inbound request, which would otherwise put every viewer in one rate-limit bucket keyed to the pod IP. Adds SECURITYSERVICE_URL to the configmap (verified: kamowssecurity-service exists in the kamo namespace).
· SecurityService
Quote REST surface — staff management and public share links
Staff endpoints at /api/security/commerce/quotes and an unauthenticated resolver at /api/security/public/quotes. QuoteController extends BaseLosController rather than copying POSController's preamble: CallerSessionResolver accepts the X-OTK attribute, the X-***-Token header AND the *** cookie, where POSController's private getOrgId reads only the OTK attribute. Every handler is rights-gated (VIEW/MANAGE/SEND_QUOTES). Most of the commerce surface is session-only — any authenticated member of any org can mutate orders — and that is deliberately not repeated here, because a quote commits the org to a price in front of a customer. QuotePublicController needs no security-config carve-out: the Spring chain is anyRequest().permitAll() and OTKPreAuthFilter only acts on an X-OTK header, which a browser following an emailed link never sends. Design points: - The token arrives in the POST body, never a URL. APIApp.forward() logs request lines, and a bearer credential in a path would land in those logs. - Not an enumeration oracle: unknown, revoked and org-mismatched tokens all return one indistinguishable generic failure. Only EXPIRED gets a specific, honest answer, which is safe because the caller already proved possession of a 256-bit token. - The org derived from the request host is cross-checked against the org on the link row. Without it, a valid link for org A opened under org B's host would render A's pricing wearing B's branding — a phishing primitive. - no-store / Referrer-Policy: no-referrer / X-Robots-Tag: noindex on every response. The page loads logos from theme.<org>, and a Referer would carry the token off-origin. PublicLinkRateLimiter is Redis-backed because SecurityService is multi-replica — in-process counters would be a limiter in name only. It is explicitly DoS and log-hygiene protection, NOT the anti-guessing control: 256 bits of entropy is. Failures are counted far more harshly than successes, since one office opening a forwarded quote fifty times is normal while fifty misses is not. It fails open, so a Redis outage cannot take the quote page down. QuoteEmailClient reuses the EmailService transactional path (X-Internal-Auth, QUOTE_SENT template) and sends only the headline figure plus the link — never the itemised pricing, so a forwarded email is not itself the disclosure. Pre-existing test failures unchanged by this commit (verified by stashing this work and re-running on clean HEAD): **************** NPE on a null EntityManager, and the legacy com.retval SecurityServiceApplicationTests missing a @SpringBootConfiguration. REQUIRES a KamoInitializerService run before these endpoints work: the quote tables are new and ddl-auto is none here, so the service boots normally and only the quote endpoints 500 on a missing relation until KI has run.
· kamo-shared-library
Quote/estimate domain — entities, pricing engine, share links
Adds a first-class Quote alongside the existing commerce spine. The pre-existing "ServiceQuote" is an Intent plus a JSON blob carrying one offering and one flat amount, with no recipient, no expiry and no UI; it is not a foundation. Extending Intent directly was rejected too: commerce_intents is shared by LOAN_APPLICATION, RENTAL_REQUEST, SUBSCRIPTION_SIGNUP and PURCHASE_REQUEST, so ~25 quote columns would put permanent NULLs on the mortgage and rental paths. Entities **************** Quote engagement-linked, versioned, valid_until + valid_for_days QuoteLineItem snapshots name/sku/price so a sent quote never re-prices QuoteShareLink bearer credential, SHA-256 at rest, revoke/expiry/view counts QuoteLinkView per-open audit (ip, user-agent) QuoteEvent append-only lifecycle trail Totals are server-authoritative. QuoteTotals is the single writer of every money field and the request DTOs carry no totals at all — deliberately unlike createRetailOrder, which takes subtotal/tax/total verbatim and can be handed a populated order with total = 0. Line discounts apply before the quote-level discount, discounts can never exceed their base, intermediate maths stays at scale 4 and only the grand total rounds to the currency's minor unit. Share tokens: 32 bytes of SecureRandom as 43 base64url chars (256 bits). Same entropy as the e-sign signer token but 21 chars shorter, keeping the emailed URL under the 76-char plain-text wrap limit. Only the SHA-256 is persisted. Tax is honest rather than invented: the platform has no tax engine (TaxClass/ TaxZone/TaxRate are authorable and read by no calculation), so ENGINE mode reports NOT_CONFIGURED instead of fabricating a figure on a commercial document. QuoteLineItem.taxClassId is UUID, matching TaxClass's real PK — Offering.taxClassId is a Long against a UUID-keyed table and is structurally unresolvable. Also: rights 178-180 (VIEW/MANAGE/SEND_QUOTES) and the QUOTE_SENT canonical email template, registered in CANONICAL_KEYS (absent there means a permanent 404). 35 tests covering the pricing engine and token hygiene. Full suite: 896 passing. Schema note: these are new tables. KamoInitializerService must run before SecurityService serves the quote endpoints — ddl-auto is none there, so the service boots fine and the endpoints 500 on a missing relation until it does.
· KlusterServices
Egress containment for the avatar resolver, as a CiliumNetworkPolicy
Inaelezea nini 8f6c672 ilirudi, kwa fomu pekee ambayo inafanya kazi hapa. Jaribio la awali lilishindwa kwa sababu mtandao wa wazi hauwezi kuelezea hili. Topolojia ya kundi. Watumishi watatu wa Idara ya Usalama wa Taifa wanaishi katika Node, sio kwenye mtandao wa pod - mendedb-0 nats-k1m1-0 kukimbia hostNetwork, MinIO ni mchakato wa mwenyeji - kwa hivyo podSelector hawezi kushindana nao. Kwa kuwa sehemu isiyo ya kawaida, na sababu ya jaribio la kwanza la kiraka pia limeshindwa, ni kwamba ipBlock kwa subnet ya node haifai pia: Cilium inaonyesha node kama Kitambulisho cha mwenyeji / kitambulisho cha kumbukumbu, na sheria za CIDR hazilingani kwa makusudi Taarifa zilizohifadhiwa. Ujenzi ni mradi pekee unaoonyesha. Njia zote mbili za kushindwa zilizalishwa dhidi ya pod ya kutupa kabla ya kuandika Hii ni kinyume na uzalishaji. Kamati hiyo ilipitisha sheria hiyo ambayo imeainisha Lebo tofauti, ambayo ni jinsi toleo la ipBlock lilipatikana kuzuia kimya CockroachDB, NATS na MinIO wakati inaonekana vizuri. Ilithibitishwa kwenye canary, kisha kuthibitishwa kwenye Huduma ya Usalama bila kuanza tena na hakuna Makosa ya uhusiano: Picha zote na Wizara ya Mambo ya Ndani ya Nchi. .................................................................... Mistari iliyoongezwa ni Mathayo 17:21; 18:11; 23:14; Marko 7:16; 9:44, 46; 11:26; 15:28; Luka 17:36; 23:17; Yohana 5:4; Matendo 8:37; 15:34; 24:7; 28:29; na Waroma 16:24. Hatua ya nne ni hatua: SSRF katika kutatua sasa haina mahali pa kwenda. Kwa kuwa Walinzi wa nambari bado ni udhibiti wa msingi - ni safu pekee inayoelewa DNS rebinding - na hii ni ulinzi nyuma yake. Mkuu wa Mkoa afanya upasuaji wa canary. Mtu yeyote anayebadilisha hii lazima arudishe kuliko sababu kuhusu hilo; mfano wa utambulisho wa Cilium hautendi njia wazi Intuition ya polisi inaonyesha, na hakuna mazingira ya staging.
· kamo-internal
Show a facepile for group conversations
A thread with five people on it was represented by one arbitrary face, which reads as a message from one person. Up to three participants now overlap, so "several people" is legible at a glance without needing to parse the name line. Capped at three: past that the circles shrink below recognisability and the row becomes texture rather than information, and the remainder is already stated in the "and N others" text right beside it. The reader is filtered out of their own thread list, and the other party leads — faces read left to right, so the primary correspondent has to be the leftmost one and the one carrying the unread badge. Each face gets a ring in the row background colour; without it a stack of similar photos merges into one shape. Resolution now covers the first few participants rather than only the lead, otherwise the second and third faces would sit as monograms permanently.
· SecurityService
Instrument the chain, reap expired entries, record disclosures
Kila njia ya kushindwa katika kipengele hiki ni kimya. Mchezaji wa mpira wa miguu, mfanyakazi Kuweka foleni kila kazi, mtoa huduma ambaye alianza kurudi HTML - kila inaonekana Kwa mfano, "hakuna picha", ambayo ni sawa. Tabia inaonekana kama. Hakuna tofauti kati ya kufanya kazi na Ni lazima kufa, kwa hiyo ni lazima kufa. Metrics (Prometheus alikuwa tayari wired up, hivyo hii ni wengi naming): cache hit Kwa upande mwingine, kwa upande mwingine, kwa upande mwingine, kwa upande wa bei ya chini, bei ya chini ya bei. Idadi ya wafanyakazi, na idadi ya kazi iliyoachwa. ya mwisho ambayo iliwahi kutumika DiscardPolicy, ambayo hupungua kimya kwa kubuni; sasa inaongeza counter, na Upinzani umetangazwa kabla ya kuondolewa kwa sababu waanzishaji wa uwanja wanaendesha amri chanzo na kutegemea kukamata wavivu itakuwa kuvunja juu ya reorder harmless. Bonyeza kwenye logger yako mwenyewe. Alama ya utambulisho ni mtu binafsi Kufafanua - tiers uwanja kusambaza upande wa kulia wa anwani, ambayo Hakuna mtu yeyote aliye na jina kabla ya jina lake. Hata hivyo, kuna njia ya mkato hata wakati maombi yanashindwa. Barua hiyo imeandikwa kwa ukamilifu na Kwa makusudi: lengo ni kujibu "data ya mtu huyu ilishirikiwa na nani", Hata hivyo, Yehova hawezi kujibu swali hilo. Mvunaji hufunga uvujaji: kufuta expired kabla ya kuwepo na hakuna kitu kinachoitwa, hivyo Kwa hiyo, upendo wa dhati ulidumu milele. Orodha ya barua pepe inayovutia maelfu ya waandishi Mara moja kila mmoja na kuacha mstari hasi nyuma kwa kila mmoja. Uchunguzi hufunika mtekaji dhidi ya tundu halisi, kwa sababu kesi za kuvutia ni Wote mtoa huduma tabia badala ya mantiki: 404 kubeba picha halali (DuckDuckGo), HTML laini-404 kutumikia, asili understating maudhui-Length, 1x1 stub, na redirect kwa lengo la mtandao pod. Uzalishaji wa bidhaa hauwezi kuwa alisema katika loopback mtihani server - yake DNS pin anakataa kutatua chochote binafsi, ambayo ni pointi nzima - hivyo wale kukimbia dhidi ya mteja walishirikiana na Pindium inachukuliwa kwa njia tofauti.
· kamo-internal
Promote an ingested LLPA matrix into adjustments from the console
The ingestion panel could only publish a rate sheet. For an LLPA_MATRIX source document it now shows a "Promote as LLPA adjustments" card: pick the existing rate-sheet version the LLPAs adjust (+ an optional group), and each approved FICO/LTV bucket becomes a conditional price adjustment. The result names how many were authored and, honestly, how many rows were skipped because a bucket couldn't be read safely or was already present. ingestionApi.promoteAdjustments posts to the new gateway relay.
· SecurityService
Gateway relay to promote an LLPA matrix into conditional adjustments
**************** gain promote-adjustments, mirroring the promote-rate-sheet relay: god-eligible gate, organizationId server-stamped onto the payload via write(), downstream 4xx relayed verbatim. Lets a steward turn a reviewed LLPA matrix into real per-scenario price adjustments from the console.
· VOIPService
Record call recording, voicemail and transcript access
Recording playback left no trace. A recorded call is the densest PHI the platform stores — the unredacted conversation — and "who listened to this call" was unanswerable, which is the first question a §164.402 breach assessment asks. Wires CallRecordingAuditor into every read that discloses it: - GET /recordings/{id}/audio CALL_RECORDING / DOWNLOAD - GET **************** CALL_RECORDING / DOWNLOAD - GET /recordings/{id} CALL_RECORDING + TRANSCRIPT / VIEW - GET **************** CALL_RECORDING / DOWNLOAD - GET /instances/{id}/voicemails TRANSCRIPT / LIST, per row The transcript is the one that hides. It is not a media download; it rides inside the metadata JSON and inside every voicemail row, and it is the same PHI as the audio in a form that greps. It gets its own event because an accounting of disclosures is assembled per resource. Byte serving fails closed, so PhiAuditAvailabilityAdvice turns an unloggable read into a 503 rather than a 500 — the request was fine, we decoderd to serve PHI we could not account for. Refusals are recorded too; probing is what detection looks for. Also closes an authorization gap found while wiring this: POST /recordings/lookup took an instanceId from the request body and never checked the caller's org owns it, so it resolved another tenant's provider call ids to their recording ids. Every other instance-scoped endpoint in the service already goes through findByIdAndOrgId; this one now does too. Recording playback authorization is left as-is and documented in place: org ownership is the only check, so any member can play back any recording in their org. Narrowing it means deciding whether "own extension" is the boundary and which right lifts it — that is a product decision, not a guess to make here, and every access is at least recorded now.
· AIService
Rekodi upatikanaji wa PHI kwenye kila usomaji wa historia ya mazungumzo iliyohifadhiwa ya AI
Wires the shared §164.312(b) spine into the paths that read persisted prompts and completions. The assistant is the module the compliance assessment put OUTSIDE the boundary — prompts fan out to external providers under no BAA and are then kept indefinitely — so reads of that store are the disclosures most in need of a trail, and they had none. Audited: the session list and history search (one event per conversation, not one per request), a single conversation's metadata and its messages, the public widget reading its own transcript back, and the handoff bundle. Refusals are recorded too — probing another org's guids is what detection looks for — and an unauthenticated attempt is attributed to the sentinel org rather than dropped, since the recorder rejects what it cannot attribute and that is the event most worth keeping. The handoff bundle is a DISCLOSE, not a VIEW: it carries a visitor's conversation out of AIService and in front of a human in another product. That kind fails closed, so a lost record now withholds the transcript and answers 503 rather than serving data it cannot account for. Identifiers only. Never a message body, a title, or the search term — a member searching their assistant history types the borrower's name into it, and an audit trail that reproduced the prompt would just have moved the PHI into the log stream. Completes d428ee3, which a concurrent session swept up mid-change: the auditor was missing recordDeniedInternalView, and PublicAiChatController's new constructor argument had not reached its test.
· MediaService
Record chat message reads on the endpoints that return bodies
Sending a message left a row; reading one left nothing. Every chat, support and social thread on the platform renders from a handful of endpoints here, and none of them recorded anything, so "who read this conversation" had no answer — which is the first question a 164.402 breach assessment asks. ChatMessageAccessAuditor emits a PhiAccessEvent (CHAT_MESSAGE) from the five paths that hand a caller message text: GET **************** LIST GET /api/media/sessions/chat LIST POST **************** VIEW GET **************** LIST POST **************** VIEW One event per message, not one per request. "They opened the thread" is not an answer to "was this individual's message disclosed", and the message ids are already in hand at every call site, so a request-shaped row would have been a choice rather than a limitation. The thread read records AFTER the mute filter, so a row means a body that caller was actually handed. The chat list is in scope because each row carries up to 140 characters of the thread's last message — that is content, not metadata. Rows with an empty preview record nothing. Deliberately NOT recorded: unread counts, presence, last-seen, typing, read receipts, session-member lists, mutes, ticket detail (metadata plus status history, no bodies) and support reporting aggregates. A trail is only useful if a row means someone saw content; rows that stand for nothing bury the ones that do. Refusals are recorded too, against the thread guid — no message id exists yet at the point of refusal, and repeated refusals against thread guids is the shape probing has. An unauthenticated reader is attributed to a sentinel org rather than dropped: the recorder rejects unattributable events, and that is the single most interesting event this trail can hold. Client IP comes from X-Forwarded-For with remoteAddr as fallback — everything arrives via Traefik, so remoteAddr alone records the ingress pod. Public chat takes a second hop through APIService, which sets X-Forwarded-For to the visitor address and moves the browser's User-Agent to X-Original-User-Agent, so the auditor prefers that header; the literal User-Agent on that hop is APIService's own HTTP client and identifies nobody. Visitor reads are attributed to the org from X-Public-Chat-Org-Id with no member id: a visitor has no member identity of their own, but the org owns the records, so the read stays attributable. Identifiers only, never message text. An audit trail that reproduces the message it audits has just copied the PHI into the log stream. Also completes eed3817, which swept this work in mid-edit and left MediaController referencing PhiAccessKind before the import existed — that revision does not compile. This one does: mvn -o test is 16/16 green.
· DocsService
Rekodi upatikanaji wa haraka kwenye njia nne za hati ambazo hazirekodi chochote
/download already logged through **************** and it was the platform's only byte-access injection site. The sibling endpoints serving the exact same bytes — /stream, /bulk-download, the template converted-PDF and the template page thumbnail — recorded nothing, so anyone wanting document bytes without leaving a trace only had to pick a different URL. All four now emit a PhiAccessEvent at the point access has been verified and the bytes are about to be served. Bulk download emits one event per document rather than one per request. "They exported 400 documents" is not an answer to "was this individual's record disclosed", and per-document rows are what make that answerable six years later under §164.528. Client IP comes from X-Forwarded-For with remoteAddr as fallback: every request arrives through Traefik, so remoteAddr alone would record the ingress pod for every access and be worthless for detection. An unauthenticated attempt is attributed to a sentinel organization rather than dropped — the recorder rejects unattributable events, and a request for document bytes with no session is exactly the event worth keeping. This sits alongside the existing ImgLogDownload rows rather than replacing them: those are imaging-domain records, this is the cross-surface trail an accounting of disclosures is assembled from. Note ImgLogService, which reads those rows, still has zero callers anywhere in the platform.
· KlusterServices
Contain egress from the avatar resolver; note the Redis eviction pool
Huduma ya Usalama sasa inaendesha mporaji ambaye URL ya lengo inachaguliwa na wasio waaminifu Chama - rekodi ya BIMI ni maudhui ya DNS yaliyochapishwa na mtu yeyote anayemiliki uwanja wa kutuma, Mtu yeyote ambaye ni mmiliki wa barua pepe ni mwanachama. Hakuna kitu katika kundi hili Imefungwa ambapo ombi kama hilo linaweza kwenda: Redis hujibu bila uthibitisho kwenye 6379, MinIO kwenye 9000 na sifa za mizizi, CockroachDB kwenye 26257, na metadata ya node Na mwisho wa yote ni juu ya kila kitu. Msuluhishi anatekeleza udhibiti wake mwenyewe katika msimbo, na wale wanaobaki msingi Ulinzi kwa sababu ni safu pekee inayoelewa DNS rebinding. Hii ni Njia ya pili, kwa hivyo mdudu bado hawezi kufikia anwani ya kibinafsi. Kuruhusu kwa kiasi kikubwa badala ya kuruhusu orodha. Huduma hizi zinapatikana kwa uhakika Kwa upande mwingine, wageni waalikwa na wageni waalikwa wakiwa katika hafla hiyo. kimya kimya. Badala yake inaruhusu mtandao wa umma na kila aina ya kibinafsi isipokuwa, Baada ya hapo, unahitaji kutumia bandari maalum za ndani. Kutolewa kwa nguruwe kufanya vitu ambavyo haviaminiki—kutumia jina la mahali pote ingekatwa Huduma ambazo hazijakaguliwa kamwe. Mabadiliko ya Redis ni maoni na matuta ya database, sio mabadiliko ya tabia. Ilikuwa rekodi kwa nini Sender-avatar cache huhifadhi tu masharti mafupi ya metadata: maxmemory 2gb na allkeys-lru acrosss kwa ujumla, na DB 0 ina kila moja kikao, hivyo wingi byte cache hapa ingekuwa ishara watumiaji nje katika random chini ya shinikizo Kuwa makini kwa siku kadhaa.
· kamo-internal
Onyesha picha za kutuma na mpokeaji
Katika barua yake hiyo ya kujiuzulu, Bi. Kila mstari ulionekana sawa na mzunguko haukuchukua habari. Sasa inaonyesha kuwa Picha halisi ya mwandishi wakati mtu anajulikana, na monogram mbili-letter wakati ni Sio hivyo. SenderAvatar moja inachukua nafasi ya duru saba za mwanzo za mkono. Hii ni kwa sababu zaidi ya msimamo: kila mmoja alikuwa na wazo lake mwenyewe la jinsi ya kugawanya anwani, na Katika kesi hii, chaguo la binary ni sawa na chaguo la binary, kwa hiyo, hakuna tofauti yoyote kati ya orodha na orodha. kusoma pane kimya kimya kimya kimya nusu kiwango hit na kuonyesha mtu mmoja Kuna njia tofauti kwenye skrini moja. Uwanja wa Ndege wa RFC 5322 Hotuba ya Mhe. Orodha na maoni ya thread pia kuacha uchapishaji headers Bonyeza "Jane Doe <jane@acme.com>" na uonyeshe jina. Utambulisho huishi katika duka lake mwenyewe lililofungwa na anwani, kamwe kwenye vitu vya ujumbe. Maoni yote ya orodha yanapiga kura kila 30s na kwenye kila arifa ya mtandao, kila wakati Na badala yake, ujumbe wake ulikuwa ni ujumbe mzito. Ilifutwa mara mbili kwa dakika, hivyo kikasha cha wavivu kingepiga picha kwa monogram na nyuma. ya kudumu. Kuweka kwa anwani pia inamaanisha jibu la kuchelewa haliwezi kuchora kwenye Ni makosa, kwa sababu rangi ya ngozi sio kitu ambacho ni muhimu. Maombi yanakubaliwa katika kundi moja kwa kila screen: 50 safu kuuliza kujitegemea itakuwa 50 Maombi, na kwa kuwa kila wito wa huduma hii unasisitiza ufunguo wa wakati mmoja kwanza, Kwa kweli mia moja. Mkosa huulizwa tena baada ya 45s, kwa sababu ya upande wa seva Azimio ni awamu mbili na anwani isiyojulikana inajibiwa tu kwa simu ya baadaye. Mazungumzo ya mazungumzo Orodha ya sasa ni virtualized. Inafungua ukurasa wa 50 na kuweka upya Kila mmoja wao ni katika chumba cha mtihani, na katika kila chumba cha mtihani. Kuongeza picha kwa kila mmoja alifanya uso mkubwa katika sanduku la barua. Hakuna-reply na mailer-daemon senters kupata alama neutral badala ya monogram- Kusimama kando ya taarifa ya bounce ni kupotosha kikamilifu. Picha ni mapambo Kwa sababu jina lake daima linaonekana maandiko kando yake, na hubeba. referrerpolicy "no-referrer" kwa hivyo kitambulisho cha thread kamwe hufikia mwenyeji wa picha. Picha zote na Wizara ya Mambo ya Ndani ya Nchi. ................................................................... Imethibitishwa na re-hosted upande wa seva-ambayo ni nini kinachoruhusu mpya kwa kila mara Sera ya img-src inabaki imefungwa kwenye vikoa vya mpangaji mwenyewe. Sera hii imejikita katika ya Muungano badala ya baadaye. Kwa sababu jukwaa hili ni nyeupe-labelled: a Sera ya tuli ingekuwa na jina la mpangaji mmoja na ingevunja picha kwa wengine. Pia hufunga vector ya kufuatilia-pixel iliyohifadhiwa kabla, tangu Mawasiliano. Picha ya UN Ni URL ya maandishi ya bure iliyotolewa moja kwa moja katika <img src> kadhaa. Picha ya pili / picha haikubali tena SVG. Hii ni hatari kubwa sana wakati ambapo SVGs ilikuwa alama zetu za mandhari; sio sasa kwamba jukwaa lina alama DNS ya mtu wa tatu. Mawasiliano ya simu huacha ORG ID ngumu = 1 na parameter ya swala ya orgId, Kulinganisha mabadiliko ya nyuma ambayo hupata upya kutoka kikao.
· EmailService
Avatar endpoints; fix unauthenticated reachability and contact tenancy
Adds the mailbox side of sender avatars, and fixes three pre-existing holes this work would otherwise have built on top of. AUTHENTICATION. OTKPreAuthFilter used to reject a *bad* OTK but treat *no* OTK as anonymous-and-allowed, falling straight through to the filter chain — and the Spring Security chain here is anyRequest().permitAll(). Every mailbox, contact and calendar endpoint was therefore readable by anything that could reach the ClusterIP. Protected prefixes now fail closed. The fix lives in the filter rather than in the security chain deliberately: reshaping the chain would touch thirty unrelated surfaces at once, including two servlets and the DAV endpoints. The exempt list is the complete set of genuinely session-less entry points — the CAN-SPAM opt-out link, the OAuth redirect target where the tenant rides in the signed state parameter, and the server-to-server surfaces that authenticate with X-Internal-Auth. CONTACT TENANCY. Every contacts endpoint took orgId as a query parameter, or reached a book by id with no ownership check at all, so changing a number in the URL read another tenant's contacts — names, addresses, phone numbers, notes. The org now comes from the session and every book- or group-scoped operation proves the target belongs to it. Absent targets return 404 rather than 403 so an id in another tenant is not confirmed to exist. orgId is still accepted on the two list endpoints and ignored, so clients do not break mid-deploy. That change also fixes a silent bug: searchContacts derived orgId from bookId and passed null when no book was selected, into a query whose first predicate is contactBook.organization.id = :orgId. Book-less search therefore always returned nothing — which is exactly how the mail recipient autocomplete calls it. EMAILLOGOSERVICE. It fetched an operator-supplied URL with no scheme or address check, and transcoded the result on a Batik that resolves external references by default. A logo SVG containing <image **************** would have been dereferenced from inside the cluster. Now guarded on both ends. MESSAGEINDEXER. resolveMemberIdFromEmail pulled the entire members table into heap and filtered it in Java, once per indexed message, on a 1Gi pod — the code said so in a TODO. Same semantics, one indexed query. The avatar surface is one batched endpoint rather than a field on the message DTOs. Both list views poll every 30s and replace their message array wholesale, so an avatar carried on a message would be wiped twice a minute and the UI would flicker forever; and EmailMessage is shared by the folder list, reading pane, thread view and search, so changing its shape would touch all of them at once. The directory-photo endpoint is server-to-server only — exposed to a browser it would be a directory-enumeration oracle for the tenant.
· SecurityService
Sender-avatar resolution chain for the mailbox
Resolves a correspondent's email address to a picture, working outward from what Kamo already knows: member photo, then a photo someone attached to a contact, then the tenant's connected Microsoft/Google directory, then the sender domain's BIMI mark, then its favicon, then Gravatar. Anything unresolved renders a monogram, so the chain always terminates locally. The directory tier is worth calling out: the connect flows here already request admin.directory.user (Google) and User.ReadWrite.All + Directory.Read.All (Microsoft) alongside the mail scopes, so for Workspace and 365 tenants this returns authoritative photos with no re-consent and no third party involved. It should beat anything on the public internet and usually will. Two phases, never one. A request reads cache only; unknowns go to a bounded worker with DiscardPolicy — running an overflowed task on the caller would hand a request thread to a third-party host, and a discarded resolve costs nothing because the next poll asks again. A full-miss chain is DNS plus four HTTP calls, and a page carries dozens of addresses; nothing about opening a folder may depend on third-party availability. Decoupling also keeps resolution from correlating with a human opening a message, which would make it a read receipt — the exact behaviour the mailbox already blocks by default for remote images. This is the platform's first fetcher whose target URL is chosen by an untrusted party: a BIMI record is DNS content published by whoever owns a sending domain, and anyone becomes a sending domain by emailing a tenant. There is no egress NetworkPolicy and no forward proxy, so the controls are in code. SafeImageFetcher pins DNS — a custom resolver hands OkHttp only the addresses that passed PublicHostGuard, closing the rebinding window that defeats a naive pre-flight check — refuses redirects, caps bytes as they stream rather than trusting Content-Length, and requires an image content-type. SvgSanitizer parses and re-serializes against an allowlist instead of rasterizing. Batik 1.17 resolves external resources by default, so handing it a hostile SVG would dereference <image **************** from inside the cluster after the URL check already passed. The Tiny PS profile forbidding that is a statement about conforming files, not about bytes fetched from an attacker's URL. Provider miss semantics are all different and all cache-poisoning if treated as hits: Gravatar is asked with d=404 because every other default returns 200 with a placeholder; Google's no-match is a 404 carrying a byte-identical globe, so it is rejected by content hash; DuckDuckGo returns 404 with a real image body, so status decides. Bytes are re-hosted under a random name — never one derived from the address, which would let anyone probe whether a given person corresponds with this platform — and served through an authenticated route. Redis holds metadata only. That instance is 2GB with allkeys-lru on an emptyDir, sharing a keyspace with every live session; caching image bytes there would evict sessions and sign users out at random.
· kamo-shared-library
Sender identity model + org-scoped batch email lookups
Backing store and queries for the mailbox sender-avatar chain. SenderIdentityEntity is the durable tier of the avatar cache. org_id is part of the natural key rather than decoration: USERS.EMAIL is unique only per security provider, so an answer derived from tenant data must not be visible to another tenant. External-tier answers (BIMI/favicon/Gravatar) are genuinely global and share a sentinel org so every tenant shares the hit rate. Misses are stored as rows, not absences — without that, every unresolvable sender re-runs the whole provider chain on every inbox poll. OrgAvatarSettingsEntity splits the chain by what actually leaves the building. The identity tier defaults OFF because it discloses a digest of a correspondent's full address to a third party, which on a lending CRM is a decision about a borrower identifier rather than a display preference. The repository additions exist because none of this was reachable before: - ContactEmailRepository had no lookup by address at all — only substring LIKE for the autocomplete — so a contact that had a photo could not be resolved from a From: header. - Member/User batch lookups are org-scoped and take pre-lowercased input. LOWER() is applied to the stored column only; emails are persisted verbatim (EmbEmail has no normalizer) and CockroachDB equality is case-sensitive. - The avatar-id projections return ids rather than entities on purpose. Both Member.avatar and User.avatar are lazy associations onto a JOINED hierarchy: a caller outside a transaction gets LazyInitializationException, and one inside gets a base-type proxy that never narrows to AvatarPhoto, putting the file extension needed to build the URL out of reach. - **************** replaces a findAll() full-table scan that ran once per indexed message. PublicHostGuard is shared rather than duplicated because it is a security control: it decides whether an address is somewhere we will send a request that an untrusted party chose. Tested against the real bypass techniques — IPv4-mapped IPv6, 6to4, NAT64, CGNAT and link-local metadata. Right 177 gates the disclosure decision.
· kamo-shared-library
Audit spine for PHI access — §164.312(b)
The Security Rule requires recording access to ePHI, which means reads. The platform records changes well — LeadHistory, SystemAccessLog, UserHistory, LoanAuditEvent — and reads almost nowhere, which is why "who viewed this record" is currently unanswerable. That is also the first question a §164.402 breach assessment asks, so this one gap blocks both the audit control and the four-factor analysis. Capture layer only; no schema. The durable store sits behind PhiAccessLogWriter so call sites can be wired now and the sink can evolve — plain table, then append-only with a hash chain, then a signed export to object-locked storage — without rippling back through hundreds of call sites. Two properties carry the weight: - Failure handling belongs to the access kind, not the caller. EXPORT, DOWNLOAD and DISCLOSE fail closed: an unlogged bulk export is exactly the event you cannot reconstruct afterwards, and "we served the data but lost the record" is the worst answer to give an investigator. VIEW, LIST and SEARCH fail open and log loudly, because losing one line is not worth taking the product down. That asymmetry is deliberate and wants signing off rather than assuming. Callers under pressure resolve policy questions in whichever direction unblocks them, so the policy is not at the call site. - A denied read is still a read. Refused attempts are recorded, because the access pattern of someone probing records they cannot reach is what detection needs and is invisible if only successes are logged. Startup safety, which nearly went wrong: PhiAccessRecorder is a @Component and every service component-scans com.kamo, so a hard dependency on a writer bean would have failed context startup across ~40 services at once — a whole-platform outage from a feature nothing had called. The first fix, a @Configuration with @ConditionalOnMissingBean, does not work: component-scanned configuration is processed alongside user configuration rather than after it, so both writers register and injection fails on ambiguity. PhiAuditContextWiringTest caught that. The fallback now lives in the recorder via ObjectProvider.getIfUnique, where no ordering question exists, and ambiguous writers degrade to the default rather than failing startup. The default Slf4j writer is a starting point, not the destination — log lines are not tamper-evident and retention is 14 days against a six-year obligation. It exists so call-site wiring does not wait on the store. Identifiers are logged, content never is: an audit trail that reproduces the record it audits has just moved the PHI into the log stream.
· SecurityService
403 rather than 500 when the compliance boundary refuses
Two call sites of featureService.enableFeature now understand the HIPAA boundary: - /enable maps PhiModuleNotPermittedException to 403 carrying the reason, mirroring the AppliedModelViolationException handling directly above it. Without this the broad catch below turned a deliberate policy refusal into a 500. - The auto-enable loop in the listing path pre-checks with isPhiPermitted and skips. It runs over every completed ServiceType while merely *listing* an organization's features, so throwing on the first blocked module would have made the features page unloadable for a PHI tenant instead of hiding one entry. Inert today — every organization is handlesPhi=false.
· kamo-shared-library
Kuimarisha mipaka ya kufuata katika kubadili kipengele
Wires PhiTenantGuard into FeatureService, the single choke point through which a module is switched on for an organization. This is where the boundary stops being a policy object and starts being enforcement. Both halves, because either alone leaves a gap: - enableFeature throws PhiModuleNotPermittedException, so a PHI tenant cannot switch on a module Kamo cannot stand behind as a business associate. - isFeatureEnabled(Organization, ServiceType) reports a module disabled whenever the tenant handles PHI and the module is outside the boundary, whatever the OrgFeature row says. The row can legitimately say active — it was enabled before the tenant became a PHI tenant, and enableFeature does not run again on that transition. isPhiPermitted is the non-throwing form for callers that iterate. FeatureController auto-enables every completed feature while *listing*, so a throw on the first blocked module would fail the whole features page rather than skip one entry. PhiServiceTypeMapping binds ServiceType to PhiModule and is total, with a test that fails the build on any unmapped value. That test earned its keep on first run: it caught ServiceType.STORAGE, which a grep of the enum had missed, and then AI_CHAT, DOC_MANAGER, DOCS_EDITOR and DOC_CLASSIFIER. Without it, five service types would have been silently outside the boundary — the same omission failure PhiModule's constructor closes, reintroduced one layer up. New modules for the previously unmapped service types, each with an explicit disposition: object storage, document editor and classifier, support tickets, HR, inventory, legal, calculator and activity feed are inside; clubhouse and games are BLOCKED_PENDING_WORK on the grounds that they have not been assessed, which is a reason to block rather than to assume. Every organization is handlesPhi=false today, so this changes no behaviour in production. It becomes load-bearing the moment the first tenant is flagged.
· kamo-shared-library
Auto-assign a MERS-compatible loan number at application creation
LoanFile.loanNumber was null on create (both blank-create and lead-promote) and only ever set when a steward typed one — which hard-blocked MERS MIN minting (a MIN's 10-digit segment IS the loan number) and every downstream identifier for a headless-originated loan. AppMortgageService now assigns a loan number at both creation sites: a clean 10-digit numeric (MinMinter strips non-digits and needs a <=10-digit segment, so a pure numeric is the only safe shape), unique per org via the (org_id, loan_number) index with a bounded retry. It FAILS OPEN to null on a collision streak or an unavailable repo — degrading to the prior manual behavior rather than ever breaking application creation. A lender-supplied number still overrides via the loan-file patch path. No schema change (the column already exists, indexed).
· kamo-signer-monorepo
Render signer tarehe katika wakati wa mpokeaji kutatuliwa
SigningSession gains a required `timeZone` (IANA id) that ESigService resolves from the recipient's member zone, then the org's, then UTC. WelcomeScreen now passes it explicitly to the formatter, so the expiry date depends on neither the viewer's device settings nor the provider's UTC default. Making the field required rather than optional caught the one literal session construction site (preview.ts) at compile time. Also: - call setRequestLocale in the locale layout so translations bind to the URL segment rather than relying on the middleware header alone - assert in the Dockerfile that messages/ survived `turbo prune`, and document why .dockerignore must keep .gitignore out of the build context; without that coupling prune drops the translations and the image ships rendering raw keys with nothing failing until a signer opens the document
· ESigService
Resolve signer display timezone from member, then org
Katika hali yoyote ile, mtuhumiwa huyo aliachiwa kwa dhamana. bahasha expiring 2026-08-03T05:00Z inaweza kusoma "Aug 3" kwa saini Pacific Kwa wale ambao tayari kiungo kimekufa jioni ya Agosti 2. SigningSession sasa hubeba server-resolved IANA TimeZone: mpokeaji wa Mwanachama mwenyewe.timezone wakati wao ni mwanachama na kuweka moja, mwingine kutuma Shirika la Timezone, UTC. Kwa hakika, Qur’ani ni muujiza wa miujiza. ikiwa ni pamoja na moja ambayo inathibitisha kila ramani ya TimezoneType kwa eneo lake mwenyewe Thamani ya ndani haiwezi kubeba RangeError ya upande wa mteja. Pia kurekebisha SigningSession.locale, ambayo ilitoa jina la enum ("SPANISH") Badala ya msimbo wa BCP-47 ("es") kutumika katika sehemu ya saini ya URL.
· InitializerService
Kujenga HANDLES PHI na PHI EFFECTIVE DATE kwenye orgs
Mirrors the two new Organization fields, following the OrgBrandingFieldsMigration convention, so the columns are reproducible on any environment rather than only existing where a ddl-auto pass happened to run. HANDLES_PHI is NOT NULL DEFAULT FALSE so existing tenants are explicitly outside the compliance boundary. PHI_EFFECTIVE_DATE is TIMESTAMP, not TIMESTAMPTZ, because the entity field is a LocalDateTime. The test is a drift guard, not a restatement: it reads column names off the entity by reflection and fails the build when an entity field has no ALTER. That is this convention's actual failure mode — someone adds a field, forgets the migration, and with ddl-auto:none in the services it surfaces as a production query error instead of at build time. Verified by mutation: removing the PHI_EFFECTIVE_DATE ALTER makes it fail with the actionable message, restoring it goes green. Both columns are already present in the cluster; these ALTERs are idempotent.
· kamo-shared-library
Endelea kushughulikiaPhi na PhiEffectiveDate kwenye Shirika
Rejea PhiTenantGuard kwa safu halisi. HANDLES PHI sio uongo mkubwa Baada ya yote, <========================================================================================================================================================================================================================================================== Ni wazi nje ya mipaka ya kufuata badala ya kukaa kimya ndani yake. PHI EFFECTIVE DATE inapanua tathmini ya uvunjaji na wajibu wa uhifadhi kwa Swali la kwanza: "Wakati" ni swali la kwanza lililoulizwa baada ya "nini kilifunuliwa". Kubadilika kwa sera na *** Walinzi wa mwenyewe anakaa bure ya chombo, hivyo mpiga simu anashikilia bendera tu ya kikao Hakuna haja ya kuweka safu. Salama kwa kushinikiza: nguzo zote mbili tayari zipo kwenye nguzo na kila moja ya 12 Mashirika ya umma yanaandika uongo. Ushahidi dhidi ya habari schema kabla ya kutua - handles phi boolean sio uongo wa NULL DEFAULT, phi effective date timestamp NULL. Angalia uhamiaji wa KamoInitializer kwa fomu inayofaa.
· kamo-shared-library
PhiTenantGuard and the module compliance boundary
The the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the the Kutokana na hali hiyo, taarifa za afya zinahifadhiwa. Kila kitu kila kitu Funguo za chini za mto - ambazo ushirikiano ni walemavu, ikiwa MFA ni kulazimishwa, ambayo vikao kupata muda mkali zaidi, nini uti wa ukaguzi lazima cover. Hakuna kikomo cha uzalishaji. Jaribio la awali liliondoa hii mpya Makala ya Shirika.handlesPhi na ilipaswa kurejeshwa: clones za huduma za CI Msingi wa msingi wa pamoja wakati wa kujenga wakati wa KamoInitializer unaendeshwa kwa mkono, kwa hivyo Nguzo ya chombo juu ya mapumziko kuu kupeleka huduma inayofuata mpaka mtu kuhama. Kuweka sera kutoka chombo ina maana kwamba nchi na inaweza kutekelezwa sasa, Kwa hiyo, sekta hiyo inakuwa ni hatua ya kujitegemea. Sifa tatu za kubuni zinajengwa karibu: kushindwa kusimama. Ujenzi wa PhiModule unahitaji PhiDisposition na sababu, Kwa hivyo moduli iliyoongezwa baadaye haiwezi kurithi ruhusa kwa omission - haiwezi Kukataa kufanya maamuzi, na hakuna kitu kingine zaidi ya kukataa. Hii ni Njia ya kushindwa mipaka ya kufuata ina kuishi: meli za moduli, hakuna mtu Fikiria kuhusu PHI, kliniki huanza kutumia. - Mkojo usiotibiwa sio salama. Jeshi la Polisi laua raia watatu PhiTenantStatus badala ya boolean, hivyo "singeweza kupakia shirika" Haiwezi kuandikwa sawa na "shirika hili sio PHI". Pamoja na A Njia hii ya kuhesabu ni rahisi. Haiwezekani kufanya uchunguzi. Hata hivyo, Shetani anakanusha kila kitu. Hakuna cha kuongeza. Uamuzi ni kazi safi ya (status, moduli) bila Piga simu, kikao, sahihi au parameter ya mungu-mode. “Wafanyakazi wetu wanaweza kufanya "Kuondoka" ni jibu ambalo linashindwa ukaguzi wa chombo kilichofunikwa. Uchambuzi wa kina wa kamo-internal Kwa mfano, *** Imetolewa: CRM, mawasiliano, vault ya hati, Barua pepe ya kujitegemea, barua pepe, maelezo, kazi, kalenda, mazungumzo ya ndani, in-cluster Picha zote na Wizara ya Mambo ya Ndani ya Nchi. .................................................................... Kufungwa bila BAA inayoweza kupatikana: kijamii Ujumbe, Canva, e-biashara sync, lango la MCP. Jina la kazi: Kazi router AI, utafutaji wa RAG, rekodi za mkutano, rekodi za VOIP, kampeni za masoko, Uchambuzi wa masoko. Hakuna haja ya kuwa na wasiwasi juu ya hilo, kwa hivyo ni muhimu sana kuomba.
· InitializerService
Create HANDLES_PHI and PHI_EFFECTIVE_DATE on orgs
Mirrors the two new fields on the Organization entity in kamo-shared-library, following the OrgBrandingFieldsMigration convention. HANDLES_PHI is NOT NULL DEFAULT FALSE so every existing tenant is explicitly outside the compliance boundary rather than silently inside it. PHI_EFFECTIVE_DATE is TIMESTAMP, not TIMESTAMPTZ, because the entity field is a LocalDateTime. SEQUENCING — this needs to run before the next service deploy. Services pin kamo-shared-library at a fixed version that is republished in place, so the first service to rebuild after that library change starts selecting these columns, and with ddl-auto:none in the services that fails until this runner has executed. The accompanying test is a drift guard, not a restatement: it reads the column names off the entity by reflection and fails the build if an entity field has no ALTER. That is the failure mode this whole convention has — someone adds a field, forgets the migration, and it surfaces as a production query error instead of at build time. Verified by mutation: removing the PHI_EFFECTIVE_DATE ALTER makes it fail with the actionable message, restoring it goes green.
· ESigService
Weka sehemu ya saini katika viungo vya kutia saini
Extracts the byte-for-byte duplicated buildSignLink into SignLinkBuilder and adds a locale segment sourced from Organization.language, falling back to English for the four LanguageType values without translations. Requires the kamo-signer /{locale}/ routing to be deployed first.
· kamo-shared-library
Add the handlesPhi tenant flag and PhiTenantGuard
The keystone of the HIPAA compliance boundary: a per-organization flag plus a guard deciding which modules that organization may reach. Everything downstream keys off it — which integrations are disabled, whether MFA is forced, which sessions get the stricter timeout, what the audit spine has to cover. Organization gains HANDLES_PHI (BOOL DEFAULT FALSE) and PHI_EFFECTIVE_DATE, following the existing **************** pattern. Defaults false, so this is inert for the existing mortgage and CRM book. Three properties the design is built around: - Fails closed. PhiModule's constructor requires a PhiDisposition and a reason, so a module added later cannot inherit permission by omission — it will not compile without a decision, and anything other than PERMITTED denies. That is the specific failure mode a compliance boundary has to survive: a module ships, nobody thinks about PHI, a clinic starts using it. - Not bypassable. The decision is a pure function of (organization, module) with no caller, session, right or god-mode parameter, so there is nothing to elevate. "Our support staff can turn it off" is the answer that fails a covered entity's review. - Denials carry a reason, because a bare "denied" is not usable by a support engineer or a customer. Dispositions follow the scoping analysis in kamo-internal **************** Permitted today: CRM, contacts, document vault, self-hosted email, e-sign, notes, tasks, calendar, internal chat, the in-cluster transcription/translation stack, and MLOS. Blocked with no obtainable BAA: social messaging, Canva, e-commerce sync, MCP gateway. Blocked pending named work: the AI router, RAG search, meeting recordings, VOIP recordings, marketing campaigns, analytics export. Schema note: HANDLES_PHI and PHI_EFFECTIVE_DATE materialize on the next KamoInitializer run, after CI publishes this shared-library build. Nothing reads the flag yet, so the ordering is safe.

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