Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
799a946
Enable exact optional property types
HardlyDifficult Jul 10, 2026
971d072
Merge commit '3a41fbf' into codex/exact-optional-tests
HardlyDifficult Jul 10, 2026
e1e77e0
Enforce canonical OCF DTO field purity
HardlyDifficult Jul 10, 2026
871c663
Guard canonical DTO properties against OCF schemas
HardlyDifficult Jul 10, 2026
d1ce1c9
Merge commit '971d072' into codex/canonical-schema-purity
HardlyDifficult Jul 10, 2026
2cbb203
Merge branch 'codex/exact-conversion-triggers' into codex/exact-optio…
HardlyDifficult Jul 10, 2026
b7d691e
Merge branch 'codex/exact-optional-tests' into codex/canonical-schema…
HardlyDifficult Jul 10, 2026
8d93d39
Test canonical stakeholder status fixture
HardlyDifficult Jul 10, 2026
2fd5d23
Merge remote-tracking branch 'origin/codex/exact-conversion-triggers'…
HardlyDifficult Jul 10, 2026
47d01b2
Merge remote-tracking branch 'origin/codex/exact-conversion-triggers'…
HardlyDifficult Jul 10, 2026
7d6aeee
Merge remote-tracking branch 'origin/codex/exact-conversion-triggers'…
HardlyDifficult Jul 10, 2026
faf3789
Merge remote-tracking branch 'origin/codex/exact-conversion-triggers'…
HardlyDifficult Jul 10, 2026
8732e30
Remove legacy PlanSecurity public surface
HardlyDifficult Jul 10, 2026
7ed5677
Merge commit '993c19eb90fd83aa9402aaa8fdbb1ae8f1c581de' into codex/ca…
HardlyDifficult Jul 10, 2026
7351f75
Merge commit 'dfb947b5c636458c2bad1f8682c4c2f849b2c888' into codex/ca…
HardlyDifficult Jul 10, 2026
0ad94bd
Merge remote-tracking branch 'origin/codex/exact-conversion-triggers'…
HardlyDifficult Jul 11, 2026
fb38d89
test: align schema purity type contracts
HardlyDifficult Jul 11, 2026
ffbb96c
test: handle composed schema discriminators
HardlyDifficult Jul 11, 2026
94db119
test: validate combined discriminator literals
HardlyDifficult Jul 11, 2026
ae0be62
fix: reject deprecated stakeholder relationship field
HardlyDifficult Jul 11, 2026
dfaa254
fix: prioritize unsupported OCF discriminators
HardlyDifficult Jul 11, 2026
2695e29
Merge PR #421 into canonical schema purity
HardlyDifficult Jul 11, 2026
cb871c8
Merge commit '54d6a34a378bfb5e9fe76d4e4893c29e61e1138f' into codex/ca…
HardlyDifficult Jul 11, 2026
6d162ea
fix: keep deprecated fields out of typed boundaries
HardlyDifficult Jul 11, 2026
b1032aa
Merge commit '46c6883797fe22d7fc0c006632612d1228b0acdf' into codex/ca…
HardlyDifficult Jul 11, 2026
e5be6b1
Merge commit '0c80270cacd8d628e68ec386e4378b3ea934ab74' into codex/ca…
HardlyDifficult Jul 11, 2026
87d3daa
test: canonicalize equity compensation fixture
HardlyDifficult Jul 11, 2026
3d73a11
Merge prepared exact trigger stack into canonical schema purity
HardlyDifficult Jul 11, 2026
bd0a2d1
Merge finalized exact trigger stack into schema purity
HardlyDifficult Jul 11, 2026
49f47b2
Merge centralized trigger validation into schema purity
HardlyDifficult Jul 11, 2026
7f152e4
Merge commit '0394fa0248036d04b77fb3cf6e5ec267346fd373' into codex/ca…
HardlyDifficult Jul 11, 2026
3b30d7d
Merge commit 'b49be4caede0b0967a14e1f9bfff726592ffd0a3' into codex/ca…
HardlyDifficult Jul 11, 2026
8c4db4d
Merge commit '3b0f8e7bae130dc81a63c3959f550dbb190b0b9f' into codex/ca…
HardlyDifficult Jul 11, 2026
9f6bb56
Merge commit '3f32f501ddd4bc718ff839afd17618ddaccae4eb' into codex/ca…
HardlyDifficult Jul 11, 2026
1941a5b
Merge commit 'deba982d55424d61feccc943a96a3dbfef37e547' into codex/ca…
HardlyDifficult Jul 11, 2026
48f7d36
Merge commit '6e2acab0914c8d4594926b38d25758309e9d11b8' into codex/ca…
HardlyDifficult Jul 11, 2026
4ae240f
Merge commit '4b1ebd82e43bda62ab2beb1ded1b2764f46739ab' into codex/ca…
HardlyDifficult Jul 11, 2026
95dbe6b
Merge commit 'eed32ce5205972d11b2a67bdcc52462700a75b1a' into codex/ca…
HardlyDifficult Jul 11, 2026
543d116
Merge commit '4debb8a9655ec2921018fcbd352f13bb45f814b7' into codex/ca…
HardlyDifficult Jul 11, 2026
c7d59ef
fix: reject special OCF own keys before parsing
HardlyDifficult Jul 12, 2026
650211c
Merge commit '3562f6986784b09c6df280d7cc419227193babe5' into codex/ca…
HardlyDifficult Jul 12, 2026
e7411b9
Merge commit '93cbf41cd8f2fad66c7d717ab4dba1dada6c4ff9' into codex/ca…
HardlyDifficult Jul 12, 2026
b195e50
fix: reject legacy stakeholder relationships
HardlyDifficult Jul 12, 2026
474e321
test: cover legacy relationship replication
HardlyDifficult Jul 12, 2026
d6771c0
fix: enforce schema-faithful purity guards
HardlyDifficult Jul 12, 2026
0c2e9b4
Merge commit '3b85edbf30dd1b7cb6a11f16c5d2050ca793358a' into codex/ca…
HardlyDifficult Jul 12, 2026
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
3 changes: 1 addition & 2 deletions src/functions/OpenCapTable/capTable/batchTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ type OcfEntityRegistry = {
/**
* Single source of truth for entity-level metadata.
*
* Schema-supported PlanSecurity objects normalize to the canonical EquityCompensation family
* before reaching typed batch operations.
* Only canonical OCF object types reach typed batch operations.
*/
export const ENTITY_REGISTRY = {
convertibleAcceptance: {
Expand Down
14 changes: 0 additions & 14 deletions src/functions/OpenCapTable/document/createDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,6 @@ function objectTypeToDaml(t: OcfObjectReference['object_type']): string {
return 'OcfObjTxEquityCompensationTransfer';
case 'TX_EQUITY_COMPENSATION_REPRICING':
return 'OcfObjTxEquityCompensationRepricing';
case 'TX_PLAN_SECURITY_ACCEPTANCE':
return 'OcfObjTxPlanSecurityAcceptance';
case 'TX_PLAN_SECURITY_CANCELLATION':
return 'OcfObjTxPlanSecurityCancellation';
case 'TX_PLAN_SECURITY_EXERCISE':
return 'OcfObjTxPlanSecurityExercise';
case 'TX_PLAN_SECURITY_ISSUANCE':
return 'OcfObjTxPlanSecurityIssuance';
case 'TX_PLAN_SECURITY_RELEASE':
return 'OcfObjTxPlanSecurityRelease';
case 'TX_PLAN_SECURITY_RETRACTION':
return 'OcfObjTxPlanSecurityRetraction';
case 'TX_PLAN_SECURITY_TRANSFER':
return 'OcfObjTxPlanSecurityTransfer';
case 'TX_STOCK_ACCEPTANCE':
return 'OcfObjTxStockAcceptance';
case 'TX_STOCK_CANCELLATION':
Expand Down
14 changes: 7 additions & 7 deletions src/functions/OpenCapTable/document/getDocumentAsOcf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,19 @@ function objectTypeToNative(t: Fairmint.OpenCapTable.OCF.Document.OcfObjectType)
case 'OcfObjTxEquityCompensationRepricing':
return 'TX_EQUITY_COMPENSATION_REPRICING';
case 'OcfObjTxPlanSecurityAcceptance':
return 'TX_PLAN_SECURITY_ACCEPTANCE';
return 'TX_EQUITY_COMPENSATION_ACCEPTANCE';
case 'OcfObjTxPlanSecurityCancellation':
return 'TX_PLAN_SECURITY_CANCELLATION';
return 'TX_EQUITY_COMPENSATION_CANCELLATION';
case 'OcfObjTxPlanSecurityExercise':
return 'TX_PLAN_SECURITY_EXERCISE';
return 'TX_EQUITY_COMPENSATION_EXERCISE';
case 'OcfObjTxPlanSecurityIssuance':
return 'TX_PLAN_SECURITY_ISSUANCE';
return 'TX_EQUITY_COMPENSATION_ISSUANCE';
case 'OcfObjTxPlanSecurityRelease':
return 'TX_PLAN_SECURITY_RELEASE';
return 'TX_EQUITY_COMPENSATION_RELEASE';
case 'OcfObjTxPlanSecurityRetraction':
return 'TX_PLAN_SECURITY_RETRACTION';
return 'TX_EQUITY_COMPENSATION_RETRACTION';
case 'OcfObjTxPlanSecurityTransfer':
return 'TX_PLAN_SECURITY_TRANSFER';
return 'TX_EQUITY_COMPENSATION_TRANSFER';
case 'OcfObjTxStockAcceptance':
return 'TX_STOCK_ACCEPTANCE';
case 'OcfObjTxStockCancellation':
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { type Fairmint } from '@fairmint/open-captable-protocol-daml-js';
import { OcpErrorCodes, OcpParseError } from '../../../errors';
import type {
CompensationType,
OcfEquityCompensationIssuance,
OcfPlanSecurityIssuance,
TerminationWindow,
} from '../../../types';
import type { CompensationType, OcfEquityCompensationIssuance, TerminationWindow } from '../../../types';
import {
cleanComments,
dateStringToDAMLTime,
Expand Down Expand Up @@ -64,34 +59,29 @@ export const terminationWindowPeriodTypeMap: Record<
YEARS: 'OcfPeriodYears',
};

type EquityCompensationIssuanceWriterInput = OcfEquityCompensationIssuance & {
id: string;
date: string;
security_id: string;
custom_id: string;
stakeholder_id: string;
stock_plan_id?: string;
stock_class_id?: string;
board_approval_date?: string;
stockholder_approval_date?: string;
consideration_text?: string;
vesting_terms_id?: string;
};

export function equityCompensationIssuanceDataToDaml(
d: OcfEquityCompensationIssuance & {
id: string;
date: string;
security_id: string;
custom_id: string;
stakeholder_id: string;
stock_plan_id?: string;
stock_class_id?: string;
board_approval_date?: string;
stockholder_approval_date?: string;
consideration_text?: string;
vesting_terms_id?: string;
}
d: EquityCompensationIssuanceWriterInput
): Record<string, unknown> {
return equityCompensationIssuanceLikeDataToDaml(d, 'equityCompensationIssuance');
return equityCompensationIssuancePayloadToDaml(d);
}

/**
* Shared canonical DAML payload builder for equity-compensation issuance aliases.
*
* Keeping the entire conversion here prevents legacy aliases from bypassing the
* discriminator-driven pricing and exact Monetary boundary used by the canonical writer.
*/
export function equityCompensationIssuanceLikeDataToDaml(
d: OcfEquityCompensationIssuance | OcfPlanSecurityIssuance,
source: 'equityCompensationIssuance' | 'planSecurityIssuance'
): Record<string, unknown> {
/** Build the complete canonical issuance payload behind the exact pricing boundary. */
function equityCompensationIssuancePayloadToDaml(d: EquityCompensationIssuanceWriterInput): Record<string, unknown> {
const source = 'equityCompensationIssuance';
const damlCompensationType = compensationTypeToDaml(d.compensation_type);
const pricing = validateEquityCompensationPricing(d.compensation_type, d.exercise_price, d.base_price, source);
return {
Expand Down
2 changes: 0 additions & 2 deletions src/functions/OpenCapTable/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ export * from './factory';
export * from './issuer';
export * from './issuerAuthorization';
export * from './issuerAuthorizedSharesAdjustment';
export * from './planSecurityExercise';
export * from './planSecurityIssuance';
export * from './stakeholder';
export * from './stakeholderRelationshipChangeEvent';
export * from './stakeholderStatusChangeEvent';
Expand Down
1 change: 0 additions & 1 deletion src/functions/OpenCapTable/planSecurityExercise/index.ts

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion src/functions/OpenCapTable/planSecurityIssuance/index.ts

This file was deleted.

This file was deleted.

14 changes: 1 addition & 13 deletions src/functions/OpenCapTable/stakeholder/stakeholderDataToDaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,6 @@ function contactInfoWithoutNameToDaml(
};
}

function getRelationshipsWithLegacyFallback(
data: OcfStakeholder
): NonNullable<OcfStakeholder['current_relationships']> {
if (Array.isArray(data.current_relationships)) {
return data.current_relationships;
}
if (data.current_relationship) {
return [data.current_relationship];
}
return [];
}

export function stakeholderDataToDaml(data: OcfStakeholder): Fairmint.OpenCapTable.OCF.Stakeholder.StakeholderOcfData {
assertSafeOcfJson(data, 'stakeholder');
// Validate input data using the entity validator
Expand All @@ -87,7 +75,7 @@ export function stakeholderDataToDaml(data: OcfStakeholder): Fairmint.OpenCapTab
addresses: (data.addresses ?? []).map(addressToDaml),
tax_ids: data.tax_ids ?? [],
comments: cleanComments(data.comments),
current_relationships: getRelationshipsWithLegacyFallback(data).map(stakeholderRelationshipTypeToDaml),
current_relationships: (data.current_relationships ?? []).map(stakeholderRelationshipTypeToDaml),
current_status: data.current_status ? stakeholderStatusToDaml(data.current_status) : null,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@ import { cleanComments, dateStringToDAMLTime, normalizeNumericString } from '../
/**
* Convert native OCF StockClassSplit data to DAML format.
*
* DAML expects split_ratio as an OcfRatio object { numerator, denominator }
* while OCF has flat split_ratio_numerator and split_ratio_denominator fields.
*
* Note: The OCF type includes optional `board_approval_date` and `stockholder_approval_date`
* fields, but the DAML StockClassSplitOcfData contract does not support these fields.
* They are intentionally omitted from the conversion.
* Both canonical OCF and DAML represent split_ratio as an OcfRatio object.
*/
export function stockClassSplitDataToDaml(d: OcfStockClassSplit): Record<string, unknown> {
if (!d.id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import { cleanComments, dateStringToDAMLTime, optionalString } from '../../../ut
/**
* Convert native OCF StockConsolidation data to DAML format.
*
* DAML expects resulting_security_id (singular) while OCF has resulting_security_ids (array).
* Takes the first item from the array to match DAML schema.
* Both canonical OCF and DAML use resulting_security_id (singular).
*/
export function stockConsolidationDataToDaml(d: OcfStockConsolidation): Record<string, unknown> {
if (!d.id) {
Expand Down
17 changes: 1 addition & 16 deletions src/functions/OpenCapTable/warrantExercise/damlToOcf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import { OcpErrorCodes, OcpValidationError } from '../../../errors';
import type { OcfWarrantExercise } from '../../../types';
import { damlTimeToDateString, normalizeNumericString } from '../../../utils/typeConversions';
import { damlTimeToDateString } from '../../../utils/typeConversions';

/**
* Convert DAML WarrantExercise data to native OCF format.
Expand All @@ -14,20 +14,6 @@ import { damlTimeToDateString, normalizeNumericString } from '../../../utils/typ
* @returns The native OCF WarrantExercise object
*/
export function damlWarrantExerciseToNative(d: Record<string, unknown>): OcfWarrantExercise {
const quantity =
d.quantity === undefined || d.quantity === null
? undefined
: typeof d.quantity === 'number'
? d.quantity.toString()
: d.quantity;
if (quantity !== undefined && typeof quantity !== 'string') {
throw new OcpValidationError('warrantExercise.quantity', `Must be string or number, got ${typeof d.quantity}`, {
code: OcpErrorCodes.INVALID_TYPE,
expectedType: 'string | number',
receivedValue: d.quantity,
});
}

// Validate resulting_security_ids
if (!Array.isArray(d.resulting_security_ids) || d.resulting_security_ids.length === 0) {
throw new OcpValidationError('warrantExercise.resulting_security_ids', 'Required field must be a non-empty array', {
Expand All @@ -50,7 +36,6 @@ export function damlWarrantExerciseToNative(d: Record<string, unknown>): OcfWarr
date: damlTimeToDateString(d.date, 'warrantExercise.date'),
security_id: d.security_id as string,
trigger_id: d.trigger_id,
...(quantity !== undefined ? { quantity: normalizeNumericString(quantity) } : {}),
resulting_security_ids: d.resulting_security_ids as string[],
...(d.consideration_text ? { consideration_text: d.consideration_text as string } : {}),
...(Array.isArray(d.comments) && d.comments.length > 0 ? { comments: d.comments as string[] } : {}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
* OCF to DAML converter for WarrantExercise entities.
*/

import type { Fairmint } from '@fairmint/open-captable-protocol-daml-js';
import { OcpValidationError } from '../../../errors';
import type { OcfWarrantExercise } from '../../../types';
import {
cleanComments,
dateStringToDAMLTime,
normalizeNumericString,
optionalString,
} from '../../../utils/typeConversions';
import { cleanComments, dateStringToDAMLTime, optionalString } from '../../../utils/typeConversions';

type DamlWarrantExerciseData = Fairmint.OpenCapTable.OCF.WarrantExercise.WarrantExerciseOcfData;

/**
* Convert native OCF WarrantExercise data to DAML format.
Expand All @@ -18,7 +16,7 @@ import {
* @returns The DAML-formatted warrant exercise data
* @throws OcpValidationError if required fields are missing
*/
export function warrantExerciseDataToDaml(d: OcfWarrantExercise): Record<string, unknown> {
export function warrantExerciseDataToDaml(d: OcfWarrantExercise): DamlWarrantExerciseData {
if (!d.id) {
throw new OcpValidationError('warrantExercise.id', 'Required field is missing or empty', {
expectedType: 'string',
Expand All @@ -36,7 +34,8 @@ export function warrantExerciseDataToDaml(d: OcfWarrantExercise): Record<string,
date: dateStringToDAMLTime(d.date, 'warrantExercise.date'),
security_id: d.security_id,
trigger_id: d.trigger_id,
quantity: d.quantity ? normalizeNumericString(d.quantity) : null,
// DAML retains this ledger-only field; canonical OCF has no corresponding input.
quantity: null,
resulting_security_ids: d.resulting_security_ids,
consideration_text: optionalString(d.consideration_text),
comments: cleanComments(d.comments),
Expand Down
Loading