import type { RawPacket } from "../../index.ts"; /** * The JSON scalar type names a {@link SchemaPrimitiveNode} declares. Closed by construction, because JSON has exactly these scalar types - which is what lets an * unrecognized type tag in a manifest file be a validation failure rather than silently accepted data. * * @category CLI */ export type JsonTypeName = "boolean" | "null" | "number" | "string"; /** * The JSON shapes an observed value can take: the scalars a manifest declares, plus the two container shapes. What a novelty finding reports about the value it found. * * @category CLI */ export type ObservedJsonType = "array" | "object" | JsonTypeName; /** * A declared array member. `element` describes what the array holds. * * @category CLI */ export interface SchemaArrayNode { element: SchemaNode; kind: "array"; } /** * A member whose keys are data rather than schema - a `Record` such as a camera's per-zone status map. Every value is described by `values`, and no key is * ever novelty, because the shape declares that any key is expected. * * @category CLI */ export interface SchemaDictionaryNode { kind: "dictionary"; values: SchemaNode; } /** * A declared object shape. `fields` names what the library knows about; `open` records whether the declaring interface carries an index signature, which the checker * resolves including signatures inherited from a base type. * * `open` is signal metadata, never a suppression: an undeclared field on an open shape is still reported, because the library having left room for unknown keys is not * the same as the library knowing what arrived. It ranks the finding rather than hiding it - novelty on a closed shape is the stronger signal. * * @category CLI */ export interface SchemaObjectNode { fields: Record; kind: "object"; open: boolean; } /** * A member the library carries but does not describe - a collection typed as raw JSON. Nothing inside it can be novelty, because nothing inside it was ever declared. * * @category CLI */ export interface SchemaOpaqueNode { kind: "opaque"; } /** * A declared scalar member. `types` is the set of JSON scalar types the declaration admits, so a nullable string arrives as both `"null"` and `"string"`. * * @category CLI */ export interface SchemaPrimitiveNode { kind: "primitive"; types: JsonTypeName[]; } /** * One node in a record's described shape. The tag says which kind it is, and each kind carries only what that kind needs. * * @category CLI */ export type SchemaNode = SchemaArrayNode | SchemaDictionaryNode | SchemaObjectNode | SchemaOpaqueNode | SchemaPrimitiveNode; /** * The modelKey vocabulary, carried as the nested tiers the library declares: the id-keyed device collections, the device-addressable keys, every key the reducer folds * into state, and every key the controller is known to emit at all. * * @category CLI */ export interface ModelKeyVocabulary { collection: string[]; device: string[]; known: string[]; state: string[]; } /** * The generated manifest. `records` is keyed by bootstrap member name and holds the tree for what that member carries - one record for the NVR singleton, the element * record for a collection. `recordKeys` resolves a realtime frame's modelKey to its `records` key, so a consumer never re-derives the protocol's pluralization. * `version` is the package version that generated the file, which is what makes version skew visible in anything the manifest is used to produce. * * @category CLI */ export interface SchemaManifest { eventTypes: string[]; modelKeys: ModelKeyVocabulary; recordKeys: Record; records: Record; version: string; } /** * A top-level bootstrap key the library does not declare at all - a whole collection or member the controller has that this version has never heard of. * * @category CLI */ export interface UnknownCollectionFinding { collection: string; kind: "unknownCollection"; valueType: ObservedJsonType; } /** * A wire event type outside the library's recognized vocabulary. Observable only on the raw rail: an unrecognized type classifies to `null`, so it never reaches the * typed event firehose. * * @category CLI */ export interface UnknownEventTypeFinding { eventType: string; kind: "unknownEventType"; } /** * A field on a known record that the library does not declare. `path` is dotted from the record root, with `[]` for an array hop and `*` for a dictionary key; `open` * carries whether the shape it appeared on admits unknown keys. * * @category CLI */ export interface UnknownFieldFinding { collection: string; kind: "unknownField"; observedType: ObservedJsonType; open: boolean; path: string; recordId: string; } /** * A record whose self-declared modelKey is outside the library's known vocabulary - a device class this version cannot place. Reported per record, because a brand-new * class never reaches the reduced state and so appears nowhere else: losing per-unit identity here would lose every unit past the first. * * @category CLI */ export interface UnknownModelKeyFinding { kind: "unknownModelKey"; modelKey: string; recordId: string; } /** * Something the controller sent that this version of the library does not describe. Each kind names its own subject, so a consumer reads the finding without * re-deriving what it is about. * * @category CLI */ export type NoveltyFinding = UnknownCollectionFinding | UnknownEventTypeFinding | UnknownFieldFinding | UnknownModelKeyFinding; /** * A few records from a bootstrap member no shape is declared for, and how many the controller actually sent. * * A novelty finding says that something is unmodeled; this says what it looks like. `kept` holds the exemplars, `seen` holds the size of the collection they were drawn * from, so a reader knows whether three records are the whole story or the first three of forty. * * @category CLI */ export interface UnmodeledSnapshot { collection: string; kept: unknown[]; seen: number; } /** * The identity of a finding - what makes two observations the same finding rather than two. * * The key is the JSON serialization of an ordered tuple rather than a joined string, because a delimiter-joined key collides whenever a field's own text contains the * delimiter, and record ids and field paths are exactly the values that would. * * @param finding - The finding to identify. * * @returns A stable key equal for two findings that describe the same thing, and different otherwise. * * @category CLI */ export declare function noveltyKey(finding: NoveltyFinding): string; /** * Diff a parsed bootstrap against the manifest, reporting every top-level key the library does not declare and every undeclared field inside the ones it does. * * The bootstrap arrives as `unknown` and is read structurally throughout. Casting it to the library's bootstrap type would assert the very thing this function exists * to check, so the walk trusts nothing about its shape. * * A `records` entry describes one record, while a bootstrap member holds either one record (the NVR singleton) or a collection of them, so an array member is fanned * across its elements here at the top level and a single object member is handed straight through. Below that, each node kind describes exactly one shape. * * Declared-but-absent is not novelty: optional fields and capability nulls are ordinary, so the walk reports only what arrived and was not declared. * * @param manifest - The loaded manifest to check against. * @param bootstrap - A parsed bootstrap document. * * @returns Every novelty finding, in the order the bootstrap presented its keys. * * @category CLI */ export declare function diffBootstrap(manifest: SchemaManifest, bootstrap: unknown): NoveltyFinding[]; /** * Take exemplar records from every bootstrap member the manifest describes no shape for. * * {@link diffBootstrap} reports that a collection is unmodeled; this carries the evidence of what is in it. Both cases matter: a member the manifest has never heard of * at all, and one the library carries as raw JSON without describing. Neither can produce a field-level finding - there is no declared shape to be undeclared against - * so without a sample of the records themselves, the very device class a capture was run to understand contributes nothing a reader could model it from. * * A few records rather than the collection: exemplars show which fields vary between units and which are fixed, while a roster only inflates the bundle. `seen` records * what was elided, so a truncated snapshot never reads as a complete one. * * The bootstrap arrives as `unknown` and is read structurally, the same posture {@link diffBootstrap} takes and for the same reason: casting it to the library's own * bootstrap type would assert exactly what a document collected for its unmodeled parts cannot be assumed to satisfy. * * The exemplars are the controller's records verbatim, so a caller that publishes them scrubs them: `ufp capture` scrubs the assembled bundle in one pass, which puts * these records under the same replacement memory as the frames and the inventory and keeps every cross-reference between them intact. * * @param manifest - The loaded manifest to check against. * @param bootstrap - A parsed bootstrap document. * @param limit - How many records to keep from each collection. * * @returns One entry per unmodeled member, in the order the bootstrap presented its keys. A member holding an empty collection contributes none. * * @category CLI */ export declare function snapshotUnmodeled(manifest: SchemaManifest, bootstrap: unknown, limit: number): UnmodeledSnapshot[]; /** * Diff one record against the manifest - the realtime counterpart of {@link diffBootstrap}, for a frame carrying a single record's payload. * * A modelKey outside the known vocabulary is itself the finding, reported per record so each physical unit of an unrecognized class is its own entry. A known modelKey * with no record tree (the activity channel, a recognized-but-unreduced class) yields nothing: the library recognizes it and describes no shape for it, so there is * nothing to be novel against. * * @param manifest - The loaded manifest to check against. * @param subject - The record's self-declared modelKey, its payload, and the id the frame header attributed it to. * * @returns Every novelty finding the record produced. * * @category CLI */ export declare function diffRecord(manifest: SchemaManifest, subject: { modelKey: string; record: unknown; recordId: string; }): NoveltyFinding[]; /** * Check a wire event type against the manifest's recognized vocabulary. * * @param manifest - The loaded manifest to check against. * @param eventType - The `type` string from an `event`-modelKey payload. * * @returns A single finding when the type is unrecognized, or an empty array when it is known. * * @category CLI */ export declare function diffEventType(manifest: SchemaManifest, eventType: string): NoveltyFinding[]; /** * Read the wire event type off a raw packet. * * An occurrence's kind lives in its payload's `type` string, and only `event`-modelKey packets carry one. This is the CLI's single reader of that wire field, and it * lives beside the vocabulary the value gets checked against so the two cannot drift apart. * * @param packet - Any decoded raw packet. * * @returns The event type, or `undefined` for a packet that is not an activity occurrence or whose payload does not name one. * * @category CLI */ export declare function eventTypeOf(packet: RawPacket): string | undefined; /** * Parse and validate a manifest document. * * The file is validated rather than trusted, the same posture the credentials loader takes at its own JSON boundary. A production manifest is complete by construction * - the generator builds it from the library's types - but this loader has no way to know it is reading one, and a half-written or hand-edited file failing loudly here * is far better than a diff quietly reporting nothing because a record tree went missing. * * @param text - The manifest file's contents. * @param source - Where the text came from, for the error messages. * * @returns The validated manifest. * * @throws {@link CliError} when the text is not valid JSON, or is valid JSON that is not a well-formed manifest. * * @category CLI */ export declare function parseSchemaManifest(text: string, source: string): SchemaManifest; /** * Load the generated schema manifest from disk. * * @param location - The manifest file to read; defaults to the one this package ships beside its compiled code. * * @returns The validated manifest. * * @throws {@link CliError} when the file is missing (with the hint that the build produces it), unreadable, or not a well-formed manifest. * * @category CLI */ export declare function loadSchemaManifest(location?: URL | string): Promise; //# sourceMappingURL=manifest.d.ts.map