@vostride/agent-qa-analytics 0.0.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/port.ts","../src/transports.ts","../src/masking-seam.ts","../src/posthog-project.ts","../src/compose.ts","../src/events.ts","../src/identity.ts","../src/run-subscriber.ts"],"sourcesContent":["import type { AgentQaAnalyticsEvent } from './events.js'\nimport type { AnalyticsPort } from './port.js'\n\n// The `@vostride/agent-qa-analytics` public surface (Phase 398 plan 03, D-17 · ANLY-01).\n//\n// WHAT IS DELIBERATELY ABSENT. There is no `getAnalytics()`, no `captureAnalytics()`, no\n// `flushAnalytics()` — no way to reach telemetry without having been handed a port. The v1 module\n// published exactly those three, and the consequence was that any file in the process could emit,\n// carrying whatever privacy decision the first constructor happened to make. A host that wants to\n// emit here has to name this package at its composition root and hold the object.\n//\n// The ownership contract is NOT re-exported: `pnpm validate:publish` refuses a dist that ships it,\n// and `packages/arch` imports it by relative path, which is the sanctioned route.\n\nexport { composeAnalytics } from './compose.js'\nexport type {\n AnalyticsOptOutConfig,\n AnalyticsOptOutReason,\n AnalyticsTransportKind,\n ComposeAnalyticsOptions,\n ComposedAnalytics,\n} from './compose.js'\n\nexport { createAnalyticsPort } from './port.js'\nexport type {\n AnalyticsCapturePayload,\n AnalyticsEventEnvelope,\n AnalyticsIdentity,\n AnalyticsIdentityResolver,\n AnalyticsPort,\n AnalyticsPortOptions,\n AnalyticsPropertyValue,\n AnalyticsTransport,\n} from './port.js'\n\n// The closed union, its vocabularies, and the range guard. `assertNoUnconstrainedStrings` is\n// published rather than kept private for the same reason the two MCP predicates are: it is a pure\n// walk over schemas that grants a consumer no capability, and a later phase adding a member wants\n// to range its own union with the SAME checker rather than a second, kinder one.\nexport {\n AgentQaAnalyticsEventSchema,\n ANALYTICS_AGENT_PRODUCTS,\n ANALYTICS_CHAT_END_REASONS,\n ANALYTICS_DAEMON_JOB_KINDS,\n ANALYTICS_DAEMON_JOB_OUTCOMES,\n ANALYTICS_EMITTING_HOSTS,\n ANALYTICS_EVENT_MEMBERS,\n ANALYTICS_EVENT_NAMES,\n ANALYTICS_EVENT_SCHEMA_BY_NAME,\n ANALYTICS_FAILURE_CATEGORIES,\n ANALYTICS_INIT_MODES,\n ANALYTICS_INIT_OUTCOMES,\n ANALYTICS_KNOWN_ID_MARKER,\n ANALYTICS_MCP_ERROR_CATEGORIES,\n ANALYTICS_MCP_SERVER_STATES,\n ANALYTICS_MCP_TOOL_NAMES,\n ANALYTICS_MCP_TOOL_STATUSES,\n ANALYTICS_MCP_TRANSPORTS,\n ANALYTICS_PLATFORMS,\n ANALYTICS_RUN_STATUSES,\n ANALYTICS_RUNNERS,\n ANALYTICS_RUNTIME_CONTEXTS,\n ANALYTICS_SUITE_EXECUTION_MODES,\n ANALYTICS_TRIGGER_SOURCES,\n ANALYTICS_VOCABULARY_META_KEY,\n analyticsRunId,\n analyticsSuiteId,\n analyticsTestId,\n assertNoUnconstrainedStrings,\n RETIRED_V33_EVENT_NAMES,\n} from './events.js'\nexport type { AgentQaAnalyticsEvent, AnalyticsEventName } from './events.js'\n\n// The send seam. `maskEventForSend` is published so a later transport (a second sink, a batching\n// relay) composes the SAME seam rather than writing its own. The host hands it the declared-secret\n// mask.\nexport { maskEventForSend } from './masking-seam.js'\nexport type { AnalyticsMaskingContext } from './masking-seam.js'\n\n// The anonymous identity store, re-homed from `core` (plan 06, D-17).\n// The store functions are published rather than hidden behind `resolveAnalyticsIdentity` because a\n// host that wants to REPORT the identity (a doctor command, a `--verbose` line) should read the\n// same record the resolver reads instead of re-deriving the path its own way.\nexport {\n ANALYTICS_USER_ID_PREFIX,\n ANALYTICS_USER_ID_WORDS,\n generateAnalyticsUserId,\n getAnalyticsIdentityPath,\n readAnalyticsIdentity,\n resolveAnalyticsAgentProduct,\n resolveAnalyticsIdentity,\n} from './identity.js'\nexport type {\n AnalyticsAgentProduct,\n AnalyticsIdentityStore,\n AnalyticsRuntimeContext,\n ResolveAnalyticsIdentityOptions,\n ResolvedAnalyticsIdentity,\n} from './identity.js'\n\n// The run-completion bus subscriber. Its `{ handler, flush }` shape is the one plan 10 attaches at\n// the CLI root, unchanged from the module it replaces so the rewire is an import and nothing else.\nexport { createAnalyticsRunSubscriber } from './run-subscriber.js'\nexport type {\n AnalyticsRunner,\n AnalyticsRunStandardProperties,\n AnalyticsRunSubscriber,\n AnalyticsRunSubscriberOptions,\n AnalyticsTriggerSource,\n} from './run-subscriber.js'\n\nexport { createPosthogTransport } from './transports.js'\nexport type {\n PosthogAnalyticsClient,\n PosthogTransportFactory,\n PosthogTransportOptions,\n} from './transports.js'\n\n// The port as a HOST should hold it: parameterised by the closed union, so `capture` refuses an\n// event that is not a member at the call site, before any parse. `AnalyticsPort` on its own is the\n// structural floor the transports are written against; this is the type with the union in it.\nexport type AgentQaAnalyticsPort = AnalyticsPort<AgentQaAnalyticsEvent>\n","// The analytics port (Phase 398 plan 03, D-17 / D-19 · ANLY-01).\n//\n// WHAT SURVIVED THE RE-DERIVATION, and what did not. The v1 module's transport split — a small\n// `capture`/`flush` interface with an injectable factory, every call wrapped best-effort — was the\n// one part of it that was right, and it is carried over here deliberately. What is NOT carried:\n// the `if (this.disabled) return` guard inside `capture()`, the per-instance privacy decision taken\n// in the constructor, the module-level `defaultAnalyticsService` singleton, and the ambient\n// `captureAnalytics`/`flushAnalytics` helpers that let any caller in the process reach telemetry\n// without ever naming it as a dependency. The opt-out decision lives in\n// `compose.ts` and nowhere else; there is no ambient anything here.\n//\n// WHY `capture` RETURNS `void`. A host emits an event on a path it does not want to await — a run\n// finishing, a tool call returning. Making the caller await a telemetry write is how telemetry\n// acquires the power to slow down or fail the thing it is measuring. The port takes the event,\n// returns immediately, and keeps the resulting work internally so `flush()` has something to wait\n// for. Nothing here throws: the port is the best-effort boundary, and the host side of it observes\n// no exception even when every transport call rejects (T-398-08).\n\n/**\n * The shape every member of the closed event union satisfies.\n *\n * The port is written against this rather than against the union itself so the transport layer\n * carries no knowledge of which events exist; `AnalyticsPort` is parameterised by the union at the\n * package surface, which is what keeps a non-member event from reaching `capture` at a call site.\n */\nexport interface AnalyticsEventEnvelope {\n readonly name: string\n readonly properties: Readonly<Record<string, AnalyticsPropertyValue>>\n}\n\nexport type AnalyticsPropertyValue =\n | string\n | number\n | boolean\n | undefined\n | Readonly<Record<string, string | number | boolean | undefined>>\n\n/** What a transport is handed: the event plus the identity it is attributed to. */\nexport interface AnalyticsCapturePayload {\n readonly distinctId: string\n readonly isInternal?: boolean\n readonly event: AnalyticsEventEnvelope\n}\n\n/**\n * The wire side. Implementations may be synchronous (test sinks) or asynchronous (PostHog);\n * the port normalises both and swallows either one's failures.\n *\n * `flush` and `shutdown` are separate on purpose. A host flushes at natural boundaries — a run\n * ending, a command returning — and shuts down once, at exit; collapsing them means either a\n * process that leaks a live client or a mid-session teardown of the thing still being used.\n */\nexport interface AnalyticsTransport {\n capture(payload: AnalyticsCapturePayload): void | Promise<void>\n flush(): void | Promise<void>\n shutdown(): void | Promise<void>\n}\n\n/** Resolves the anonymous distinct id an event is attributed to. */\nexport type AnalyticsIdentityResolver = () => AnalyticsIdentity | Promise<AnalyticsIdentity>\n\nexport interface AnalyticsIdentity {\n readonly distinctId: string\n readonly isInternal?: boolean\n}\n\nexport interface AnalyticsPort<TEvent extends AnalyticsEventEnvelope = AnalyticsEventEnvelope> {\n capture(event: TEvent): void\n flush(): Promise<void>\n shutdown(): Promise<void>\n}\n\nexport interface AnalyticsPortOptions {\n /** Absent when opted out or keyless: the port then drops every capture (447-04, D-08). */\n readonly transport?: AnalyticsTransport\n readonly identity: AnalyticsIdentityResolver\n /**\n * How long `shutdown` will wait for the transport before releasing its caller (T-398-35 / WR-02).\n *\n * A host calls `shutdown` on its way OUT — at a terminal exit, in a `finally`, on the error path.\n * Whatever it waits for is time a person spends looking at a terminal that has already finished\n * its work. posthog-node's own shutdown defaults to 30 000 ms and races retriable flushes against\n * it, so an unreachable network turns a completed run into half a minute of dead prompt.\n *\n * The default is deliberately small. This is the LAST thing a process does; delivering the final\n * batch is worth a couple of seconds and no more.\n */\n readonly shutdownTimeoutMs?: number\n}\n\n/** See `AnalyticsPortOptions.shutdownTimeoutMs`. */\nexport const DEFAULT_ANALYTICS_SHUTDOWN_TIMEOUT_MS = 2_000\n\n/**\n * The only `AnalyticsPort` implementation, and the best-effort boundary itself.\n *\n * EVERY method is wrapped. That is not defensive habit: this object is held by a CLI process, a\n * chat session and a daemon, and an unhandled rejection out of a telemetry write would surface as\n * the host crashing on something the user never asked for. Losing in-flight events instead is the\n * trade the whole package is built around.\n */\nclass BestEffortAnalyticsPort<TEvent extends AnalyticsEventEnvelope> implements AnalyticsPort<TEvent> {\n private readonly transport: AnalyticsTransport | undefined\n private readonly identity: AnalyticsIdentityResolver\n private readonly shutdownTimeoutMs: number\n private inFlight: Promise<void>[] = []\n private capturedSinceFlush = 0\n private readonly sending = new Set<Promise<void>>()\n\n constructor(options: AnalyticsPortOptions) {\n this.transport = options.transport\n this.identity = options.identity\n this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? DEFAULT_ANALYTICS_SHUTDOWN_TIMEOUT_MS\n }\n\n /**\n * There is NO try/catch here, and the absence is deliberate — measured, not assumed.\n *\n * 2026-08-17 (Phase 398 plan 03, mutation leg M6). This method was first written with the v1\n * module's `try { ... } catch {}` wrapper around the body. Deleting that wrapper outright left\n * every cell green, including the one named \"never throws into the host\", because there is no\n * synchronous throw for it to catch: an `async` function body's throw — including one out of the\n * identity resolver, the only caller-supplied code reachable from here — becomes a REJECTED\n * PROMISE, never a synchronous exception. The wrapper read like the boundary and guarded nothing.\n *\n * The real boundary is the `.catch` below, attached in the same tick the promise is created so a\n * rejection cannot become an unhandled rejection even if `flush()` is never called — which is the\n * normal case for a host that emits on a path it does not await. Leg M8 removes that `.catch` and\n * reds; that is the guard this method actually has.\n */\n capture(event: TEvent): void {\n const transport = this.transport\n if (transport === undefined) return\n this.capturedSinceFlush += 1\n this.inFlight.push(\n (async () => {\n const identity = await this.identity()\n await transport.capture({\n distinctId: identity.distinctId,\n ...(identity.isInternal === true ? { isInternal: true } : {}),\n event,\n })\n })().catch(() => {}),\n )\n }\n\n /**\n * EDGE ANLY-01/empty. A flush with nothing captured touches the transport at ALL — not \"makes no\n * network call\", but makes no call. The distinction matters because `flush` on the PostHog\n * transport is a send, so a host that flushes on every command return would otherwise poke the\n * wire once per invocation whether or not anything happened. Zero captures in, zero calls out,\n * and the promise still resolves.\n *\n * 2026-10-21 (447-07, D-20): flush awaits the local captures and STARTS the send without awaiting\n * it, so a stalled network never holds a run's verdict or a command's return; `shutdown` waits\n * for the started sends inside its bound.\n */\n async flush(): Promise<void> {\n const transport = this.transport\n if (transport === undefined) return\n try {\n const pending = this.inFlight\n const captured = this.capturedSinceFlush\n this.inFlight = []\n this.capturedSinceFlush = 0\n\n await Promise.all(pending)\n if (captured === 0) return\n const send = (async () => {\n await transport.flush()\n })().catch(() => {})\n this.sending.add(send)\n void send.finally(() => this.sending.delete(send))\n } catch {\n // Best-effort.\n }\n }\n\n /**\n * Shutdown always reaches the transport, even with an empty queue: it releases whatever the\n * transport holds (a PostHog client owns timers), and \"release nothing\" is cheap while \"leak a\n * timer because the session happened to be silent\" keeps a process alive.\n */\n async shutdown(): Promise<void> {\n const transport = this.transport\n if (transport === undefined) return\n // THE TIME BOUND (T-398-35 / review WR-02). Everything below is still best-effort; what the\n // race adds is that \"best effort\" cannot mean \"forever\". The transport is ASKED to wind down\n // and given its budget — the bound releases the CALLER, it does not skip the drain, and events\n // still in flight when the budget expires are lost exactly as the package's stated trade says.\n //\n // The timer is `unref`'d: a bound that itself keeps the event loop alive would hold the very\n // exit it exists to release.\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n await Promise.race([\n (async () => {\n await this.flush()\n await Promise.all([...this.sending])\n await transport.shutdown()\n })(),\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, this.shutdownTimeoutMs)\n if (typeof timer.unref === 'function') timer.unref()\n }),\n ])\n } catch {\n // Best-effort.\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n }\n}\n\nexport function createAnalyticsPort<TEvent extends AnalyticsEventEnvelope = AnalyticsEventEnvelope>(\n options: AnalyticsPortOptions,\n): AnalyticsPort<TEvent> {\n return new BestEffortAnalyticsPort<TEvent>(options)\n}\n","// The PostHog transport (Phase 398 plan 03, D-17 / D-19 · ANLY-01). It reads no environment and\n// decides nothing: the opt-out was answered in `compose.ts`, and the answer is whether a transport\n// exists at all (447-04, D-08). There is no module-level state in this file.\n\nimport { PostHog } from 'posthog-node'\nimport type { PostHogOptions } from 'posthog-node'\nimport { maskEventForSend } from './masking-seam.js'\nimport type { AnalyticsMaskingContext } from './masking-seam.js'\nimport type { AnalyticsCapturePayload, AnalyticsTransport } from './port.js'\n\n/**\n * The subset of the PostHog client this transport uses.\n *\n * `flush` is REQUIRED, not optional, and that is the whole content of the fix (398-15, CR-01): the\n * interface previously declared only `capture`/`shutdown`, so the type system could not express the\n * correct per-boundary call and the transport reached for `shutdown` instead. An optional member\n * with a shutdown fallback would reinstate the same defect behind a spelling. posthog-node 5.33.2\n * exposes both. The contract being honoured is stated at `port.ts:49-51`.\n */\nexport interface PosthogAnalyticsClient {\n capture(input: {\n distinctId: string\n event: string\n properties: Record<string, unknown>\n }): void\n flush(): void | Promise<void>\n shutdown(): void | Promise<void>\n}\n\nexport interface PosthogTransportOptions {\n readonly projectKey: string\n readonly host: string\n readonly disableGeoip?: boolean\n /**\n * posthog-node's own `fetch` option, passed straight through.\n *\n * 2026-08-17 (Phase 398 plan 03): this exists for plan 06's sandboxed-sink arm of the zero-network\n * differential. posthog-node 5.33.2 sends through `this.options.fetch ?? fetch`, so injecting here\n * observes the SHIPPED send path rather than a parallel one built for the test — which is the\n * difference between measuring the product and measuring the fixture.\n */\n readonly fetch?: PostHogOptions['fetch']\n /** The host's declared-secret mask, applied at the send seam. Absent means no masking. */\n readonly maskingContext?: AnalyticsMaskingContext\n /** An already-constructed client, for tests that need to observe construction itself. */\n readonly client?: PosthogAnalyticsClient\n}\n\nclass PosthogTransport implements AnalyticsTransport {\n private readonly client: PosthogAnalyticsClient\n private readonly maskingContext: AnalyticsMaskingContext | undefined\n\n constructor(options: PosthogTransportOptions) {\n this.maskingContext = options.maskingContext\n this.client = options.client ?? new PostHog(options.projectKey, {\n host: options.host,\n disableGeoip: options.disableGeoip ?? false,\n ...(options.fetch ? { fetch: options.fetch } : {}),\n })\n }\n\n /**\n * THE SEND CHOKE POINT. Masking is the first thing that happens here and it happens exactly once\n * (a cell counts the call sites in shipped source and refuses any number but one).\n *\n * `flush`/`shutdown` are not second seams: everything they drain arrived through this method, so\n * masking here ranges the whole queue. `is_internal` is added AFTER the mask because it is a\n * boolean this file writes — there is no caller string in it to mask. It is an event property,\n * sent only when true (447-07, D-13).\n */\n capture(payload: AnalyticsCapturePayload): void {\n let masked: AnalyticsCapturePayload\n try {\n masked = maskEventForSend(payload, this.maskingContext)\n } catch {\n // 2026-10-14 (441-08, E3): a mask that cannot run drops the event; it is never sent unmasked.\n return\n }\n const properties: Record<string, unknown> = { ...masked.event.properties }\n if (masked.isInternal === true) {\n properties.is_internal = true\n }\n\n this.client.capture({\n distinctId: masked.distinctId,\n event: masked.event.name,\n properties,\n })\n }\n\n async flush(): Promise<void> {\n await this.client.flush()\n }\n\n async shutdown(): Promise<void> {\n await this.client.shutdown()\n }\n}\n\nexport function createPosthogTransport(options: PosthogTransportOptions): AnalyticsTransport {\n return new PosthogTransport(options)\n}\n\n/** The factory shape `composeAnalytics` calls, so a cell can spy on whether it was called at all. */\nexport type PosthogTransportFactory = (options: PosthogTransportOptions) => AnalyticsTransport\n","// THE SEND SEAM (Phase 398 plan 06, D-17 / D-19 · ANLY-01 · T-398-06).\n//\n// THERE IS ONE CHOKE POINT AND IT IS THE TRANSPORT'S `capture`. Not per-event-site masking, not a\n// helper each emitter is expected to remember: every outgoing payload passes through\n// `maskEventForSend` in `transports.ts`, and a cell counts the call sites in shipped source and\n// refuses any number but one. Masking that has to be REMEMBERED is masking that will be forgotten\n// exactly once, in the payload that mattered.\n//\n// STRUCTURE-AWARE, AND BEFORE SERIALIZATION. This is the 396 escape-blind lesson applied. A masker\n// that runs on the serialized JSON string searches for the raw secret value — but serialization has\n// already rewritten it: a secret containing a double quote and a newline is on the wire as\n// `\\\"` and `\\n`, so the raw-value search finds nothing and reports success. Walking the payload\n// structure and replacing STRING LEAVES happens before any of that rewriting exists, so there is no\n// encoding for the value to hide inside. The cells prove the difference rather than assert it: the\n// control cell sends the same payload unmasked and pins that the ESCAPED form is what arrives.\n//\n// WHAT IS DELIBERATELY NOT MASKED, and why each is a decision rather than an oversight:\n//\n// - THE EVENT NAME. It is drawn from a closed literal set (`ANALYTICS_EVENT_NAMES`), so it cannot\n// carry a user's data — every possible value of it is already written in this repo's source.\n// Rewriting it could therefore only corrupt attribution, never protect anything.\n// - PROPERTY KEYS. Every member is a `.strictObject`, so an unknown key REFUSES at parse rather\n// than riding along; the key space is closed for the same reason the name is. Masking keys\n// would put `run_id` one collision away from being renamed to a placeholder.\n//\n// 2026-10-14 (441-08, D-12): the host supplies the declared-secret mask; nothing here decides\n// what is a secret.\n\nimport type { AnalyticsCapturePayload } from './port.js'\n\n/** The only thing the seam needs from a mask: the host binds the declared-secret mask here. */\nexport interface AnalyticsMaskingContext {\n mask(text: string): string\n}\n\n/**\n * Mask every string leaf of an outgoing payload, before anything has been serialized.\n *\n * Returns a NEW payload rather than mutating: the caller's object is the host's, and a host that\n * kept a reference to an event it emitted should not find its own data rewritten underneath it.\n */\nexport function maskEventForSend(\n payload: AnalyticsCapturePayload,\n context: AnalyticsMaskingContext | undefined,\n): AnalyticsCapturePayload {\n if (context === undefined) return payload\n\n return {\n ...payload,\n // The distinct id IS a masked surface: it is the one caller-supplied string on the envelope, and\n // if a registered secret is what a host handed in as an identity, that is precisely a leak.\n distinctId: context.mask(payload.distinctId),\n event: {\n name: payload.event.name,\n properties: maskLeaves(payload.event.properties, context) as AnalyticsCapturePayload['event']['properties'],\n },\n }\n}\n\n/**\n * The walk. Objects and arrays recurse; strings are masked; everything else is returned as-is.\n *\n * `null`, numbers and booleans carry no text to mask, and a value that is none of the above (a\n * `Date`, a class instance) is returned untouched because rewriting it would corrupt it — such a\n * value cannot reach a payload through the closed union anyway, whose leaves are strings, numbers\n * and booleans.\n */\nfunction maskLeaves(value: unknown, context: AnalyticsMaskingContext): unknown {\n if (typeof value === 'string') return context.mask(value)\n if (Array.isArray(value)) return value.map((entry) => maskLeaves(entry, context))\n if (value !== null && typeof value === 'object' && isPlainObject(value)) {\n const out: Record<string, unknown> = {}\n for (const [key, entry] of Object.entries(value)) {\n out[key] = maskLeaves(entry, context)\n }\n return out\n }\n return value\n}\n\nfunction isPlainObject(value: object): value is Record<string, unknown> {\n const prototype = Object.getPrototypeOf(value) as object | null\n return prototype === Object.prototype || prototype === null\n}\n","export const AGENT_QA_POSTHOG_KEY = \"phc_siDVZ3DEoSCiBSocn2GZMRhx5UcRwekJDVtap5Zteii3\"\nexport const AGENT_QA_POSTHOG_HOST = \"https://us.i.posthog.com\"\n","// The one opt-out decision site. It is made once, before any transport exists, and its answer is\n// the transport: opted out or keyless builds none at all (447-04, D-08).\n// `analytics.privacy: true` is the only input (447-03, D-06).\n\nimport { createAnalyticsPort } from './port.js'\nimport { createPosthogTransport } from './transports.js'\nimport { AGENT_QA_POSTHOG_HOST, AGENT_QA_POSTHOG_KEY } from './posthog-project.js'\nimport type { AnalyticsMaskingContext } from './masking-seam.js'\nimport type {\n AnalyticsEventEnvelope,\n AnalyticsIdentityResolver,\n AnalyticsPort,\n AnalyticsTransport,\n} from './port.js'\nimport type { PosthogTransportFactory } from './transports.js'\n\nexport type AnalyticsOptOutReason = 'config-privacy'\n\nexport type AnalyticsTransportKind = 'none' | 'posthog'\n\nexport interface AnalyticsOptOutConfig {\n readonly analytics?: {\n readonly privacy?: boolean\n }\n}\n\nexport interface ComposeAnalyticsOptions {\n readonly config?: AnalyticsOptOutConfig\n readonly identity?: AnalyticsIdentityResolver\n readonly projectKey?: string\n readonly projectHost?: string\n /**\n * The host's declared-secret mask, which the send seam applies (D-19, plan 06). It reaches only\n * the opted-IN arm: the deny arm has no transport, so there is no payload to mask.\n */\n readonly maskingContext?: AnalyticsMaskingContext\n /** Replaces the transport on the OPTED-IN arm only. Never consulted once the answer is deny. */\n readonly transportOverride?: AnalyticsTransport\n /** Replaces the PostHog transport CONSTRUCTOR, so a cell can observe whether it was called. */\n readonly posthogTransportFactory?: PosthogTransportFactory\n}\n\nexport interface ComposedAnalytics<TEvent extends AnalyticsEventEnvelope = AnalyticsEventEnvelope> {\n readonly port: AnalyticsPort<TEvent>\n /** The transport the port was built over; `undefined` when opted out or keyless. */\n readonly transport: AnalyticsTransport | undefined\n readonly optedOut: boolean\n readonly optOutReasons: readonly AnalyticsOptOutReason[]\n readonly transportKind: AnalyticsTransportKind\n}\n\nexport function composeAnalytics<TEvent extends AnalyticsEventEnvelope = AnalyticsEventEnvelope>(\n options: ComposeAnalyticsOptions = {},\n): ComposedAnalytics<TEvent> {\n const optedOut = options.config?.analytics?.privacy === true\n const optOutReasons: AnalyticsOptOutReason[] = optedOut ? ['config-privacy'] : []\n\n // The whole decision, in one expression, before any transport exists. The deny arm reaches\n // neither the override, the factory nor the project key, and builds no transport.\n const { transport, transportKind } = optedOut ? NO_TRANSPORT : enabledTransport(options)\n\n return {\n port: createAnalyticsPort<TEvent>({\n transport,\n identity: options.identity ?? noIdentity,\n }),\n transport,\n optedOut,\n optOutReasons,\n transportKind,\n }\n}\n\n// No shared default id: a port composed without a resolver sends nothing (447-05, D-05).\nconst noIdentity: AnalyticsIdentityResolver = () =>\n Promise.reject(new Error('no analytics identity was supplied'))\n\ninterface ChosenTransport {\n readonly transport: AnalyticsTransport | undefined\n readonly transportKind: AnalyticsTransportKind\n}\n\nconst NO_TRANSPORT: ChosenTransport = { transport: undefined, transportKind: 'none' }\n\n/**\n * The opted-in arm. A blank project key also builds no transport, and that is NOT an opt-out: the\n * committed key file ships blank, so a developer checkout must not report a user who declined.\n */\nfunction enabledTransport(options: ComposeAnalyticsOptions): ChosenTransport {\n if (options.transportOverride) {\n return { transport: options.transportOverride, transportKind: 'posthog' }\n }\n\n const projectKey = (options.projectKey ?? AGENT_QA_POSTHOG_KEY).trim()\n if (projectKey === '') return NO_TRANSPORT\n\n const factory = options.posthogTransportFactory ?? createPosthogTransport\n return {\n transport: factory({\n projectKey,\n host: options.projectHost ?? AGENT_QA_POSTHOG_HOST,\n disableGeoip: false,\n ...(options.maskingContext ? { maskingContext: options.maskingContext } : {}),\n }),\n transportKind: 'posthog',\n }\n}\n","// THE CLOSED EVENT UNION (Phase 398 plan 03, D-18 · ANLY-01).\n//\n// WHAT THIS FILE IS FOR. The process this package runs in holds customer credentials, and the only\n// thing standing between them and a third-party endpoint is what an event is ALLOWED to carry. The\n// v1 module answered that question with `.strip()` schemas whose string fields were\n// `z.string().min(1).optional()` — `model_name`, `provider_mode`, `app_state`,\n// `execution_destination`, `browser_name` and eight more. Every one of them was a field into which\n// any string in the process could be written and would be sent, and `.strip()` meant an unexpected\n// key was silently DROPPED rather than refused, so nobody ever found out.\n//\n// Both defects are abolished by construction here, not patched:\n//\n// 1. `.strictObject` everywhere. An unknown key REFUSES at parse. A caller who invents a field is\n// told; a caller who smuggles one is stopped. There is no `.strip(` anywhere in this package\n// and a cell asserts that over the source.\n// 2. NO UNCONSTRAINED STRING EXISTS. Every string-typed field in every member draws from a\n// `z.enum`, a `z.literal`, or a known-id codec that is BOTH tagged and refined. The rule is\n// mechanical: `assertNoUnconstrainedStrings` walks the union and throws naming the member and\n// the field path, and a planted plain string reds it. A free-text field is unrepresentable,\n// not discouraged.\n//\n// WHAT IS COUNTED RATHER THAN CARRIED. `explain` reports two counters and no query text. The\n// abstained query is free text by definition and it is exactly the thing a docs-gap signal wants,\n// which is why D-11 keeps that log LOCAL, under `.agent-qa/`, and lets analytics carry the count\n// only. If a later phase wants the text, the answer is not a field here.\n\nimport { isCanonicalRunId, isCanonicalSuiteId, isCanonicalTestId } from '@vostride/agent-qa-ids'\nimport { AGENT_QA_TOOL_PROJECTIONS } from '@vostride/agent-qa-mcp'\nimport { z } from 'zod'\n\n// ---------------------------------------------------------------------------------------------\n// The vocabulary primitives\n// ---------------------------------------------------------------------------------------------\n\n/**\n * The metadata key `assertNoUnconstrainedStrings` looks for.\n *\n * A tag alone buys NOTHING — the checker also requires the schema to carry at least one check, so a\n * bare `z.string()` wearing this marker still reds. That is deliberate: an allow-list whose entry\n * condition is \"say the magic word\" is an allow-list anyone can join, and the planted-tag cell\n * exists to keep it that way.\n */\nexport const ANALYTICS_VOCABULARY_META_KEY = 'analyticsVocabulary'\nexport const ANALYTICS_KNOWN_ID_MARKER = 'known-id'\n\n/**\n * A string whose whole value space is a codec this repo owns.\n *\n * The predicates come from `@vostride/agent-qa-ids` rather than being restated as regexes: the id\n * shape is that package's to define, and a second spelling of it here would be a second answer to\n * \"what is a run id\".\n */\nfunction knownId(codec: string, accepts: (value: string) => boolean): z.ZodType<string> {\n return z\n .string()\n .refine(accepts, { message: `expected a ${codec} id` })\n .meta({ [ANALYTICS_VOCABULARY_META_KEY]: ANALYTICS_KNOWN_ID_MARKER, analyticsCodec: codec })\n}\n\n// A version is a closed vocabulary too — the set of strings semver admits — and the refinement is\n// what makes that true rather than asserted. It is the one string on the base properties.\nconst SEMVER_PATTERN = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/u\n\nconst AgentQaVersionSchema = z\n .string()\n .regex(SEMVER_PATTERN, 'expected a semver version')\n .meta({ [ANALYTICS_VOCABULARY_META_KEY]: ANALYTICS_KNOWN_ID_MARKER, analyticsCodec: 'semver' })\n\n/**\n * The known-id fields' guards, as FUNCTIONS a host can call (398-10).\n *\n * Published beside the schemas because an emitter has to make the same decision the schema makes\n * and has to make it BEFORE capture: nothing parses an event on the way to the transport, so a\n * foreign string put on `run_id` is not refused, it is SENT — and `run_id` is a known-id codec\n * precisely so that cannot happen. A host filtering ids for itself would need `@vostride/agent-qa-ids`,\n * which the surface firewall does not admit; asking the package that owns the union is the route\n * that exists. Anything that is not a canonical id becomes an absence, which every field allows.\n */\nexport function analyticsRunId(value: unknown): string | undefined {\n return typeof value === 'string' && isCanonicalRunId(value) ? value : undefined\n}\n\nexport function analyticsTestId(value: unknown): string | undefined {\n return typeof value === 'string' && isCanonicalTestId(value) ? value : undefined\n}\n\nexport function analyticsSuiteId(value: unknown): string | undefined {\n return typeof value === 'string' && isCanonicalSuiteId(value) ? value : undefined\n}\n\nconst RunIdSchema = knownId('run', isCanonicalRunId)\nconst TestIdSchema = knownId('test', isCanonicalTestId)\nconst SuiteIdSchema = knownId('suite', isCanonicalSuiteId)\n\nconst NonNegativeInteger = z.number().int().min(0)\nconst NonNegativeNumber = z.number().min(0)\n\n// ---------------------------------------------------------------------------------------------\n// The enumerated vocabularies\n// ---------------------------------------------------------------------------------------------\n\n/**\n * WHICH HOST EMITTED, and deliberately NOT the 397 D-04 surface roster.\n *\n * 2026-08-17. The roster answers \"which invocation doors does the parity suite measure\" and is\n * pinned closed at `chat` and `mcp`. This list answers a different question — which process sent\n * the event — and `cli` and `daemon` are real answers to it while being no kind of surface. Tying\n * the two would make a roster change an analytics schema change for no reason, and would make this\n * list wrong the moment a host emits that is not a door.\n */\nexport const ANALYTICS_EMITTING_HOSTS = ['cli', 'chat', 'mcp', 'daemon'] as const\n\n// 2026-10-21 (447-02, D-12): a coding agent is a user; its product rides as `agent_product`.\nexport const ANALYTICS_RUNTIME_CONTEXTS = ['user', 'ci'] as const\n\n// Carried from the v1 module unchanged. Retune when a new coding agent shows up in the wild; the\n// `unknown` arm is what keeps an unrecognised one from needing a schema change to be reported.\nexport const ANALYTICS_AGENT_PRODUCTS = [\n 'claude_code',\n 'cursor',\n 'gemini_cli',\n 'augment',\n 'goose',\n 'opencode',\n 'codex',\n 'cline',\n 'amp',\n 'unknown',\n] as const\n\nexport const ANALYTICS_TRIGGER_SOURCES = ['cli', 'chat', 'mcp', 'daemon', 'unknown'] as const\nexport const ANALYTICS_RUNNERS = ['local', 'browserstack', 'unknown'] as const\nexport const ANALYTICS_PLATFORMS = ['web', 'android', 'ios', 'unknown'] as const\nexport const ANALYTICS_RUN_STATUSES = [\n 'passed',\n 'failed',\n 'skipped',\n 'cancelled',\n 'timeout',\n 'unknown',\n] as const\nexport const ANALYTICS_SUITE_EXECUTION_MODES = ['sequential', 'parallel', 'unknown'] as const\n\n// Carried from the v1 module. These are CATEGORIES, never messages: a failure category is a bucket\n// this repo defines, while the failure's text is the thing that would carry a credential.\nexport const ANALYTICS_FAILURE_CATEGORIES = [\n 'assertion',\n 'timeout',\n 'cancelled',\n 'setup',\n 'hook',\n 'appium',\n 'browser_install',\n 'llm_context',\n 'llm_provider',\n 'memory',\n 'framework',\n 'unknown',\n] as const\n\nexport const ANALYTICS_MCP_SERVER_STATES = ['started', 'stopped'] as const\nexport const ANALYTICS_MCP_TRANSPORTS = ['stdio', 'http'] as const\nexport const ANALYTICS_MCP_TOOL_STATUSES = ['success', 'error'] as const\nexport const ANALYTICS_MCP_ERROR_CATEGORIES = [\n 'validation',\n 'scope_denied',\n 'unknown_tool',\n 'tool_error',\n 'unknown',\n] as const\n\nexport const ANALYTICS_DAEMON_JOB_KINDS = ['run', 'suite', 'recovery', 'unknown'] as const\nexport const ANALYTICS_DAEMON_JOB_OUTCOMES = [\n 'completed',\n 'failed',\n 'cancelled',\n 'abandoned',\n 'unknown',\n] as const\n\nexport const ANALYTICS_INIT_MODES = ['interactive', 'agent', 'scaffold'] as const\nexport const ANALYTICS_INIT_OUTCOMES = ['created', 'updated', 'unchanged', 'failed'] as const\n\nexport const ANALYTICS_CHAT_END_REASONS = ['exit', 'interrupt', 'error', 'unknown'] as const\n\n/**\n * The MCP tool-name vocabulary, PRODUCED from the declared projection table.\n *\n * Not retyped, on purpose: this is the second consumer of the same twelve names, and the first\n * thing a second transcription does is survive a rename that the first one did not.\n */\nexport const ANALYTICS_MCP_TOOL_NAMES = AGENT_QA_TOOL_PROJECTIONS.map(\n (projection) => projection.name,\n) as [string, ...string[]]\n\n// ---------------------------------------------------------------------------------------------\n// The properties every member carries\n// ---------------------------------------------------------------------------------------------\n\nconst BaseProperties = {\n agent_qa_version: AgentQaVersionSchema.optional(),\n surface: z.enum(ANALYTICS_EMITTING_HOSTS).optional(),\n runtime_context: z.enum(ANALYTICS_RUNTIME_CONTEXTS).optional(),\n is_internal: z.literal(true).optional(),\n agent_product: z.enum(ANALYTICS_AGENT_PRODUCTS).optional(),\n // PostHog's own directive, a literal `false` and never anything else: it suppresses person-profile\n // creation, which is what keeps an anonymous distinct id anonymous on their side too.\n $process_person_profile: z.literal(false).optional(),\n} as const\n\nfunction member<TName extends string, TShape extends z.ZodRawShape>(name: TName, shape: TShape) {\n return z.strictObject({\n name: z.literal(name),\n properties: z.strictObject({ ...BaseProperties, ...shape }),\n })\n}\n\n// ---------------------------------------------------------------------------------------------\n// The members (D-18)\n// ---------------------------------------------------------------------------------------------\n\nconst RunCompletionShape = {\n trigger_source: z.enum(ANALYTICS_TRIGGER_SOURCES).optional(),\n runner: z.enum(ANALYTICS_RUNNERS).optional(),\n platform: z.enum(ANALYTICS_PLATFORMS).optional(),\n status: z.enum(ANALYTICS_RUN_STATUSES).optional(),\n failure_category: z.enum(ANALYTICS_FAILURE_CATEGORIES).optional(),\n duration_ms: NonNegativeNumber.optional(),\n run_id: RunIdSchema.optional(),\n // 2026-08-28 (405-13, D-21/DETL-06). The parent-link property was deleted here. It was declared\n // with no producer anywhere in this package — 402-09 put one run row per run and moved member\n // identity onto handles, and 405-07 then deleted the column it named — so it could only ever\n // have travelled as `undefined`. Same disposition and same reason as the healing counter below.\n step_count: NonNegativeInteger.optional(),\n passed_step_count: NonNegativeInteger.optional(),\n failed_step_count: NonNegativeInteger.optional(),\n skipped_step_count: NonNegativeInteger.optional(),\n retry_count: NonNegativeInteger.optional(),\n input_tokens: NonNegativeInteger.optional(),\n output_tokens: NonNegativeInteger.optional(),\n total_tokens: NonNegativeInteger.optional(),\n is_flaky: z.boolean().optional(),\n memory_enabled: z.boolean().optional(),\n} as const\n\n// 2026-08-17. `model_name`, `provider`, `provider_mode`, `planner_model`, `verifier_model`,\n// `browser_name`, `mobile_transport`, `mobile_provider`, `app_state` and `execution_destination`\n// were all `z.string().min(1)` in v1 and are GONE rather than ported. Each named a real dimension,\n// and each was a hole: a provider id read from a config file lands in the same field as anything\n// else that config file happens to hold. Retune: when one of them is wanted back, it comes back as\n// an ENUM of the values this repo actually supports, which is a decision with a list attached.\n\nconst TestRunCompleted = member('agent-qa.test_run.completed', {\n ...RunCompletionShape,\n test_id: TestIdSchema.optional(),\n})\n\nconst SuiteRunCompleted = member('agent-qa.suite_run.completed', {\n ...RunCompletionShape,\n suite_id: SuiteIdSchema.optional(),\n suite_child_count: NonNegativeInteger.optional(),\n suite_passed_count: NonNegativeInteger.optional(),\n suite_failed_count: NonNegativeInteger.optional(),\n suite_skipped_count: NonNegativeInteger.optional(),\n suite_execution_mode: z.enum(ANALYTICS_SUITE_EXECUTION_MODES).optional(),\n})\n\n// AGGREGATES, never message text. A turn count and a duration say how the product is used; the\n// turn's content says what the user was working on, and that is theirs.\nconst ChatSessionCompleted = member('agent-qa.chat.session.completed', {\n duration_ms: NonNegativeNumber.optional(),\n turn_count: NonNegativeInteger.optional(),\n slash_command_count: NonNegativeInteger.optional(),\n function_call_count: NonNegativeInteger.optional(),\n end_reason: z.enum(ANALYTICS_CHAT_END_REASONS).optional(),\n})\n\nconst ChatTurnCompleted = member('agent-qa.chat.turn.completed', {\n duration_ms: NonNegativeNumber.optional(),\n tool_call_count: NonNegativeInteger.optional(),\n input_tokens: NonNegativeInteger.optional(),\n output_tokens: NonNegativeInteger.optional(),\n total_tokens: NonNegativeInteger.optional(),\n errored: z.boolean().optional(),\n})\n\nconst DaemonJobCompleted = member('agent-qa.daemon.job.completed', {\n job_kind: z.enum(ANALYTICS_DAEMON_JOB_KINDS).optional(),\n outcome: z.enum(ANALYTICS_DAEMON_JOB_OUTCOMES).optional(),\n duration_ms: NonNegativeNumber.optional(),\n attempt_count: NonNegativeInteger.optional(),\n run_id: RunIdSchema.optional(),\n})\n\nconst McpServerLifecycle = member('agent-qa.mcp.server.lifecycle', {\n mcp_server_state: z.enum(ANALYTICS_MCP_SERVER_STATES).optional(),\n mcp_transport: z.enum(ANALYTICS_MCP_TRANSPORTS).optional(),\n mcp_tool_count: NonNegativeInteger.optional(),\n})\n\nconst McpToolInvoked = member('agent-qa.mcp.tool.invoked', {\n tool_name: z.enum(ANALYTICS_MCP_TOOL_NAMES).optional(),\n mcp_tool_status: z.enum(ANALYTICS_MCP_TOOL_STATUSES).optional(),\n mcp_error_category: z.enum(ANALYTICS_MCP_ERROR_CATEGORIES).optional(),\n mcp_transport: z.enum(ANALYTICS_MCP_TRANSPORTS).optional(),\n duration_ms: NonNegativeNumber.optional(),\n})\n\nconst InitCompleted = member('agent-qa.init.completed', {\n init_mode: z.enum(ANALYTICS_INIT_MODES).optional(),\n outcome: z.enum(ANALYTICS_INIT_OUTCOMES).optional(),\n created_file_count: NonNegativeInteger.optional(),\n duration_ms: NonNegativeNumber.optional(),\n})\n\n// COUNTERS ONLY (D-11 / Pitfall 10). There is no query-text field on either explain member and\n// there must never be one: the coverage log under `.agent-qa/` is where the query lives, local to\n// the machine, and it is the only place the demand signal SURF-FUT-02 reads is allowed to exist.\nconst ExplainAnswered = member('agent-qa.explain.answered', {\n cited_row_count: NonNegativeInteger.optional(),\n duration_ms: NonNegativeNumber.optional(),\n})\n\nconst ExplainAbstained = member('agent-qa.explain.abstained', {\n suggested_row_count: NonNegativeInteger.optional(),\n catalog_empty: z.boolean().optional(),\n duration_ms: NonNegativeNumber.optional(),\n})\n\n/**\n * The membership, in the order D-18 states it.\n *\n * The names keep the `agent-qa.` prefix the v33 set used: the events land in a project shared with\n * nothing else today, but a prefix costs nothing and un-prefixing later is a rename of history.\n */\nexport const ANALYTICS_EVENT_NAMES = [\n 'agent-qa.test_run.completed',\n 'agent-qa.suite_run.completed',\n 'agent-qa.chat.session.completed',\n 'agent-qa.chat.turn.completed',\n 'agent-qa.daemon.job.completed',\n 'agent-qa.mcp.server.lifecycle',\n 'agent-qa.mcp.tool.invoked',\n 'agent-qa.init.completed',\n 'agent-qa.explain.answered',\n 'agent-qa.explain.abstained',\n] as const\n\nexport type AnalyticsEventName = (typeof ANALYTICS_EVENT_NAMES)[number]\n\n/**\n * The v33 names that are NOT members, listed rather than merely absent.\n *\n * 2026-08-17 (Phase 398 plan 03, D-18 · RESEARCH Open Question 2). Two different reasons:\n *\n * `analytics.initialized` and `analytics.test_event` were TRANSPORT SELF-TESTS — an event whose\n * only content is \"the thing that sends events works\" is a diagnostic, and shipping it as product\n * signal means every dashboard has to remember to exclude it. Dropped outright; the zero-network\n * differential in plan 06 is the same claim, proven rather than reported.\n *\n * The three `dashboard.*` rows are dropped BECAUSE THE DASHBOARD IS BEING REBUILT. M5 re-adds\n * what it needs against this port, which is the right moment to decide what a rebuilt dashboard\n * is actually worth measuring — carrying the v1 answers forward would have pre-decided it.\n *\n * RETUNE: a name re-added here must be REMOVED from this list in the same change. The disjointness\n * cell fires if it appears in both, so the two lists cannot quietly disagree.\n */\nexport const RETIRED_V33_EVENT_NAMES = [\n 'agent-qa.analytics.initialized',\n 'agent-qa.analytics.test_event',\n 'agent-qa.dashboard.opened',\n 'agent-qa.dashboard.live_mode.started',\n 'agent-qa.dashboard.entity.created',\n] as const\n\nexport const ANALYTICS_EVENT_SCHEMA_BY_NAME = {\n 'agent-qa.test_run.completed': TestRunCompleted,\n 'agent-qa.suite_run.completed': SuiteRunCompleted,\n 'agent-qa.chat.session.completed': ChatSessionCompleted,\n 'agent-qa.chat.turn.completed': ChatTurnCompleted,\n 'agent-qa.daemon.job.completed': DaemonJobCompleted,\n 'agent-qa.mcp.server.lifecycle': McpServerLifecycle,\n 'agent-qa.mcp.tool.invoked': McpToolInvoked,\n 'agent-qa.init.completed': InitCompleted,\n 'agent-qa.explain.answered': ExplainAnswered,\n 'agent-qa.explain.abstained': ExplainAbstained,\n} as const\n\nexport const ANALYTICS_EVENT_MEMBERS = [\n TestRunCompleted,\n SuiteRunCompleted,\n ChatSessionCompleted,\n ChatTurnCompleted,\n DaemonJobCompleted,\n McpServerLifecycle,\n McpToolInvoked,\n InitCompleted,\n ExplainAnswered,\n ExplainAbstained,\n] as const\n\nexport const AgentQaAnalyticsEventSchema = z.discriminatedUnion('name', ANALYTICS_EVENT_MEMBERS)\n\nexport type AgentQaAnalyticsEvent = z.infer<typeof AgentQaAnalyticsEventSchema>\n\n// ---------------------------------------------------------------------------------------------\n// The range guard\n// ---------------------------------------------------------------------------------------------\n\n/**\n * Walks every member of the union and throws naming the member and the field path of any string\n * that is not drawn from a closed vocabulary.\n *\n * FAIL-CLOSED. A construct this walker does not recognise is reported, not skipped. The alternative\n * — treat the unknown as fine — means the rule silently stops covering whatever zod grows next, and\n * a guard that quietly narrows is worse than no guard, because the suite keeps saying yes.\n *\n * The bar this has to clear is not \"the shipped union passes\". It is that a planted plain string\n * REDS, that a string wearing the known-id tag without a refinement ALSO reds, and that neither an\n * optional, a record value nor an array element is a place to hide one.\n */\nexport function assertNoUnconstrainedStrings(members: readonly z.ZodType[]): void {\n const offenders: string[] = []\n\n for (const memberSchema of members) {\n const name = memberNameOf(memberSchema)\n walk(memberSchema, '', name, offenders)\n }\n\n if (offenders.length > 0) {\n throw new Error(\n `analytics event union carries unconstrained string field(s):\\n ${offenders.join('\\n ')}`,\n )\n }\n}\n\nfunction memberNameOf(schema: z.ZodType): string {\n if (schema instanceof z.ZodObject) {\n const nameSchema = (schema.shape as Record<string, z.ZodType | undefined>).name\n if (nameSchema instanceof z.ZodLiteral) {\n const values = nameSchema.def.values\n const first = Array.isArray(values) ? values[0] : values\n if (typeof first === 'string') return first\n }\n }\n return '<unnamed member>'\n}\n\nfunction walk(schema: z.ZodType, path: string, member: string, offenders: string[]): void {\n const unwrapped = unwrap(schema)\n\n if (unwrapped instanceof z.ZodObject) {\n for (const [key, child] of Object.entries(unwrapped.shape as Record<string, z.ZodType>)) {\n walk(child, path === '' ? key : `${path}.${key}`, member, offenders)\n }\n return\n }\n\n if (unwrapped instanceof z.ZodEnum || unwrapped instanceof z.ZodLiteral) return\n if (\n unwrapped instanceof z.ZodNumber\n || unwrapped instanceof z.ZodBoolean\n || unwrapped instanceof z.ZodNull\n || unwrapped instanceof z.ZodUndefined\n ) {\n return\n }\n\n if (unwrapped instanceof z.ZodArray) {\n walk(unwrapped.element as z.ZodType, path, member, offenders)\n return\n }\n\n if (unwrapped instanceof z.ZodRecord) {\n walk(unwrapped.keyType as z.ZodType, path, member, offenders)\n walk(unwrapped.valueType as z.ZodType, path, member, offenders)\n return\n }\n\n if (unwrapped instanceof z.ZodUnion) {\n for (const option of unwrapped.options as readonly z.ZodType[]) {\n walk(option, path, member, offenders)\n }\n return\n }\n\n if (unwrapped instanceof z.ZodString) {\n if (!isKnownIdCodec(unwrapped)) {\n offenders.push(`${member} :: ${path} | unconstrained string`)\n }\n return\n }\n\n offenders.push(`${member} :: ${path} | unrecognised construct, cannot prove it is constrained`)\n}\n\n/**\n * Two conditions, both required.\n *\n * The TAG says a human decided this string is an identifier from a codec this repo owns. The CHECK\n * is what makes that decision true of the values rather than of the intention — without it the tag\n * is a comment with a runtime cost, and tagging becomes the escape hatch from the rule.\n */\nfunction isKnownIdCodec(schema: z.ZodString): boolean {\n const meta = schema.meta() as Record<string, unknown> | undefined\n if (meta?.[ANALYTICS_VOCABULARY_META_KEY] !== ANALYTICS_KNOWN_ID_MARKER) return false\n return (schema.def.checks ?? []).length > 0\n}\n\nfunction unwrap(schema: z.ZodType): z.ZodType {\n let current: z.ZodType = schema\n for (;;) {\n if (\n current instanceof z.ZodOptional\n || current instanceof z.ZodNullable\n || current instanceof z.ZodReadonly\n ) {\n current = current.unwrap() as z.ZodType\n continue\n }\n if (current instanceof z.ZodDefault) {\n current = current.def.innerType as z.ZodType\n continue\n }\n return current\n }\n}\n","// The anonymous installation identity (re-homed from core in 398-06; race-safe since 447-02).\n// The record is `{distinctId, is_internal}` at the carried path and nothing else (prohibition P3).\n// Whether anything is sent is `compose.ts`'s call; this module only answers \"who, anonymously\".\n\nimport { randomUUID } from 'node:crypto'\nimport { chmod, link, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\nimport { idAgent } from 'id-agent'\nimport { ANALYTICS_AGENT_PRODUCTS, ANALYTICS_RUNTIME_CONTEXTS } from './events.js'\n\n// Read off the union's own vocabularies, so a resolved value is always one the union accepts.\nexport type AnalyticsRuntimeContext = (typeof ANALYTICS_RUNTIME_CONTEXTS)[number]\nexport type AnalyticsAgentProduct = (typeof ANALYTICS_AGENT_PRODUCTS)[number]\n\n/** The id scheme, named so a cell can assert it rather than a reader having to trust a comment. */\nexport const ANALYTICS_USER_ID_PREFIX = 'u'\nexport const ANALYTICS_USER_ID_WORDS = 10\n\n/** What a resolved identity is, before anything decides whether to send on its behalf. */\nexport interface ResolvedAnalyticsIdentity {\n distinctId: string\n runtimeContext: AnalyticsRuntimeContext\n agentProduct?: AnalyticsAgentProduct\n isInternal?: boolean\n}\n\n/** The on-disk record. Two keys, carried verbatim, and the exact set a cell pins. */\nexport interface AnalyticsIdentityStore {\n distinctId: string\n is_internal: boolean\n}\n\ntype StoreRead =\n | { kind: 'missing' }\n | { kind: 'invalid' }\n | { kind: 'valid'; record: AnalyticsIdentityStore }\n\nexport interface ResolveAnalyticsIdentityOptions {\n env?: Record<string, string | undefined>\n ci?: boolean\n identityPath?: string\n homeDir?: string\n}\n\n// A stored id is adopted only in this shape; anything else is replaced (D-01).\nconst LOCAL_ANALYTICS_ID_PATTERN = /^u_([a-z]+-){9}[a-z]+$/\n\nexport function getAnalyticsIdentityPath(\n env: Record<string, string | undefined> = process.env,\n homeDir = homedir(),\n): string {\n if (env.XDG_DATA_HOME) return join(env.XDG_DATA_HOME, 'agent-qa', 'analytics.json')\n return join(homeDir, '.agent-qa', 'analytics.json')\n}\n\nexport function generateAnalyticsUserId(): string {\n return idAgent({ prefix: ANALYTICS_USER_ID_PREFIX, words: ANALYTICS_USER_ID_WORDS })\n}\n\nfunction parseAnalyticsIdentityStore(value: unknown): AnalyticsIdentityStore | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null\n const distinctId = (value as { distinctId?: unknown }).distinctId\n if (typeof distinctId !== 'string' || !LOCAL_ANALYTICS_ID_PATTERN.test(distinctId)) return null\n return { distinctId, is_internal: (value as { is_internal?: unknown }).is_internal === true }\n}\n\nasync function readStore(identityPath: string): Promise<StoreRead> {\n let data: string\n try {\n data = await readFile(identityPath, 'utf-8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { kind: 'missing' }\n throw error\n }\n try {\n const record = parseAnalyticsIdentityStore(JSON.parse(data))\n return record === null ? { kind: 'invalid' } : { kind: 'valid', record }\n } catch {\n return { kind: 'invalid' }\n }\n}\n\n// Publish a whole record in one step: link refuses an existing file (the loser of a first-use race\n// reads back the winner's id); rename atomically replaces an invalid one.\nasync function placeNewRecord(identityPath: string, replace: boolean): Promise<void> {\n await mkdir(dirname(identityPath), { recursive: true })\n const temp = `${identityPath}.${randomUUID()}.tmp`\n const record: AnalyticsIdentityStore = {\n distinctId: generateAnalyticsUserId(),\n is_internal: false,\n }\n try {\n await writeFile(temp, JSON.stringify(record, null, 2), { mode: 0o600, flag: 'wx' })\n await chmod(temp, 0o600)\n if (replace) {\n await rename(temp, identityPath)\n } else {\n await link(temp, identityPath).catch((error: NodeJS.ErrnoException) => {\n if (error.code !== 'EEXIST') throw error\n })\n }\n } finally {\n await rm(temp, { force: true })\n }\n}\n\nexport async function readAnalyticsIdentity(path?: string): Promise<AnalyticsIdentityStore | null> {\n try {\n const found = await readStore(path ?? getAnalyticsIdentityPath())\n return found.kind === 'valid' ? found.record : null\n } catch {\n return null\n }\n}\n\nexport function resolveAnalyticsAgentProduct(\n env: Record<string, string | undefined> = process.env,\n): AnalyticsAgentProduct | undefined {\n if (env.CLAUDECODE !== undefined || env.CLAUDE_CODE_ENTRYPOINT !== undefined) return 'claude_code'\n if (env.CURSOR_AGENT !== undefined) return 'cursor'\n if (env.GEMINI_CLI !== undefined) return 'gemini_cli'\n if (env.AUGMENT_AGENT !== undefined) return 'augment'\n if (env.GOOSE_TERMINAL !== undefined || env.AGENT === 'goose') return 'goose'\n if (env.OPENCODE_CLIENT !== undefined) return 'opencode'\n if (env.CODEX_SANDBOX !== undefined) return 'codex'\n if (env.CLINE_ACTIVE !== undefined) return 'cline'\n if (env.AGENT === 'amp') return 'amp'\n if (typeof env.AGENT === 'string' && env.AGENT.length > 0) return 'unknown'\n return undefined\n}\n\n// The one entry point a composition root calls. The host decides `ci` (D-11); a CI resolution is a\n// fresh in-memory id and touches no file (D-04).\nexport async function resolveAnalyticsIdentity(\n options: ResolveAnalyticsIdentityOptions = {},\n): Promise<ResolvedAnalyticsIdentity> {\n const env = options.env ?? process.env\n const agentProduct = resolveAnalyticsAgentProduct(env)\n const attribution = agentProduct === undefined ? {} : { agentProduct }\n if (options.ci === true) {\n return { distinctId: 'ci_' + randomUUID(), runtimeContext: 'ci', ...attribution }\n }\n\n const identityPath = options.identityPath ?? getAnalyticsIdentityPath(env, options.homeDir)\n const found = await readStore(identityPath)\n if (found.kind !== 'valid') await placeNewRecord(identityPath, found.kind === 'invalid')\n const stored = found.kind === 'valid' ? found : await readStore(identityPath)\n if (stored.kind !== 'valid') {\n throw new Error('the analytics identity store did not hold a valid record')\n }\n return {\n distinctId: stored.record.distinctId,\n runtimeContext: 'user',\n isInternal: stored.record.is_internal,\n ...attribution,\n }\n}\n","// The run-completion bus subscriber (Phase 398 plan 06 task 3, D-16 / D-17 · RUNX-03).\n//\n// RE-HOMED FROM `core/src/analytics/run-reporter.ts`, keeping the subscription shape byte-for-byte\n// where it faces the bus: `{ handler(envelope), flush() }`. That is deliberate and it is the whole\n// interface plan 10 has to rewire — the CLI root's attach site changes which module it imports and\n// nothing else. The accumulation logic is the part that was worth moving intact: the per-member\n// step tallies, the retry reset that keeps a re-run from double-counting, the outcome→status\n// mapping, and the suite aggregate derived from the members rather than from a second stream.\n//\n// THREE THINGS DID NOT COME ACROSS, and each absence is a defect the re-derivation closes:\n//\n// 1. The `disabled` flag. The v1 subscriber re-read the privacy posture and no-op'd inside its\n// own handler, which made it the SECOND place in the process deciding whether telemetry\n// happens. It is not a decision here at all: an opted-out host composes a port with no\n// transport, so this module emits into a wall and never learns the difference. One decision\n// site, in `compose.ts`, is what D-19 buys and what the ownership contract forbids re-opening.\n// 2. The environment read for run attributes. `AGENT_QA_RUN_ATTRIBUTES_JSON` was parsed here,\n// which put an env read in a module a run drives. The trigger and runner are now handed in by\n// whoever composed the port — they are facts about how the process was started, and the\n// composition root is the only thing that actually knows them.\n// 3. Raw id pass-through. v1 forwarded whatever string the bus carried into `run_id`/`test_id`.\n// Those fields are known-id codecs in the closed union now, so a foreign string reaching one\n// is exactly the free-text channel the union exists to close. Ids are filtered through the\n// ids package's canonical predicates and DROPPED when they do not match, rather than sent and\n// refused downstream where nobody would see it.\n//\n// Best-effort is unchanged and load-bearing: every handler body and the flush body swallow their\n// own errors, because a malformed envelope must never be able to fail the run it is describing.\n\nimport type {\n RunEventEnvelope,\n RunFinishedPayload,\n StepFinalizedPayload,\n TestFinishedPayload,\n TestStartedPayload,\n VerdictPayload,\n} from '@vostride/agent-qa-schema'\nimport {\n analyticsRunId,\n analyticsSuiteId,\n analyticsTestId,\n ANALYTICS_AGENT_PRODUCTS,\n ANALYTICS_EMITTING_HOSTS,\n ANALYTICS_RUNNERS,\n ANALYTICS_RUNTIME_CONTEXTS,\n ANALYTICS_RUN_STATUSES,\n ANALYTICS_TRIGGER_SOURCES,\n type AgentQaAnalyticsEvent,\n} from './events.js'\nimport type { AnalyticsPort, AnalyticsPropertyValue } from './port.js'\n\ntype AnalyticsRunStatus = (typeof ANALYTICS_RUN_STATUSES)[number]\nexport type AnalyticsTriggerSource = (typeof ANALYTICS_TRIGGER_SOURCES)[number]\nexport type AnalyticsRunner = (typeof ANALYTICS_RUNNERS)[number]\n\ntype CompletionEvent<TName extends AgentQaAnalyticsEvent['name']> = Extract<\n AgentQaAnalyticsEvent,\n { name: TName }\n>\n\n/**\n * The identity-shaped properties every event in a session carries.\n *\n * Handed in rather than resolved here: `resolveAnalyticsIdentity` answers who, `compose.ts` answers\n * whether, and a subscriber that reached for either would be a third opinion on both.\n */\nexport interface AnalyticsRunStandardProperties {\n agent_qa_version?: string\n surface?: (typeof ANALYTICS_EMITTING_HOSTS)[number]\n runtime_context?: (typeof ANALYTICS_RUNTIME_CONTEXTS)[number]\n agent_product?: (typeof ANALYTICS_AGENT_PRODUCTS)[number]\n $process_person_profile?: false\n}\n\nexport interface AnalyticsRunSubscriberOptions {\n /** The composed port. An opted-out host passes one with no transport; see the header. */\n port: AnalyticsPort<AgentQaAnalyticsEvent>\n /** How the run was triggered, as known at the composition root. */\n triggerSource?: string\n /** Which runner executed it, as known at the composition root. */\n runner?: string\n standardProperties?: AnalyticsRunStandardProperties\n}\n\nexport interface AnalyticsRunSubscriber {\n handler(envelope: RunEventEnvelope): void\n flush(): Promise<void>\n}\n\ninterface MemberStepTally {\n step_count: number\n passed_step_count: number\n failed_step_count: number\n skipped_step_count: number\n /**\n * 2026-08-27 (Phase 405 plan 04, D-07): the member's measured model-call usage, accumulated ONLY\n * from steps whose `stepFinalized` carried a `tokenUsage`. Absent while no step has been\n * measured — which is why it is optional rather than a zeroed triple: `captureTestRun` spreads\n * the three analytics fields in only when this member exists, so a run nobody measured reports\n * no cost at all instead of reporting a cost of zero.\n */\n tokens?: { input: number; output: number; total: number }\n}\n\ninterface MemberRecord {\n testId?: string\n outcome: string\n durationMs?: number\n retryCount?: number\n steps: MemberStepTally\n}\n\nfunction emptyStepTally(): MemberStepTally {\n return { step_count: 0, passed_step_count: 0, failed_step_count: 0, skipped_step_count: 0 }\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined\n}\n\nfunction readNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined\n}\n\n/**\n * The member index, or nothing — and every handler below bails on nothing.\n *\n * 2026-08-17 (Phase 398 plan 06 task 3). This guard was NOT in the module that moved here, and its\n * absence was measured rather than reasoned about: the \"never throws into the run\" cell fed a\n * `testFinished` envelope whose payload was the number `7`, expected no emission, and got a fully\n * formed `test_run.completed` event with `status: 'unknown'` and a zeroed step tally. Reading a\n * property off a number is legal in JavaScript, so the surrounding try/catch — the thing that looks\n * like the boundary — never fired, and the malformed envelope was recorded as a member keyed on\n * `undefined`. A best-effort subscriber is allowed to drop what it cannot read; it is not allowed\n * to invent a test run that never happened, because that number is indistinguishable downstream\n * from a real one.\n */\nfunction readIndex(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined\n}\n\n/**\n * Normalisers written against the union's OWN vocabulary arrays.\n *\n * A hand-written `value === 'cli' || value === 'dashboard'` list is how the v1 subscriber came to\n * emit trigger sources the schema had stopped accepting: two lists, one of them stale, no signal.\n * Membership is asked of the array the union is built from, so the two cannot disagree.\n */\nfunction normalizeTriggerSource(value: unknown): AnalyticsTriggerSource {\n return ANALYTICS_TRIGGER_SOURCES.find((candidate) => candidate === value) ?? 'unknown'\n}\n\nfunction normalizeRunner(value: unknown): AnalyticsRunner {\n return ANALYTICS_RUNNERS.find((candidate) => candidate === value) ?? 'unknown'\n}\n\n/**\n * Map a member/aggregate outcome to the analytics status vocabulary.\n *\n * Carried from the v1 mapping unchanged so a dashboard reading these numbers across the cutover\n * sees one series rather than two: `healed` counts as `passed` (the heal marker lives on the step),\n * and `timeout` collapses to `failed`. The union does carry a `timeout` arm — splitting it out is a\n * retune with a migration attached, not a side effect of moving a file.\n */\nfunction outcomeToAnalyticsStatus(outcome: unknown): AnalyticsRunStatus {\n switch (outcome) {\n case 'passed':\n case 'healed':\n return 'passed'\n case 'failed':\n case 'timeout':\n return 'failed'\n case 'skipped':\n return 'skipped'\n case 'cancelled':\n return 'cancelled'\n default:\n return 'unknown'\n }\n}\n\nfunction definedProperties(\n properties: Record<string, AnalyticsPropertyValue>,\n): Record<string, AnalyticsPropertyValue> {\n const output: Record<string, AnalyticsPropertyValue> = {}\n for (const [key, value] of Object.entries(properties)) {\n if (value !== undefined) output[key] = value\n }\n return output\n}\n\nexport function createAnalyticsRunSubscriber(\n options: AnalyticsRunSubscriberOptions,\n): AnalyticsRunSubscriber {\n const port = options.port\n const standard = options.standardProperties ?? {}\n const triggerSource = normalizeTriggerSource(options.triggerSource)\n const runner = normalizeRunner(options.runner)\n\n // Accumulated run state, filled from the envelope stream and read once at flush.\n let runId: string | undefined\n let suiteId: string | undefined\n let finished = false\n let aggregateVerdict: string | undefined\n let sawSuite = false\n const members = new Map<number, MemberRecord>()\n // Live step tallies keyed by testIndex; `testRetryScheduled` resets a member's tally so a\n // re-run's repeated `stepFinalized` sequence never double-counts (schema §2.2 retry note).\n const liveSteps = new Map<number, MemberStepTally>()\n\n function stepTallyFor(testIndex: number): MemberStepTally {\n let tally = liveSteps.get(testIndex)\n if (!tally) {\n tally = emptyStepTally()\n liveSteps.set(testIndex, tally)\n }\n return tally\n }\n\n function handleTestStarted(payload: TestStartedPayload): void {\n const testIndex = readIndex(payload.testIndex)\n if (testIndex === undefined) return\n const payloadSuiteId = readString(payload.suiteId)\n if (payloadSuiteId) {\n sawSuite = true\n suiteId = analyticsSuiteId(payloadSuiteId) ?? suiteId\n }\n liveSteps.set(testIndex, emptyStepTally())\n }\n\n function handleStepFinalized(payload: StepFinalizedPayload): void {\n const testIndex = readIndex(payload.testIndex)\n if (testIndex === undefined) return\n const tally = stepTallyFor(testIndex)\n tally.step_count += 1\n switch (payload.status) {\n case 'passed':\n case 'healed':\n tally.passed_step_count += 1\n break\n case 'failed':\n case 'cancelled':\n tally.failed_step_count += 1\n break\n case 'skipped':\n tally.skipped_step_count += 1\n break\n default:\n break\n }\n // 405 D-07: the fields `analytics/src/events.ts` has declared since v33 with no producer get\n // one here. A step with no `tokenUsage` is a step nobody measured (a cache hit, a bypass, a\n // provider that reports none) and contributes NOTHING — not a zero.\n const usage = payload.tokenUsage\n if (usage !== undefined) {\n const tokens = tally.tokens ?? { input: 0, output: 0, total: 0 }\n tokens.input += usage.promptTokens\n tokens.output += usage.completionTokens\n tokens.total += usage.totalTokens\n tally.tokens = tokens\n }\n }\n\n function handleTestFinished(payload: TestFinishedPayload): void {\n const testIndex = readIndex(payload.testIndex)\n if (testIndex === undefined) return\n const steps = liveSteps.get(testIndex) ?? emptyStepTally()\n // Only the final attempt emits `testFinished` — overwrite any prior record for this index.\n members.set(testIndex, {\n testId: readString(payload.testId),\n outcome: readString(payload.outcome) ?? 'unknown',\n durationMs: readNumber(payload.durationMs),\n retryCount: readNumber(payload.retryCount),\n steps,\n })\n liveSteps.delete(testIndex)\n }\n\n function captureTestRun(record: MemberRecord): void {\n const status = outcomeToAnalyticsStatus(record.outcome)\n const testId = analyticsTestId(record.testId)\n // The four flat counts are analytics property names verbatim and spread as-is; `tokens` is an\n // accumulator, NOT a property, so it is destructured away rather than allowed to ride into the\n // bag as a nested object the strict schema would refuse.\n const { tokens: _tokens, ...steps } = record.steps\n const properties = definedProperties({\n ...standard,\n run_id: runId,\n test_id: testId,\n trigger_source: triggerSource,\n runner,\n status,\n duration_ms: record.durationMs,\n retry_count: record.retryCount,\n is_flaky: record.retryCount !== undefined\n ? status === 'passed' && record.retryCount > 0\n : undefined,\n ...steps,\n // 405 D-07. Spread in only when the member measured at least one step. `definedProperties`\n // would drop three `undefined`s anyway, but building them unconditionally would mean writing\n // `input_tokens: tokens?.input ?? 0` somewhere — and that `?? 0` is exactly the fabricated\n // measurement this whole path refuses to make.\n ...(record.steps.tokens === undefined\n ? {}\n : {\n input_tokens: record.steps.tokens.input,\n output_tokens: record.steps.tokens.output,\n total_tokens: record.steps.tokens.total,\n }),\n }) as CompletionEvent<'agent-qa.test_run.completed'>['properties']\n\n port.capture({ name: 'agent-qa.test_run.completed', properties })\n }\n\n function captureSuiteRun(records: readonly MemberRecord[]): void {\n const statuses = records.map((record) => outcomeToAnalyticsStatus(record.outcome))\n const properties = definedProperties({\n ...standard,\n run_id: runId,\n suite_id: suiteId,\n trigger_source: triggerSource,\n runner,\n status: outcomeToAnalyticsStatus(aggregateVerdict),\n duration_ms: records.reduce((sum, record) => sum + (record.durationMs ?? 0), 0),\n suite_child_count: records.length,\n suite_passed_count: statuses.filter((status) => status === 'passed').length,\n suite_failed_count: statuses.filter((status) => status === 'failed').length,\n suite_skipped_count: statuses.filter((status) => status === 'skipped').length,\n }) as CompletionEvent<'agent-qa.suite_run.completed'>['properties']\n\n port.capture({ name: 'agent-qa.suite_run.completed', properties })\n }\n\n return {\n handler(envelope: RunEventEnvelope): void {\n try {\n runId = analyticsRunId(envelope.runId)\n switch (envelope.type) {\n case 'testStarted':\n handleTestStarted(envelope.payload as TestStartedPayload)\n break\n case 'stepFinalized':\n handleStepFinalized(envelope.payload as StepFinalizedPayload)\n break\n case 'testRetryScheduled': {\n const retryIndex = readIndex((envelope.payload as { testIndex?: unknown }).testIndex)\n if (retryIndex !== undefined) liveSteps.delete(retryIndex)\n break\n }\n case 'testFinished':\n handleTestFinished(envelope.payload as TestFinishedPayload)\n break\n case 'verdict':\n aggregateVerdict = readString((envelope.payload as VerdictPayload).verdict) ?? aggregateVerdict\n break\n case 'runFinished':\n aggregateVerdict =\n readString((envelope.payload as RunFinishedPayload).aggregateVerdict) ?? aggregateVerdict\n finished = true\n break\n default:\n break\n }\n } catch {\n // A malformed envelope must never break a run — analytics is best-effort (RUNX-03).\n }\n },\n\n async flush(): Promise<void> {\n if (!finished) {\n // Still settle the port so a composed transport drains cleanly on an abandoned run.\n await port.flush()\n return\n }\n\n try {\n const records = [...members.values()]\n for (const record of records) captureTestRun(record)\n if (sawSuite) captureSuiteRun(records)\n } catch {\n // Analytics is intentionally best-effort — never surface a fault to the run.\n }\n\n await port.flush()\n },\n }\n}\n"],"mappings":"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","names":["member"]}