/* RFC-0565: Zod schemas for the S/Kademlia-hardened DHT site registry and content placement subsystem. Defines machine-checkable contracts for DHT site entries, config, lookup results, placement results, and workshop capacity. These schemas are the operations-layer contract consumed by @warpgogol/site-kernel DHT commands. Do not perform file IO or network operations — pure shape only. Do not define DHT routing protocol logic — that lives in site-kernel. Do not define Ed25519 signing logic — that lives in @warpgogol/werkstatt-shared/passport/dht-sign. RFC-0565: initial DHT schemas (DHTSiteEntry, DHTConfig, DHTLookupResult, DHTPlacementResult, WorkshopCapacity). */ import { z } from "zod"; const kebabRe = /^[a-z0-9]+(-[a-z0-9]+)*$/; const didWebRe = /^did:web:[a-z0-9.-]+#.+$/; const endpointRe = /^.+:\d+$/; export const dhtSiteEntrySchema = z.object({ siteId: z.string().regex(kebabRe, "siteId must be kebab-case, lowercase, latin-only"), owner: z .string() .regex(didWebRe, "owner must be a did:web identifier (did:web:#)") .describe("VC subject id of the site owner (RFC-0558, RFC-0561)"), workshopEndpoint: z.string().regex(endpointRe, "workshopEndpoint must be host:port format"), mirrors: z.array(z.string().url()).default([]), registeredAt: z.string().datetime(), lastUpdated: z.string().datetime(), signature: z.string().min(1, "signature must be a non-empty Ed25519 multibase signature"), }); export const dhtConfigSchema = z.object({ bindAddr: z.string().regex(endpointRe, "bindAddr must be host:port format"), bootstrapNodes: z .array(z.string().regex(endpointRe, "each bootstrapNode must be host:port format")) .default([]), replicationFactor: z.number().int().min(1).max(20).default(5), lookupTimeoutMs: z.number().int().min(100).default(5000), cacheTtlMs: z.number().int().min(1000).default(300000), }); export const dhtLookupResultSchema = z.object({ found: z.boolean(), entry: dhtSiteEntrySchema.nullable(), hops: z.number().int().min(0), latencyMs: z.number().int().min(0), cached: z.boolean().default(false), signatureValid: z.boolean().default(true), diagnostics: z.array(z.string()).optional(), }); export const dhtPlacementReasonSchema = z.enum([ "least-loaded", "nearest", "owner-preference", "local-fallback", ]); export const workshopCapacitySchema = z.object({ workshopId: z.string().min(1), endpoint: z.string().regex(endpointRe, "endpoint must be host:port format"), availableSlots: z.number().int().min(0), storageLimitMb: z.number().int().min(0).default(0), bandwidthLimitMbps: z.number().int().min(0).default(0), activeMissions: z.number().int().min(0).default(0), maxMissions: z.number().int().min(1).default(1), cpuLoad: z.number().min(0).max(1).default(0), diskFree: z.number().int().min(0).default(0), updatedAt: z.string().datetime(), signature: z.string().min(1), }); export const dhtPlacementResultSchema = z.object({ siteId: z.string().regex(kebabRe, "siteId must be kebab-case"), assignedWorkshop: z.string().min(1), reason: dhtPlacementReasonSchema, capacity: workshopCapacitySchema.nullable(), }); export const dhtCacheEntrySchema = dhtSiteEntrySchema.extend({ cachedAt: z.string().datetime(), expiresAt: z.string().datetime(), }); export type DHTSiteEntry = z.infer; export type DHTConfig = z.infer; export type DHTLookupResult = z.infer; export type DHTPlacementReason = z.infer; export type WorkshopCapacity = z.infer; export type DHTPlacementResult = z.infer; export type DHTCacheEntry = z.infer;