Changelog
Cosa c'è di nuovo.
Ogni miglioramento alla piattaforma, come si spedisce.
· SecurityService
Record lead legge e rifiuta le esportazioni non verificabili
HIPAA §164.312(b) per il servizio di sicurezza. La piattaforma registra ogni piombo CHANGE (LeadHistory) e ogni login (SystemAccessLog) e non registra READ all, so "chi ha guardato questo mutuatario" — la prima domanda §164.402 domande di valutazione della violazione — era insoddisfacente. PhiAccessAuditor costruisce eventi dalla mappa di sessione OTK/*** endpoint già risolto, quindi l'identità nella riga di audit è la stessa identità che ha autorizzato la richiesta. L'IP client proviene da X-Forwarded-For; dietro Traefik, remoteAddr registra il pod di ingresso e è inutile per il rilevamento. Le negazioni sono registrate anche — l'educazione è ciò il rilevamento cerca — e un caller senza sessione atterra su un org senzinale piuttosto che essere caduto. Reads: single lead fetch = VIEW, grid = un evento LIST per lead spedito (trattato dai DTO proiettati, quindi un piombo filtrato da VIEW UNASSIGNED LEADS non appare mai come uno che è stato letto). 401/403 tutti registrano una negazione. Esportazioni non chiuse — accesso-log CSV, HMDA LAR, il corpo di formazione QC e la piattaforma di estrazione corpus. Ogni record di un evento per record PRIMA del file è costruito, e un audit fallito scrive restituisce 503: "abbiamo tramandato i dati ma abbiamo perso il record" è l'unica risposta che Non si può tornare indietro. Il corpus su piattaforma attraversa ogni inquilino, quindi ogni record è attribuito all'org che lo possiede, non l'operatore. Solo gli identificatori sono registrati, mai contenuti. ******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* per tenere le sue file per registrare chi era in loro.
· kamo-signer-monorepo
Citazione/documento di stima del marchio pubblico a /{locale}/q/{token}
La superficie orientata al cliente per il sistema di citazione commerciale. Server-rendered così il il marchio di org è nella prima vernice — un flash non firmato su un documento commerciale è esattamente ciò che questo non deve fare, ed è per questo che la pagina emette marchio personalizzato proprietà inline via QuoteBrandVars piuttosto che scambiarle dopo l'idratazione. Vive in kamo-signer perché quella app ha già l'ingresso HostRegexp, autocert copertura, alias DNS e il pacchetto tema con hostnameOverride — e perché citazione -> accettare -> e-sign è allora un hop, dal momento che EsignGenericEnvelope lega qualsiasi record tramite (context type, contest id). Riutilizzarlo era possibile solo una volta che il /{locale}/ i18n lavoro sbarcato; tutte le 31 stringhe sono chiavi di traduzione provenienti dal dizionario. Formato di denaro e date attraverso il prossimo-intl utilizzando la valuta propria del preventivo e la fuso orario recuperato dal server (membro destinatario -> org -> UTC), mai il dispositivo dello spettatore. Una scadenza resi nella zona sbagliata può indicare una data in cui l'offerta è già morto. Aggiunge il primo vero blocco di stampa @media della piattaforma. "Print Invoice" in kamo-internal ha sempre stampato l'intera applicazione cromata perché nessuna regola di stampa @media esisteva ovunque; qui le stesse stampe di markup come documento lettera pulita con il marchio accento conservato (print-color-adjust: esatto — una citazione incolore si legge come una bozza), la barra d'azione soppressa, e pagina-break-inside: evitare su file e totali. Gli stati terminali sono deliberatamente asimmetrici: EXPIRED ottiene una pagina di marca onesta nominare la data e i dettagli di contatto dell'org, sicuro perché il chiamante già ha dimostrato il possesso di un token a 256 bit; sconosciuto/revoked/wrong-org tutti crollano a uno pagina generica in modo che la superficie non possa essere probed. Il cliente sbianca il motivo prima di rendering. Il gettone non viaggia mai verso l'interno di un URL — il lato server risolve lo pubblica in un corpo, e il BFF inoltra il reale IP client esplicitamente perché APIService timbri X-Forwarded-Per da una propria richiesta in entrata, che altrimenti mettere ogni visualizzatore in un secchio limite di velocità collegato al pod IP. Aggiunge SECURITYSERVICE URL alla configurazione (verified: kamowssecurity-service esiste nel namespace kamo).
· 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
Contenimento di Egress per il risolutore di avatar, come CiliumNetworkPolicy
Reinstate ciò che 8f6c672 ha revertito, nell'unica forma che funziona realmente qui. Il tentativo precedente è fallito perché una semplice NetworkPolicy non può esprimere questo La topologia del cluster. Tre delle quattro dipendenze critiche di SecurityService il nodo, non nella rete pod — cockroachdb-0 e nats-k1m1-0 run hostNetwork, e MinIO è un processo host — così un podSelector non può mai abbinarli. The parte non ovvia, e la ragione per cui il primo tentativo di patch anche fallito, è che un ipBlock per il nodo subnet non lo risolve nemmeno: Cilium classifica il nodo come l'identità host/remote-node riservata e le regole CIDR deliberatamente non corrispondono identità riservate. toEntities è l'unico costrutto che lo esprime. Entrambe le modalità di guasto sono state riprodotte contro un pod canarino buttafuori prima di scrivere questo, piuttosto che contro la produzione. Il canarino ha portato la stessa regola impostata sotto un'etichetta distinta, che è come la versione ipBlock è stata catturata silenziosamente bloccando CockroachDB, NATS e MinIO mentre sembra perfettamente ragionevole. Verificato sul canario, poi confermato su SecurityService senza riavvicinamenti e no errori di connessione: ALLOW dns, crdb:26257, nats:4222, minio:9000, redis:6379, fratelli:80, mondo:443 Nodo BLOCK:22, nodo:443, kube-api:443, redis-via-nodo:6379, 169.254.169.254, loopback:9000 Gli ultimi quattro sono il punto: un SSRF nel risolutore ora non ha nessun posto dove andare. The in-code guard rimane il controllo primario — è l'unico strato che comprende La ricombinazione DNS — e questa è la difesa dietro di esso. L'intestazione ha la procedura canaria. Chiunque cambi questo dovrebbe riesaminarlo piuttosto che la ragione su di esso; il modello di identità di Cilium non si comporta come semplice NetworkPolicy intuito suggerisce, e non c'è ambiente di staging.
· kamo-internal
Mostra un facepile per conversazioni di gruppo
Un filo con cinque persone su di esso è stato rappresentato da una faccia arbitraria, che legge come un messaggio da una persona. Fino a tre partecipanti ora si sovrappongono, quindi "tutte le persone" è leggibile a colpo d'occhio senza bisogno di analizzare la linea di nome. Catturato a tre: passato che i cerchi si restringono sotto riconoscibilità e la fila diventa texture piuttosto che informazione, e il resto è già dichiarato in il testo "e N altri" proprio accanto a esso. Il lettore è filtrato dalla propria lista di filetti, e l'altra parte conduce — le facce leggono da sinistra a destra, quindi il corrispondente primario deve essere quello più sinistro e quello che porta il distintivo non letto. Ogni faccia ottiene un anello nella fila colore di sfondo; senza di esso una pila di foto simili si fonde in una forma. La risoluzione ora copre i primi partecipanti piuttosto che solo il piombo, altrimenti la seconda e la terza faccia si sederebbero come monogrammi in modo permanente.
· SecurityService
Strumento la catena, il salto scaduto voci, rivelazioni di record
Ogni modalità di guasto in questa funzione è silenziosa. Un interruttore chiuso, un lavoratore coda scartando ogni compito, un provider che ha iniziato a restituire HTML — ogni aspetto esattamente come "questo corrispondente non ha foto", che è anche quello corretto Sembra un comportamento. Non c'è differenza visibile dall'utente tra il lavoro e completamente morto, quindi deve essere misurato. Metrics (Prometheus era già collegato, quindi questo è principalmente il nome): rapporto, durata della catena taggata dal livello che ha risposto, per-provider hit/miss, profondità della coda del lavoratore, e un totale scartato-task. L'ultimo usato in precedenza DiscardPolicy, che scende silenziosamente dal design; ora aumenta un contatore, e il contatore è dichiarato prima dell'esecutore perché gli inizializzatori di campo funzionano l'ordine sorgente e basandosi sulla cattura pigri si romperebbe su un innocuo riordino. I discorsi vanno al loro logger. Solo il livello di identità è una persona divulgazione — i livelli di dominio trasmettono il lato destro di un indirizzo, che non identifica nessuno — e viene registrato prima della chiamata piuttosto che dopo, quindi il il sentiero esiste anche quando la richiesta fallisce. L'indirizzo è scritto in pieno e deliberatamente: il punto è quello di rispondere "con i dati di questa persona condivisi", e un hash non può rispondere. Il mietitore chiude una perdita: deleteExpiredBefore esisteva e niente lo chiamava, quindi sender identity è cresciuto per sempre. Una mailbox impegnata tocca migliaia di corrispondenti una volta ogni e lascia una fila negativa dietro per ogni. I test coprono il fetcher contro una presa reale, perché i casi interessanti sono tutti i comportamenti dei fornitori piuttosto che la logica: un 404 che trasporta un'immagine valida (DuckDuckGo), un soft-404 che serve HTML, un'origine che sottolinea Content-Length, un 1x1 stub, e un reindirizzamento rivolto alla rete pod. Il cliente di produzione non può essere indicato in un server di test loopback — il suo pin DNS si rifiuta di risolvere qualsiasi cosa privato, che è tutto il punto — in modo che quelli correre contro un cliente rilassato e pin viene affermato separatamente.
· kamo-internal
Promuovere una matrice LLPA ingerita in regolazioni dalla console
Il pannello di ingestione potrebbe solo pubblicare un foglio di tasso. Per una sorgente LLPA MATRIX documento mostra ora una scheda "Promote as LLPA Adjusts": selezionare l'esistente versione del foglio di calcolo LLPAs regolare (+ un gruppo opzionale), e ogni approvato Il secchio FICO/LTV diventa una regolazione del prezzo condizionale. I nomi dei risultati come molti sono stati autorizzati e, onestamente, quanti file sono stati saltati perché un secchio non poteva essere letto in modo sicuro o era già presente. ingestioneApi.promoteAdjustments messaggi al nuovo relè gateway.
· SecurityService
Relè gateway per promuovere una matrice LLPA in regolazioni condizionali
****************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** relè del foglio di promozione: cancello dio-eleggibile, organizzazioneId server-stamped su il payload via write(), downstream 4xx relayed verbatim. Che uno steward giri un matrice LLPA revisionata in reali regolazioni di prezzo per-scenario dalla console.
· VOIPService
Registrazione delle chiamate, segreteria e accesso trascrizione
La riproduzione di registrazione non ha lasciato traccia. Una chiamata registrata è il PHI più denso negozi di piattaforma — la conversazione irrinunciata — e "che ha ascoltato questo chiamata" è stato insopportabile, che è la prima domanda una violazione §164.402 la valutazione chiede. Wires CallRecordingAuditor in ogni lettura che lo rivela: - GET /registrazioni/{id}/audio CALL RECORDING / DOWNLOAD - E' una cosa da fare. CALL RECORDING / DOWNLOAD - GET/registrazioni/{id} CALL RECORDING + TRANSCRIPT / VIEW - E' una cosa da fare. CALL RECORDING / DOWNLOAD - GET /instances/{id}/voicemails TRANSCRIPT / LIST, per fila La trascrizione è quella che nasconde. Non è un download multimediale; cavalca all'interno dei metadati JSON e all'interno di ogni riga di segreteria, ed è lo stesso PHI come l'audio in una forma che scorre. Si ottiene il proprio evento perché un la contabilità delle informazioni è assemblata per risorsa. Il servizio Byte non è chiuso, quindi PhiAuditAvailabilityAdvice trasforma un inloggable leggere in un 503 piuttosto che un 500 — la richiesta era eccellente, abbiamo decoderd servire PHI non potevamo spiegare. Si registrano anche i rifiuti; si procede che cosa il rilevamento cerca. Chiude anche un gap di autorizzazione trovato durante il cablaggio questo: POST /recordings/lookup ha preso un istanzaId dal corpo di richiesta e mai ha controllato l'org del chiamante, quindi ha risolto il fornitore di un altro inquilino chiamare ids alla loro registrazione ids. Ogni altro punto finale a caso inciso servizio già passa attraverso findByIdAndOrgId; questo ora fa troppo. L'autorizzazione alla riproduzione di registrazione viene lasciata as-is e documentata in luogo: org la proprietà è l'unico controllo, così qualsiasi membro può riprodurre qualsiasi registrazione in il loro org. L'abbattimento significa decidere se "l'estensione" è la — cioè una decisione di prodotto, non una indovina di fare qui, e ogni accesso è almeno registrato ora.
· AIService
Registra l'accesso PHI su ogni lettura della storia di conversazione AI memorizzata
Fili la colonna vertebrale condivisa §164.312(b) nei sentieri che leggono i suggerimenti perseverati e completamenti. L'assistente è il modulo di valutazione della conformità messo OUTSIDE il confine — spinge il ventilatore verso i fornitori esterni senza BAA e sono poi tenuti indefinitamente — così le letture di quel negozio sono le rivelazioni più in bisogno di un sentiero, e non ne avevano. Audited: la lista di sessione e la ricerca di storia (un evento per conversazione, non uno a richiesta), i metadati di una sola conversazione e i suoi messaggi, il pubblico widget leggendo la propria trascrizione indietro, e il fascio di handoff. I rifiuti sono anche registrato — probing un altro org's guids è ciò che il rilevamento cerca — e un il tentativo non autenticato è attribuito all'orgo senzinale piuttosto che cadere, dal momento che il registratore rifiuta ciò che non può attribuire e che è l'evento più Vale la pena tenerlo. Il fascio del handoff è un DISCLOSE, non un VIEW: trasporta un visitatore conversazione fuori di AIService e di fronte a un umano in un altro prodotto. Che cosa? tipo fallisce chiuso, quindi un record perso ora tiene la trascrizione e le risposte 503 piuttosto che servire i dati per cui non può contare. Identifichi solo. Mai un corpo di messaggio, un titolo, o il termine di ricerca — un membro ricercare la loro storia assistente tipi il nome del mutuatario in esso, e un percorso di audit che ha riprodotto il prompt avrebbe appena spostato il PHI nel flusso di registro. Completa d428ee3, che una sessione concomitante ha spazzato a metà cambiamento: l'auditor mancava il recordDeniedInternalView e il nuovo di PublicAiChatController argomento costruttore non aveva raggiunto il suo 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
Registra l'accesso byte sui quattro percorsi documentali che non hanno registrato nulla
E' stato il momento giusto. piattaforma è solo il sito di iniezione byte-access. Gli endpoint dei fratelli che servono i stesso byte esatto — /stream, /bulk-download, il modello convertito-PDF e il pagina del modello thumbnail — non ha registrato nulla, quindi chiunque voglia il documento bytes senza lasciare una traccia doveva solo scegliere un URL diverso. Tutti e quattro ora emettono un PhiAccessEvent al punto di accesso è stato verificato e il I byte stanno per essere serviti. Bulk download emette un evento per documento piuttosto che uno per richiesta. Loro 400 documenti esportati" non è una risposta a "era il record di questo individuo rivelato", e le righe per-documento sono ciò che rende questo rispondebile sei anni dopo ai sensi del §164.528. L'IP client viene da X-Forwarded-For con RemoteAddr come fallback: ogni richiesta arriva attraverso Traefik, così remotoAddr da solo registrerebbe la capsula di ingresso per ogni accesso ed essere inutile per il rilevamento. Un tentativo non autenticato è attribuito ad un'organizzazione senziente piuttosto che a un — il registratore rifiuta gli eventi imperdibili, e una richiesta di documento byte senza sessione è esattamente l'evento che vale la pena mantenere. Questo si trova accanto alle righe ImgLogDownload esistenti piuttosto che sostituirle: questi sono record di imaging-domain, questo è il percorso di cross-surface una contabilità la divulgazione è assemblata da. Nota ImgLogService, che legge quelle righe, ancora ha zero chiamanti ovunque nella piattaforma.
· KlusterServices
Contiene evasione dal risolutore avatar; nota il pool di evizione Redis
SecurityService ora gestisce un fetcher il cui URL di destinazione è scelto da un non attendibile party — un record BIMI è contenuto DNS pubblicato da chiunque possieda un dominio di invio, e chiunque diventa un dominio di invio e-mail di un inquilino. Niente in questo cluster dove tale richiesta potrebbe andare: Redis risponde non autenticato il 6379, MinIO su 9000 con credenziali di root, CockroachDB su 26257, e i metadati del nodo endpoint è raggiungibile da qualsiasi capsula. Il risolutore applica i propri controlli in codice, e quelli rimangono il primario difesa perché sono l'unico strato che comprende la rilegatura DNS. Questo è il secondo, quindi un bug non può ancora raggiungere un indirizzo privato. Deliberatamente permettono-più piuttosto che consentire-list. Questi servizi giungono legittimamente padroni di casa, così le destinazioni innumerevoli sarebbe insostenibile e si romperebbe silenziosamente. Invece permette l'internet pubblico con ogni gamma privata eccetto, poi ri-permette i porti specifici in-cluster effettivamente necessari. Scoperto ai baccelli che eseguono fetches non attendibili — applicandolo namespace-wide sarebbe tagliato servizi il cui egresso non è mai stato verificato. Il cambiamento Redis è un commento e un urto di database, non un cambiamento di comportamento. Si'. registra perché la cache sender-avatar memorizza solo stringhe di metadati brevi: maxmemory 2gb con allkeys-lru evicts in tutta l'istanza, e DB 0 tiene ogni live sessione, quindi una cache di byte di massa qui firmerebbe gli utenti fuori a caso sotto pressione e essere diagnosticato male per giorni.
· kamo-internal
Mostra foto del mittente e del destinatario
La casella di posta ha reso una singola lettera su un unico gradiente condiviso per tutti, quindi ogni riga ha guardato lo stesso e il cerchio non ha portato alcuna informazione. Ora mostra il foto reale del corrispondente quando si conosce, e un monogramma a due lettere quando è non lo è. Un SenderAvatar sostituisce sette cerchi iniziali laminati a mano. Questo è importante per più della consistenza: ciascuno ha portato la propria idea di come dividere un indirizzo, e la cache di avatar è in chiave su quella divisione, quindi qualsiasi disaccordo tra la lista e il riquadro di lettura avrebbe interrotto silenziosamente il tasso di successo e mostrare la stessa persona due modi diversi su uno schermo. parseAddress è ora l'unico posto un RFC 5322 l'indirizzo è smontato. La lista e la vista del thread anche smettere di stampare intestazioni crude come "Jane Doe <jane@acme.com>" e mostrare il nome. L'identità vive nel suo negozio chiave per indirizzo, mai sugli oggetti del messaggio. Entrambe le viste elenco sondaggio ogni 30s e su ogni notifica websocket, ogni volta sostituire il loro array di messaggi all'ingrosso — qualsiasi cosa fusa su un messaggio sarebbe asciugato due volte al minuto, quindi una casella di posta idle si flicker foto a monogramma e indietro Per sempre. Chiavi per indirizzo significa anche una risposta in ritardo non può dipingere su la riga sbagliata, perché le righe non sono ciò che è in chiave. Le richieste sono a carbone in un lotto per schermo: cinquanta righe che chiedono indipendentemente sarebbero cinquanta richieste, e dato che ogni chiamata a questo servizio minte una chiave una volta prima, In realta' cento. Una miss viene riascoltata una volta dopo 45 anni, perché lato server la risoluzione è due fasi e un indirizzo sconosciuto viene risposto solo in una chiamata successiva. Conversazione ListView è ora virtualizzata. Fetches 50 righe a pagina e re-rendered tutti loro all'interno di un AnimatePresence con uno stagger per-indice su ogni sondaggio; l'aggiunta di un'immagine a ciascuno ha reso la superficie più pesante nella casella di posta. I mittenti no-reply e mailer-daemon ottengono un segno neutro piuttosto che un monogramma — un faccia accanto a un avviso di rimbalzo è attivamente fuorviante. Le foto sono decorative (alt=") perché il nome è sempre visibile testo accanto a loro, e portare referrerpolicy="no-referrer" quindi un identificatore filettato non raggiunge mai un host immagine. Ogni avatar è servito dalla nostra origine — immagini di terze parti sono catturati, convalidato e ri-hosted lato server — che è ciò che consente la nuova per-request politica img-src rimanere pinned ai domini dell'inquilino. Questa politica è costruita il proxy piuttosto che il prossimo. config perché questa piattaforma è white-labelled: a politica statica dovrebbe nominare un inquilino e rompere le immagini per il resto. Chiude anche un vettore di monitoraggio-pixel preesistente, dal Contatto. foto è un URL di testo gratuito reso direttamente in diversi <img src>. successivo/immagine non accetta più SVG remoto. Questo è stato un rischio accettabile quando l'unico SVGs erano i nostri loghi a tema; non è ora che la piattaforma ingerisce segni pubblicato in DNS di terze parti. Contatti chiamanti rilasciano il codice ORG ID = 1 e il parametro orgId query, abbinare il cambiamento backend che deriva tenancy dalla sessione.
· 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 modello di identità + org-scoped batch 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 anziché 500 quando il limite di conformità rifiuta
Due siti di chiamata di featureService.enableFeature ora capiscono il confine HIPAA: - /enable maps PhiModuleNotEccezione permessa a 403 portando la ragione, rispecchiando la gestione di AppliedModelViolationException direttamente sopra di esso. Senza questo l'ampia cattura di seguito ha trasformato un rifiuto politico deliberato in un 500. - L'anello auto-abilitato nel percorso di elenco pre-controlla con isPhiPermesso e skips. Funziona su ogni ServiceType completato mentre semplicemente *list* un caratteristiche dell'organizzazione, così gettare il primo modulo bloccato avrebbe fatto la pagina delle caratteristiche scaricabile per un inquilino PHI invece di nascondere una voce. Inerti oggi — ogni organizzazione è manigliePhi=false.
· kamo-shared-library
Eseguire il limite di conformità all'interruttore di funzionalità
Fili PhiTenantGuard in FeatureService, il singolo punto di colata attraverso il quale un il modulo è acceso per un'organizzazione. Questo è dove il confine smette di essere un oggetto politico e inizia ad essere l'applicazione. Entrambe le metà, perché o da sole lascia un divario: - abilitareFeature lancia PhiModuleNotException, quindi un inquilino PHI non può accendere un modulo Kamo non può stare dietro come socio di affari. - isFeatureEnabled(Organization, ServiceType) segnala un modulo disabilitato ogni volta l'inquilino gestisce PHI e il modulo è al di fuori del confine, qualunque sia il La riga di OrgFeature dice. La riga può legittimamente dire attiva — è stato abilitato prima che l'inquilino diventasse un inquilino PHI, e consentireFeature non funziona di nuovo Quella transizione. èPhiPermesso è il modulo non-diffamante per i chiamanti che iterare. CaratteristicaController abilita ogni funzione completata mentre *listing*, quindi un lancio sul primo modulo bloccato fallirebbe l'intera pagina delle caratteristiche piuttosto che saltare una voce. PhiServiceTypeMapping lega ServiceType a PhiModule ed è totale, con un test che fallisce la costruzione su qualsiasi valore non mappato. Quel test ha guadagnato il suo mantenimento prima run: ha catturato ServiceType.STORAGE, che una grana dell'enum aveva perso, e poi AI CHAT, DOC MANAGER, DOCS EDITOR e DOC CLASSIFIER. Senza di esso, cinque servizio i tipi sarebbero stati silenziosamente al di fuori del confine — lo stesso fallimento di omissione Il costruttore di PhiModule chiude, reintroduce uno strato su. Nuovi moduli per i tipi di servizio precedentemente non mappati, ciascuno con un esplicito disposizione: archiviazione oggetti, editor di documenti e classificatore, biglietti di supporto, HR, inventario, legale, calcolatore e feed di attività sono all'interno; clubhouse e giochi sono BLOCKED PENDING WORK sul fatto che non sono stati valutati, che è un ragione per bloccare piuttosto che assumere. Ogni organizzazione è gestiscePhi=false oggi, quindi questo non cambia comportamento in produzione. Diventa carico-portando il momento in cui il primo inquilino è contrassegnato.
· kamo-shared-library
Auto-assegnare un numero di prestito compatibile MERS alla creazione di applicazione
LoanFile.loanNumber è stato nullo su creazione (sia bianco-creato che piombo-promoto) e fissato solo quando uno steward ha digitato uno — che ha bloccato MERS MIN minting (un segmento di 10 cifre di MIN è il numero di prestito) e ogni a valle identificatore per un prestito senza titolo. AppMortgageService ora assegna un numero di prestito in entrambi i siti di creazione: un pulito 10 cifre numeriche (MinMinter strisce non cifre e ha bisogno di un segmento <=10-digit, così un numerico puro è l'unica forma sicura), unico per org tramite (org id, prestito number) indice con un riesame limitato. E FAILS OPEN a null su un striscia di collisione o un repo non disponibile — degradante al manuale precedente comportamento piuttosto che mai rompere la creazione di applicazione. Un lender-fornito il numero passa ancora attraverso il percorso di patch del file di prestito. Nessun cambiamento di schema (il colonna già esistente, indicizzata).
· kamo-signer-monorepo
Date del registratore di rimborso nella zona di tempo risolta del destinatario
SigningSession ottiene un `timeZone` richiesto (IANA id) che ESigService risolve dalla zona membro del destinatario, poi l'org, poi UTC. WelcomeScreen ora passa esplicitamente al formatore, quindi la data di scadenza non dipende né dal Impostazioni del dispositivo del visualizzatore o di default del provider UTC. Fare il campo richiesto piuttosto che facoltativo catturato il sito di costruzione di una sessione letterale (preview.ts) al momento della compilazione. Anche: - call setRequestLocale nel layout locale in modo che le traduzioni si legano all'URL segmento piuttosto che affidarsi all'intestazione middleware da solo - affermare nel Dockerfile che i messaggi/ sopravvissero `turbo prune`, e documento perché .dockerignore deve tenere .gitignore fuori dal contesto di costruzione; senza che accoppiamento prune gocce le traduzioni e le navi immagine rendering chiavi crude con nulla fallire fino a quando un firmatario apre il documento
· ESigService
Risolvere il fuso orario di visualizzazione del firmatario dal membro, poi org
Le date di firma sono state rese in qualsiasi zona in cui il client ha predefinito, quindi un 2026-08-03T05:00Z potrebbe leggere "Aug 3" a un cartello del Pacifico per il quale il collegamento è già morto la sera del 2 agosto. SigningSession ora trasporta un server-risolto IANA timeZone: il destinatario proprio Member.timezone quando sono un membro e hanno stabilito uno, altrimenti l'invio Organization.timezone, altrimenti UTC. SignerZone è un puro risolutore con test, incluso uno che afferma ogni TimezoneType mappe alla propria zonaId così un nuovo il valore enum non può spedire un RangeError lato cliente. Corregge anche SigningSession.locale, che ha emesso il nome enum ("SPANISH") piuttosto che il codice BCP-47 ("es") utilizzato nel segmento locale dell'URL di firma.
· InitializerService
Creare HANDLES PHI e PHI EFFECTIVE DATE su org
Specchi i due nuovi campi organizzativi, seguendo le OrgBrandingFieldsMigration convenzione, così le colonne sono riproducibili su qualsiasi ambiente piuttosto che solo esistente dove è avvenuto un passaggio ddl-auto. HANDLES PHI NON è NULL DEFAULT FALSE così gli inquilini esistenti sono esplicitamente fuori il limite di conformità. PHI EFFECTIVE DATE è TIMESTAMP, non TIMESTAMPTZ, perché il campo entità è un LocalDateTime. Il test è una guardia alla deriva, non un riesame: legge i nomi delle colonne dal entità per riflessione e fallisce la costruzione quando un campo entità non ha ALTER. Che cosa? è la modalità di fallimento di questa convenzione — qualcuno aggiunge un campo, dimentica il migrazione, e con ddl-auto:none nei servizi che ricopre come produzione errore di query invece di al momento della costruzione. Verificato dalla mutazione: rimuovere PHI EFFECTIVE DATE ALTER fa fallire con il messaggio attuabile, ripristinandolo diventa verde. Entrambe le colonne sono già presenti nel cluster; queste ALTER sono idempote.
· kamo-shared-library
Maniglie persistPhi e phiEffectiveDate su Organizzazione
Backs PhiTenantGuard con una colonna reale. HANDLES PHI NON è NULL DEFAULT FALSE seguire il modello, quindi ogni inquilino esistente è esplicitamente al di fuori del confine di conformità piuttosto che silenziosamente dentro di esso. PHI EFFECTIVE DATE applica una valutazione della violazione e un obbligo di conservazione finestra destra — "da quando" è la prima domanda posta dopo "che cosa è stato esposto". Converti in politica con... La guardia se stesso rimane libero dell'entità, quindi un chiamante che tiene solo una bandiera a sessione non deve caricare una riga. Sicuro da spingere: entrambe le colonne esistono già nel cluster e ciascuna delle 12 le organizzazioni leggono false. Verificato contro l'informazione schema prima dell'atterraggio — handles phi boolean NON NULL DEFAULT falso, phi efficace date timestamp NULL. Vedere la migrazione KamoInitializer per la forma riproducibile.
· kamo-shared-library
PhiTenantGuard e il limite di conformità del modulo
La chiave del limite di conformità HIPAA: che moduli un inquilino può raggiungere, dato se l'inquilino gestisce informazioni protette sulla salute. Tutto a valle chiavi fuori di esso — quali integrazioni sono disabilitate, se MFA è costretto, quali sessioni ottenere il timeout più rigoroso, che cosa la spina dorsale di revisione deve coprire. Non trasporta nessuna dipendenza dagli schemi. Un precedente tentativo ha appeso questo nuovo Organization.handlesPhi colonna e doveva essere ritorto: servizio cloni CI in comune-libraria principale a tempo di costruzione mentre KamoInitializer è gestito a mano, quindi un colonna entità su interruzioni principali il servizio successivo dispiegare fino a quando qualcuno migra. Decoupling della politica dall'entità significa che atterra e può essere applicato ora, e la colonna diventa un passo indipendente. Tre proprietà il design è costruito intorno: - Falli chiusi. Il costruttore di PhiModule richiede un PhiDisposition e un motivo, così un modulo aggiunto in seguito non può ereditare il permesso da omissione — non sarà compilazione senza decisione, e qualsiasi altra cosa che non nega PERMITTED. Questo è la modalità di fallimento un limite di conformità deve sopravvivere: un modulo navi, nessuno pensa al PHI, una clinica inizia a usarlo. - Un inquilino irrisolto non è un inquilino sicuro. La guardia prende un tri-stato PhiTenantStatus piuttosto che un booleano, quindi "Non potevo caricare l'organizzazione" non può essere scritto come "questa organizzazione non è PHI". Con un boolean, il percorso di errore èPermesso(falso, ...) — facile da raggiungere per, impossibile da individuare in recensione. UNKNOWN nega tutto. - Niente da elevare. La decisione è una pura funzione di (status, modulo) senza chiamante, sessione, giusto o Dio-mode parametro. "Il nostro staff di supporto può girare off" è la risposta che fallisce la revisione di un'entità coperta. Le disposizioni seguono l'analisi dello scoping in kamo-internal Non e' vero. Permesso: CRM, contatti, cassaforte documentale, email self-hosted, e-sign, note, compiti, calendario, chat interna, l'incluster trascrizione / stack di traduzione, MLOS. Bloccato senza BAA: sociale Messaggi, Canva, sincronizzazione e-commerce, gateway MCP. Bloccato in attesa di lavoro nominato: il router AI, ricerca RAG, registrazioni delle riunioni, registrazioni VOIP, campagne di marketing, esportazione di analisi. Niente chiama ancora la guardia, quindi questo e' inerte finche' non e' collegato.
· InitializerService
Creare HANDLES PHI e PHI EFFECTIVE DATE su org
Specchi i due nuovi campi sull'entità dell'organizzazione in kamo-shared-library, a seguito della convenzione OrgBrandingFieldsMigration. HANDLES PHI NON è NULL DEFAULT FALSE così ogni inquilino esistente è esplicitamente al di fuori della conformità piuttosto che silenziosamente dentro di esso. PHI EFFECTIVE DATE è TIMESTAMP, non TIMESTAMPTZ, perché il campo entità è un LocalDateTime. SEQUENCING — questo deve funzionare prima del prossimo servizio dispiegare. Perno servizi kamo-shared-library in una versione fissa che viene ripubblicata in luogo, quindi il primo servizio per ricostruire dopo che la modifica della libreria inizia selezionando queste colonne, e con ddl-auto:none nei servizi che fallisce fino a quando questo corridore non ha eseguito. Il test di accompagnamento è una guardia alla deriva, non un riesame: legge la colonna nomi fuori l'entità da riflessione e fallisce la costruzione se un campo entità non ha Sempre. Questo è il modo di fallimento di tutta la convenzione — qualcuno aggiunge un campo, dimentica la migrazione, e superfici come un errore di query di produzione invece di costruire il tempo. Verificato dalla mutazione: rimuovendo il PHI EFFECTIVE DATE ALTER lo rende fallire con il messaggio attuabile, ripristinarlo diventa verde.
· ESigService
Emettere il segmento locale del firmatario nei link di firma
Estrae la build duplicata byte-for-byteSignLink in SignLinkBuilder e aggiunge un segmento locale derivato da Organization.language, cadendo torna in inglese per i quattro valori LanguageType senza traduzioni. Richiede il kamo-signer /{locale}/ routing da distribuire prima.
· kamo-shared-library
Aggiungere le manigliePhi inquilino bandiera e PhiTenantGuard
La chiave del limite di conformità HIPAA: una bandiera per-organizzazione più una guardia decidere quali moduli che l'organizzazione può raggiungere. Tutto a valle le chiavi — quali integrazioni sono disabilitate, se l'AMF è costretto, che sessioni ottenere il timeout più rigoroso, quello che la spina dorsale di revisione deve coprire. L'organizzazione guadagna HANDLES PHI (BOOL DEFAULT FALSE) e PHI EFFECTIVE DATE, seguendo il modello esistente. Predefinisce il falso, così questo è inerte per il mutuo esistente e CRM libro. Tre proprietà il design è costruito intorno: - Falli chiusi. Il costruttore di PhiModule richiede un PhiDisposition e un motivo, così un modulo aggiunto in seguito non può ereditare il permesso da omissione — non sarà compilazione senza decisione, e qualsiasi altra cosa che non nega PERMITTED. Questo è la modalità di fallimento specifica che un limite di conformità deve sopravvivere: un modulo navi, Nessuno pensa al PHI, una clinica inizia a usarlo. - Non bypassabile. La decisione è una pura funzione di (organizzazione, modulo) con nessun parametro caller, session, right o God-mode, quindi non c'è nulla Eleva. "Il nostro personale di supporto può spegnerlo" è la risposta che fallisce un coperto La recensione dell'entità. - Le negazioni portano un motivo, perché un nudo "negato" non è utilizzabile da un supporto ingegnere o cliente. Le disposizioni seguono l'analisi dello scoping in kamo-internal Non e' vero. Permesso oggi: CRM, contatti, documento caveau, email self-hosted, e-sign, note, compiti, calendario, chat interna, in-cluster trascrizione / stack di traduzione, e MLOS. Bloccato senza ottenibile BAA: messaggistica sociale, Canva, sincronizzazione e-commerce, gateway MCP. Bloccato in attesa di nome lavoro: router AI, ricerca RAG, registrazioni di riunione, registrazioni VOIP, marketing campagne, esportazione di analisi. Nota schema: HANDLES PHI e PHI EFFECTIVE DATE si materializzano sul prossimo KamoInitializer eseguire, dopo CI pubblica questa costruzione di biblioteca condivisa. Niente legge la bandiera, quindi l'ordine è sicuro.

Il tuo universo ti sta aspettando.
Aggiungi i prodotti quando sei pronto: nessuna migrazione, nessun nuovo login, nessuna richiesta di pagamento.
Gratis per sempre — nessuna carta di credito richiesta