One security right per Human Resources tab
The /hr tabs were gated on whichever pre-existing HR right happened to be an OR-term in the front end: Attendance opened on VIEW_SCHEDULES, Training on VIEW_EMP...
Measure a draft against the send limits before uploading it
A body carrying inline base64 — HTML pasted from a web page, a forward's quoted content, a signature's images — can pass the per-part ceiling on its own. The se...
The executed package certificate and the ordered merge
The certificate carries the package, the frozen version, the subject, the moment and address of execution, the attestation wording verbatim and both digests the...
Expose the content-addressed store so chat attachments can dedup
Chat attachments never went through the hashing pipeline — they wrote a private "<imgId>/<fileName>" object per upload, so the same file attached three times oc...
The two byte sources for the executed artifact
LegalPinnedPdf extracts the frozen-digest rule out of the member read path so the wizard and the filed artifact read the SAME bytes through the SAME check — con...
Member legal-compliance page and the package execution wizard
**************** is gated on identity (memberID === getMemberIdString(), compared as STRINGS because getMemberId() is lossy past MAX_SAFE_INTEGER) and includes ...
Member execution proxy routes and the pure wizard model
Eight 1:1 routes: /api/docs/legal/member/** through forwardToApi (PATH ONLY — forwardToApi owns the query string) and /api/esig/member/** direct to ESIG_SERVICE...
Port the kamo-signer signing core into kamo-internal
Every @kamo/* package is private:true with exports:"./src/index.ts" and no build step, so there is no publishable artifact — the files are copied and rewired on...
Mount the member surface at /api/docs/legal/member/**
A separate controller with a separate funnel, because authorization here is identity and not an HR right. Routing these through guarded() would mean either lock...
Package attestation on finish, and the EXECUTED/AWAITING_OTHERS split
Writes the WORM LegalPackageAttestation: attestationTextSha256 from the FROZEN version (the package's draft wording is mutable and may already have been reworde...
Identity-authorized member read surface for assignments
LegalAssignmentOwnership is a SEPARATE guard from LegalPackageAccess on purpose: a member executing their own agreement holds no HR right, and widening the HR r...
Manifest canonical form and a spoof-resistant client IP
manifestSha256 hashes the ordered [{documentUid, contentSha256, orderIndex}] the member was actually shown, length-prefixed so a crafted uid cannot imitate a fi...
Staff-authenticated member signing surface at /api/esig/member/**
OTKAuthFilter bypasses only /actuator, /api/esig/public and /api/esig/internal, so this prefix is behind staff OTK auth with orgId/memberId already on the reque...
Assigned Packages sub-tab and deliver/redeliver/void actions
AG Grid Community over a MUI-paginated fetch — Community has no server-side row model, so the server pages and the grid renders exactly what it was handed. The ...
Legal package deliver/redeliver/void proxies and assignment client
Thin 1:1 forwardToApi routes — one upstream request each, because OTKAuthFilter does getAndDelete and a per-member loop in a Next route would need one OTK per m...
Org-scoped, paged, filterable legal package assignment listing
Built on the EntityManager rather than a shared-lib repository method: the filter set is dynamic and adding it to kamo-shared-library would force a shared-artif...
Void legal package assignments, org-wide or for named members
Per-assignment outcomes — voided, skipped-executed, not-found — because an admin voiding a package for six named people needs to know which had already executed...
Redeliver a changed package, superseding the open assignments
Refused unless the live fingerprint differs from the published one. Binder.dateUpdated is dead and Img has no revision counter, so that fingerprint is the only ...
Deliver a legal package to its audience, off the request thread
Plan on the request thread (two cheap queries, so the response is exact about who is included and who was skipped and why), write off it: 200 members x 8 docume...
Delivery planner — incremental targeting, email pre-filter, due dates
Deliver is the new-hire path and is meant to be re-run, so members who already hold an assignment are subtracted rather than given a second copy of the same ins...
ESigService internal client for legal package delivery
DocsService's first outbound service-to-service client. Posts to **************** (origin MANUAL, never metered) with suppressInvite=true — HR sends one notific...
Package Templates sub-tab for Legal & Compliance
Replaces the HrTabPlaceholder body only — the route, the tab entry and the gate already exist in hrTabs.ts. AG Grid Community list plus a create/edit dialog wit...
Legal package proxy routes, client api and pure helpers
Thin 1:1 forwardToApi routes under /api/docs/legal — one upstream request each, because OTKAuthFilter does getAndDelete and a fan-out loop in a Next route would...
Audience resolution preview
Resolves a saved or draft audience through TeamMemberRepo.findAudience, which is the only reader carrying the mandatory ACTIVE + recordState eligibility predica...
Publish freezes a version with byte-pinned documents
Pins img_dat_id plus a content hash, not img_id: saveImgContent repoints the same Img at new bytes on every Docs save, and resolveSource walks to the dat live a...
Legal package CRUD at /api/docs/legal/packages
List, create, get, update and delete, with ordered binder refs and audience rows replaced as a set. Org comes from the session on every path — DocsService is pe...
Legal package wire contract and request normalization
Ids cross as strings (unique_rowid exceeds MAX_SAFE_INTEGER). Binder order is the list order, de-duplicated, and a non-numeric entry 400s rather than being skip...
Content fingerprint for legal packages
Binder.dateUpdated never moves after INSERT, so a package's content has no change signal to compare. Adds a SHA-256 over the canonical ordered binder/item tuple...
Push platform-admin state changes instead of being polled for them
Every platform screen backed by this service refreshed on a timer, because this service has no WebSocket and therefore no way to tell anyone anything: the close...
Publish mailbox-change events so the mail views stop polling
The folder tree, conversation list and message list each ran their own 30-second timer, reloading on the chance that something had moved. The WebSocket already ...
Relay SecurityService platform events onto STOMP
SecurityService owns the platform-admin surface — system configuration, DNS provisioning, commerce sync — but has no WebSocket endpoint, so every screen backed ...
Let sync progress and domain state be announced instead of polled
Two hooks for the push conversion in SecurityService, where several platform screens refreshed on timers because there was no way to tell them anything. Retail...
Allow a sender to suppress the per-envelope invite email
Creating an envelope unconditionally emailed an invite, so a caller batching many envelopes behind one notification had no way to stay quiet. Null preserves the...
Push INBOX unread counts over NATS/WebSocket instead of polling
The nav-top mail badge in kamo-internal refreshed itself by calling /api/email/folders every 30 seconds, per browser tab. That endpoint walks the entire folder ...
Land the legal package schema
Adds com.kamo.z.shared.hr.legal to the repository scan list and ensures the uniqueness and lookup indexes the entity mapping cannot express safely.
Record package attestation as append-only evidence
The package-level 'I have read and agree' is a different legal instrument from the E-SIGN consent to transact and from any per-document signature, and it is the...
Add per-member assignments and per-document progress
The document row is the join that makes N e-sign envelopes read as one package. The badge count comes off the assignment status, not e-sign status: recipient st...
Freeze package content into immutable versions
Pins the ImgDat blob id and its SHA-256, not just imgId — a Docs save repoints the same Img at new bytes, so imgId alone would let two members sign different do...
Target a package at departments, job types or named members
Shaped on SystemUserAccess. Adds the audience resolver to TeamMemberRepo, which was an empty interface; the ACTIVE + recordState predicate is what keeps termina...
Add the legal package template entities
A package is an ordered set of binders plus an audience. Binder references are soft ids because binders are user-deletable and cascade their items. The attestat...
Make the binder store aware of its collection
The store is a module singleton, so it now tracks the active scope, clears on a scope change, and drops stale in-flight responses.
List and create binders within one collection
Scopes the visible-binder query to the DocManager's (assocType, assocObjectId), stamps the scope on create, inherits it on duplicate, and bumps last_modified fr...
Backfill the collection each existing binder belongs to
Derives the scope from the first DOC item's Img; empty binders fall back to the owner's My Docs. Applies NOT NULL only after the backfill.
Narrow binder visibility to one document collection
Replaces findVisibleToMember with a scoped variant and drops the two unreferenced finders that sorted by the dead date_updated column.
Give a binder the document collection it belongs to
Binders had no assoc scoping, so every DocManager rendered the same global per-member pool and a binder built under Marketing could be filled with HR documents....
Group the meeting-invite fields by meaning when maximized
Splitting the form into equal halves used the wrong unit. It stretched a date input across half the window and stranded its All-day box at the far edge, left tw...
Keep the quoted original out of the editable body
Replying dropped the whole quoted message into the Quill editor as body text. Quill can only model a simple grid, so every layout table in the quote was flatten...
Pair the meeting-invite fields into two columns when maximized
The invite form stacks eight full-width rows, which in a maximized compose window pushed the body editor most of the way off screen while leaving half the windo...
Apply page operations and modifiers to the signed document
The designer has always offered page rotate, delete, reorder and add-blank, plus watermark, page-number and label modifiers, and the flatten read none of it: it...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.