Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Vesting and cliff durations are now expressed in MONTHS instead of years.

- Rename "vestingYears" -> "vestingMonths" and "cliffYears" -> "cliffMonths"
on the "Share" and "Option" tables.
- Convert existing values from years to months by multiplying by 12
(e.g. a 4-year / 1-year-cliff grant becomes 48 / 12 months).
*/

-- AlterTable
ALTER TABLE "Share" RENAME COLUMN "vestingYears" TO "vestingMonths";
ALTER TABLE "Share" RENAME COLUMN "cliffYears" TO "cliffMonths";

-- AlterTable
ALTER TABLE "Option" RENAME COLUMN "vestingYears" TO "vestingMonths";
ALTER TABLE "Option" RENAME COLUMN "cliffYears" TO "cliffMonths";

-- Convert existing durations from years to months
UPDATE "Share" SET "vestingMonths" = "vestingMonths" * 12, "cliffMonths" = "cliffMonths" * 12;
UPDATE "Option" SET "vestingMonths" = "vestingMonths" * 12, "cliffMonths" = "cliffMonths" * 12;
8 changes: 4 additions & 4 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -690,8 +690,8 @@ model Share {
debtCancelled Float? // Amount of debt cancelled
otherContributions Float? // Other contributions

cliffYears Int @default(0) // 0 means immediate vesting, 1 means vesting starts after 1 year
vestingYears Int @default(0) // 0 means immediate vesting, 1 means vesting over 1 year
cliffMonths Int @default(0) // vesting cliff in months; 0 means immediate vesting
vestingMonths Int @default(0) // total vesting period in months; 0 means immediate vesting
Comment on lines +693 to +694

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify the zero-cliff semantics.

cliffMonths = 0 means “no cliff”, not necessarily immediate vesting when vestingMonths is still positive. Keep “immediate vesting” on vestingMonths = 0 only.

Proposed wording fix
-  cliffMonths   Int `@default`(0) // vesting cliff in months; 0 means immediate vesting
+  cliffMonths   Int `@default`(0) // vesting cliff in months; 0 means no cliff
   vestingMonths Int `@default`(0) // total vesting period in months; 0 means immediate vesting
-  cliffMonths   Int              `@default`(0) // vesting cliff in months; 0 means immediate vesting
+  cliffMonths   Int              `@default`(0) // vesting cliff in months; 0 means no cliff
   vestingMonths Int              `@default`(0) // total vesting period in months; 0 means immediate vesting

Also applies to: 745-746

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prisma/schema.prisma` around lines 693 - 694, The comment for cliffMonths
incorrectly conflates "no cliff" with "immediate vesting". Update the
cliffMonths comment to clarify that a value of 0 means "no cliff" (not immediate
vesting), while keeping the vestingMonths comment unchanged since it correctly
states that 0 means immediate vesting. Apply the same comment fix to the
duplicate cliffMonths and vestingMonths fields that appear later in the schema
around lines 745-746.


companyLegends ShareLegendsEnum[] @default([])

Expand Down Expand Up @@ -742,8 +742,8 @@ model Option {

type OptionTypeEnum
status OptionStatusEnum @default(DRAFT)
cliffYears Int @default(0) // 0 means immediate vesting, 1 means vesting starts after 1 year
vestingYears Int @default(0) // 0 means immediate vesting, 1 means vesting over 1 year
cliffMonths Int @default(0) // vesting cliff in months; 0 means immediate vesting
vestingMonths Int @default(0) // total vesting period in months; 0 means immediate vesting

issueDate DateTime
expirationDate DateTime
Expand Down
12 changes: 6 additions & 6 deletions src/components/securities/options/steps/vesting-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import { EmptySelect } from "../../shared/EmptySelect";

const formSchema = z.object({
equityPlanId: z.string(),
cliffYears: z.coerce.number().min(0),
vestingYears: z.coerce.number().min(0),
cliffMonths: z.coerce.number().min(0),
vestingMonths: z.coerce.number().min(0),
exercisePrice: z.coerce.number(),
stakeholderId: z.string(),
});
Expand Down Expand Up @@ -85,7 +85,7 @@ export const VestingDetails = (props: VestingDetailsProps) => {
<div className="flex-1">
<FormField
control={form.control}
name="vestingYears"
name="vestingMonths"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
Expand All @@ -105,7 +105,7 @@ export const VestingDetails = (props: VestingDetailsProps) => {
<NumericFormat
thousandSeparator
allowedDecimalSeparators={["%"]}
suffix={field.value > 1 ? " years" : " year"}
suffix={field.value > 1 ? " months" : " month"}
decimalScale={0}
{...rest}
customInput={Input}
Expand All @@ -126,7 +126,7 @@ export const VestingDetails = (props: VestingDetailsProps) => {
<div className="flex-1">
<FormField
control={form.control}
name="cliffYears"
name="cliffMonths"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
Expand All @@ -147,7 +147,7 @@ export const VestingDetails = (props: VestingDetailsProps) => {
thousandSeparator
allowedDecimalSeparators={["%"]}
decimalScale={0}
suffix={field.value > 1 ? " years" : " year"}
suffix={field.value > 1 ? " months" : " month"}
{...rest}
customInput={Input}
onValueChange={(values) => {
Expand Down
12 changes: 6 additions & 6 deletions src/components/securities/shares/steps/general-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ const formSchema = z.object({
certificateId: z.string(),
status: z.nativeEnum(SecuritiesStatusEnum),
quantity: z.coerce.number().min(0),
cliffYears: z.coerce.number().min(0),
vestingYears: z.coerce.number().min(0),
cliffMonths: z.coerce.number().min(0),
vestingMonths: z.coerce.number().min(0),
companyLegends: z.nativeEnum(ShareLegendsEnum).array(),
pricePerShare: z.coerce.number().min(0),
});
Expand Down Expand Up @@ -258,7 +258,7 @@ export const GeneralDetails = ({ shareClasses = [] }: GeneralDetailsProps) => {
<div className="flex-1">
<FormField
control={form.control}
name="vestingYears"
name="vestingMonths"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
Expand All @@ -280,7 +280,7 @@ export const GeneralDetails = ({ shareClasses = [] }: GeneralDetailsProps) => {
thousandSeparator
allowedDecimalSeparators={["%"]}
decimalScale={0}
suffix={field.value > 1 ? " years" : " year"}
suffix={field.value > 1 ? " months" : " month"}
{...rest}
customInput={Input}
onValueChange={(values) => {
Expand All @@ -301,7 +301,7 @@ export const GeneralDetails = ({ shareClasses = [] }: GeneralDetailsProps) => {
<div className="flex-1">
<FormField
control={form.control}
name="cliffYears"
name="cliffMonths"
render={({ field }) => {
const { onChange, ...rest } = field;
return (
Expand All @@ -322,7 +322,7 @@ export const GeneralDetails = ({ shareClasses = [] }: GeneralDetailsProps) => {
thousandSeparator
allowedDecimalSeparators={["%"]}
decimalScale={0}
suffix={field.value > 1 ? " years" : " year"}
suffix={field.value > 1 ? " months" : " month"}
{...rest}
customInput={Input}
onValueChange={(values) => {
Expand Down
12 changes: 6 additions & 6 deletions src/server/api/schema/shares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ export const ShareSchema = z
example: 0,
}),

cliffYears: z.number().openapi({
description: "Cliff Years",
example: 1,
cliffMonths: z.number().openapi({
description: "Vesting cliff in months",
example: 12,
}),
vestingYears: z.number().openapi({
description: "Vesting Years",
example: 4,
vestingMonths: z.number().openapi({
description: "Total vesting period in months",
example: 48,
Comment on lines +60 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add integer and non-negative constraints to month fields in public API schema.

The ShareSchema uses z.number() for cliffMonths and vestingMonths, which accepts negative and fractional values. However, the Prisma model defines both as Int type, and internal tRPC/UI validation already enforces .min(0). Update the public API schema to match the storage and internal validation layer.

Proposed validation fix
-    cliffMonths: z.number().openapi({
+    cliffMonths: z.number().int().min(0).openapi({
       description: "Vesting cliff in months",
       example: 12,
     }),
-    vestingMonths: z.number().openapi({
+    vestingMonths: z.number().int().min(0).openapi({
       description: "Total vesting period in months",
       example: 48,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/api/schema/shares.ts` around lines 60 - 66, Update the schema
validation for the month fields in ShareSchema to enforce both integer and
non-negative constraints. For both cliffMonths and vestingMonths fields, change
z.number() to z.number().int().min(0) to match the Prisma model's Int type
definition and align with the internal tRPC/UI validation that already enforces
these constraints. This ensures the public API schema is consistent with the
storage layer and prevents invalid negative or fractional month values from
being accepted.

}),

companyLegends: z
Expand Down
4 changes: 2 additions & 2 deletions src/trpc/routers/securities-router/procedures/add-option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const addOptionProcedure = withAuth
exercisePrice: input.exercisePrice,
type: input.type,
status: input.status,
cliffYears: input.cliffYears,
vestingYears: input.vestingYears,
cliffMonths: input.cliffMonths,
vestingMonths: input.vestingMonths,
issueDate: new Date(input.issueDate),
expirationDate: new Date(input.expirationDate),
vestingStartDate: new Date(input.vestingStartDate),
Expand Down
6 changes: 2 additions & 4 deletions src/trpc/routers/securities-router/procedures/add-share.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { ZodAddShareMutationSchema } from "../schema";
export const addShareProcedure = withAuth
.input(ZodAddShareMutationSchema)
.mutation(async ({ ctx, input }) => {
console.log({ input }, "#############");

const { userAgent, requestIp } = ctx;

try {
Expand All @@ -33,8 +31,8 @@ export const addShareProcedure = withAuth
ipContribution: input.ipContribution,
debtCancelled: input.debtCancelled,
otherContributions: input.otherContributions,
cliffYears: input.cliffYears,
vestingYears: input.vestingYears,
cliffMonths: input.cliffMonths,
vestingMonths: input.vestingMonths,
companyLegends: input.companyLegends,
issueDate: new Date(input.issueDate),
rule144Date: new Date(input.rule144Date),
Expand Down
4 changes: 2 additions & 2 deletions src/trpc/routers/securities-router/procedures/get-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export const getOptionsProcedure = withAuth.query(
exercisePrice: true,
type: true,
status: true,
cliffYears: true,
vestingYears: true,
cliffMonths: true,
vestingMonths: true,
issueDate: true,
expirationDate: true,
vestingStartDate: true,
Expand Down
4 changes: 2 additions & 2 deletions src/trpc/routers/securities-router/procedures/get-shares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export const getSharesProcedure = withAuth.query(
ipContribution: true,
debtCancelled: true,
otherContributions: true,
cliffYears: true,
vestingYears: true,
cliffMonths: true,
vestingMonths: true,
companyLegends: true,
status: true,

Expand Down
8 changes: 4 additions & 4 deletions src/trpc/routers/securities-router/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export const ZodAddOptionMutationSchema = z.object({
exercisePrice: z.coerce.number().min(0),
type: z.nativeEnum(OptionTypeEnum),
status: z.nativeEnum(OptionStatusEnum),
cliffYears: z.coerce.number().min(0),
vestingYears: z.coerce.number().min(0),
cliffMonths: z.coerce.number().min(0),
vestingMonths: z.coerce.number().min(0),
issueDate: z.string().date(),
expirationDate: z.string().date(),
vestingStartDate: z.string().date(),
Expand Down Expand Up @@ -57,8 +57,8 @@ export const ZodAddShareMutationSchema = z.object({
debtCancelled: z.coerce.number().min(0),
otherContributions: z.coerce.number().min(0),
status: z.nativeEnum(SecuritiesStatusEnum),
cliffYears: z.coerce.number().min(0),
vestingYears: z.coerce.number().min(0),
cliffMonths: z.coerce.number().min(0),
vestingMonths: z.coerce.number().min(0),
companyLegends: z.nativeEnum(ShareLegendsEnum).array(),
issueDate: z.string().date(),
rule144Date: z.string().date(),
Expand Down
Loading