மாற்று பதிவேடு
என்ன புதிது.
அது கப்பல்கள் போல், தளத்தில் ஒவ்வொரு மேம்பாடு.
· kamo-internal
Hide empty departments, move the status filter into the Directory
- Departments with no members no longer render. The by-department endpoint returns every department in the org including empty ones, so the roster was showing sections that could never contain anyone. The per-department "no members" placeholder goes with them, being unreachable now. - The All/Online/Offline filter moves from the panel-wide header down beneath the tab strip, so it sits in the Directory's own area with the roster it filters instead of appearing to filter the whole panel (it never applied to Chats or Mobile Access). - The empty state only suggests adjusting the search/filter when one is actually applied; an org with no members has nothing to adjust.
· kamo-internal
Rebuild the slideout as one continuous surface
Member rows now form the department body directly — no card-on-card nesting — and departments run full-bleed with sticky bands, reclaiming ~30px of row width and cutting the chrome before the first member from 228px to ~170px. Layout - Delete the card-in-card stack: rows are flat, hairline-separated, full width. - Sticky department band (ink on an 8% primary tint, never white-on-primary) carrying the disclosure chevron, a live presence meter, an online/total fraction and a Select toggle. Drops the identical per-department Group icon. - One background instead of three stacked gradients; one scroll-edge cue. - Status is carried by a dot on the avatar plus a word only when it adds information, replacing five per-row encodings of the same value. Behavior - Row click opens the 1:1 chat. It previously did nothing: MemberList never passed onClick, so a fully-styled clickable row was inert. - Restore the All/Online/Offline filter, which only existed inside MemberList's header — a branch the live panel never rendered (showHeader={false}), leaving statusFilter pinned to 'all'. Lifted into NavTopPanel beside the search field. - Multi-select gains real affordances: a Select toggle per band, ctrl/cmd-click, and touch long-press; a selection bar rises from the first selection. - One hoisted MeetingDialog instead of one mounted per row, and ownMemberId is threaded down instead of every row fetching /api/user-info. Motion - MotionConfig reducedMotion="user", plus prefers-reduced-motion guards on the CSS keyframes it cannot reach. - Staggered section entrance, framer height-collapse with a rotating chevron, layout-spring row reflow, odometer counts, unread pop, directional tab slides. Accessibility - Real disclosure semantics, ul/li rows, one stretched row button with sibling (not nested) actions, focus trap with focus restore, arrow-key navigation, a polite live region for selection, and >=24px targets. - The panel openers in NavTop were non-focusable Boxes; they are buttons now. - Chats rows were click-only divs with no keyboard path; they are list items with real buttons and accessible names. Theming - New --on-primary token so white text on a light tenant primary is fixable. - Shared availability palette ends the NavTop/MemberItem hue drift, and the hairlines stop referencing --color-primary-rgb, a variable never defined. - Relative timestamps go through Intl on the app locale instead of emitting hardcoded English, and last-active labels tick again via one shared 30s clock.
· SecurityService
Detection and alerting over the record-access audit trail
phi_access_log has been accumulating evidence nobody looks at. That satisfies §164.312(b) — the trail exists — and satisfies nothing about §164.308(a)(1)(ii)(D), which asks for review, or §164.400-414, whose clock cannot start until somebody notices. This is the thing that notices. Five detectors over the closed hour, per actor: BULK_EXPORT 250 distinct records exported/downloaded/disclosed MASS_READ 200 distinct records read REPEATED_DENIALS 10 refused attempts (events — a refusal names no record) OFF_HOURS_ACCESS 20 distinct records outside the TENANT's own week PLATFORM_STAFF_ACCESS 1 — a covered entity is told regardless of justification Counts are DISTINCT records, not events: PhiAccessAuditor emits one LIST row per grid row, so a member reloading the same page forty times is 2,000 events and 50 individuals — and "how many individuals" is the number a breach assessment is made of. Rationale for every default is on PhiDetectionSettings; all of them are configmap-tunable, because a detector that fires constantly gets muted and a muted detector still reads as coverage. Off-hours is judged in the tenant's zone via its existing OFF_HOURS_ACCESS rule (business hours the Security screen already collects) falling back to Organization.timezone. occurred_at is UTC, so a UTC-anchored check would page a Sydney tenant every working morning and never page a New York one. Delivery reuses what exists: EmailTemplateServiceClient -> EmailService /api/email/templates/send with the new PHI_ACCESS_ALERT canonical key, to the addresses the org already registered in org_suspicious_detection_rules .notification_emails. A tenant that configured none gets the review log and nothing else — mailing a guessed address would disclose that a named workforce member is under suspicion. Every finding is logged at a dedicated PHI-DETECTION logger; only notification is throttled (6h per detector/tenant/ actor, in the Redis the login-anomaly rules already use). Alerts carry identifiers and counts, never a record. Two deliberate structural choices: - The aggregates run through an EntityManager, not Spring Data @Query. Spring Data validates a declared query by creating it at repository bootstrap, so a mistake there fails context startup — and a SecurityService that will not start takes every login with it, as on 2026-08-03. Here the worst case is a sweep that logs and retries next hour. It also removes this package from the @EnableJpaRepositories question entirely: no repository bean, nothing to forget. PhiDetectionQueryTest checks every property path reflectively since no test in this service can boot a context. - Every query is constrained by organization id first. phi_access_log's only usable index is (ORGANIZATION_ID, OCCURRED_AT); filtering on time alone full-scans an append-only table that only ever grows. Scheduling: this joins taskScheduler (4 threads, sched-), which seven sweeps already share. It does NOT touch translationExecutor — the 3-thread/500-queue @Async pool observed saturated — and submits to no executor at all. Hourly, five grouped aggregates per tenant batch, no network I/O inside the scan, and a reentrancy latch so a slow run skips the next tick rather than stacking behind it and starving the pool it shares. No entity or column was added or changed.
· EmailService
Preview variables for PHI_ACCESS_ALERT
Without a sampleVariables arm the admin Email Templates preview renders every placeholder blank, so whoever is asked to approve the wording of a security alert sees an empty table. The sample models identifiers and counts only — the same shape the real alert carries, because the real alert never carries a record.
· kamo-shared-library
PHI_ACCESS_ALERT canonical template
SecurityService's hourly review of phi_access_log needs somewhere to send what it finds. Rather than invent a channel it uses the existing transactional-email rail, which means a canonical key: absent from CANONICAL_KEYS a template can never be seeded, never self-heals, and every send returns a permanent 404 — an alerting control that looks like coverage and delivers nothing. The template carries identifiers, counts and a time window only. A detection artefact that reproduces what it detected has relocated the PHI into an inbox with no retention policy, so there is deliberately no placeholder for a record. PhiAccessAlertTemplateTest pins that, and pins the two silent failure modes: a placeholder nothing supplies renders as an empty string with only a WARN, and the renderer does no HTML escaping, so anything reaching the body uses the escaped twin. EmailService must redeploy with this shared-lib before SecurityService starts sending; until then the send is caught and the finding stays review-log only.
· kamo-internal
Mirror the lead minimum-necessary mask on the client (§164.502(b))
SecurityService now withholds custom-form answers from a caller who is not the lead's assignee (and, in a handlesPhi tenant, even from a holder of right 161). That mask is the enforcement; this is not. Two paths still bypass it: - LeadRealtimePublisher puts a FULL, unmasked LeadDTO on security.leads.rt.{orgId} — one payload fanned out to every subscriber in the org, with no per-recipient projection possible. Until that event carries identifiers only and the client refetches, app/lib/leads/leadFieldMask.ts is the only thing between a colleague's intake answers and every open lead pane in the tenant. LeadViewClient now runs incoming realtime payloads through it before any of it reaches state. - Rendering: a withheld form and a never-filled-in one arrive looking identical, so the decision has to be recomputed, not inferred. The custom-form pane drops its Edit affordance when answers are restricted — saving a blank form would PUT {} over the real answers, which the server now 403s anyway. Organization.handlesPhi is delivered on the existing column -> DTO -> OrgContext path (the org entity already serialises it; the zod schema and Organization.fromJSON allow-list were the missing links), not a second channel. No new user-visible copy: the explanatory "restricted" label needs a key in the translation-dictionary repo and is deliberately left for that change.
· SecurityService
Minimum-necessary masking of lead custom-form answers (§164.502(b))
The platform's only field mask was **************** applied by LeadController.getLeadById to a caller who is neither god nor the assignee and lacks VIEW_LEAD_CONTACT_INFO_OTHERS (161). It covers e-mail, phones, date of birth and the street parts of the mailing/property addresses — and nothing else. customFormData rode straight through it, which is the one place on the platform a healthcare tenant will actually put intake and symptom questions. LeadFieldMask now owns the decision and the projection: - any org: custom-form answers ride with the contact mask. If you may not see the lead's e-mail you may not see what it answered. - handlesPhi tenants: answers are withheld from anyone the lead is not assigned to even WITH right 161. That right is scoped, by its own name, to contact information; a symptom answer is not contact information. The form title goes too — "Diabetes Intake Questionnaire" discloses the condition on its own. The form entry survives masking carrying only its id, so the UI can say "restricted" rather than draw a blank questionnaire. An org that will not load is treated as PHI-handling, matching PhiTenantStatus's UNKNOWN-denies posture. PUT /leads/{id} gains the symmetric write gate. The form editor PUTs the whole fieldValues map, so without it a member shown a blank questionnaire who pressed Save would have replaced the real answers with {} — the read mask would have become a silent delete. Static utility, no stereotype and no repository: the decision is pure and the caller already holds the org.
· kamo-internal
PHI access audit viewer — org review and per-record accounting
The trail was being written and nothing could read it. This is the screen half: an org-wide access review under Settings → Security, and a "who has accessed this record" dialog opened from the lead itself, which is the shape a §164.528 accounting request actually arrives in — an individual asking about their file, not someone with time to filter a global log. Three things here are not cosmetic. Timestamps are absolute and always name the zone they were rendered in, with a one-click switch to UTC. This evidence is read months later by someone who was not present; a bare wall-clock read from another zone silently misplaces an access by hours, which is the difference between "during the shift" and "at 3am". Relative times are excluded outright — they drift every time the page is opened. Denied attempts and platform-staff access are marked on the row (tint, left rule, leading icon) rather than left in a column the eye skips, because a compliance officer scans hundreds of rows for the one that should not be there. Ordinary reads are deliberately plain: decorating everything is the same as decorating nothing. Denied outranks platform-staff when a row is both — Kamo staff being refused is the most interesting line the screen can show. Export is gated on EXPORT_ACCESS_LOGS separately from the view right, and a 503 from the upstream is surfaced as "nothing was disclosed" rather than smoothed into a generic error. EXPORT is a fail-closed audit kind: the refusal means the disclosure could not be recorded, which is a correct outcome the officer needs to see. Query building, timestamp formatting and row classification live in app/lib so vitest can reach them; the components are assembly. A 403 never falls through to an empty grid — "nobody has ever touched this record" is the most dangerous wrong answer this screen can give.
· kamo-internal
Retitle member page to Profile, shorten tab labels, add tab icons
The member settings page header becomes "My {memberLabel} Profile" (or "{memberLabel} Profile" for another member) and the nav-top link becomes My Profile. Tab labels shorten — Mailbox(s), Phone, Audio & Video, Permissions, Activity — while the phone and roles helpTips keep their descriptive headings via new **************** keys. Every tab now carries an icon in the tab bar; tab query-param ids are unchanged so deep links keep working.
· kamo-internal
Tone slider — whitewashed to full color to blacked out
A per-member backgroundTone ui-preference (0 white / 50 full color / 100 black) rendered as a wash above the readability scrim in both the console slideshow layer and the editor's WYSIWYG stage. Slider lives in the Stage's media chrome with live drag preview, center snap, and persist-on-release.
· kamo-internal
Launcher cards and hero use the org's brand color instead of per-module accents
The home launchpad's option cards, drag overlay, and first-org hero now take their accent from var(--color-primary) (the org's dynamically set theme color) rather than the fixed multi-color --accent-* family map.
· kamo-internal
Break-glass god mode — reason prompt, countdown, standing indicator
God mode had no UI at all in kamo-internal: a localStorage boolean that a mount effect mirrored into ***, re-asserted on every new tab forever. That made the browser the authority on whether platform staff were elevated, and made elevation something you could be in without ever deciding to be. - GodModeJustificationDialog prompts for the reason BEFORE elevating. A reason collected afterwards is a reason written to fit what was already done. The copy states plainly what the operator is about to see and that their name goes on it. - GodModeIndicator is a full-width bottom bar plus a red frame around the whole viewport, with a live countdown and "End now". Someone who forgets they are in god mode is the exact scenario the control exists to prevent, so it is not a subtle badge — but it is pointer-events:none outside the bar and sits outside layout flow, so it never intercepts a click or reflows a screen. - Both are mounted once in the root layout, next to AutomaticLogoff, so the indicator is present wherever the operator ends up rather than on the screens someone remembered. Renders nothing unless the session is god-ELIGIBLE. - The server owns the state. app/lib/godMode holds a single coalesced store; the client only ever reads the expiry SecurityService stamped and can never extend it. setIsGodMode is gone — elevating takes a justification, so it cannot be a setter. - The status poll doubles as the durable clear: GET /session/god-mode is where SecurityService writes an elapsed elevation out of Redis for the services that read the session directly. usePlatformAdminGuard now waits for the god status for god-eligible sessions. Access used to be readable synchronously from localStorage; without the wait an operator with an open window would be bounced to /settings in the gap. Strings are in **************** (pushed first, per the build's clone order). npx vitest run: 329 passed (32 files), 17 of them new. npx tsc --noEmit: clean.
· SecurityService
Read the PHI access trail — §164.528 accounting and activity review
The trail has been written for a while and nothing could read it, which left two obligations unmet: an accounting of disclosures for one individual's record (§164.528) and the periodic information system activity review (§164.308(a)(1)(ii)(D)). Both are queries. This adds the query surface. Authorization is the feature, not the grid. The organization is taken from the session and from nowhere else — there is deliberately no orgId parameter on any handler, and PhiAuditQueryService is a single choke point that adds the tenant predicate before it even consults the filter. Four sibling query methods would have been four chances to forget it. Scoping is a WHERE clause rather than a post-filter, because filtering a returned page would leave totalElements counting rows the caller may not see. The read side gets its own repository interface. The shared PhiAccessLogRepository finders are deliberately cross-tenant — correct for a platform tool, a disclosure if handed to a customer screen — so the screen gets an interface whose only entry point takes a Specification. It lives under com.kamo.SecurityService, which the existing @EnableJpaRepositories entry already covers, so no scan change is needed and none can be forgotten. Rights are VIEW_ACCESS_LOGS (29) and EXPORT_ACCESS_LOGS (32) — the pair that already gates the authentication log, so anyone who administers security today can use this on day one. A new right id would have shipped the screen dead: it needs a KamoInitializer run and a grant before any member holds one. Reading the trail is itself recorded, as REPORT/SEARCH for a read and REPORT/EXPORT for a copy, including refused attempts. The search criteria are not recorded — a filter carries member and record ids, and a trail that stores what was searched for accumulates the identifiers it exists to protect. Everything is bounded: page size clamps to 200, an export caps at 10,000 rows. A six-year trail is millions of rows and the capture layer writes one row per grid row per load, so an unclamped size is a reachable OOM rather than a theoretical one. Timestamps leave as explicit UTC instants. The column is zone-less wall-clock, and this evidence is read months later by someone who was not there.
· kamo-internal
MFA enrollment screen and the god-mode PHI boundary control
Two settings surfaces for backends that had none. MFA enrollment (settings → my profile → Sign-in Security, self only). SecurityService's /api/security/mfa endpoints have been complete and entirely unreachable, so MFA existed and nobody on the platform could turn it on. The tab is self-only because those endpoints resolve the user from the caller's own session and take no user parameter — an admin opening someone else's profile would be enrolling their own authenticator on that screen. The QR code uses qrcode.react, already a dependency; no package was added. The secret is shown next to it regardless, because an authenticator app on the same device as the browser cannot scan the screen it is covering. Two failure paths get first-class handling rather than a generic error. A 409 means a confirmed factor already exists and says so, with the remedy (an administrator reset) — telling the user to retry would send them back into the one action that cannot succeed. A wrong confirmation code keeps the pending enrollment: the secret is displayed once, so discarding it would mean deleting the authenticator entry and rescanning, and the server's pending enrollment would still be there, so the retry would come back 409 and strand them. Recovery codes are shown once and cannot be reissued. The dialog cannot be dismissed by escape, backdrop or a close button; Done unlocks only after the codes have been copied or downloaded AND the warning acknowledged. Both are required: a checkbox alone is clicked reflexively, and a clipboard alone is overwritten by the next copy — and nothing looks wrong until the day the user loses their phone. PHI boundary (settings → security → Health Data, god-eligible only). The tab is visible to god-eligible operators and the control inside requires god mode actually switched on, which SecurityService re-checks; eligibility alone is not activation. It never flips without first listing the modules that stop working — that list comes from the server, derived from PhiModule through PhiTenantGuard, so it cannot drift from what is enforced. A 503 is surfaced as "the change was not applied because it could not be recorded", because that is what happened. Decision logic lives in app/lib/security so vitest can reach it; the components hold rendering only. Verified: npx vitest run — 329 tests, 32 files, all passing (43 of them new). npx tsc --noEmit reports only three errors in **************** an untracked file from another session in this tree, in none of the files here.
· SecurityService
Endpoint to read and move an organization's HIPAA boundary
Organization.handlesPhi has existed as a column with no way to set it, so PhiTenantGuard has been enforcing a flag nobody could turn on. This is the missing half: GET/PUT /api/security/phi/tenant. Authority is god eligibility AND an active godModeActive toggle, never one. Eligibility alone is what left the platform's god gates dead until 2026-07-22; here it would let any god-eligible operator switch off a live tenant's modules with one unremarkable click. The target is always the caller's own session org — there is no organizationId parameter, so no crafted request can reach a tenant the caller is not already inside. phiEffectiveDate is stamped on first enable and never rewritten, including on a later disable/re-enable. It scopes the breach-assessment and retention windows, so moving it forward would quietly shrink the period under review, and an audit date the application can advance is not evidence. The audit row is written BEFORE the flag moves, and CONFIGURE fails closed, so a trail that cannot be written refuses the change and returns 503 rather than leaving a boundary that moved with no record of when. Denied attempts are recorded too. Every row carries identifiers and the transition only, never content. blockedModules is derived from PhiModule through PhiTenantGuard rather than listed, so the warning the UI shows cannot drift from what the guard enforces. No new repository package: OrganizationRepository is already in this service's @EnableJpaRepositories list, so there is nothing to add alongside this bean. Verified: 298 tests, 1 error — **************** EntityManager NPE, which is pre-existing (baseline at 7968a21: 281 tests, same 1 error). RepositoryScanCoverageTest and **************** both pass.
· kamo-internal
Rebuild the quote form as a guided flow, add an output preview
The details tab was a flat stack of outlined boxes that gave every field the same weight, so nothing signalled which inputs actually change what the customer sees. It now reads as the four decisions a quote is made of — what it is for, what is being sold, how it is adjusted, what the customer reads — each with a one-line reason it matters, and the running total sits in a sticky rail rather than buried at the bottom. Motion carries meaning rather than decoration: sections arrive in sequence, a conditional field expands from the control that revealed it, the pricing area cross-fades when its shape changes, and a total counts to its new value so a mistyped unit price is noticed instead of silently absorbed. All of it respects prefers-reduced-motion. The new Preview action renders the finished document — letterhead, options or table, totals, terms — and prints it on its own. It projects the CURRENT editor state, not the saved record, because the moment you need to see the finished quote is while deciding whether to send it. Branding comes from the server's own resolution so the previewed letterhead is the one the customer gets, and the option arithmetic is imported from the editor rather than reimplemented.
· kamo-shared-library
Audit vocabulary for a change to the compliance boundary itself
Adds PhiResourceType.ORGANIZATION and PhiAccessKind.CONFIGURE so that turning Organization.handlesPhi on or off can be recorded as what it is. Every existing resource type names a person's record. Filing a change of the tenant's compliance posture under one of them would put a lie in the §164.528 accounting of disclosures, and "when did this tenant become a covered entity's business associate" is the question the rest of the trail is scoped against — it needs its own row shape, not a borrowed one. CONFIGURE fails closed for a different reason from the bulk kinds: nothing is disclosed, so refusing costs the operator a retry, while a boundary that moved with no record of when is the state a §164.402 assessment cannot reconstruct. No schema implication. phi_access_log.RESOURCE_TYPE and ACCESS_KIND are plain VARCHARs created by PhiAccessLogTableMigration with no enum whitelist CHECK, so neither constant needs a KamoInitializer run before it can be inserted, and no entity or @Column is touched. Verified no exhaustive switch over either enum exists in any service.
· SecurityService
Serve the customer-facing view to staff for preview
GET /{uid}/preview returns the same PublicQuoteView the share link serves, from the same QuotePresentation resolution, so a previewed letterhead cannot differ from a sent one. It deliberately does not mint a token, count a view or record an event — looking at your own draft is not customer engagement, and letting it into the activity trail would make the view counter useless as a signal.
· ConversionService
Scan uploads for malware at ingest (§164.308(a)(5)(ii)(B))
The document vault accepted arbitrary uploads with no malware scanning anywhere on the platform, and it is multi-tenant — a file one org uploads is opened by another org's users through the viewer, the WOPI editor and the binder. ClamAV over INSTREAM, spoken directly to clamd (no client library; the protocol is a socket and a length prefix). The gate runs in ImagingUploadController on /upload and /templates/upload, before the member lookup and before any bytes reach MinIO — scanning after storage leaves the object retrievable for the width of the race. Scanner-unreachable behaviour is FAIL OPEN by default, and that is a decision rather than an accident. clamd is not deployed yet: this is the application half, and the deployment is a separate operational act (KlusterServices auto-applies on push and is not this change's to touch). Shipping fail-closed would take document upload down for every tenant the moment the image rolls, with no staging in which that would have been caught. Fail-open here is not "no protection", it is the status quo of the last several years, now logged at ERROR every time it happens. Every knob — including fail-closed — lives in kamowsconversion-config, so tightening it once clamd is stable is a configmap edit and a restart, not a release. A circuit breaker bounds the cost of an absent scanner: after 3 consecutive failures the guard stops dialling for 60s, so uploads do not each pay a connect timeout for a control that is effectively off. On detection: 422 naming the signature (not a 5xx — retrying will not help), the bytes are preserved in a dedicated quarantine bucket rather than dropped, and the rejection is recorded through PhiAccessRecorder. The audit row carries identifiers only; the uploaded filename stays out of it because filenames here routinely contain borrower names. PhiAccessKind has no UPLOAD member and adding one would alter an @Enumerated(STRING) column's constraint in the shared library, so DOWNLOAD + permitted=false is used and the reason is documented at the call site. Also switches this service's PHI trail to the durable phi_access_log writer. The matching com.kamo.z.shared.phi.audit entry is in the same commit's @EnableJpaRepositories list, and PhiAuditRepositoryScanTest fails the build if the two ever drift — that pairing is what took every login down on 2026-08-03. Retro-scan (POST **************** sweeps documents already in the vault, tenant-scoped and paged. It reports and preserves, never blocks or deletes: making a stored object unretrievable needs a persisted quarantine flag, i.e. a shared-library column applied by a hand-run initializer while service CI builds against main, and deleting on a false positive would destroy a tenant's record with no recovery. Tests: 36 new. Pre-existing **************** at_or_below_1080 still fails on a clean tree; untouched. Not covered, deliberately: RecordingIngestController (internal-auth Asterisk push) and DocsService's WOPI PutFile are second ingest paths that want the same gate, but the scanner lives in this service and WOPI has no *** token to attribute an audit row to. Both are follow-ups, not oversights.
· SecurityService
Break-glass god mode — justification + 30-minute time-box
God mode activated silently and lasted as long as the session. That is the control a covered entity's security review pushes hardest on, because "your staff can read our patient data" is always asked and an unjustified, unbounded elevation has no answer to it. - POST /api/security/session/god-mode now requires a typed justification to step UP. Stepping DOWN never does — a control that gates the safe action is a control that keeps operators elevated. - Both edges are recorded through PhiAccessRecorder with the justification and platformStaff(true). Identifiers only; the justification is the operator's own words about why, never anything they went on to read. - The window is 30 minutes and NON-SLIDING. The expiry is an absolute instant in the session body, not a TTL, so the sliding session refresh cannot extend it, and re-asserting from a second tab returns the original expiry rather than buying another window. - KSessionService.getSession masks an elapsed flag, so all ~10 existing `session.get("godModeActive")` gates honour the time-box without changing. GET /session/god-mode writes the clear through to Redis (doing it inside getSession would recurse) and is what the UI polls for the countdown. Deliberately fails OPEN on the audit write: god mode is how support fixes production, and refusing an elevation because phi_access_log blinked is worse than a row the recorder has already logged loudly. No entity or @Column changed. PhiResourceType gains no constant either — it is @Enumerated(STRING) with a CREATE TABLE value whitelist that ddl-auto:update never widens, so a new constant would fail every insert until KamoInitializer was run by hand. The elevation is recorded against the operator's own MEMBER record with a **************** prefix instead.
· kamo-internal
Reorder steward tabs; fold Identifiers into a pipeline find-loan popover
Tab order is now Pipeline, Documents, Underwriting, Pricing, Capital & Risk, Governance, Ingestion, Catalog — Pipeline becomes the default landing screen. The standalone Identifiers tab is gone; its lookup lives in the Pipeline toolbar as a compact Find-loan popover: search by loan UID (default) or resolve any external identifier (MERS MIN, agency case #, e-sign envelope, etc.) to its loan, with a rich result card, the loan's identifier ledger, lifecycle history, and one-click open / show-in-pipeline.
· kamo-signer-monorepo
Compare options on the public quote page
Options render as selectable cards rather than rows, because they are choices and not additions. The period total is the headline; the monthly equivalent sits beside it as the figure that makes different billing terms comparable, and a savings badge appears only when the difference is worth naming. The flat table and the combined totals block are both suppressed when options are present — a grand total across mutually exclusive alternatives is a number nobody can pay. Print rules keep the cards side by side so a printed quote still compares.
· kamo-internal
Author multi-option quotes from the editor
A quote is either a flat price sheet or a set of alternatives, so the editor offers a mode toggle rather than letting both exist at once — sending both would show the customer a price they cannot buy. Switching to options carries the existing lines into the first option instead of discarding typed work. Each option gets its own line items, billing term, commitment length, discount and setup fee, with a live preview of the period total, monthly equivalent, contract value and savings against the baseline. The preview mirrors QuoteOptionTotals; the server recomputes on save and its figures win. Seat count lives on the quote, so 40 -> 60 reprices every option at once.
· SecurityService
Accept an option on the public quote endpoint
Multi-option quotes cannot be accepted blind — the request now carries the chosen optionUid and the service refuses acceptance without one.
· kamo-shared-library
Multi-option quotes with per-option comparison totals
A quote can now present several mutually-exclusive ways to buy — monthly, annual, three-year — instead of forcing three separate documents that drift apart the moment the seat count changes. - QuoteOption owns its own line items and totals; siblings are never summed. - QuoteOptionTotals normalises each option to a monthly equivalent, which is the only basis on which different billing terms can be compared, plus a contract total and savings against the flagged baseline. - Billing term and commitment length stay separate: paying annually is not the same commitment as a three-year term billed annually. - Quote.quantity is a shared driver; a line opting in with usesQuoteQuantity reprices across every option at once. QuoteTotals honours it identically on flat sheets so a line cannot price differently inside and outside an option. - Accepting a multi-option quote requires naming the option; siblings close as NOT_SELECTED and acceptedOptionUid records the choice. QuoteTotalsTest 15, QuoteOptionTotalsTest 11, QuoteShareTokenTest 9 — all pass.
· kamo-internal
Delivery coverage, pricing sign-off, large-deposit panels
Replaces the final Coming-soon chips (Hedge & coverage, Pricing sign-off, Large-deposit) with live panels wired to the new steward proxies.
· SecurityService
Steward proxies for coverage, pricing sign-off, large-deposit
Operator-gated, session-stamped org+member, fail-closed ownership (no cross-org existence oracle). New endpoints baselined in the unauthenticated-endpoint ratchet.
· kamo-shared-library
WORM pricing sign-off entity (mlos_pricing_signoff)
Append-only steward APPROVE/REJECT judgement on a frozen priced result. @PreUpdate throws (a reversal is a new row). Table + 4 indexes (org, run, result, loan) applied to prod CockroachDB via KamoInitializer.
· kamo-internal
Steward console round 2 — exception queues, governance, MERS recon
- Capital + Underwriting: StewardExceptionQueue (shared panel) wired to the real steward-tasks endpoint — lock exceptions **************** and guideline exceptions **************** honest empty states. - Governance: CalibrationReliabilityPanel (predicted vs observed per segment) and PullThroughFunnelPanel (real stage distribution, labelled a snapshot not a cohort conversion). Replaced the two coming-soon chips. - Documents: MERS reconciliation panel over the real internal-ledger recon read. Real endpoints throughout; plain-string copy (i18n follow-up); tsc --noEmit clean.
· SecurityService
Gateway relays for steward-tasks, calibration/funnel, MERS reconciliation
god-eligible, org server-stamped GET relays: **************** **************** MlosDocOrchestration **************** (fail-closed org filter, drops non-owned package rows + re-derives the tally). All four baselined in the unguarded-endpoints ratchet (delegated gate() guard, like their siblings).

உங்கள் உலகம் காத்திருக்கிறது.
நிமிடங்களில் இலவசமாகத் தொடங்கவும். நீங்கள் தயாராக இருக்கும் போது பொருட்களை சேர்க்கவும் - எந்த மாற்றமும் இல்லை, புதிய உள்நுழைவுகள் இல்லை.
எப்பொழுதும் இலவசம் — கடன் அட்டை தேவையில்லை