136 lines
4.2 KiB
TypeScript
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();
|