refactor(validations): split lib/validations.ts into a barrel'd folder (slice 1)
1141-line lib/validations.ts → lib/validations/<feature>.ts + primitives.ts + an index.ts barrel. '@/lib/validations' now resolves to the barrel, so all 40 importers are untouched. Every one of the 143 exports is preserved verbatim (verified by symbol-diff); schemas moved unchanged. tsc 0, 635 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c8c60358c7
commit
18d5e22ad4
@@ -0,0 +1,113 @@
|
||||
import { z } from 'zod'
|
||||
import { flexibleDateTime } from './primitives'
|
||||
|
||||
// ─── Governance: proposals ───────────────────────────────────────────────────
|
||||
|
||||
export const proposalTypes = ['bylaw_change','board_action','membership_vote','advisory_poll','recall','budget'] as const
|
||||
export const proposalStatuses = ['draft','open','decided','withdrawn','expired'] as const
|
||||
export const thresholdRules = ['simple_majority','two_thirds','three_fourths','unanimous'] as const
|
||||
export const votePositions = ['yes','no','abstain','recused'] as const
|
||||
|
||||
export const createProposalSchema = z.object({
|
||||
title: z.string().min(1, 'Title is required').max(200, 'Title is too long'),
|
||||
body: z.string().max(20000).default(''),
|
||||
proposal_type: z.enum(proposalTypes).default('advisory_poll'),
|
||||
threshold: z.enum(thresholdRules).optional(),
|
||||
policy_ref_id: z.string().uuid().optional().nullable(),
|
||||
parent_incident_id: z.string().uuid().optional().nullable(),
|
||||
voting_opens_at: flexibleDateTime().optional(),
|
||||
voting_closes_at: flexibleDateTime().optional(),
|
||||
open_immediately: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const openProposalSchema = z.object({
|
||||
proposalId: z.string().uuid('Invalid proposal ID'),
|
||||
voting_closes_at: flexibleDateTime().optional(),
|
||||
})
|
||||
|
||||
export const castVoteSchema = z.object({
|
||||
proposalId: z.string().uuid('Invalid proposal ID'),
|
||||
position: z.enum(votePositions),
|
||||
recusal_reason: z.string().max(1000).optional().nullable(),
|
||||
comment: z.string().max(2000).optional().nullable(),
|
||||
}).refine(
|
||||
v => v.position !== 'recused' || (v.recusal_reason && v.recusal_reason.trim().length > 0),
|
||||
{ message: 'Recusal requires a reason', path: ['recusal_reason'] },
|
||||
)
|
||||
|
||||
export const decideProposalSchema = z.object({
|
||||
proposalId: z.string().uuid('Invalid proposal ID'),
|
||||
})
|
||||
|
||||
export const withdrawProposalSchema = z.object({
|
||||
proposalId: z.string().uuid('Invalid proposal ID'),
|
||||
})
|
||||
|
||||
// ─── Governance: incidents ───────────────────────────────────────────────────
|
||||
|
||||
export const incidentSeverities = ['low','medium','high','critical'] as const
|
||||
export const incidentStatuses = ['received','under_review','decided','appealed','closed'] as const
|
||||
export const incidentUpdateVisibilities = ['reporter_only','all_parties','board_only'] as const
|
||||
|
||||
export const fileIncidentSchema = z.object({
|
||||
title: z.string().min(1, 'Title is required').max(200),
|
||||
body: z.string().min(1, 'Description is required').max(20000),
|
||||
category: z.string().max(50).default('general'),
|
||||
severity: z.enum(incidentSeverities).default('medium'),
|
||||
subjects: z.array(z.string().uuid()).max(20).optional(),
|
||||
is_anonymous: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const updateIncidentStatusSchema = z.object({
|
||||
incidentId: z.string().uuid('Invalid incident ID'),
|
||||
status: z.enum(incidentStatuses),
|
||||
disposition: z.string().max(20000).optional().nullable(),
|
||||
})
|
||||
|
||||
export const addIncidentUpdateSchema = z.object({
|
||||
incidentId: z.string().uuid('Invalid incident ID'),
|
||||
body: z.string().min(1, 'Update body is required').max(20000),
|
||||
visibility: z.enum(incidentUpdateVisibilities).default('all_parties'),
|
||||
})
|
||||
|
||||
export const appealIncidentSchema = z.object({
|
||||
incidentId: z.string().uuid('Invalid incident ID'),
|
||||
title: z.string().min(1).max(200),
|
||||
body: z.string().max(20000).default(''),
|
||||
})
|
||||
|
||||
// Anonymous reporters look their case up with the opaque token they were
|
||||
// given at filing time (48 hex chars today; bound loosely so the format can
|
||||
// evolve without breaking old tokens).
|
||||
export const trackIncidentSchema = z.object({
|
||||
token: z.string().trim().min(16).max(128),
|
||||
})
|
||||
|
||||
// ─── Governance: policies ────────────────────────────────────────────────────
|
||||
|
||||
export const policyStatuses = ['draft','active','deprecated','superseded'] as const
|
||||
|
||||
export const createPolicySchema = z.object({
|
||||
slug: z.string().min(1, 'Slug is required').max(80).regex(/^[a-z0-9-]+$/, 'Slug must be lowercase letters, numbers, and hyphens only'),
|
||||
title: z.string().min(1, 'Title is required').max(200),
|
||||
section_ref: z.string().max(80).optional().nullable(),
|
||||
parent_policy_id: z.string().uuid().optional().nullable(),
|
||||
body_formal: z.string().max(100000).default(''),
|
||||
body_plain: z.string().max(100000).optional().nullable(),
|
||||
effective_at: flexibleDateTime().optional(),
|
||||
})
|
||||
|
||||
export const supersedePolicySchema = z.object({
|
||||
policyId: z.string().uuid('Invalid policy ID'),
|
||||
body_formal: z.string().max(100000).default(''),
|
||||
body_plain: z.string().max(100000).optional().nullable(),
|
||||
section_ref: z.string().max(80).optional().nullable(),
|
||||
title: z.string().min(1).max(200).optional(),
|
||||
adopted_by_proposal_id: z.string().uuid().optional().nullable(),
|
||||
effective_at: flexibleDateTime().optional(),
|
||||
})
|
||||
|
||||
export const updatePolicyStatusSchema = z.object({
|
||||
policyId: z.string().uuid('Invalid policy ID'),
|
||||
status: z.enum(policyStatuses),
|
||||
})
|
||||
Reference in New Issue
Block a user