Files
suaveplan/packages/networking/drive/drive-mesh-protocol/src/schemas/envelope.schema.ts
2026-05-27 23:47:53 +01:00

136 lines
4.2 KiB
TypeScript

/**
* Zod boundary schemas for every wire-format payload exchanged by
* `@suaveplan/drive-mesh-protocol`. The codec modules invoke
* `safeParse` on the decoded JSON before any logic touches the
* payload, satisfying DMP-21's "schema validation before payload
* processing" mandate.
*/
import * as z from "zod";
/** Hex-encoded 32-byte file id; matches the format produced by drive-plugin-chunks. */
export const FileIdSchema = z
.string()
.min(1)
.max(128)
.regex(/^[0-9a-f]+$/i, "fileId must be lowercase hex");
/** Five-priority tier (DMP-06). */
export const ChunkRequestPrioritySchema = z.enum([
"critical",
"high",
"normal",
"low",
"bulk",
]);
/** Discriminated union for the three request forms (DMP-01). */
export const ChunkRequestSchema = z.discriminatedUnion("kind", [
z.object({ kind: z.literal("whole") }),
z.object({
kind: z.literal("specific"),
// Empty arrays are caught by the explicit DMP-03 / DMP-22 gates in
// the codec; keeping the schema permissive lets those throws fire.
chunkIds: z.array(z.number().int().nonnegative()),
}),
z.object({
kind: z.literal("bitset"),
bytes: z.instanceof(Uint8Array),
}),
]);
export const ChunkRequestPayloadSchema = z
.object({
fileId: FileIdSchema,
request: ChunkRequestSchema,
priority: ChunkRequestPrioritySchema,
// The hard cap is enforced by the codec with `MAX_CHUNKS_EXCEEDED`
// so consumers see the spec-mandated DMP-07 code; Zod only checks
// that maxChunks is a positive integer.
maxChunks: z.number().int().min(1).optional(),
})
.strict();
/** Hard cap for `maxChunks` per DMP-07. */
export const MAX_CHUNKS_HARD_CAP = 1024 as const;
/** Default `maxChunks` per DMP-07. */
export const MAX_CHUNKS_DEFAULT = 64 as const;
/** Threshold above which a `bitset` request is more compact than `specific` (DMP-05). */
export const SELECT_FORM_BITSET_THRESHOLD = 64 as const;
/** Default 1-in-100 sampling for healthy peers (DMP-17). */
export const SAMPLE_RATE_HEALTHY = 100 as const;
/** Default 1-in-10 sampling for suspect peers (DMP-17). */
export const SAMPLE_RATE_SUSPECT = 10 as const;
/** Trust threshold below which a peer is "suspect" (DMP-17). */
export const TRUST_SUSPECT_THRESHOLD = 0.95 as const;
/** Miss-rate floor below which trust decay is dormant (DMP-15). */
export const TRUST_MISS_RATE_FLOOR = 0.05 as const;
/** Multiplier on (missRate - floor) to compute P(skip) (DMP-15). */
export const TRUST_DECAY_SLOPE = 20 as const;
/** Default throttle window between outgoing advertisements (DMP-10). */
export const ADVERTISE_THROTTLE_MS_DEFAULT = 1_000 as const;
/** Hard upper bound on the throttle window. */
export const ADVERTISE_THROTTLE_MS_MAX = 60_000 as const;
/** Generation wrap detection threshold (DMP-09). */
export const GENERATION_WRAP_THRESHOLD = 2 ** 31;
export const ChunkAdvertisePayloadSchema = z
.object({
fileId: FileIdSchema,
presence: z.instanceof(Uint8Array),
totalChunks: z.number().int().min(1),
generation: z.number().int().min(0).max(0xff_ff_ff_ff),
timestamp: z.number().int().min(0),
})
.strict();
export const ChunkBytesSchema = z
.object({
chunkId: z.number().int().nonnegative(),
bytes: z.instanceof(Uint8Array),
})
.strict();
export const ChunkResponsePayloadSchema = z
.object({
fileId: FileIdSchema,
chunks: z.array(ChunkBytesSchema),
presence: z.instanceof(Uint8Array),
generation: z.number().int().min(0).max(0xff_ff_ff_ff),
})
.strict();
export const ChunkErrorCodeSchema = z.enum([
"NOT_FOUND",
"ACCESS_DENIED",
"BANDWIDTH_EXCEEDED",
"GENERATION_STALE",
"INTERNAL",
]);
export const ChunkErrorPayloadSchema = z
.object({
fileId: FileIdSchema,
correlationId: z.string().min(1),
code: ChunkErrorCodeSchema,
chunkId: z.number().int().nonnegative().optional(),
retryAfterMs: z.number().int().nonnegative().optional(),
responderGeneration: z
.number()
.int()
.min(0)
.max(0xff_ff_ff_ff)
.optional(),
})
.strict();