/** * The v0.1 wire format for symptom reporting. See PROTOCOL.md. * * NOT EXPORTED from the package surface, for the same reason `symptoms.ts` is not: * nothing transmits on its own (D17, D20), and an exported encoder with no * transmitter invites use it has no semantics for. What lives here is the *shape*, * the degradation rules, and the prohibition the format rests on, so the spec has an * executable counterpart and cannot drift from the vocabulary it describes. * * `examples/protocol/` holds the other side, including a receiver written from * PROTOCOL.md rather than from this file. `protocol/fixtures/v0.1/` is the contract * the two of them have to agree on. * * The encoder deliberately has no field for raw observation, identity, or trust * state. That is the privacy argument made structural rather than documented: * there is nowhere for such data to go. */ import { type SymptomCategory, type SymptomDetail } from './symptoms.ts'; export declare const PROTOCOL_VERSION = "sg/0.1"; /** Major version a receiver must recognise. Section 6, rule 4. */ export declare const PROTOCOL_MAJOR = 0; /** * Field names a conforming message cannot contain. Section 1. * * This is the one place where an extra field is a rejection rather than an * ignore: a receiver that reads and ignores a session id has already written it * to an access log, and that is the guarantee the privacy argument rests on. The * scan runs before every other rule, so a message cannot smuggle a field past it * by failing an earlier check. D70. */ export declare const FORBIDDEN_FIELDS: readonly ["address", "agent", "at", "authorization", "cookie", "count", "counts", "decision", "dwell", "email", "entity", "interval", "intervals", "ip", "mass", "mean", "observations", "path", "paths", "recordId", "recordIds", "records", "remoteAddress", "score", "session", "sessionId", "stage", "timestamp", "timestamps", "trust", "url", "userAgent", "userId", "variance", "verdict"]; /** * Bucketed entity counts. Section 4. * * Ordered labels, never numbers: an exact count is identifying in a small * deployment, and no receiver decision improves from the precision. */ export declare const ENTITY_BUCKETS: readonly ["one", "few", "several", "many"]; export type EntityBucket = (typeof ENTITY_BUCKETS)[number]; /** * Bucket boundaries are sender-side policy and are not part of the spec: * publishing them lets an attacker infer deployment size from a boundary * crossing. These are this implementation's, and an adopter may replace them. */ export declare function bucketEntities(count: number): EntityBucket; export type SymptomEnvelope = { readonly protocol: string; readonly schema: number; readonly reports: readonly { readonly category: SymptomCategory; readonly detail: string; readonly entities: EntityBucket; }[]; }; /** Build a conforming request. Section 3. */ export declare function encodeEnvelope(reports: readonly { detail: SymptomDetail; category: SymptomCategory; entities: number; }[]): SymptomEnvelope; export type DecodedReport = { readonly category: SymptomCategory; /** `undefined` when the detail is newer than this receiver. Rule 1. */ readonly detail: SymptomDetail | undefined; readonly entities: EntityBucket; }; /** Stable failure codes. A receiver answers with one of these, never with prose. */ export type DecodeFailureCode = 'NOT_OBJECT' | 'MISSING_PROTOCOL' | 'UNPARSEABLE_PROTOCOL' | 'UNSUPPORTED_MAJOR' | 'MISSING_SCHEMA' | 'INVALID_SCHEMA' | 'MISSING_REPORTS' | 'FORBIDDEN_FIELD'; export type DecodeResult = { readonly ok: true; readonly reports: readonly DecodedReport[]; readonly dropped: number; } | { readonly ok: false; readonly code: DecodeFailureCode; readonly reason: string; /** The canonical name found, when the code is FORBIDDEN_FIELD. */ readonly field?: string; }; /** * Read an envelope, applying every degradation rule in section 6. * * An unknown *detail* degrades to its category and is kept (rule 1). An unknown * *category* drops that report and is counted (rule 2). A newer schema is accepted * (rule 3). An unknown protocol major rejects the whole message (rule 4). Unknown * fields are ignored by construction, since nothing reads them (rule 5), except a * field named in section 1: those are rejected before any of this runs (D70). */ export declare function decodeEnvelope(value: unknown): DecodeResult; /** * The first forbidden field name anywhere in the message, or . * * Nested on purpose: a report is where an entity key would hide, and a top-level * scan would report a clean message. Names are compared case-insensitively and * reported in their canonical spelling, so two implementations agree on the * answer without agreeing on how they walk the tree. */ export declare function forbiddenFieldIn(value: unknown): string | undefined; //# sourceMappingURL=protocol.d.ts.map