Cancelling stops future billing and nothing else
Cancel no longer takes an "immediate" flag, and no customer-facing route can ask for one. Passing it true ended the subscription the same day while asking Strip...
Tell people when a bill needs them, and let them stop paying
A banner on the home page when a payment this person is responsible for has failed, linking straight to their billing page. Until now a failed payment showed up...
One plan per organization, a 25-seat Enterprise ceiling, and a way to stop paying
The plan is now an organization-level decision and nothing else can override it. Letting a self-payer or a group manager pick their own reads like a kindness an...
Approval-tier relay with role resolved from rights
THE ROLE IS DECIDED HERE, from the caller's rights and their relationship to the subject -- never from the request body. A body that could name its own role wou...
APPROVE_TIMECARD_ADJUSTMENTS (197) and FINALIZE_PAY_PERIODS (198)
Split so separation of duties is expressible: whoever approves a timecard is not automatically whoever may close a pay period over it. MANAGE_TIMECARDS (183) st...
The approval tier state machine
Employee submits -> manager approves -> payroll closes, per the owner's decision. Four rules, enforced in a pure transition table and pinned by 25 tests: 1. A...
Member Timecard tab and the HRS Timecards settings tab
The member tab is SELF-SERVICE ONLY and gated on identity, exactly like legal-compliance: its endpoints take no member id at all, so it is structurally incapabl...
/hr/attendance roster and the NavTop clock control
Replaces the HrTabPlaceholder body. The route, HR_TABS entry, MANAGE_TIMECARDS gate and tab i18n keys all already existed; nothing here registers anything. Ros...
Build log for phases 1-3
Records what shipped, the shared-lib push-ordering contract that bit once, the production census that settled the timezone questions, and the pre-existing KamoI...
Database-clock accessor on the punch repository
Every punch instant comes from the DB, never the JVM: pods restart and node clocks skew, and a wage record whose ordering depends on which pod answered is not a...
BFF route for /api/security/timecards
TimecardService is ClusterIP with no ingress, so without this route the whole feature 404s in the browser while every service is healthy and every build is gree...
SecurityService relay to TimecardService
The authorization boundary. TimecardService holds the data and the engine but no session; this holds the session and decides who may do what, then relays. The e...
Workday bucketing, stage 2 of the pipeline
Resolved intervals become per-business-date seconds, ready for the allocator. An interval is SPLIT at every workday boundary it crosses: a 22:00-06:00 shift un...
KI registration and schema migration
Tables come from ddl-auto; this runner supplies only what Hibernate cannot -- the partial unique indexes on one open primary assignment and one open pay-rate wi...
Recognise an existing CHECK constraint through Spring's wrapper
TrainingSchemaMigration @Order(43) has been aborting EVERY KamoInitializer run, silently withholding all 29 runners ordered after it (orders 45-112). Observed i...
Workweek and workday boundaries
The workweek is the FLSA overtime unit -- a fixed, regularly recurring 168-hour period -- and it is NOT the pay period. A biweekly period holds exactly two work...
Interval resolution, stage 1 of the calculation pipeline
Punches become resolved spans. Pure, total, and over value types rather than entities so every stage is fixture-testable with no persistence context. Durations...
Non-pyramiding overtime allocator with jurisdiction presets
Rules are DATA on the RequirementRule shape already used in this library, not code branches, so California is a configuration row instead of a rewrite. Allocat...
Deliver the org timezone to the browser
The client Organization model had no timezone field, useFormatters and next-intl are both configured with no timeZone, and timezoneService's backend call 404'd ...
HR timecards design and plan series
Design grounded in a 25-agent cross-repo survey. Part II closes the twelve blocker-level gaps an enterprise feature-completeness review found between the punch ...
GET ****************
timezoneService.ts has called this endpoint since it was written and it existed in no Java service; the client swallowed the 404 and resolved to UTC. Meanwhile ...
WorkSite, the jurisdiction anchor
Named WorkSite, not WorkLocation: TeamMember.workLocation is an existing office/remote enum and a second symbol by that name would collide in every grep. zoneI...
Employment and effective-dated assignment revisions
Punches will reference an ASSIGNMENT, not a member. That is what makes concurrent positions, the 29 CFR 778.115 weighted-average regular rate, multi-site jurisd...
Effective-dated, append-only pay rate
No employee compensation existed anywhere on this platform before this entity. It is required: overtime is computed against the FLSA regular rate, CA requires t...
Append-only punch log and audit event
Both served by NARROW Repository interfaces, not JpaRepository: deleteAllInBatch and bulk JPQL bypass entity callbacks, so the interface shape is what makes app...
Timezone-of-record resolver
Authority order work site -> member -> org -> UTC. The work site wins because wage-and-hour rules follow where the work is PERFORMED. A production census this ...
Shared WORM refusal guard
@Column(updatable=false) does NOT enforce immutability -- Hibernate silently omits the column from the UPDATE and the stale write looks like it succeeded. The o...
Domain enums and point-in-time version resolver
Enums are all @Enumerated(STRING) at the call site: ORDINAL emits a numeric range CHECK containing AND, which SchemaCleanupRunner's IN-list sweep deliberately s...
Put a person's own bill somewhere they can actually reach
The self-pay and seat-coverage panels were on /settings/account?tab=billing. That page redirects anyone without CONFIGURE_SYSTEM or a finance right back to /set...
Retry a serialization conflict instead of aborting the whole initializer
Found by running it. YugabyteDB performs online schema change, so a CREATE UNIQUE INDEX issued while Hibernate's ddl-auto pass is still settling can lose the ra...
Show members who pays for their seat, and stop offering a second one
The coverage endpoint shipped with translation keys and no component, so the question it answers — who pays for my seat — still had nowhere to be asked. SeatCov...
Let a group's plan be cancelled, and stop double-paying for one person
Three gaps in what shipped last: A group with a plan could never be removed. delete() refuses while a live subscription exists — correct, since the group row i...
A group you can put people on and pay for
Creating a group produced a name and nothing else — no way to add anyone, no way for its manager to choose a plan, nowhere to pay. Opening a group now shows who...
Make billing groups work end to end, and stop self-pay charging for nothing
A group can now gather people, buy a plan on its manager's own card, and hand its seats out. None of that existed: the only operations were create, approve and ...
Follow the subscription finder rename
**************** is gone from the shared library. Its Optional return depended on a unique constraint that no longer exists, so once one account holds both a pe...
Schema for groups that can actually pay
Three runners: BillingGroupRosterMigration creates billing_pay_group_members — whom a group means to pay for, before it has bought anything. org_id is denormal...
Let a billing group be its own payer
A group could be named and nothing else. Its subscription was never set — nothing in the platform called setSubscription — so subscription_uid stayed null forev...
The source half of 1b3553f, which committed only its tests
1b3553f staged its two new test files and nothing else, so it landed asserting a nullable FolderManager.setFlags that did not exist yet and cannot compile on it...
Stop an unnamed IMAP flag from being overwritten
IMAP flags are independent, so "mark this read" must say nothing about the star. EmailImapService.setFlags has always taken nullable Booleans and skipped the nu...
The HR course editor, assignments grid and member course player
/hr/training replaces its placeholder with the two sub-tabs the brief asked for, and the member's Assigned Training grid gains the action column it was holding ...
The hourly training reminder tick
HOURLY at :10, unlike the legal sweep's daily 13:00. Training reminds each org at its OWN local send hour, and Organization.timezone spans 21 hours (Pacific/Hon...
The complete HR Training backend
Course authoring, publishing, assignment, the member player, server-side grading, WORM evidence and the reminder sweep — the whole module behind /api/docs/train...
The four training email templates
Same constant/dotted-key split as the HR legal family: the CONSTANT is the filename stem, the templateKey the platform SENDS is the dotted value inside each .js...
One member surface for both obligations, one badge for either
The member's compliance tab had two sub-tabs, Pending and Completed, for one kind of obligation. Those are a STATE, not a kind — so they collapse into a status ...
The member-facing Training API
Registers com.kamo.z.shared.hr.training in @EnableJpaRepositories — @EntityScan is broad so the entities are found either way, but repositories are not. Two au...
Let document uploads actually reach the 3 GiB backend ceiling
The backend now streams to 3 GiB, but three things in the browser and the BFF made that unreachable, each fatal on its own. The upload proxy did `await request...
Raise the document upload ceiling from 500MB to 3 GiB
Chat attachments have run at 3 GiB through this same imaging pipeline for a while; documents were capped at 500MB. The difference was not a policy, it was that ...
Create the HR Training schema
Registers com.kamo.z.shared.hr.training in the explicit @EnableJpaRepositories list — @EntityScan is broad so the entities are found either way, but repositorie...
Stream document uploads and add the HR Training schema
ImageService gains uploadDocumentStreaming, the streaming counterpart of uploadDocument. The buffered form cannot express a large upload at all: readInputStream...
Rebuild against the shared-library JetStream consumer reconcile
Picks up kamo-shared-library f3a7283. Eight members hold an email-ws-<id> consumer still filtered on email.inbox.<addr> from before subscribeToNats moved to ema...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.