export const CUSTODY_DB_ACTION_ARGUMENT_COUNTS = { migrate: 0, 'migrate-mainnet-schema': 0, 'initialize-mainnet-custody-identity': 1, 'migrate-pilot-schema': 0, 'initialize-pilot-custody-identity': 1, 'grant-pilot-deposit-reservation-runtime': 1, 'grant-pilot-deposit-address-runtime': 1, 'grant-pilot-monero-observer-runtime': 1, 'grant-pilot-monero-review-runtime': 1, 'pilot-deposit-prepare-review': 2, 'grant-runtime': 1, 'grant-monero-observer-runtime': 1, 'grant-monero-deposit-allocator-runtime': 1, 'grant-monero-withdrawal-runtime': 1, 'grant-monero-credit-runtime': 1, 'import-sqlite': 1, audit: 0, 'resolve-integrity': 1, 'deposit-prepare-credit': 2, 'deposit-stage-credit': 1, 'deposit-prepare-release': 1, 'deposit-release': 1, 'deposit-reconcile-holds': 1, 'deposit-prepare-resolve-hold': 2, 'deposit-resolve-hold': 1, } as const; export type CustodyDbAction = keyof typeof CUSTODY_DB_ACTION_ARGUMENT_COUNTS; export type CustodyDbArguments = { action: CustodyDbAction; argument?: string; extraArgument?: string }; export function assertCustodyDbActionAllowedOnNetwork(action: CustodyDbAction, isMainnet: boolean): void { if ((action === 'migrate-mainnet-schema' || action === 'initialize-mainnet-custody-identity') && !isMainnet) { throw new Error('Mainnet custody operation requires the mainnet profile'); } const mainnetAllowed = ['grant-monero-observer-runtime', 'grant-monero-deposit-allocator-runtime', 'grant-monero-withdrawal-runtime', 'grant-monero-credit-runtime', 'migrate-mainnet-schema', 'initialize-mainnet-custody-identity', 'deposit-prepare-credit', 'deposit-stage-credit', 'deposit-prepare-release', 'deposit-release', 'deposit-reconcile-holds', 'deposit-prepare-resolve-hold', 'deposit-resolve-hold']; if (isMainnet && !mainnetAllowed.includes(action)) { throw new Error('Only scoped Monero infrastructure grants, identity initialization, and reviewed credit/withdrawal-intent operations are allowed on mainnet'); } } export function assertMainnetSchemaMigrationAcknowledged(action: CustodyDbAction, acknowledgement: string | undefined): void { if (action === 'migrate-mainnet-schema' && acknowledgement !== 'explicit-schema-migration-only') { throw new Error('Mainnet schema migration requires explicit schema-migration acknowledgement'); } } export function assertMainnetCustodyOperationAcknowledged(action: CustodyDbAction, acknowledgement: string | undefined): void { const requiresReviewAck = action === 'initialize-mainnet-custody-identity' || action === 'grant-monero-credit-runtime' || action.startsWith('deposit-'); if (requiresReviewAck && acknowledgement !== 'reviewed-credit-only') { throw new Error('Mainnet deposit operations require WIKIDECK_MAINNET_CUSTODY_OPERATIONS=reviewed-credit-only'); } } export function assertPilotCustodyActionAllowed(action: CustodyDbAction, isPilot: boolean): void { const pilotActions = ['migrate-pilot-schema', 'initialize-pilot-custody-identity', 'grant-pilot-deposit-reservation-runtime', 'grant-pilot-deposit-address-runtime', 'grant-pilot-monero-observer-runtime', 'grant-pilot-monero-review-runtime', 'pilot-deposit-prepare-review']; if ((isPilot && !pilotActions.includes(action)) || (!isPilot && pilotActions.includes(action))) { throw new Error(isPilot ? 'Pilot profile permits only pilot-specific schema, identity, scoped-role, and review-preparation operations through this CLI' : 'Pilot custody operation requires the pilot profile'); } } export function assertPilotReservationGrantAcknowledged(action: CustodyDbAction, acknowledgement: string | undefined): void { if (action === 'grant-pilot-deposit-reservation-runtime' && acknowledgement !== 'reservation-only-no-addresses') { throw new Error('Pilot reservation role grant requires explicit reservation-only/no-addresses acknowledgement'); } } export function assertPilotAddressGrantAcknowledged(action: CustodyDbAction, acknowledgement: string | undefined): void { if (action === 'grant-pilot-deposit-address-runtime' && acknowledgement !== 'operator-triggered-addresses-only') { throw new Error('Pilot address role grant requires explicit operator-triggered/address-only acknowledgement'); } } export function assertPilotObserverGrantAcknowledged(action: CustodyDbAction, acknowledgement: string | undefined): void { if (action === 'grant-pilot-monero-observer-runtime' && acknowledgement !== 'pilot-observation-only-no-credit') { throw new Error('Pilot observer role grant requires explicit observation-only/no-credit acknowledgement'); } } export function assertPilotDepositReviewAcknowledged(action: CustodyDbAction, acknowledgement: string | undefined): void { if (action === 'grant-pilot-monero-review-runtime' && acknowledgement !== 'read-only-pilot-review-role-only') { throw new Error('Pilot review role grant requires the exact read-only-role acknowledgement'); } if (action === 'pilot-deposit-prepare-review' && acknowledgement !== 'pilot-deposit-review-preparation-only') { throw new Error('Pilot deposit review preparation requires the exact preparation-only acknowledgement'); } } export function assertPilotSchemaMigrationAcknowledged(action: CustodyDbAction, acknowledgement: string | undefined): void { if (action === 'migrate-pilot-schema' && acknowledgement !== 'explicit-pilot-schema-migration-only') { throw new Error('Pilot schema migration requires explicit pilot schema-migration acknowledgement'); } } export function assertPilotCustodyIdentityAcknowledged(action: CustodyDbAction, identityAcknowledgement: string | undefined, operationAcknowledgement: string | undefined): void { if (action === 'initialize-pilot-custody-identity' && (identityAcknowledgement !== 'initialize-pilot-custody-v1' || operationAcknowledgement !== 'identity-only-no-funds')) { throw new Error('Pilot identity initialization requires exact identity-only/no-funds acknowledgements'); } } export function parseCustodyDbArguments(args: readonly string[]): CustodyDbArguments { const [action, ...values] = args; if (!action || !Object.hasOwn(CUSTODY_DB_ACTION_ARGUMENT_COUNTS, action)) { throw new Error(`Expected one of: ${Object.keys(CUSTODY_DB_ACTION_ARGUMENT_COUNTS).join(', ')}`); } const count = CUSTODY_DB_ACTION_ARGUMENT_COUNTS[action as CustodyDbAction]; if (values.length !== count || values.some(value => value.length === 0)) { throw new Error(`Invalid argument count for custody operation: ${action}`); } return { action: action as CustodyDbAction, argument: values[0], extraArgument: values[1] }; }