Changelog
What's new.
Every improvement to the platform, as it ships.
· MediaService
The internal DM path goes through chat policy and the AI turn guard, and every chat line says who wrote it (KamoCollab CP00 T7)
POST **************** now requires source MEMBER or SYSTEM (else 400 SOURCE_REQUIRED). A MEMBER line is checked by chat policy as a member's own send (open-then-send when no DM exists; the existing DM's guid for the ONE_WAY reply window), refused 403 CHAT_POLICY, and an AI sender's line is counted by the turn guard (429 AI_TURN_LIMIT). The AI's line to its own human supervisor is never counted, and passes the rules while protectManagerLine is on. A SYSTEM line (calendar notices) skips both. The handler opens no transaction: the turn limit is read first, then InternalDirectMessageWriter (@Transactional @RetryOnDbConflict) opens the DM, admits the turn, writes the line and prepares the fan-out; the announcement follows the commit. media_objs.source is written: MEMBER by member sends and Exec2Exec posts, SYSTEM by internal SYSTEM lines and the Exec2Exec welcome, SERVICE by alerts. A SYSTEM or SERVICE line is not a human turn, so it no longer resets the AI counter. The fan-out frames, the send response, its NATS frame and history rows carry source (MEMBER for a NULL row).
· kamo-internal
SP96 hire form asks an AI's sex and lets the hire leave out its phone extension or workstation; people get an optional sex; the checklist's Set up now (SP96 T18)
- AI hire: a required Female/Male select (the hire button waits for it), and Phone extension / Workstation switches, both on; the payload always sends aiPhoneExtension and aiWorkstation as booleans, and the form's checklist preview lists only what this hire will set up. - People (owner, 2026-10-04): both create forms offer an optional Sex (Not specified / Female / Male) to a creator who may write it (sexEditAtCreate = sexEdit for a new record); sent only when chosen. - The AI contact note promises its own mailbox only. - Hiring checklist: NOT_REQUESTED_AT_HIRE is said in words; a skipped-at-hire row offers Set up now, the same retry, with its own toast.
· kamo-internal
SP96 hire form asks an AI's sex and lets the hire leave out its phone extension or workstation; people get an optional sex; the checklist's Set up now (SP96 T18)
- AI hire: a required Female/Male select (the hire button waits for it), and Phone extension / Workstation switches, both on; the payload always sends aiPhoneExtension and aiWorkstation as booleans, and the form's checklist preview lists only what this hire will set up. - People (owner, 2026-10-04): both create forms offer an optional Sex (Not specified / Female / Male) to a creator who may write it (sexEditAtCreate = sexEdit for a new record); sent only when chosen. - The AI contact note promises its own mailbox only. - Hiring checklist: NOT_REQUESTED_AT_HIRE is said in words; a skipped-at-hire row offers Set up now, the same retry, with its own toast.
· MediaService
MediaService caches the org graph, reads chat policy facts from it, and the managed tracking scope is the whole subtree (KamoCollab CP02 T17)
MediaService declares the org graph loader and its 60 s cache, and a listener on security.orggraph.changed.* that evicts the graph and then the chat policy snapshot on every pod. Chat policy builds its member facts from the cached graph instead of its own queries. Marketing tracking's managed scope is now the caller plus everyone below them at any depth who is record-active and not synthetic, the same reading as a report's TEAM scope.
· KBService
A supervisor's read of an AI member's notes is written to phi_access_log (KamoCollab CP00 T8)
GET /api/notes/members/{memberId}, .../{noteId} and .../{noteId}/versions now record the read after it succeeds, through the shared PhiAccessRecorder: one LIST row per note listed (one recordAll batch), one VIEW row for a note or its history; resourceType NOTE, the caller's org and member, purposeOfUse OPERATIONS, justification **************** A refused read (403, 404) or an empty list writes nothing; VIEW and LIST fail open, so a write failure never reaches the supervisor's answer. kbservice gains its PHI writer: PhiAuditPersistenceConfig declares **************** with destroyMethod shutdown, and KBServiceApplication scans com.kamo.z.shared.phi.audit in the same commit (the 2026-08-03 lesson; KbPhiAuditWiringTest holds the two together). The audit stamps occurredAt itself: PhiAccessRecorder rebuilds an unstamped event without its purposeOfUse.
· SecurityService
The team domain — wire, validation, writer, service and the org-graph seam (KamoCollab CP01 T9)
TeamWire reads a team body tri-state (absent / null / value); TeamValidator checks titles, descriptions, timeouts, roles, rights (normalized as a level), staff-only membership, the lead and the caps against the organization before anything is written; TeamWriter does every team write in one retried transaction, replacing roles and rights by key under the unique indexes (deletes flushed before inserts, a kept right updated in place); TeamService serves the reads and afterWrite: the bounded recalculation in its own transaction, the charter announcement, and TeamChangeListener for graph facts only (R1-25). LayerCharterSupport gains teamCounts. Nothing serves it yet (Task 10).
· kamo-shared-library
Build member facts from the org graph, next to the old query path (KamoCollab CP02 T5)
ChatPolicySnapshotLoader gains a constructor taking LongFunction<OrgGraph>. With it, load() maps the organization's graph to MemberFacts through the new MemberFactsFromGraph, asking for the graph once per load of an org with an active set. The ChatPolicyFactsRepository constructor and loadFacts are unchanged; Task 18 removes them once both services have switched. **************** feeds the same rows to a verbatim copy of today's loadFacts and to OrgGraphRows.fromSql + OrgGraphBuilder + MemberFactsFromGraph, over 2,000 random orgs and the fixed loop, dangling and out-of-range cases, and asserts the two maps are equal.
· kamo-shared-library
Bounded recalculation by member id, and role edits that reach team members (KamoCollab CP01 T7)
**************** recalculates exactly these members: read by id in chunks of 500 (never the organization's members), each chunk through recalcChunk (the TEAM level loaded once per chunk; only a changed member is written and notified). Null and repeated ids are dropped, none left is 0 with no query, more than 1,000 distinct ids is refused, and a proxied member is unwrapped before the waterfall. updateRoleMemberRightsApplied also recalculates the members of every team that carries the role (findTeamIdsByRoleId, findMemberIdsByTeamIdIn), each once, never a direct holder twice, taken from the organization's members it already read, in chunks of 500.
· SecurityService
SecurityService's org graph beans, the after-commit change publisher, its listener and the two internal routes (KamoCollab CP02 T7)
OrgGraphBeanConfig declares the loader (a read-only TransactionTemplate of its own, Clock.systemUTC()) and the 60 s OrgGraphCache. OrgGraphChangePublisher collects one transaction's changes per org, and on completion evicts this pod's graph and chat policy and publishes **************** once per org; a rollback announces nothing, no transaction runs at once, and it never throws. OrgGraphEventListener evicts both caches on every pod's event. OrgGraphInternalController serves GET .../org-graph/{orgId} and .../{orgId}/pair behind X-Internal-Auth; an unknown org is an empty graph.
· kamo-shared-library
The loader, its eight org-bounded queries, the 60 s cache, the change event, the wire codec and the department head mapping (KamoCollab CP02 T4)
- OrgGraphFactsRepository (donotmodify.dao.repos): eight native queries, each bounded by org_id through an index; users and CP01's team tables are pinned onto their keys (EXPLAIN-verified). - OrgGraphRows.fromSql: the lower-case maps to rows, leniently (unknown enums read as the entity defaults, ChatPolicySnapshotLoader's number and boolean rules). - OrgGraphLoader: one read-only transaction per load; TransientDbRetry outside a transaction, one attempt inside one. - OrgGraphCache: Caffeine, 60 s after write, at most 1,000 orgs, one load per org; fresh() bypasses it. - OrgGraphEvents: **************** its body, and a dispatcher subscription that drops malformed subjects and contains handler failures. - OrgGraphWire: the internal snapshot's maps (ids as strings) and back, refused on a stamp mismatch. - Department.headMemberId (read-only mapping), DepartmentDTO head fields, the TEAM share Javadoc and the waterfall's leaf-only comment.
· kamo-shared-library
The TEAM level of the rights and session-timeout waterfalls (KamoCollab CP01 T6)
TeamLevels (pure, not a bean) reads a set of members' teams in four queries whatever their number (one when none is in a team): memberships, the teams, their roles with each role's rights, and their direct rights. Every team a member belongs to folds into ONE peer level by the waterfall's own addSettingToLevel rule (GRANTED beats NUKE), overlaid after the job title and before the member; FORCE, closure and the AI guardrails still follow. The session timeout gains the same level: the shortest non-null team timeout overrides the job title, and the member's own still overrides it. Staff only, like department and job title: a customer costs no team query. Single-member paths load one member's level; recalcChunk loads the chunk's once. The four team repositories are optional; without them, or for a member in no team, every waterfall resolves exactly as before: the T1 goldens stay green and unedited in both modes.
· kamo-internal
Link the AI screens to their help articles by path, and fix the AI settings tips that showed placeholders (SP98 T23)
- /help-center/<path> (app/help-center/[...path]): opens the article in /kb when the reader's own knowledge base holds the help center (the platform organization), else the public help center in the reader's language. The help center's own cross-references use the same address. Only plain slugs are accepted; anything else goes to the help center's front page. - HelpArticleLink: same tab; "Learn more in the help center" or a named subject; ink for the surface it sits on (the Quick Tips column is light on either palette). - Linked: all eight tabs of Settings > Artificial Intelligence, Hosted Computers > Computers, Essential Setup > Security Roles, Departments and Job Titles, the profile's AI room, and the AI calls console. - The Security Models tab read "security-models", and its Quick Tips and those of Usage & Overview read "What Title" / "Understanding Body": real copy from new keys. - HELP_ARTICLES is held to tools/help-center/manifest.json by a test.
· kamo-internal
Link the AI screens to their help articles by path, and fix the AI settings tips that showed placeholders (SP98 T23)
- /help-center/<path> (app/help-center/[...path]): opens the article in /kb when the reader's own knowledge base holds the help center (the platform organization), else the public help center in the reader's language. The help center's own cross-references use the same address. Only plain slugs are accepted; anything else goes to the help center's front page. - HelpArticleLink: same tab; "Learn more in the help center" or a named subject; ink for the surface it sits on (the Quick Tips column is light on either palette). - Linked: all eight tabs of Settings > Artificial Intelligence, Hosted Computers > Computers, Essential Setup > Security Roles, Departments and Job Titles, the profile's AI room, and the AI calls console. - The Security Models tab read "security-models", and its Quick Tips and those of Usage & Overview read "What Title" / "Understanding Body": real copy from new keys. - HELP_ARTICLES is held to tools/help-center/manifest.json by a test.
· kamo-internal
The AI Team area, fifteen KamoAI articles, refreshed AI and notification articles, and links that work on the public site (SP98 T23)
Content (authored, under content/): - New category AI Team (after AI Assistant) with three sections and 15 articles: AI team members explained, hiring, supervising, the charter, job templates and personas, approvals; the Workforce hub, pausing all AI members, Routing & Budgets, AI workstations; AI phone calls and consent, AI members in meetings, Checklists & Tasks, repeating events and AI work blocks, mentions and AI members in chat. Labels are the screens' own, from the dictionary. - Refreshed to what shipped: AI providers (the AI Provider Registry), adding a provider, MCP servers (only read-only tools reach anyone), AI sales agents (personas), AI security models, general AI settings, access policies, AI usage, email headers and signatures (the AI disclosure), the three notifications articles, voicemail, and creating an event (Repeat, Find a time). - Two hand-corrected articles (843d0291b) were not marked authored, so a rebuild reverted them; now they are. Pipeline: - Cross-references link to /help-center/<path> (lib/paths.mjs) instead of /kb/<guid>, which is a 404 on the public site. Inside the product the /help-center route opens the same article. - import.mjs: --only takes a comma-separated list (parents first); --positions moves only what differs from the manifest (the categories below an inserted one); --relations-only sets see-also links without a new version. - An authored article's related: [slugs] seeds its see-also links (lib/related.mjs), and a slug that names nothing fails the build. - manifest.json and relations.json rebuilt.
· kamo-internal
The AI Team area, fifteen KamoAI articles, refreshed AI and notification articles, and links that work on the public site (SP98 T23)
Content (authored, under content/): - New category AI Team (after AI Assistant) with three sections and 15 articles: AI team members explained, hiring, supervising, the charter, job templates and personas, approvals; the Workforce hub, pausing all AI members, Routing & Budgets, AI workstations; AI phone calls and consent, AI members in meetings, Checklists & Tasks, repeating events and AI work blocks, mentions and AI members in chat. Labels are the screens' own, from the dictionary. - Refreshed to what shipped: AI providers (the AI Provider Registry), adding a provider, MCP servers (only read-only tools reach anyone), AI sales agents (personas), AI security models, general AI settings, access policies, AI usage, email headers and signatures (the AI disclosure), the three notifications articles, voicemail, and creating an event (Repeat, Find a time). - Two hand-corrected articles (843d0291b) were not marked authored, so a rebuild reverted them; now they are. Pipeline: - Cross-references link to /help-center/<path> (lib/paths.mjs) instead of /kb/<guid>, which is a 404 on the public site. Inside the product the /help-center route opens the same article. - import.mjs: --only takes a comma-separated list (parents first); --positions moves only what differs from the manifest (the categories below an inserted one); --relations-only sets see-also links without a new version. - An authored article's related: [slugs] seeds its see-also links (lib/related.mjs), and a slug that names nothing fails the build. - manifest.json and relations.json rebuilt.
· EmailService
Calendar chat lines say they are system lines (KamoCollab CP00 T5)
MediaChatClient.sendDirect now posts "source": "SYSTEM" beside fromMemberId, toMemberId and message: a calendar invitation or cancellation is a notice written in the organizer's name, not the organizer's words. MediaService (CP00 Task 7) stores it as MEDIA_OBJS.SOURCE = SYSTEM and keeps it out of chat policy and the AI turn guard; until then it ignores the unknown key. Test: **************** pins the four-key body.
· KlusterServices
Org-graph listeners may subscribe to the org-graph change subject
KamoCollab CP02 Task 6A (master R1-23, CP-D75). SecurityService publishes **************** after every org-graph write; each service that caches the org graph evicts on it. No NATS user could subscribe to the subject, so every listener would have hit a Subscription Violation and its cache would have evicted only by the 60 s TTL. Adds "security.orggraph.changed.>" to the subscribe allow list of the nine listener users: svc_security, svc_media, svc_kamoai (CP02), svc_kb, svc_email, svc_voip, svc_docs (CP05), svc_mcpgateway (CP07) and svc_rag (CP11). svc_docs subscribed to nothing (deny ">"); it now may subscribe to that subject alone. No password hash, publish list or other user changes.
· kamo-shared-library
CharterLevel.TEAM, between the job title and the member (KamoCollab CP01 T5)
Appends TEAM to CharterLevel (after DEFAULT; ORG from CP03 comes next, R1-11) and states in its Javadoc that resolution order is CharterLayer.rank, never the declaration order. CharterItems.validate needs no change: TEAM is neither ROLE nor MEMBER, so it follows the job title's rules. Tests: a team may hide and lock; every section validates at TEAM as at JOB_TITLE; two peer TEAM layers between a job title and a member (first in list order wins a SPECIFIC key and the conflict names the other, ADDITIVE unions, a team hides a job-title item, the member hides a team item, peers cannot hide each other); the L|/I| lines of a team-less chain's canonical stamp text equal the lines captured on main (no hash pinned, R1-1); a TEAM layer adds only its own lines. CharterEnumsTest names the levels in any order (R1-11).
· SecurityService
Right-table housekeeping covers org_team_rights (KamoCollab CP01 T8)
RoleRightsSyncService names org_team_rights in all three of its lists, so on every boot a frozen CHECK on it is dropped, a row naming a removed right is deleted, and a split right is seeded for the teams that granted its parent, exactly as for dept_rights. The seed's NOT EXISTS guard is keyed on (team_id, type), the table's unique index, so it never duplicates a team's right or trips the index.
· kamo-internal
The phone server's outbound policy in Settings: new extensions' outside calls, and the toll-fraud guards (SP93 T14)
The AI voice card on a FreePBX server gains the two SP93 settings VOIPService already stores: whether a brand-new extension starts allowed to dial outside, and the four optional toll-fraud guards (international, premium-rate, high-risk area codes, custom prefixes), under a heading that says 911 and 933 can never be blocked. - Every save sends all five, as V10's whole-replacement PUT requires (SP93-T4-3). - A custom prefix VOIPService would refuse (not 1-15 digits, or 911, 933 or the start of either) is refused on Add and on Save, before anything is sent; a prefix typed but not added is saved too; the list stops at 25. - A Kamo PBX shows none of them, since only FreePBX's dial plan enforces them, and still sends the stored values back on a save (SP93-T14-1). - The callers' preview box takes a theme token, so a dark theme can read it.
· kamo-shared-library
The pure org graph core: OrgGraph, its builder, nodes and relationships (KamoCollab CP02 T3)
Package com.kamo.z.shared.org.graph (pure, no beans), per KamoCollab master §6.2 and CP02 spec §5.2-§5.3: - enums OrgRelationship, CounterpartKind, OrgGraphChangeCause (STRING, append-only); - records OrgNode, OrgDepartment, OrgTeam and OrgGraphRows with its eight nested row records; - OrgGraphBuilder.build: department tree and root-to-leaf paths (chat's ancestry reversed), nodes, effective levels (D5), then OrgGraph.of; - OrgGraph: relationships (what b is to a), managementChain (BFS over manager and AI-supervisor edges), managerChain (chat's walk), reports, derivedTeam, lowestCommonManager (D9), outranks (D6, CP-D15), effectiveLevel, department heads, team leads, departmentManagerCandidates (D12), and a 24-hex content stamp. Cycle-safe throughout. Tests pin every row of spec §5.3's expected-answers table on the reference org, the cycle cases, 2,000 random shapes under a 5 s budget, the five invariants, and the stamp. Rulings CP02-T3-1..5.
· kamo-shared-library
The team entities, repositories and limits (KamoCollab CP01 T4)
Team (org_teams), TeamMembership (org_team_members, @IdClass Key), TeamRole, TeamRight (type through RoleRightTypeIdConverter) and TeamCharterItem (team_charter_items, text column 'text'), the five repositories with spec section 5.2's methods and JPQL, and TeamLimits (D11). The tables are created by the hand-applied DDL of securityservice **************** Tests pin every column, nullability and length to the DDL, parse every query against the real mapping (selected types and Object[] row shapes included), resolve every derived finder, and check the @IdClass; green on Hibernate 6.5.3 and on the fleet's 6.2.13.
· SecurityService
The hand-applied DDL for department heads and the org-graph load indexes, and its contract test (KamoCollab CP02 T1)
· SecurityService
The hand-applied DDL for teams, and its contract test (KamoCollab CP01 T2)
**************** is spec §5.1's block verbatim: org_teams, org_team_members, org_team_roles, org_team_rights (type INT4, unique (team_id, type), master CP-D3) and team_charter_items. Applied by CP01 Task 3 before kamo-shared-library ships the team entities. Cp01TeamsDdlContractTest pins the file's own shape as fixed lists (R1-12): re-runnable CREATEs only; no CHECK, JSON, DROP, UPDATE or DELETE; every NOT NULL column has a DEFAULT except the key and owner columns; the five tables, the uniques and lookups, the cascades, and team_charter_items' exact columns.
· MediaService
The DDL for media_objs.source, who wrote a chat line (KamoCollab CP00 T1)
A nullable VARCHAR(16) column, re-runnable, with no CHECK and no backfill; NULL reads as MEMBER. Hand-applied by CP00 Task 3. **************** keeps the file additive: every statement an ADD COLUMN IF NOT EXISTS, no NOT NULL without a DEFAULT, no CHECK, JSON, DROP or UPDATE.
· SecurityService
Impersonation and Enter-As write access-log rows (KamoCollab CP00 T9)
Both impersonation doors write IMPERSONATION_STARTED (HIGH) in the target's org with logEventSync after every refusal check and before the session is minted; a row that cannot be written answers 503 AUDIT_UNAVAILABLE and mints nothing. Enter-As writes ENTER_AS in the destination org before minting: MEDIUM for one's own membership (a failed write is a WARN), HIGH as the System User (a failed write is 503, nothing minted). The row names the ticket the System User grant rests on, never the one the body claimed. Logout of a session whose SUDO_MEMBER_ID differs from its memberID also writes IMPERSONATION_ENDED (MEDIUM). The cross-org door now reads the caller's orgID for the row and answers 401 when the session lacks it, as the same-org door already did. New: security/ImpersonationAudit (spec 5.6, D12-D14).
· AIService
The brief names DEPARTMENT_MANAGER as the head of your department (KamoCollab CP02)
DEPARTMENT_MANAGER now routes to the department head (the org graph's departmentManagerCandidates), so the brief says "the head of your department". CharterCompiler.VERSION is deliberately not bumped (CP02 spec D23, ruling CP02-P-5): no production charter routes to DEPARTMENT_MANAGER, so no stored brief carries the old words, and a bump would recompile every AI's brief for nothing. No test pins VERSION (R1-24).
· kamo-shared-library
MediaObjSource and MediaObj.source, createMessage with a source, findTwoPersonChatSession, and the impersonation/Enter-As access-log events (KamoCollab CP00 T4)
- MediaObjSource {MEMBER, SYSTEM, SERVICE}; MediaObj.source (STRING, column SOURCE, length 16, nullable, no default) and sourceOrMember(), which reads NULL as MEMBER. - MediaService.createMessage gains a seven-argument overload taking the source; the six-argument overload delegates with null, so every existing caller stores NULL. - **************** b): the search half of findOrCreateChatSession, extracted; findOrCreateChatSession creates only when it is empty, so its behaviour is unchanged. - AccessLogEventType appends IMPERSONATION_STARTED, IMPERSONATION_ENDED, ENTER_AS. The MEDIA_OBJS.SOURCE column must exist (CP00 Task 3 DDL) before this is pushed.
· kamo-internal
FreePBX voicemail in Settings: the manager-interface fields, shared voicemail boxes, and a player that says why
SP98 Task 15. - The FreePBX form gets the four Asterisk Manager Interface fields voicemail needs (host, port, username, secret). The stored secret is never shown ("(unchanged)") and is sent only when typed; moving the host asks for it again and an empty host turns voicemail off, as the server rules are. - A phone server's Voicemail tab plays each row by its own id: it asked for /voicemails/<externalId>/audio, which is no id, so no row ever played there. Rows say which mailbox the message was left in; a FreePBX server's shared boxes (mailboxes no Kamo extension owns, such as the main line's) are listed with who each notifies, chosen from the members who manage phone servers. - Settings > Phone > Voicemails names the mailbox and marks shared ones. - Both lists play through VoicemailAudioPlayer: nothing loads until play, and a message whose audio Kamo is fetching from the phone system says so, looks again every 20 s and plays once it is there; the other reasons are said plainly, with Try again. - The SIP WebSocket helper shows its text instead of its key (the dictionary value held <host> tags ICU could not format).
· kamo-asterisk-support
Voicemail uploads name their message, and Kamo can ask for one it has no audio of
A voicemail upload now carries the message's own id, <mailbox>/<msg_id> from the msgNNNN.txt Asterisk writes beside it, as its unique id: VOIPService files FreePBX voicemail under exactly that id, so the two pair exactly instead of by time. One upload per message, from its PCM copy (msgNNNN.wav). The wav49 copy (.WAV), the greetings (unavail, busy, greet, temp) and a mailbox's tmp files are no longer uploaded, so they are no longer deleted from the spool either: uploading them used to take a mailbox's greeting away and give each message a second, competing upload. A message whose sidecar names no msg_id is not uploaded and stays where it is. The policy pull now carries voicemailAudio: messages Kamo has no audio of and asks for (KamoAI SP98). VoicemailAudioSender finds each one by msg_id in its mailbox's folders, takes the best copy the spool still has (wav, WAV, gsm, ...), has Asterisk convert it into a WAV in its own tmp directory over the helper's manager user (file convert), uploads it under the message's id and removes the temporary file. The spool is never changed. Ids that are not <mailbox>/<msg_id> are dropped at the pull; each message is attempted once per ten minutes, in the background. Agent version 2.1.0.

Your universe is waiting.
Start free in minutes. Add products whenever you are ready — no migration, no new logins.
Free forever — no credit card required