import type { H2AInstanceMirrorBody } from "./build.js"; import { sanitizePresenceForMirror, sanitizeRegistrationForMirror, sanitizeSubagentForMirror, type H2AMirroredRegistration as SanitizedRegistration, type H2AMirroredSession as SanitizedSession, type H2AMirroredSubagentBinding as SanitizedSubagentBinding } from "./sanitize.js"; /** * An ingest-only nominal marker. The send-side types deliberately describe the * shape of a serializable record, so raw local records with extra fields are * structurally assignable to them. Callbacks need a stronger guarantee: only a * value returned by one of this module's narrowers may reach a store writer. * * `declare` keeps the marker type-only: it never becomes a field in a mirrored * record or on disk. The casts below are the sole fabrication points. */ declare const MIRROR_INGEST_NARROWED: unique symbol; export type H2AMirroredRegistration = SanitizedRegistration & { readonly [MIRROR_INGEST_NARROWED]: true; }; export type H2AMirroredSession = SanitizedSession & { readonly [MIRROR_INGEST_NARROWED]: true; }; export type H2AMirroredSubagentBinding = SanitizedSubagentBinding & { readonly [MIRROR_INGEST_NARROWED]: true; }; /** * Members of the mirror body that carry RECORDS — i.e. whose type is an array of * objects. `kind` (a string literal) and `seq` (a number) are excluded by the * conditional, so the map below is not asked to narrow a scalar. * * `-?` strips optionality before the test, so `presence?` and `subagents?` are * required entries in the map: an OPTIONAL member is still a member that lands * records when it is present, and the ratchet must not be dodgeable by adding a * `?`. */ export type MirrorBodyRecordMember = { [K in keyof H2AInstanceMirrorBody]-?: NonNullable extends readonly object[] ? K : never; }[keyof H2AInstanceMirrorBody]; /** * The narrowing applied to each record-carrying body member on arrival. * * This map is not documentation — it is the only route by which a record reaches * a store writer, so deleting an entry breaks the build at its use site rather * than silently disabling a boundary. Each value is the SEND-side function, * reused verbatim; see the module header for why there is no ingest-side copy. */ export declare const INGEST_NARROWERS: { registrations: typeof sanitizeRegistrationForMirror; presence: typeof sanitizePresenceForMirror; subagents: typeof sanitizeSubagentForMirror; }; /** Narrow an arriving registration to the fields the mirror boundary permits. */ export declare function narrowIngestedRegistration(registration: Parameters[0]): H2AMirroredRegistration; /** Narrow an arriving presence record to the fields the mirror boundary permits. */ export declare function narrowIngestedPresence(session: Parameters[0]): H2AMirroredSession; /** Narrow an arriving subagent binding to the fields the mirror boundary permits. */ export declare function narrowIngestedSubagent(binding: Parameters[0]): H2AMirroredSubagentBinding; /** What narrowing removed from one mirror push. */ export interface MirrorNarrowingReport { /** Records that lost at least one key path. Zero ⇒ the sender is up to date. */ readonly records: number; /** Sorted, de-duplicated key paths that were dropped. Names only, never values. */ readonly fields: readonly string[]; } /** * Key paths present in `before` and absent from `after` — i.e. what the plans * actually removed. * * Deliberately an OBSERVATION of the narrowing rather than a second reading of * the plans. Re-deriving "what is withheld" from `MIRROR_*_PLAN` here would * reintroduce the duplication this module exists to avoid, and would miss the * two things the plans do not express as withheld fields at all: nested * composites rebuilt by their own plan (`workspace.path` is withheld by * `WORKSPACE_PLAN`, not by `PRESENCE_PLAN`), and endpoints removed by the scheme * filter, which is not a field classification at all. Diffing catches both for * free and cannot drift from the plans, because it has no opinion of its own. * * ── EXACT WHERE IT MATTERS, INDICATIVE WHERE IT DOES NOT ─────────────────── * * This value is a DIAGNOSTIC — it gates nothing and authorizes nothing — so it is * worth being precise about which parts are load-bearing: * * - **Exact**: whether the list is empty. It is empty iff narrowing removed no * key path, which is the property the operator signal rests on. Object keys * and array-length shrinkage are both compared directly. * - **Exact**: object key paths, at every depth (`workspace.path`, * `launchContext`). * - **Indicative**: an index inside an array path. Elements are compared * positionally, and the endpoint scheme filter REMOVES elements, so after a * filtered element the survivors shift left and `endpoints[0].x` names a * position, not a stable address. The length-shrink marker `endpoints[]` is * exact; the per-element paths after it are a hint. Today this is moot — * `ENDPOINT_PLAN` withholds nothing, so a surviving element loses no keys — * and it is written down so that stops being an accident. * * `null`/`undefined` in `before` are not reported: `applyPlan` omits them by * design, so treating an absent-because-empty field as "dropped" would report * every optional field a sender left unset and drown the real signal. */ export declare function droppedKeyPaths(before: unknown, after: unknown, prefix?: string): string[]; /** * Accumulates {@link MirrorNarrowingReport} across the records of one push. * * Stateful on purpose and scoped to a single envelope: `accept.ts` is otherwise * pure, and this keeps the impurity in one named, obviously-per-request object * instead of threading counters through the accept logic. */ export declare function createNarrowingTally(): { note: (before: unknown, after: unknown) => void; report: () => MirrorNarrowingReport; }; export {}; //# sourceMappingURL=ingest.d.ts.map