@agenthoney/analytics 0.0.0-stage → 0.14.0
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.
- package/LICENSE +21 -0
- package/README.md +85 -2
- package/dist/answers.cjs +20034 -0
- package/dist/answers.cjs.map +1 -0
- package/dist/answers.d.ts +148 -0
- package/dist/answers.js +139 -0
- package/dist/answers.js.map +1 -0
- package/dist/chunk-CD4WLJX7.js +48 -0
- package/dist/chunk-CD4WLJX7.js.map +1 -0
- package/dist/chunk-F3PRHEXB.js +20143 -0
- package/dist/chunk-F3PRHEXB.js.map +1 -0
- package/dist/chunk-L22VERBM.js +911 -0
- package/dist/chunk-L22VERBM.js.map +1 -0
- package/dist/chunk-OH4H2B7O.js +150 -0
- package/dist/chunk-OH4H2B7O.js.map +1 -0
- package/dist/chunk-R76CTIBG.js +701 -0
- package/dist/chunk-R76CTIBG.js.map +1 -0
- package/dist/chunk-UG3REZCJ.js +147 -0
- package/dist/chunk-UG3REZCJ.js.map +1 -0
- package/dist/core/breaker.d.ts +33 -0
- package/dist/core/collector.d.ts +51 -0
- package/dist/core/config.d.ts +124 -0
- package/dist/core/encode.d.ts +32 -0
- package/dist/core/queue.d.ts +39 -0
- package/dist/core/record-gate.d.ts +17 -0
- package/dist/core/safe.d.ts +17 -0
- package/dist/core/transport.d.ts +45 -0
- package/dist/express.cjs +21789 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.ts +65 -0
- package/dist/express.js +6 -0
- package/dist/express.js.map +1 -0
- package/dist/index.cjs +22118 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/next.cjs +21186 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.ts +90 -0
- package/dist/next.js +5 -0
- package/dist/next.js.map +1 -0
- package/dist/observe/client-ip.d.ts +109 -0
- package/dist/observe/next-router.d.ts +22 -0
- package/dist/observe/redact.d.ts +58 -0
- package/dist/observe/request.d.ts +75 -0
- package/dist/observe/response.d.ts +24 -0
- package/dist/runtime.d.ts +27 -0
- package/dist/serve/accept.d.ts +7 -0
- package/dist/serve/discovery.d.ts +56 -0
- package/dist/serve/hosted.d.ts +135 -0
- package/dist/serve/source.d.ts +48 -0
- package/dist/serve/tag-asset.generated.d.ts +14 -0
- package/dist/serve/tag.d.ts +131 -0
- package/dist/serve/twin.d.ts +162 -0
- package/dist/web.cjs +21225 -0
- package/dist/web.cjs.map +1 -0
- package/dist/web.d.ts +52 -0
- package/dist/web.js +6 -0
- package/dist/web.js.map +1 -0
- package/install.md +463 -0
- package/package.json +76 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/breaker.ts","../src/core/config.ts","../src/core/encode.ts","../src/core/queue.ts","../src/observe/client-ip.ts","../src/core/safe.ts","../src/core/transport.ts","../src/core/collector.ts","../src/serve/accept.ts","../src/core/record-gate.ts","../src/observe/response.ts","../src/runtime.ts","../src/serve/hosted.ts","../src/serve/twin.ts","../src/serve/source.ts"],"names":[],"mappings":"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","file":"chunk-L22VERBM.js","sourcesContent":["export type BreakerState = \"closed\" | \"open\" | \"half-open\";\n\nexport interface Breaker {\n /** May an attempt be made right now? */\n allow(): boolean;\n success(): void;\n failure(): void;\n readonly state: BreakerState;\n}\n\nexport interface BreakerOptions {\n failureThreshold?: number;\n openMs?: number;\n now?: () => number;\n}\n\n/**\n * Stop calling an endpoint that is not answering.\n *\n * ── What it is actually protecting ───────────────────────────────────────────\n * Not us -- the customer. When our ingest is down, every flush costs the host\n * process a DNS lookup, a connection attempt and a timeout. A fleet of customer\n * servers doing that every two seconds turns our outage into measurable latency\n * and socket pressure in their applications. The breaker makes an outage cost\n * approximately nothing on their side.\n *\n * ⚠️ **Only `retryable` outcomes count as failures.** A 400 means our payload is\n * wrong, which is our bug and must be loud in debug -- but it is not an outage,\n * and counting it would let one malformed field stop all telemetry for thirty\n * seconds at a time, forever, while the endpoint is perfectly healthy.\n *\n * ⚠️ While open, `record()` still ENQUEUES. The ring buffer bounds the memory,\n * and a thirty-second blip should not lose events the buffer would have held.\n * What the breaker stops is the network call, not the collection.\n */\nexport function createBreaker(options: BreakerOptions = {}): Breaker {\n const failureThreshold = options.failureThreshold ?? 5;\n const openMs = options.openMs ?? 30_000;\n const now = options.now ?? (() => Date.now());\n\n let consecutiveFailures = 0;\n let openedAt = 0;\n let state: BreakerState = \"closed\";\n\n return {\n get state() {\n return state;\n },\n allow(): boolean {\n if (state === \"closed\") return true;\n if (state === \"half-open\") return true;\n if (now() - openedAt >= openMs) {\n // One probe. If it fails we go straight back to open with a fresh\n // window, rather than letting a long outage produce a probe every\n // interval.\n state = \"half-open\";\n return true;\n }\n return false;\n },\n success(): void {\n consecutiveFailures = 0;\n state = \"closed\";\n },\n failure(): void {\n consecutiveFailures += 1;\n if (state === \"half-open\" || consecutiveFailures >= failureThreshold) {\n state = \"open\";\n openedAt = now();\n }\n },\n };\n}\n","import type { ClientIpSource } from \"../observe/client-ip.js\";\n\n/**\n * Turn options and environment into one frozen, validated configuration.\n *\n * Precedence, decided once here so no caller has to: explicit option, then\n * environment variable, then default. The same order `maxidomo-cli` settled on,\n * and for the same reason -- a precedence rule implemented twice is implemented\n * differently.\n */\n\n/**\n * What `isInternal` is shown. Structurally a subset of `ObservedRequest`, and\n * declared here rather than imported so config does not depend on the observer\n * that depends on it.\n */\nexport interface InternalTrafficContext {\n method: string;\n path: string;\n host?: string | undefined;\n userAgent?: string | undefined;\n}\n\nexport interface AgentHoneyConfig {\n ingestUrl?: string;\n /** ⚠️ Server-only. Never logged, never in a browser bundle. */\n serverKey?: string;\n siteId?: string;\n enabled?: boolean;\n debug?: boolean;\n batchSize?: number;\n flushIntervalMs?: number;\n maxQueueEvents?: number;\n maxBodyBytes?: number;\n requestTimeoutMs?: number;\n /**\n * Collapse identifiers out of a path: `/users/42` -> `/users/:id`.\n *\n * This is the main defence against both unbounded cardinality and per-user\n * values reaching analytics, and only the customer knows their routes.\n */\n routeTemplate?: (path: string) => string | undefined;\n /** Path SEGMENTS matching any of these become `[redacted]`. */\n redactPatterns?: readonly RegExp[];\n /**\n * ⚠️ **Default ON.** Replaces path segments that look like credentials --\n * uuids, cuids, JWTs, long hex, dense mixed-case strings -- with\n * `[redacted]`, so a site with tokens in its paths does not ship them here\n * merely because nobody configured `redactPatterns`.\n *\n * Set `false` to keep every segment verbatim. Doing so does NOT disable\n * `redactPatterns`; those are yours and always apply.\n *\n * See `observe/redact.ts` for what it does and does not catch, and why the\n * trade is made in the direction of redacting.\n */\n redactHighEntropyPaths?: boolean;\n /** Mark traffic the customer does not want counted or billed. */\n isInternal?: (request: InternalTrafficContext) => boolean;\n /**\n * Where the END CLIENT's address comes from. Default `\"platform\"`.\n *\n * ⚠️ The default reads edge headers a proxy overwrites (`cf-connecting-ip`\n * and friends) and, on Express, whatever `trust proxy` already made `req.ip`.\n * It deliberately does NOT read a bare `x-forwarded-for`, which any client\n * can send — see `observe/client-ip.ts` for why a spoofable address is worse\n * than none at all.\n *\n * - `\"forwarded\"` — also trust `x-forwarded-for`. Correct behind a proxy\n * that overwrites it; a self-service `VERIFIED` badge if it does not.\n * - `false` — never send an address. Country still arrives from the\n * platform header, because a country is not an address.\n * - a function — supply it yourself from whatever your edge sets.\n *\n * Also settable as `AGENTHONEY_CLIENT_IP=platform|forwarded|off`, so an\n * operator can turn it off without a deploy.\n */\n clientIp?: ClientIpSource;\n}\n\n/**\n * What the SDK sends when no site id was configured.\n *\n * ⚠️ Deliberately NOT a plausible id. Ingest replaces it from the credential,\n * so it never reaches storage -- but if it ever shows up somewhere, it should\n * read as \"nobody configured this\" rather than as a real site.\n */\nexport const UNSET_SITE_ID = \"unset\";\n\nexport type DisabledReason =\n | \"explicitly-disabled\"\n | \"missing-key\"\n | \"malformed-key\"\n | \"missing-url\"\n | \"browser-environment\";\n\n/**\n * The credential's SHAPE, checked offline.\n *\n * ⚠️ **This exists because of a real production incident**, 2026-09-17:\n * `maxidomo-app` was deployed, instrumented and healthy, and every request it\n * observed was rejected for two hours. The configured value was the key's\n * PREFIX — `ep_live_server_<id>` — which the dashboard displays permanently\n * because it is deliberately not a secret, next to the real key which is shown\n * once. They look alike. Nothing surfaced it: the SDK fails open (correctly),\n * ingest logs the refusal only on its own box, and the dashboard could not show\n * it.\n *\n * A shape check cannot tell a wrong secret from a right one — that needs the\n * pepper and the database, and it is ingest's job. It CAN tell a string that\n * could not possibly be a credential, with no network call, no dependency and\n * no ambiguity. That covers the entire class of mistake above.\n *\n * ── ⚠️ DELIBERATELY LOOSER than ingest's parser, and this is the whole design ─\n * The first version of this copied `credentials.ts`'s `KEY_RE` exactly. That is\n * the wrong call for code that lives in a customer's process **forever**:\n * `AGENTS.md` is explicit that nobody upgrades an analytics SDK, so an exact\n * copy of today's format would mean that the day AgentHoney issues a longer\n * key id, a different environment word, or a `v2` prefix, **every deployed SDK\n * refuses the new key and disables itself silently** — the customer rotates a\n * credential and their analytics goes dark with no error anywhere.\n *\n * That is a worse failure than the one this check prevents, and it would arrive\n * later, all at once, and be blamed on the rotation.\n *\n * So the check refuses only what CANNOT be a credential under any future\n * format: the `ep_` family prefix, and a final `_`-delimited segment long enough\n * to be a secret. That is exactly the incident — a prefix pasted where the key\n * belonged — and it stays true across format changes. Being loose is safe;\n * ingest still rejects anything genuinely invalid, which is the status quo.\n *\n * `tests/key-shape-agreement.test.ts` pins the one direction that matters: this\n * must never refuse something `parseKey` accepts.\n */\nconst KEY_FAMILY = /^ep_[A-Za-z0-9]+_[A-Za-z0-9]+_[A-Za-z0-9-]+_([A-Za-z0-9_-]+)$/;\n\n/** The shortest a secret could plausibly be. Today's are 43 characters. */\nconst MIN_SECRET = 16;\n\nexport function looksLikeKey(value: string): boolean {\n const match = KEY_FAMILY.exec(value.trim());\n return match !== null && (match[1]?.length ?? 0) >= MIN_SECRET;\n}\n\nexport interface ResolvedConfig {\n readonly ingestUrl: string;\n readonly serverKey: string;\n readonly siteId: string;\n readonly debug: boolean;\n readonly batchSize: number;\n readonly flushIntervalMs: number;\n readonly maxQueueEvents: number;\n readonly maxBodyBytes: number;\n readonly requestTimeoutMs: number;\n readonly routeTemplate: ((path: string) => string | undefined) | undefined;\n readonly redactPatterns: readonly RegExp[];\n readonly redactHighEntropyPaths: boolean;\n readonly isInternal: ((request: InternalTrafficContext) => boolean) | undefined;\n /** Never `undefined`: resolved to `\"platform\"` when nothing set it. */\n readonly clientIp: ClientIpSource;\n /** Non-null means the collector is a transparent no-op. */\n readonly disabled: DisabledReason | null;\n}\n\nexport const DEFAULTS = {\n batchSize: 20,\n maxBatchSize: 100,\n flushIntervalMs: 2_000,\n maxQueueEvents: 1_000,\n maxBodyBytes: 512 * 1024,\n requestTimeoutMs: 2_000,\n} as const;\n\n/**\n * Environment names a customer might create that would put the server key in a\n * browser bundle.\n *\n * ⚠️ The likeliest real-world leak of a server credential is not a bad import.\n * It is somebody writing `NEXT_PUBLIC_AGENTHONEY_SERVER_KEY` because that is\n * how they made the last environment variable work. Every one of these prefixes\n * means \"inline this into client-side JavaScript\", so a key under one of them\n * is already published.\n */\nconst PUBLIC_ENV_PREFIXES = [\"NEXT_PUBLIC_\", \"VITE_\", \"PUBLIC_\", \"REACT_APP_\", \"NUXT_PUBLIC_\"];\n\nexport class AgentHoneyConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AgentHoneyConfigError\";\n }\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction boolFromEnv(raw: string | undefined): boolean | undefined {\n if (raw === undefined) return undefined;\n const v = raw.trim().toLowerCase();\n if (v === \"1\" || v === \"true\" || v === \"yes\") return true;\n if (v === \"0\" || v === \"false\" || v === \"no\") return false;\n return undefined;\n}\n\nexport function resolveConfig(\n options: AgentHoneyConfig = {},\n env: Record<string, string | undefined> = typeof process === \"undefined\"\n ? {}\n : (process.env as Record<string, string | undefined>),\n): ResolvedConfig {\n // ⚠️ The ONE place this package is allowed to throw, and it throws before any\n // request is served rather than during one. A leaked server credential is not\n // something to degrade gracefully around: the customer needs to know now, by\n // name, which variable is the problem.\n for (const prefix of PUBLIC_ENV_PREFIXES) {\n const name = `${prefix}AGENTHONEY_SERVER_KEY`;\n if (env[name]) {\n throw new AgentHoneyConfigError(\n `${name} is set. The \"${prefix}\" prefix inlines a value into client-side JavaScript, ` +\n `so this key is already public. Revoke it, then set AGENTHONEY_SERVER_KEY instead ` +\n `(no prefix) so it stays on the server.`,\n );\n }\n }\n\n const ingestUrl = options.ingestUrl ?? env[\"AGENTHONEY_INGEST_URL\"] ?? \"\";\n const serverKey = options.serverKey ?? env[\"AGENTHONEY_SERVER_KEY\"] ?? \"\";\n // ⚠️ **Defaulted, never empty, and this is a data-loss fix rather than a\n // nicety.**\n //\n // The wire schema requires `siteId` with min length 1, so an unset value\n // rejects the ENTIRE BATCH as `invalid_batch` -- and because the SDK fails\n // open, that rejection is silent. The symptom is an empty dashboard and a\n // perfectly healthy application, which is the hardest thing this product can\n // do to somebody. Found by a real install attempt\n // (`docs/design/install-findings-2026-09-16.md`), reproduced against the real\n // schema rather than assumed.\n //\n // ⚠️ The honest reason a default is CORRECT here, rather than a papering-over:\n // ingest OVERWRITES `siteId` from the credential, because a body value is a\n // request to write into somebody else's data. So the field is never trusted\n // and never stored as sent -- its only job is passing validation. Requiring a\n // customer to supply it correctly was asking them to get right something that\n // cannot be wrong, and punishing them silently for not.\n //\n // A customer may still set it, and the dashboard still shows it, because an\n // operator matching a log line to a site wants to see it.\n const siteId = options.siteId ?? env[\"AGENTHONEY_SITE_ID\"] ?? UNSET_SITE_ID;\n const enabled = options.enabled ?? boolFromEnv(env[\"AGENTHONEY_ENABLED\"]) ?? true;\n const debug = options.debug ?? boolFromEnv(env[\"AGENTHONEY_DEBUG\"]) ?? false;\n\n let disabled: DisabledReason | null = null;\n if (typeof window !== \"undefined\") {\n // Belt and braces behind the `browser: null` export condition. If a bundler\n // that ignores export conditions got us here, collect nothing and -- above\n // all -- do not transmit the key.\n disabled = \"browser-environment\";\n } else if (!enabled) {\n disabled = \"explicitly-disabled\";\n } else if (!ingestUrl) {\n disabled = \"missing-url\";\n } else if (!serverKey) {\n disabled = \"missing-key\";\n } else if (!looksLikeKey(serverKey)) {\n // ⚠️ Refused OFFLINE, and only for a string that CANNOT be a credential.\n // Sending it would produce a silent `auth_failed` per batch forever, which\n // is what happened in production for two hours. Disabling is the same\n // fail-open no-op as `missing-key`: the customer's app is untouched, and the\n // reason is reported in debug and, unlike a rejected batch, is visible in\n // their own process.\n disabled = \"malformed-key\";\n }\n\n return Object.freeze({\n ingestUrl,\n serverKey,\n siteId,\n debug,\n batchSize: clamp(options.batchSize ?? DEFAULTS.batchSize, 1, DEFAULTS.maxBatchSize),\n flushIntervalMs: clamp(options.flushIntervalMs ?? DEFAULTS.flushIntervalMs, 100, 60_000),\n maxQueueEvents: clamp(options.maxQueueEvents ?? DEFAULTS.maxQueueEvents, 1, 100_000),\n maxBodyBytes: clamp(options.maxBodyBytes ?? DEFAULTS.maxBodyBytes, 1_024, DEFAULTS.maxBodyBytes),\n requestTimeoutMs: clamp(options.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs, 100, 30_000),\n routeTemplate: options.routeTemplate,\n redactPatterns: options.redactPatterns ?? [],\n // ⚠️ Defaults to TRUE. An `?? true` that a refactor turns into `?? false`\n // is the entire bug returning, so `config.test.ts` asserts the default.\n redactHighEntropyPaths: options.redactHighEntropyPaths ?? true,\n isInternal: options.isInternal,\n clientIp: normaliseClientIpOption(options.clientIp) ?? clientIpFromEnv(env[\"AGENTHONEY_CLIENT_IP\"]) ?? \"platform\",\n disabled,\n });\n}\n\n/**\n * ⚠️ An UNRECOGNISED value is ignored rather than treated as \"off\".\n *\n * A typo (`AGENTHONEY_CLIENT_IP=fowarded`) must not silently disable a\n * signal; it falls through to the default, which is the safe source anyway.\n * Absence is never the permissive branch here either — the permissive setting\n * is `forwarded`, and it is reachable only by spelling it correctly.\n */\n/**\n * ⚠️ **The same \"unrecognised is ignored\" rule applies to the OPTION, not\n * just the env var.** `clientIp` is typed, but a plain-JS caller with no\n * type-checker can pass anything -- `clientIp: \"Forwarded\"` was reaching\n * `resolveClientIp`'s `source === \"forwarded\"` check, failing it silently,\n * and then failing the wire schema's `ipSource` enum on every batch, which\n * discarded the batch entirely (`collector.ts`). A typo in this option must\n * cost an address, never a customer's telemetry.\n */\nfunction normaliseClientIpOption(value: ClientIpSource | undefined): ClientIpSource | undefined {\n if (value === undefined || value === false || typeof value === \"function\") return value;\n return value === \"platform\" || value === \"forwarded\" ? value : undefined;\n}\n\nfunction clientIpFromEnv(value: string | undefined): ClientIpSource | undefined {\n if (value === undefined) return undefined;\n const normalised = value.trim().toLowerCase();\n if (normalised === \"off\" || normalised === \"false\") return false;\n if (normalised === \"platform\" || normalised === \"forwarded\") return normalised;\n return undefined;\n}\n\n/**\n * Redact anything key-shaped before it reaches a log line.\n *\n * The SDK must never log the server key (§8.2). Debug output is written by\n * people debugging, pasted into issues, and captured by log aggregators.\n */\nexport function redact(text: string): string {\n return text.replace(/\\bep_(live|test)_server_[A-Za-z0-9_-]+/g, \"ep_$1_server_[redacted]\");\n}\n","import type { RequestEvent } from \"@agenthoney/event-schema\";\n\n/**\n * Turn events into a request body, stopping before a byte ceiling.\n *\n * ── Why this is not `JSON.stringify({ events })` ─────────────────────────────\n * The ingest refuses a body over 512 KiB. Encoding the whole batch and then\n * checking its size means either sending something that will be refused, or\n * truncating a JSON document -- which produces a body that is not JSON at all\n * and fails in a way no error message explains.\n *\n * So events are measured one at a time and the encoder stops BEFORE the one\n * that would cross the line. The untaken events stay at the head of the queue\n * for the next flush.\n */\n\n/** The largest encoded body the ingest will accept, in bytes. */\nexport const MAX_BODY_BYTES = 512 * 1024;\n\nconst OPEN = '{\"events\":[';\nconst CLOSE = \"]}\";\n\nexport interface EncodedBatch {\n body: string;\n /** How many events from the front of the input are in `body`. */\n taken: number;\n /**\n * An event that can NEVER be sent because it alone exceeds the ceiling.\n *\n * ⚠️ This is head-of-line blocking, and it is the reason this field exists.\n * Without it, one oversized event sits at the front of the queue forever:\n * every flush encodes zero events, commits nothing, and the SDK goes\n * permanently silent with a full buffer and no error. The caller drops it and\n * counts it as dropped.\n */\n oversized: boolean;\n}\n\nconst byteLength =\n typeof TextEncoder === \"function\"\n ? (s: string): number => new TextEncoder().encode(s).length\n : // Node 18+ always has TextEncoder; this is here so the module cannot\n // throw at import time on an exotic runtime, which would take the host\n // application down with it.\n (s: string): number => s.length;\n\nexport function encodeBatch(\n events: readonly RequestEvent[],\n maxBytes: number = MAX_BODY_BYTES,\n): EncodedBatch {\n if (events.length === 0) {\n return { body: OPEN + CLOSE, taken: 0, oversized: false };\n }\n\n const overhead = byteLength(OPEN) + byteLength(CLOSE);\n let used = overhead;\n const parts: string[] = [];\n\n for (const event of events) {\n const encoded = JSON.stringify(event);\n // Every event after the first costs a comma.\n const cost = byteLength(encoded) + (parts.length > 0 ? 1 : 0);\n if (used + cost > maxBytes) break;\n parts.push(encoded);\n used += cost;\n }\n\n return {\n body: OPEN + parts.join(\",\") + CLOSE,\n taken: parts.length,\n oversized: parts.length === 0,\n };\n}\n","/**\n * A bounded ring buffer of pending events.\n *\n * ── Why bounded, and why drop-OLDEST ─────────────────────────────────────────\n * This queue lives in a customer's production process. An unbounded one turns\n * our outage into their out-of-memory kill, which is the single worst thing an\n * analytics package can do to a host application. So it has a hard ceiling.\n *\n * When the ceiling is reached the OLDEST event is discarded, not the newest.\n * During an outage the recent past is what a site owner is looking at; a buffer\n * that refused new events would preserve a frozen window from whenever the\n * outage began and discard everything since.\n *\n * ⚠️ **Drops are COUNTED and reported on the wire** (`sdk.dropped`). A silently\n * short count is worse than a visibly short one: the product's whole claim is\n * that it shows traffic other tools miss, so under-reporting without saying so\n * is the one failure mode that discredits the number rather than the outage.\n *\n * ── peek/commit rather than drain ────────────────────────────────────────────\n * A send can partially succeed: the encoder stops before the byte limit, so\n * fewer events go out than were offered. `peek` then `commit(n)` keeps the\n * untaken ones at the head with no re-insertion, which is both cheaper and\n * impossible to get out of order.\n */\nexport class BoundedQueue<T> {\n readonly capacity: number;\n #items: (T | undefined)[];\n #head = 0;\n #size = 0;\n #dropped = 0;\n\n constructor(capacity: number) {\n if (!Number.isInteger(capacity) || capacity < 1) {\n throw new TypeError(`capacity must be a positive integer, got ${capacity}`);\n }\n this.capacity = capacity;\n this.#items = new Array<T | undefined>(capacity);\n }\n\n get size(): number {\n return this.#size;\n }\n\n /** How many events have been discarded because the buffer was full. */\n get dropped(): number {\n return this.#dropped;\n }\n\n push(item: T): void {\n if (this.#size === this.capacity) {\n // Full: overwrite the oldest and advance the head past it.\n this.#items[this.#head] = item;\n this.#head = (this.#head + 1) % this.capacity;\n this.#dropped += 1;\n return;\n }\n this.#items[(this.#head + this.#size) % this.capacity] = item;\n this.#size += 1;\n }\n\n /** The first `max` items, without removing them. */\n peek(max: number): T[] {\n const n = Math.min(max, this.#size);\n const out: T[] = new Array<T>(n);\n for (let i = 0; i < n; i += 1) {\n out[i] = this.#items[(this.#head + i) % this.capacity] as T;\n }\n return out;\n }\n\n /** Remove the first `n` items. Called only after they are safely sent. */\n commit(n: number): void {\n const count = Math.min(n, this.#size);\n for (let i = 0; i < count; i += 1) {\n this.#items[(this.#head + i) % this.capacity] = undefined;\n }\n this.#head = (this.#head + count) % this.capacity;\n this.#size -= count;\n }\n\n /** Reset the drop counter, once the count has been reported on the wire. */\n clearDropped(): void {\n this.#dropped = 0;\n }\n}\n","import type { RequestEvent } from \"@agenthoney/event-schema\";\n\n/**\n * The end client's address, and the coarse country — the two facts this SDK is\n * the ONLY thing positioned to supply.\n *\n * ── ⚠️ Why the SDK has to send it ────────────────────────────────────────────\n * Ingest's socket peer is the customer's SERVER, not their visitor. So unlike a\n * browser beacon, AgentHoney cannot observe a visitor's address itself, and\n * `network.ip` is the one field whose absence quietly disables a whole tier of\n * the product: **verification is IP work** — forward-confirmed reverse DNS, or\n * a vendor-published range. With no address every crawler stays `CLAIMED`\n * forever and `VERIFIED` is unreachable. Measured on live traffic 2026-09-17,\n * before this existed: 590 stored events, 581 `UNVERIFIABLE`, 9 `CLAIMED`,\n * **0 `VERIFIED`**, 2 country codes, and those two came from a hand-made test\n * payload rather than from any adapter.\n *\n * ── ⚠️ Trust is the whole design, not a detail ───────────────────────────────\n * `x-forwarded-for` is **client-supplied**. Anyone can send one. And a spoofed\n * address is not merely a wrong row: `GPTBot` in the user agent plus a real\n * OpenAI address in `x-forwarded-for` would forward-confirm and mint a\n * **`VERIFIED` GPTBot** row in a customer's dashboard. Verification would then\n * be a badge an attacker can award themselves, which is worse than not having\n * one — the honesty of that pill is the product.\n *\n * So the default source is **platform edge headers plus what the framework\n * already believes**, never a bare `x-forwarded-for`:\n *\n * - Edge headers (`cf-connecting-ip` and friends) are written by the\n * proxy itself and OVERWRITE whatever the client sent -- ON that\n * platform. **Their PRESENCE proves nothing by itself**: a visitor\n * talking directly to a customer's own nginx can send a `cf-connecting-ip`\n * that nothing ever overwrites, because there is no Cloudflare in the\n * path to overwrite it. Reading these in `platform` mode is trusting that\n * the deployment matches the header's name, which is the whole of what\n * the mode name promises the customer is asserting.\n * - Express's `req.ip` is the customer's OWN `trust proxy` decision. We\n * inherit it rather than inventing a trust policy on their behalf; with no\n * `trust proxy` configured it is simply the socket address.\n * - `x-forwarded-for` is read only when a customer sets\n * `clientIp: \"forwarded\"`, and then only its RIGHTMOST hop -- see\n * `rightmost` below, which is what makes that opt-in safe rather than a\n * promise the customer has to keep. It is a statement that a trusted proxy sets\n * it. The option's name says what it trusts.\n *\n * ⚠️ **`forwarded` mode never reads a platform header.** Choosing `forwarded`\n * is the customer saying \"my own reverse proxy is what's in front of me, and\n * it sets `x-forwarded-for` honestly\" -- which is precisely the case where a\n * platform edge is NOT terminating the connection, so a `cf-connecting-ip` on\n * that request was written by the visitor, not overwritten by Cloudflare.\n * Reading it anyway, as this did before 2026-09-20, let `GPTBot` plus a\n * forged `cf-connecting-ip` reverse-DNS-confirm and mint a `VERIFIED` row on\n * exactly the self-hosted installs Phase 69 pointed `forwarded` at. See\n * `docs/design/accepted-risks.md` R14.\n *\n * ⚠️ **Country is independent of the address.** A platform that already\n * resolved one hands it over in a header, and taking it costs nothing and\n * discloses nothing: it is coarse by construction. So `clientIp: false` still\n * yields geography, which is the cheapest honest version of this feature.\n */\n\n/** Where an address may come from. `false` sends none, ever. */\nexport type ClientIpSource =\n | \"platform\"\n | \"forwarded\"\n | false\n | ((facts: IpFacts) => string | undefined);\n\nexport interface IpFacts {\n /** Case-insensitive header lookup. Lowercase names in, single value out. */\n header: (name: string) => string | undefined;\n /** The peer on the socket. Express only; a `Request` does not carry one. */\n socketIp?: string | undefined;\n /** Express's `req.ip` — the app's own `trust proxy` verdict. */\n frameworkIp?: string | undefined;\n}\n\n/**\n * Headers a proxy sets ABOUT the connection it terminated, in preference order.\n *\n * ⚠️ Every one of these is overwritten by the edge that sets it, which is what\n * separates them from `x-forwarded-for`. A request that arrives at Cloudflare\n * carrying its own `cf-connecting-ip` does not keep it.\n */\nconst PLATFORM_IP_HEADERS = [\n \"cf-connecting-ip\", // Cloudflare, including a tunnel\n \"true-client-ip\", // Cloudflare Enterprise, Akamai\n \"x-vercel-forwarded-for\", // Vercel\n \"fly-client-ip\", // Fly.io\n \"x-nf-client-connection-ip\", // Netlify\n \"x-azure-clientip\", // Azure Front Door\n] as const;\n\n/** Country as the edge already resolved it. */\nconst COUNTRY_HEADERS = [\n \"cf-ipcountry\",\n \"x-vercel-ip-country\",\n \"x-nf-geo-country\",\n \"fastly-client-country\",\n \"x-azure-clientip-country\",\n] as const;\n\n/**\n * ⚠️ NOT countries, and both pass an `[A-Z]{2}` test. Cloudflare sends `XX`\n * when it could not resolve one and `T1` for a Tor exit. Storing either would\n * put a country on the map that does not exist — and `XX` in particular would\n * become the largest \"country\" a busy site has.\n */\nconst NON_COUNTRIES = new Set([\"XX\", \"T1\", \"A1\", \"A2\", \"AP\", \"EU\"]);\n\nconst IPV4 = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/;\nconst IPV6_CHARS = /^[0-9a-f:.]+$/;\n\n/**\n * One address, in one canonical form, or nothing.\n *\n * ⚠️ **`::ffff:1.2.3.4` and `1.2.3.4` are the SAME client**, and a Node socket\n * reports the first form on a dual-stack listener while every proxy header\n * reports the second. Left alone they hash to two different pseudonyms, so one\n * visitor becomes two and one crawler's verification is done twice — which is\n * exactly the kind of quiet wrongness this product cannot afford. They are\n * folded here, once, rather than at three call sites.\n *\n * ⚠️ A port is stripped (`1.2.3.4:51234`, and `[::1]:8080`), because a port is\n * per-connection: keeping it would make every request from one client a\n * different address.\n */\nexport function normaliseIp(raw: string | undefined | null): string | undefined {\n if (!raw) return undefined;\n let value = raw.trim().toLowerCase();\n if (!value || value === \"unknown\") return undefined;\n\n // `[::1]:8080` — bracketed IPv6 with a port.\n const bracketed = /^\\[([^\\]]+)\\](?::\\d+)?$/.exec(value);\n if (bracketed) value = bracketed[1] as string;\n // `1.2.3.4:51234` — IPv4 with a port. A bare IPv6 has many colons, so a\n // single colon is the discriminator rather than a guess.\n else if (value.split(\":\").length === 2 && value.includes(\".\")) {\n value = value.slice(0, value.indexOf(\":\"));\n }\n\n // IPv4-mapped IPv6, in both spellings.\n if (value.startsWith(\"::ffff:\") && value.includes(\".\")) value = value.slice(7);\n\n const v4 = IPV4.exec(value);\n if (v4) {\n // ⚠️ Range-checked. `999.1.1.1` matches the shape and is not an address;\n // sending it would put a row in `ip_holds` that no lookup can ever resolve.\n return v4.slice(1).every((octet) => Number(octet) <= 255) ? value : undefined;\n }\n\n // IPv6: colon-bearing, hex only, and within the column's 45 characters.\n if (value.includes(\":\") && IPV6_CHARS.test(value) && value.length <= 45) return value;\n return undefined;\n}\n\n/** The leftmost entry of a chain — the claimed origin, and the forgeable end. */\nfunction leftmost(chain: string | undefined): string | undefined {\n if (!chain) return undefined;\n return normaliseIp(chain.split(\",\")[0]);\n}\n\n/**\n * The RIGHTMOST entry of an `x-forwarded-for` chain, and the reason\n * `clientIp: \"forwarded\"` is safe to offer at all.\n *\n * ⚠️ **A visitor can write the left of this header; they cannot write the\n * right.** Each proxy APPENDS the peer it actually observed, so the last entry\n * is written by the hop nearest us and a forged prefix stays a prefix:\n *\n * - overwriting proxy (`proxy_set_header X-Forwarded-For $remote_addr`):\n * one entry, and rightmost == leftmost == the visitor.\n * - appending proxy (`$proxy_add_x_forwarded_for`, nginx's usual form, and\n * what was live on futureofagentic.com on 2026-09-20): rightmost is what\n * nginx saw, which IS the visitor.\n * - forged header with no proxy in front: the forgery is leftmost, our own\n * peer is rightmost, and the forgery is ignored.\n *\n * Reading the leftmost is what used to make this option dangerous, and the\n * only reason the install had to ask a customer to reconfigure their proxy --\n * a rule about somebody else's infrastructure that nothing could check. See\n * `docs/plans/69-an-install-that-sends-no-address-says-so.md`.\n *\n * ⚠️ **The trade, stated rather than buried:** with two or more REAL proxies\n * (a CDN in front of their nginx) the rightmost hop is the CDN, so this yields\n * the edge rather than the visitor -- wrong-but-safe, where leftmost is\n * right-but-forgeable. It is mostly moot: a CDN in front means a platform\n * header is present, and `platformIp` is consulted before this.\n */\nfunction rightmost(chain: string | undefined): string | undefined {\n if (!chain) return undefined;\n const hops = chain.split(\",\");\n return normaliseIp(hops[hops.length - 1]);\n}\n\n/**\n * ⚠️ An address that cannot be a visitor is not an address.\n *\n * With an appending chain and a sidecar (a service mesh, a local reverse proxy,\n * a health checker) the rightmost hop is `127.0.0.1` or a `10.x` -- shaped like\n * an address, useless as one. Stored, it becomes a \"visitor\" that every request\n * in the fleet shares, a pseudonym that groups strangers together, and a\n * verification job that can never resolve.\n */\nfunction isUnroutable(ip: string): boolean {\n if (ip === \"::1\" || ip.startsWith(\"fe80:\") || ip.startsWith(\"fc\") || ip.startsWith(\"fd\")) {\n return true;\n }\n const v4 = IPV4.exec(ip);\n if (!v4) return false;\n const [a, b] = [Number(v4[1]), Number(v4[2])];\n return (\n a === 10 ||\n a === 127 ||\n a === 0 ||\n (a === 192 && b === 168) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 169 && b === 254) ||\n (a === 100 && b >= 64 && b <= 127)\n );\n}\n\nfunction platformIp(facts: IpFacts): string | undefined {\n for (const name of PLATFORM_IP_HEADERS) {\n // ⚠️ `leftmost`, not `normaliseIp`: `x-vercel-forwarded-for` is a CHAIN,\n // not a single value. Reading it whole yields nothing at all whenever there\n // is more than one hop, which is the case this header exists for.\n const found = leftmost(facts.header(name));\n if (found) return found;\n }\n return undefined;\n}\n\nexport function resolveClientIp(facts: IpFacts, source: ClientIpSource): string | undefined {\n if (source === false) return undefined;\n if (typeof source === \"function\") {\n /*\n ⚠️ **A customer's hook that throws costs the ADDRESS, and nothing else.**\n\n It used to cost every event. `resolveClientIp` runs inside the adapters'\n one `safe()` wrapper around building the event, so a throw here skipped\n `recordOrCount` entirely -- and the site went on serving perfectly while\n NOTHING was recorded. Measured 2026-09-22 on 0.9.0 and 0.10.0: three\n requests, zero batches, and the debug line still reading \"collection on;\n client address from: custom\". An install with a custom address hook looked\n healthy and sent nothing.\n\n The mistake is easy and the type cannot stop it: the hook is handed\n `IpFacts`, a plain-JS caller assumes a `Request`, reaches for\n `request.headers.get(...)`, and throws on the first visitor.\n\n ⚠️ This is the rule the rest of this package already follows -- a\n customer's twin resolver that throws costs a `Link` header\n (`serve/source.ts`), not the response -- applied where it was missing. It\n is also why the catch is HERE rather than in each adapter: three call\n sites would be three chances to forget.\n */\n try {\n return normaliseIp(source(facts));\n } catch {\n return undefined;\n }\n }\n\n if (source === \"forwarded\") {\n // ⚠️ No `platformIp` here -- see the docblock above. `rightmost`, never\n // `leftmost`: see the docblock on `rightmost`. An unroutable hop (a\n // sidecar's `127.0.0.1`) is dropped rather than stored as a visitor.\n const forwarded = rightmost(facts.header(\"x-forwarded-for\"));\n if (forwarded && !isUnroutable(forwarded)) return forwarded;\n } else {\n const platform = platformIp(facts);\n if (platform) return platform;\n }\n\n // ⚠️ `frameworkIp` before `socketIp`: on Express they are the same value\n // until the app sets `trust proxy`, at which point the framework's answer is\n // the customer's own considered one and the socket is a load balancer.\n //\n // ⚠️ Unroutable here too. Without `trust proxy`, Express's own `req.ip` IS\n // the socket peer -- which, behind a customer's reverse proxy with no\n // `trust proxy` set, is the proxy's OWN loopback or link-local address, not\n // a visitor. Storing it made every request in the fleet share one\n // pseudonym and made `addressHealth`'s \"no address\" warning stay silent,\n // because a shaped-like-an-address string was there.\n const fallback = normaliseIp(facts.frameworkIp) ?? normaliseIp(facts.socketIp);\n return fallback && !isUnroutable(fallback) ? fallback : undefined;\n}\n\nexport function resolveCountry(facts: IpFacts): string | undefined {\n for (const name of COUNTRY_HEADERS) {\n const value = facts.header(name)?.trim().toUpperCase();\n if (value && /^[A-Z]{2}$/.test(value) && !NON_COUNTRIES.has(value)) return value;\n }\n return undefined;\n}\n\n/**\n * What the OPERATOR configured, which is not the same question as where this\n * one address came from.\n *\n * ⚠️ It reports the SETTING, not the header that happened to answer. \"platform\n * mode, and nothing is arriving\" is an install that was never finished;\n * \"off\" is an answer somebody gave. Without this field a dashboard cannot tell\n * them apart, and the only honest thing it can say to a customer who chose to\n * send nothing is to accuse them of a broken install -- which is what the first\n * draft of Phase 69 would have shipped.\n */\nexport function ipSourceName(source: ClientIpSource): \"platform\" | \"forwarded\" | \"off\" | \"custom\" {\n if (source === false) return \"off\";\n if (typeof source === \"function\") return \"custom\";\n return source;\n}\n\n/**\n * The `network` block.\n *\n * ⚠️ **It now carries `ipSource` even when there is no address**, which is the\n * one case that matters: a block saying \"platform, and nothing arrived\" is how\n * an install that can never verify a crawler becomes visible. That is not the\n * `{}` the old docblock refused -- an empty object says \"we looked and found\n * nothing\" with no way to act on it; this says which setting produced the\n * silence. An absent BLOCK stays the fourth state and means an older SDK.\n */\nexport function observeNetwork(\n facts: IpFacts,\n source: ClientIpSource,\n): RequestEvent[\"network\"] | undefined {\n const ip = resolveClientIp(facts, source);\n const countryCode = resolveCountry(facts);\n return {\n ...(ip ? { ip } : {}),\n ...(countryCode ? { countryCode } : {}),\n ipSource: ipSourceName(source),\n };\n}\n","/**\n * The wrapper every entry point into this package wears.\n *\n * ⚠️ **This is the most important twenty lines in the SDK.** Our code runs\n * inside other people's request handlers. A throw that escapes becomes a 500 on\n * a page we were only supposed to be counting -- the analytics package breaking\n * the application it measures, which is unforgivable in a way that a missing\n * metric is not.\n *\n * So every public surface is wrapped, and the wrapper swallows. It reports\n * through the debug channel when the customer asked for one, and is otherwise\n * silent: a warning printed on every request of a busy server is its own\n * outage.\n */\nexport function safe(fn: () => void, onError?: (error: unknown) => void): void {\n try {\n fn();\n } catch (error) {\n try {\n onError?.(error);\n } catch {\n // The error reporter threw. There is nowhere left to report that, and\n // trying would be the same bug one level up.\n }\n }\n}\n\n/** The async form, for flush paths. Returns a promise that never rejects. */\nexport async function safeAsync(\n fn: () => Promise<void>,\n onError?: (error: unknown) => void,\n): Promise<void> {\n try {\n await fn();\n } catch (error) {\n try {\n onError?.(error);\n } catch {\n // As above.\n }\n }\n}\n","/**\n * The seam between the collector and the network.\n *\n * Exists so the collector can be tested without a socket, and so a runtime\n * without `fetch` can supply its own. The three-way outcome below is the\n * important part of the contract.\n */\nexport type SendOutcome =\n | { outcome: \"accepted\"; status: number }\n /**\n * A 4xx that is not 408 or 429: our payload is wrong.\n *\n * ⚠️ **Drop the batch. Do not retry, and do not count it against the\n * breaker.** Retrying a body the server has already judged malformed just\n * sends it again, and treating it as an outage would let one bad field\n * silence all telemetry for thirty seconds at a time while the endpoint is\n * perfectly healthy. This is the same distinction maxidomo-app's\n * `ExpectedError` draws: a failure somebody wrote on purpose is not an\n * incident.\n */\n | { outcome: \"rejected\"; status: number }\n /** Network error, timeout, 408, 429 or 5xx. Retry, and count it. */\n | { outcome: \"retryable\"; status?: number; retryAfterMs?: number };\n\nexport interface Transport {\n send(\n body: string,\n headers: Readonly<Record<string, string>>,\n signal: AbortSignal,\n ): Promise<SendOutcome>;\n}\n\n/** Map an HTTP status onto the outcome taxonomy above. */\nexport function outcomeForStatus(status: number, retryAfterMs?: number): SendOutcome {\n if (status >= 200 && status < 300) return { outcome: \"accepted\", status };\n if (status === 408 || status === 429 || status >= 500) {\n return retryAfterMs === undefined\n ? { outcome: \"retryable\", status }\n : { outcome: \"retryable\", status, retryAfterMs };\n }\n return { outcome: \"rejected\", status };\n}\n\nfunction parseRetryAfter(value: string | null): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;\n const date = Date.parse(value);\n if (Number.isFinite(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n\n/**\n * The default transport: `fetch`, which every supported runtime has.\n *\n * ⚠️ It never throws. A thrown transport would reach the flush loop, and a\n * flush loop that can throw is one unhandled rejection away from taking a\n * customer's process down with it.\n */\nexport function fetchTransport(url: string): Transport {\n return {\n async send(body, headers, signal): Promise<SendOutcome> {\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body,\n signal,\n // Never follow a redirect: this request carries a bearer token, and\n // a redirect is an instruction from the network to send that\n // credential somewhere we did not choose.\n redirect: \"error\",\n keepalive: false,\n });\n return outcomeForStatus(\n response.status,\n parseRetryAfter(response.headers.get(\"retry-after\")),\n );\n } catch {\n // DNS failure, connection refused, TLS error, abort. All retryable, and\n // none of them is ever allowed to surface.\n return { outcome: \"retryable\" };\n }\n },\n };\n}\n","import type { RequestEvent } from \"@agenthoney/event-schema\";\nimport type { DropReason } from \"@agenthoney/event-schema/record-rule\";\nimport { createBreaker, type Breaker, type BreakerState } from \"./breaker.js\";\nimport { redact, type ResolvedConfig } from \"./config.js\";\nimport { encodeBatch } from \"./encode.js\";\nimport { BoundedQueue } from \"./queue.js\";\nimport { ipSourceName } from \"../observe/client-ip.js\";\nimport { safeAsync } from \"./safe.js\";\nimport { fetchTransport, type SendOutcome, type Transport } from \"./transport.js\";\n\nexport interface CollectorStats {\n queued: number;\n dropped: number;\n sent: number;\n failed: number;\n /** Requests deliberately not stored (Phase 55), not yet reported to ingest. */\n notRecorded: number;\n breaker: BreakerState;\n}\n\nexport interface Collector {\n /**\n * Hand an event to the collector.\n *\n * ⚠️ **Synchronous, returns void, and never throws.** Those three properties\n * are the contract with the host application and each is asserted by a test.\n * It is deliberately impossible for a caller to await this: the moment an\n * adapter can await the collector, somebody will, and analytics will be on\n * the critical path.\n */\n record(event: RequestEvent): void;\n /**\n * Count a request the recording rule declined (Phase 55). Same contract as\n * `record`: synchronous, void, never throws. The tally rides the next batch,\n * or a batch of its own when there are no events to send.\n */\n notRecorded?(reason: DropReason): void;\n /** Attempt to send what is buffered. Never rejects. */\n flush(): Promise<void>;\n readonly stats: CollectorStats;\n /** Stop the timer. For tests and for a host that manages its own lifecycle. */\n close(): void;\n}\n\nexport interface CollectorDeps {\n transport?: Transport;\n breaker?: Breaker;\n /** Injected so backoff is deterministic under test. */\n random?: () => number;\n /** Injected so tests need no real timers. */\n setTimer?: (fn: () => void, ms: number) => { cancel(): void };\n log?: (message: string) => void;\n}\n\n/**\n * How many consecutive addressless requests before the SDK says so, once.\n *\n * ⚠️ Chosen so a serverless cold start that handles ONE request never warns,\n * and a long-lived server warns within seconds of starting. See `watchAddress`.\n */\nconst ADDRESSLESS_WARN_AFTER = 20;\n\nconst MAX_RETRIES = 2;\n\n/** `NotRecordedSchema`'s per-reason ceiling. */\nconst MAX_NOT_RECORDED_COUNT = 10_000_000;\n\nfunction defaultSetTimer(fn: () => void, ms: number): { cancel(): void } {\n const handle = setInterval(fn, ms);\n // ⚠️ `unref()` is not an optimisation, it is a correctness requirement.\n //\n // Without it an interval keeps Node's event loop alive, so any short-lived\n // process that imports the SDK -- a CLI, a migration script, a build step --\n // hangs for the flush interval before exiting. To the customer that presents\n // as \"the analytics package broke my build\", and they are right.\n //\n // `unref` is Node-only; a runtime without it simply keeps the timer, which is\n // correct for a long-lived server.\n (handle as unknown as { unref?: () => void }).unref?.();\n return { cancel: () => clearInterval(handle) };\n}\n\n/** Ours always counts; only a customer-supplied `Collector` may lack the method. */\nexport type CountingCollector = Collector & Required<Pick<Collector, \"notRecorded\">>;\n\nexport function createCollector(config: ResolvedConfig, deps: CollectorDeps = {}): CountingCollector {\n const log = deps.log ?? ((message: string) => console.warn(redact(message)));\n const debug = (message: string): void => {\n if (config.debug) log(`[AgentHoney] ${message}`);\n };\n\n // A disabled collector is a transparent no-op -- not a broken one, and not a\n // noisy one. Missing configuration in production is normal (a preview\n // deployment, a contributor's laptop), so it is reported once in debug and\n // never again.\n if (config.disabled) {\n debug(`collection disabled: ${config.disabled}`);\n const noop: CountingCollector = {\n record: () => {},\n notRecorded: () => {},\n flush: async () => {},\n stats: { queued: 0, dropped: 0, sent: 0, failed: 0, notRecorded: 0, breaker: \"closed\" },\n close: () => {},\n };\n return noop;\n }\n\n // ⚠️ Debug says WHERE the address will come from, because the commonest\n // install failure is silent: the mode is right, the header is absent, and\n // nothing downstream can tell that from \"this site has no visitors\".\n debug(`collection on; client address from: ${ipSourceName(config.clientIp)}`);\n\n const queue = new BoundedQueue<RequestEvent>(config.maxQueueEvents);\n const transport = deps.transport ?? fetchTransport(config.ingestUrl);\n const breaker = deps.breaker ?? createBreaker();\n const random = deps.random ?? Math.random;\n const setTimer = deps.setTimer ?? defaultSetTimer;\n\n let sent = 0;\n let failed = 0;\n /**\n * Counts only, bounded by the fixed set of reasons -- this cannot grow with\n * traffic, which is why it needs no ceiling of its own.\n */\n const tally = new Map<DropReason, number>();\n const tallyTotal = (): number => [...tally.values()].reduce((a, b) => a + b, 0);\n /** Remove what a send reported, keeping anything counted during the send. */\n const settle = (reported: ReadonlyMap<DropReason, number>): void => {\n for (const [reason, n] of reported) {\n const left = (tally.get(reason) ?? 0) - n;\n if (left > 0) tally.set(reason, left);\n else tally.delete(reason);\n }\n };\n let inFlight: Promise<void> | null = null;\n\n /**\n * ⚠️ **The install that collects everything except the one thing verification\n * needs.** Measured 2026-09-20: a self-hosted Next site sent ~18,000 events a\n * day for three days, every one of them with no address, so every crawler it\n * saw stayed `CLAIMED` and its all-time verified count was zero. Nothing said\n * a word -- events flowed, charts filled, and the failure was invisible until\n * somebody went looking. See `docs/plans/69-...`.\n *\n * ⚠️ **\"Once per process\" is NOT once in a serverless runtime.** A cold start\n * is a new process, so a line printed on the first addressless request would\n * be a line per container -- thousands a day in somebody else's logs, from an\n * analytics SDK whose first promise is to be unobtrusive. So it waits for\n * `ADDRESSLESS_WARN_AFTER` requests IN THIS PROCESS to have all gone out with\n * no address: a one-request cold start never reaches it, and a long-lived\n * server reaches it in seconds.\n *\n * ⚠️ **Never when `clientIp` is `off`.** That is an answer, not a mistake,\n * and warning about it would nag the one customer who read the docs.\n */\n let addressless = 0;\n let warnedAddressless = false;\n const watchAddress = (event: RequestEvent): void => {\n if (warnedAddressless || config.clientIp === false) return;\n // An address on any request means this install can verify. One is enough:\n // a crawler behind a different edge is not this install's problem.\n if (event.network?.ip) {\n warnedAddressless = true;\n return;\n }\n if (++addressless < ADDRESSLESS_WARN_AFTER) return;\n warnedAddressless = true;\n log(\n `[AgentHoney] ${addressless} requests have been recorded with no client address, so ` +\n `crawler verification cannot run and every bot will stay \"claimed\". This server is ` +\n `behind a proxy that writes no platform header. Set AGENTHONEY_CLIENT_IP=forwarded ` +\n `to read the hop your proxy already writes, or AGENTHONEY_CLIENT_IP=off to silence ` +\n `this and send no address at all.`,\n );\n };\n\n const timer = setTimer(() => {\n void flush();\n }, config.flushIntervalMs);\n\n function headers(): Record<string, string> {\n return {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${config.serverKey}`,\n };\n }\n\n async function sendOnce(body: string): Promise<SendOutcome> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), config.requestTimeoutMs);\n (timeout as unknown as { unref?: () => void }).unref?.();\n try {\n return await transport.send(body, headers(), controller.signal);\n } finally {\n clearTimeout(timeout);\n }\n }\n\n /** Full jitter. A fleet of customer servers must not retry in lockstep. */\n function backoffMs(attempt: number, retryAfterMs?: number): number {\n if (retryAfterMs !== undefined) return Math.min(retryAfterMs, 30_000);\n return random() * Math.min(5_000, 200 * 3 ** attempt);\n }\n\n const sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => {\n const t = setTimeout(resolve, ms);\n (t as unknown as { unref?: () => void }).unref?.();\n });\n\n async function flushOnce(): Promise<void> {\n if (queue.size === 0 && tally.size === 0) return;\n if (!breaker.allow()) {\n debug(\"breaker open, skipping flush\");\n return;\n }\n\n const candidates = queue.peek(config.batchSize);\n const droppedSoFar = queue.dropped;\n if (droppedSoFar > 0 && candidates[0]) {\n // Report the shortfall on the wire rather than under-counting silently.\n candidates[0] = {\n ...candidates[0],\n sdk: { ...candidates[0].sdk, dropped: droppedSoFar },\n };\n }\n\n // ⚠️ Snapshot the tally: counts arriving during the send belong to the next.\n const reported = new Map(tally);\n // A tally is a few dozen bytes; reserve room so it never pushes the events\n // over the ceiling the encoder measured against.\n const encoded = encodeBatch(candidates, config.maxBodyBytes - (reported.size > 0 ? 512 : 0));\n if (reported.size > 0) {\n // ⚠️ Clamped to the wire maximum. An over-limit count would get the WHOLE\n // batch refused -- and a refusal commits past the real events beside it.\n const counts = Object.fromEntries([...reported].map(([reason, n]) => [reason, Math.min(n, MAX_NOT_RECORDED_COUNT)]));\n encoded.body = `${encoded.body.slice(0, -1)},\"notRecorded\":${JSON.stringify(counts)}}`;\n }\n\n if (candidates.length > 0 && encoded.oversized) {\n // One event alone exceeds the body limit and can never be sent. Drop it,\n // or it blocks the head of the queue forever and the SDK goes silent with\n // a full buffer and no error anywhere.\n queue.commit(1);\n debug(\"dropped one event larger than the body limit\");\n return;\n }\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {\n const result = await sendOnce(encoded.body);\n\n if (result.outcome === \"accepted\") {\n settle(reported);\n queue.commit(encoded.taken);\n if (droppedSoFar > 0) queue.clearDropped();\n sent += encoded.taken;\n breaker.success();\n return;\n }\n\n if (result.outcome === \"rejected\") {\n // Our payload is wrong. Retrying sends the same bad body again, and\n // counting it against the breaker would silence a healthy endpoint.\n settle(reported);\n queue.commit(encoded.taken);\n failed += encoded.taken;\n debug(`batch rejected with ${result.status}; dropped ${encoded.taken} event(s)`);\n return;\n }\n\n if (attempt === MAX_RETRIES) break;\n await sleep(backoffMs(attempt, result.retryAfterMs));\n }\n\n failed += encoded.taken;\n breaker.failure();\n debug(`batch failed after ${MAX_RETRIES + 1} attempt(s); ${queue.size} event(s) still buffered`);\n }\n\n function flush(): Promise<void> {\n // One flush at a time. Two concurrent flushes would both peek the same\n // head of the queue and send it twice -- which ingest deduplicates, but\n // only because every event carries an id. Not relying on that.\n if (inFlight) return inFlight;\n inFlight = safeAsync(flushOnce, (error) => debug(`flush failed: ${String(error)}`)).finally(\n () => {\n inFlight = null;\n },\n );\n return inFlight;\n }\n\n return {\n record(event: RequestEvent): void {\n // No try/catch needed for the push itself -- the ring buffer cannot throw\n // -- but the contract is \"never throws\" and a future edit here must not\n // be able to break it.\n try {\n queue.push(event);\n watchAddress(event);\n } catch {\n /* never allowed to surface */\n }\n },\n notRecorded(reason: DropReason): void {\n try {\n tally.set(reason, (tally.get(reason) ?? 0) + 1);\n } catch {\n /* never allowed to surface */\n }\n },\n flush,\n get stats(): CollectorStats {\n return {\n queued: queue.size,\n dropped: queue.dropped,\n sent,\n failed,\n notRecorded: tallyTotal(),\n breaker: breaker.state,\n };\n },\n close(): void {\n timer.cancel();\n },\n };\n}\n","//\n// Decide whether a client asked for markdown.\n//\n// ⚠️ **This function is the whole safety of the serve half.** Getting it wrong\n// in the permissive direction means serving markdown to a human's browser,\n// which is a broken website -- and it is easy to get wrong, because every\n// browser on earth ends its Accept header with the catch-all range `*/*` at a\n// low q-value. Matching a wildcard would serve markdown to everyone.\n//\n// So the rule is: an EXPLICIT `text/markdown` with a non-zero q, which must\n// also be at least as preferred as `text/html`. A wildcard never qualifies. A\n// client that did not name markdown does not get markdown.\n//\n// Measured against caprail.dev on 2026-09-16: GPTBot sending `Accept: */*`\n// receives HTML, byte-identical to what a browser receives. That is the correct\n// behaviour and this reproduces it -- not by checking WHO is asking, but by\n// checking WHAT was asked for. Identity-based serving would be cloaking, would\n// force `Vary: User-Agent` and destroy shared caching, and would let a\n// spoofable string decide what a visitor receives.\n//\n\nexport interface MediaRange {\n type: string;\n q: number;\n}\n\n/**\n * ⚠️ **A q-value grammar, not `Number(...)`.** `Number` parses far more than\n * RFC 9110's `qvalue` — hex (`\"0x0\"` is `0`), an empty string (`\"\"` is `0`,\n * not \"absent\"), leading/trailing junk after a `split(\"=\")` with more than\n * one `=` in it. `clients/wordpress/src/Accept.php`'s `is_numeric` accepts a\n * different, also-too-wide set, and the two disagreeing on a malformed q was\n * a silent drift between the two implementations of the same contract.\n * Matched here so both sides parse exactly the same numbers: optional\n * digits, an optional single dot, at least one trailing digit.\n */\nconst QVALUE = /^\\d*\\.?\\d+$/;\n\nexport function parseAccept(header: string | null | undefined): MediaRange[] {\n if (!header) return [];\n const ranges: MediaRange[] = [];\n for (const part of header.split(\",\")) {\n const segments = part.trim().split(\";\");\n const type = segments[0]?.trim().toLowerCase();\n if (!type) continue;\n let q = 1;\n for (const segment of segments.slice(1)) {\n // ⚠️ **Split on the FIRST `=` only, matching `explode('=', $s, 2)` on\n // the PHP side.** `segment.split(\"=\")` with no limit silently dropped\n // everything after a SECOND `=` — `q=0.5=x` extracted the value\n // `\"0.5\"`, parsed it, and read the range as `q=0.5`. PHP's bounded\n // explode keeps `\"0.5=x\"` whole, which no q-grammar accepts, so PHP\n // already read the same header as malformed (q=1). Splitting the same\n // way here is what makes the two implementations agree, not the\n // grammar check below on its own.\n const eq = segment.indexOf(\"=\");\n const key = (eq === -1 ? segment : segment.slice(0, eq)).trim().toLowerCase();\n if (key === \"q\") {\n const value = eq === -1 ? undefined : segment.slice(eq + 1).trim().toLowerCase();\n // A malformed q is treated as 1, which is what RFC 9110 implies and\n // what every server does in practice.\n q = value !== undefined && QVALUE.test(value) ? Math.min(1, Math.max(0, Number(value))) : 1;\n }\n }\n ranges.push({ type, q });\n }\n return ranges;\n}\n\n/** The q-value for an EXACT type. Wildcard ranges deliberately do not count. */\nfunction exactQ(ranges: readonly MediaRange[], type: string): number {\n let best = 0;\n for (const range of ranges) {\n if (range.type === type) best = Math.max(best, range.q);\n }\n return best;\n}\n\n/** The q-value for a type, allowing a `type/` wildcard or a full wildcard. */\nfunction effectiveQ(ranges: readonly MediaRange[], type: string): number {\n const prefix = `${type.split(\"/\")[0]}/*`;\n let best = 0;\n for (const range of ranges) {\n if (range.type === type || range.type === prefix || range.type === \"*/*\") {\n best = Math.max(best, range.q);\n }\n }\n return best;\n}\n\nexport const MARKDOWN_TYPES = [\"text/markdown\", \"text/x-markdown\"] as const;\n\nexport function prefersMarkdown(header: string | null | undefined): boolean {\n const ranges = parseAccept(header);\n if (ranges.length === 0) return false;\n\n let markdown = 0;\n for (const type of MARKDOWN_TYPES) {\n markdown = Math.max(markdown, exactQ(ranges, type));\n }\n if (markdown === 0) return false;\n\n // `text/html` reached through a wildcard still counts here: a client sending\n // `text/markdown;q=0.1` alongside a broad wildcard prefers something else,\n // and should get it.\n const html = effectiveQ(ranges, \"text/html\");\n return markdown >= html;\n}\n","import type { RequestEvent } from \"@agenthoney/event-schema\";\nimport { shouldRecord, type RecordDecision, type RecordFacts } from \"@agenthoney/event-schema/record-rule\";\nimport { prefersMarkdown } from \"../serve/accept.js\";\nimport type { Collector } from \"./collector.js\";\n\n/**\n * Store the event, or count it (Phase 55). Every adapter's last step.\n *\n * `shouldRecord` in `@agenthoney/event-schema/record-rule` is the rule and its\n * reasoning; this only supplies what the wire event cannot carry -- whether the\n * client's `Accept` preferred markdown, and whether the request was the page's\n * own background traffic (the Next adapter's `nextBackground`) -- and applies\n * the answer.\n *\n * ⚠️ **A throw RECORDS.** An event kept by mistake is a row an operator can see\n * and question; an event dropped by mistake is traffic that silently never\n * existed. Keeping is the only failure direction anyone can notice.\n */\nexport function recordOrCount(\n collector: Collector,\n event: RequestEvent,\n accept: string | null | undefined,\n background?: RecordFacts[\"background\"],\n): void {\n let decision: RecordDecision;\n try {\n decision = shouldRecord({ ...event, acceptPrefersMarkdown: prefersMarkdown(accept), background });\n } catch {\n decision = { record: true, because: \"page\" };\n }\n if (decision.record) collector.record(event);\n // ⚠️ Optional: `Collector` is a public option, and a customer's own collector\n // written before Phase 55 has no such method. Not counting beats throwing.\n else collector.notRecorded?.(decision.reason);\n}\n","import type { RequestEvent } from \"@agenthoney/event-schema\";\n\n/**\n * Response facts, read from headers that already exist.\n *\n * ⚠️ **Nothing here ever touches the body.** A response is a single-read\n * stream: to learn what it contained you would have to wrap `write`/`end` and\n * buffer it, which adds latency, adds memory proportional to the page, and can\n * corrupt what the customer sends if the encoding or backpressure handling is\n * wrong. `contentLength` is therefore read from the header and is simply absent\n * for a chunked or streamed response -- the dashboard shows \"not measured\",\n * which is honest and free.\n */\n\nexport interface ResponseFacts {\n status?: number | undefined;\n contentType?: string | null | undefined;\n contentLength?: string | number | null | undefined;\n latencyMs?: number | undefined;\n observation: RequestEvent extends { response?: infer R }\n ? R extends { observation: infer O }\n ? O\n : never\n : never;\n}\n\nexport function observeResponse(facts: ResponseFacts): NonNullable<RequestEvent[\"response\"]> {\n const response: NonNullable<RequestEvent[\"response\"]> = { observation: facts.observation };\n\n // ⚠️ `observation: \"unknown\"` means \"we looked and could not tell\", and the\n // wire schema refuses a status or content type alongside it. Enforced here\n // too, because an adapter is where the mistake would be made -- and an event\n // rejected at ingest is an event silently lost.\n if (facts.observation === \"unknown\") {\n if (typeof facts.latencyMs === \"number\" && Number.isFinite(facts.latencyMs)) {\n response.latencyMs = Math.max(0, Math.round(facts.latencyMs));\n }\n return response;\n }\n\n if (typeof facts.status === \"number\" && facts.status >= 100 && facts.status <= 599) {\n response.status = facts.status;\n }\n if (facts.contentType) {\n response.contentType = String(facts.contentType).slice(0, 255);\n }\n const length = typeof facts.contentLength === \"string\" ? Number(facts.contentLength) : facts.contentLength;\n if (typeof length === \"number\" && Number.isFinite(length) && length >= 0) {\n response.contentLength = Math.round(length);\n }\n if (typeof facts.latencyMs === \"number\" && Number.isFinite(facts.latencyMs)) {\n response.latencyMs = Math.max(0, Math.round(facts.latencyMs));\n }\n\n return response;\n}\n","/**\n * ⚠️ The SDK's own name and version, in ONE place.\n *\n * They were declared separately in `web.ts` and `express.ts`, and adding a\n * third adapter would have made three copies of a version string that must\n * match `package.json` -- a list nothing checks, written down three times.\n * `runtime.test.ts` asserts the version against the manifest.\n */\nexport const SDK_NAME = \"@agenthoney/analytics\";\nexport const SDK_VERSION = \"0.14.0\";\n\n/**\n * Identify the runtime, for the `sdk.runtime` field.\n *\n * Kept deliberately dumb and total: it must never throw at import time, on any\n * runtime, because that would take the host application down before it serves\n * a request. Every branch is guarded and there is a final fallback.\n */\nexport function runtimeName(): string {\n try {\n const g = globalThis as Record<string, unknown>;\n const deno = g[\"Deno\"] as { version?: { deno?: string } } | undefined;\n if (deno?.version?.deno) return `deno-${deno.version.deno}`;\n const bun = g[\"Bun\"] as { version?: string } | undefined;\n if (bun?.version) return `bun-${bun.version}`;\n if (typeof process !== \"undefined\" && process.versions?.node) {\n return `node-${process.versions.node}`;\n }\n const nav = g[\"navigator\"] as { userAgent?: string } | undefined;\n if (nav?.userAgent?.includes(\"Cloudflare-Workers\")) return \"workerd\";\n } catch {\n /* fall through */\n }\n return \"unknown\";\n}\n\n/**\n * A unique event id.\n *\n * `crypto.randomUUID()` where available -- it is in every supported runtime and\n * costs no dependency. The fallback exists so an exotic runtime degrades to a\n * slightly weaker id rather than to a crash; a collision costs one deduplicated\n * event, a throw costs the customer a 500.\n */\nexport function newEventId(): string {\n try {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (typeof c?.randomUUID === \"function\") return c.randomUUID();\n } catch {\n /* fall through */\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;\n}\n","import { normalisePath } from \"../observe/request.js\";\nimport type { Twin } from \"./twin.js\";\n\n/**\n * Twins the customer never wrote: resolved from what their own visitors agreed\n * a page says.\n *\n * ── ⚠️ Why this REFRESHES rather than fetches ────────────────────────────────\n * `TwinOptions.resolve` runs inside the customer's request path, and this SDK's\n * first rule is that **nothing awaits the network there**. A resolver that\n * fetched per request would add our latency -- and our availability -- to every\n * page of their site, which is precisely the trade this product refuses to make\n * anywhere else.\n *\n * So the whole published set is pulled into memory and `resolve` is a `Map`\n * lookup, with the network happening in the background. ⚠️ **One measured\n * exception**, below: a COLD process awaits its first load, because answering\n * \"no twin\" on a fresh isolate sent 2 of 8 live `text/markdown` requests an\n * HTML page instead.\n *\n * ── ⚠️ Demand-driven, with no timer and no I/O at construction ───────────────\n * The first version created this with `setInterval` and fired a refresh from\n * the constructor. **Both are forbidden in a Cloudflare Worker**: I/O in the\n * global scope throws, and a timer does not survive an isolate being evicted.\n * It would have failed on the first runtime it was built for -- agenthoney.ai\n * is a Worker -- and it would have failed at deploy, not in a test.\n *\n * So refreshing is something the HOST asks for: `refreshIfStale()` is cheap to\n * call on every request and does nothing until the data is older than\n * `refreshMs`. A Worker passes it to `ctx.waitUntil`; a Node server can call it\n * per request or on its own interval.\n *\n * ── ⚠️ The cold await was DELIBERATE; where it leaked to was not ─────────────\n * A cold `resolve` awaits, and `hosted.test.ts` carries the measurement that\n * made it so. What nobody noticed is which requests reach `resolve`: until\n * Phase 49, `./express`'s ADVERTISE branch called it on **every passing GET**\n * and deferred `next()` until it settled, and `./web` awaited it after the\n * handler had already built the response. So an exception argued for a client\n * asking for markdown was being paid by a human loading HTML -- the one thing\n * this SDK is built not to do -- on the one deployment that uses this module.\n *\n * Two rules now, and `lookup` exists so a caller can obey them:\n *\n * 1. **`lookup` is synchronous and never touches the network.** Anything\n * running on a request that did NOT ask for markdown -- advertising, above\n * all -- uses it, and a cold process simply advertises nothing.\n * 2. **`resolve` may await on a cold process, and is now BOUNDED.** It is for\n * requests that asked for markdown by `.md` suffix or `Accept`, where the\n * alternative is never serving a twin on a serverless isolate at all. The\n * bound is `coldTimeoutMs`; past it the answer is \"no twin\" and the\n * customer's own handler runs. It had no bound before, which meant a\n * hanging ingest held a customer's request for as long as it liked.\n *\n * ── ⚠️ Every failure resolves to \"no twin\" ───────────────────────────────────\n * Before the first refresh completes, during an outage, after a 500: `resolve`\n * returns undefined and the customer's own HTML is served unchanged. A twin\n * that is missing is a page that works normally; there is no failure here that\n * degrades to anything worse.\n */\n\nexport interface HostedTwinsOptions {\n /** The SERVER key. This corpus is not public and a browser never reads it. */\n serverKey: string;\n /** Defaults to the SDK's configured ingest origin. */\n ingestUrl?: string;\n /**\n * How stale the set may get. ⚠️ A page wins consensus over DAYS, so there is\n * nothing to gain from a tight interval and a shared database to protect.\n */\n refreshMs?: number;\n /**\n * The ceiling on the one await this module is allowed (see rule 2 above).\n *\n * ⚠️ **A default, not a measurement.** Nobody has measured p99 of a corpus\n * pull from a serverless region yet, and this number lands on somebody's page\n * load. Phase 49 records that measuring it is outstanding; until then it is\n * short enough that the worst case is a page that renders normally.\n */\n coldTimeoutMs?: number;\n /**\n * How long a failed refresh stops the next one being attempted.\n *\n * ⚠️ **The breaker the telemetry half has had since the beginning, and this\n * half did not.** `refreshIfStale()` runs on every request, and a failure\n * does not move `fetchedAt` -- so for the whole of an ingest outage, every\n * request through the customer's server started another refresh the moment\n * the last one failed. `breaker.ts` says exactly why that is the customer's\n * problem and not ours: a fleet of their servers paying a DNS lookup, a\n * connect and a timeout, continuously, because OUR endpoint is down.\n *\n * A held-back refresh is free: the corpus already in memory keeps serving.\n */\n failureBackoffMs?: number;\n /**\n * The ceiling on a single refresh's network call.\n *\n * ⚠️ Nothing awaits this on a request, so this bound is not about latency --\n * it is about a HANGING ingest. Without it a stalled connection kept\n * `inFlight` set for as long as the runtime's own socket timeout (undici:\n * five minutes), and `refreshIfStale()` returns that same promise, so the\n * corpus stopped refreshing entirely for the duration.\n */\n refreshTimeoutMs?: number;\n /** Injected for tests. */\n now?: () => number;\n /** Injected for tests. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface HostedTwins {\n /**\n * One twin, straight from ingest, for a COLD process.\n *\n * ⚠️ **A single row, never the corpus.** The bulk refresh can be megabytes\n * and an isolate that will serve one request must not pay for it on that\n * request. Bounded by the caller; fails to `undefined`, never throws.\n */\n fetchOne: (path: string) => Promise<Twin | undefined>;\n /**\n * ⚠️ **Synchronous, and it never starts a fetch.** For every caller that runs\n * on a request which has not asked for markdown. A cold process answers\n * `undefined`, which is the truth: this isolate does not know of a twin.\n */\n lookup: (path: string) => Twin | undefined;\n resolve: (path: string) => Twin | undefined | Promise<Twin | undefined>;\n /** Refresh unconditionally. Mostly for tests and for a deliberate warm-up. */\n refresh: () => Promise<void>;\n /**\n * Refresh only if the set is older than `refreshMs`, and never more than one\n * at a time.\n *\n * ⚠️ Safe to call on every request: it is a clock comparison in the common\n * case. Hand it to `ctx.waitUntil` on a Worker so the refresh outlives the\n * response without delaying it.\n */\n refreshIfStale: () => Promise<void>;\n readonly size: number;\n}\n\nfunction originOf(url: string): string | undefined {\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The ceiling ingest itself applies, stated here so the two agree.\n *\n * ⚠️ A response longer than this is a response from an ingest that changed its\n * mind without telling this file, and the honest thing to do with the excess is\n * drop it rather than hold an unbounded map in somebody else's process.\n * `Corpus.php` carries the same constant for the same reason.\n */\nconst PAGE_LIMIT = 2000;\n\nexport function hostedTwins(options: HostedTwinsOptions): HostedTwins {\n const base = originOf(options.ingestUrl ?? \"https://ingest.agenthoney.ai/v1/events\");\n const refreshMs = options.refreshMs ?? 300_000;\n const coldTimeoutMs = options.coldTimeoutMs ?? 750;\n // ⚠️ 30s, the same window `createBreaker` opens for telemetry. Two different\n // numbers for \"stop calling a dead endpoint\" would be two behaviours to\n // explain to a customer watching their egress.\n const failureBackoffMs = options.failureBackoffMs ?? 30_000;\n const refreshTimeoutMs = options.refreshTimeoutMs ?? 5_000;\n const fetchImpl = options.fetchImpl ?? fetch;\n\n const now = options.now ?? (() => Date.now());\n let twins = new Map<string, Twin>();\n let fetchedAt = 0;\n /** When the last refresh failed, so the next one can be held back. 0 = none. */\n let failedAt = 0;\n // ⚠️ One in flight at a time. Without this a burst against a cold isolate\n // starts a refresh per request -- a stampede against our own ingest, caused\n // by traffic to somebody else's site.\n let inFlight: Promise<void> | undefined;\n\n /**\n * ⚠️ A timeout the CALLER owns, not one on the shared promise.\n *\n * `AbortSignal.timeout` is not on every runtime this SDK supports, and\n * `AbortController` is -- so the signal is built here rather than assumed.\n * Aborting is safe on THIS path, unlike in `withDeadline`: the abort ends the\n * whole refresh, which is the thing that has failed, rather than one waiter's\n * share of a load others are waiting on.\n */\n function abortAfter(ms: number): { signal: AbortSignal | undefined; done: () => void } {\n const Controller = (globalThis as { AbortController?: typeof AbortController }).AbortController;\n if (!Controller) return { signal: undefined, done: () => {} };\n const controller = new Controller();\n const timer = setTimeout(() => controller.abort(), ms);\n (timer as unknown as { unref?: () => void }).unref?.();\n return { signal: controller.signal, done: () => clearTimeout(timer) };\n }\n\n async function refresh(): Promise<void> {\n if (!base) return;\n const deadline = abortAfter(refreshTimeoutMs);\n try {\n const response = await fetchImpl(`${base}/v1/twins`, {\n headers: { authorization: `Bearer ${options.serverKey}` },\n ...(deadline.signal ? { signal: deadline.signal } : {}),\n });\n if (!response.ok) {\n // ⚠️ A 5xx is the endpoint failing and backs off. So does a 401: a\n // revoked key will not start working because we asked again in 200ms.\n failedAt = now();\n return;\n }\n const body = (await response.json()) as {\n twins?: Array<{ path?: unknown; markdown?: unknown }>;\n };\n if (!Array.isArray(body.twins)) {\n // Malformed is the endpoint misbehaving, and backs off like a 5xx.\n failedAt = now();\n return;\n }\n\n // ⚠️ **An empty answer NEVER erases a populated corpus.** A parseable 200\n // carrying no rows is indistinguishable, at this layer, from a site whose\n // twins were all withdrawn -- and one of those two readings silently\n // unpublishes everything a customer serves. The safe reading is the one\n // that keeps serving what we already had; a real withdrawal reaches the\n // customer through a suppression row and the next non-empty refresh.\n // `Corpus.php` calls this `empty_would_erase` and learned it first.\n if (body.twins.length === 0 && twins.size > 0) {\n // ⚠️ A HEALTHY answer, so the clock moves: without this the refresh\n // was retried on every request for as long as the answer stayed empty.\n fetchedAt = now();\n failedAt = 0;\n return;\n }\n\n // ⚠️ Built into a NEW map and swapped, never mutated in place. A resolve\n // running concurrently with a refresh must see either the old set or the\n // new one, never a half-populated one -- which would serve a 404 for a\n // page that has a twin, intermittently, for the duration of the refresh.\n const next = new Map<string, Twin>();\n for (const row of body.twins.slice(0, PAGE_LIMIT)) {\n if (typeof row?.path !== \"string\" || typeof row.markdown !== \"string\") continue;\n // ⚠️ The title is NOT carried onto the Twin: a `Twin` is a body and its\n // headers, and the title is already the first heading of the markdown.\n // Adding a field the serve path does not read would be an API surface\n // with no reader.\n // ⚠️ **Normalised on WRITE as well as on read.** A WordPress permalink\n // ends in a slash, so the tag reports `/about/` and every lookup arrives\n // as `/about` -- and nothing would ever match. The failure is silent and\n // total: harvests upload, twins publish, the dashboard shows a healthy\n // corpus, and not one page serves markdown. `Corpus.php:238-255` found\n // it in the rig; this file is what that file was copied FROM and never\n // got the fix back.\n next.set(normalisePath(row.path), { body: row.markdown });\n }\n twins = next;\n fetchedAt = now();\n failedAt = 0;\n } catch {\n // ⚠️ A throw is a network failure or our own abort, and both mean the\n // next refresh waits. Before this, a failure left `fetchedAt` untouched\n // and every subsequent request started another attempt.\n failedAt = now();\n // ⚠️ Keep the previous set. An outage must not empty a customer's twins;\n // stale markdown is better than none, and none is what an agent reads as\n // \"this site has nothing for me\".\n } finally {\n deadline.done();\n }\n }\n\n function refreshIfStale(): Promise<void> {\n if (inFlight) return inFlight;\n if (fetchedAt !== 0 && now() - fetchedAt < refreshMs) return Promise.resolve();\n // ⚠️ After the staleness check, so a healthy warm process is never held\n // back, and before any I/O: this is the whole point of the backoff.\n if (failedAt !== 0 && now() - failedAt < failureBackoffMs) return Promise.resolve();\n inFlight = refresh().finally(() => {\n inFlight = undefined;\n });\n return inFlight;\n }\n\n /**\n * The one bounded await, and the bound is the whole point.\n *\n * ⚠️ `AbortSignal` is NOT plumbed into the fetch, deliberately: `refresh` is\n * shared with the background path, and aborting it because THIS request gave\n * up would cancel a refresh every other request is waiting on. We stop\n * waiting; the refresh continues and warms the process for the next one.\n */\n /**\n * Stop WAITING at the deadline — never stop the work.\n *\n * ⚠️ No `AbortSignal` on the fetch, deliberately. This request giving up must\n * not cancel a load another request may be waiting on, and on a cold isolate\n * a burst of requests is exactly the case.\n */\n function withDeadline(work: Promise<Twin | undefined>): Promise<Twin | undefined> {\n return new Promise((done) => {\n let settled = false;\n const finish = (value: Twin | undefined): void => {\n if (settled) return;\n settled = true;\n done(value);\n };\n const timer = setTimeout(() => finish(undefined), coldTimeoutMs);\n // ⚠️ `unref` where it exists, so a pending deadline cannot hold a Node\n // process open. It does not exist on a Worker timer, hence the guard.\n (timer as unknown as { unref?: () => void }).unref?.();\n work.then(finish, () => finish(undefined)).finally(() => clearTimeout(timer));\n });\n }\n\n const lookup = (path: string): Twin | undefined => twins.get(normalisePath(path));\n\n /**\n * ⚠️ **The cold path, and it asks for ONE path rather than everything.**\n *\n * Until Phase 49 a cold `resolve` awaited the whole corpus, so the first\n * request on a fresh isolate paid for every twin the site has in order to\n * answer with one. `GET /v1/twins?path=` exists for exactly this, and its\n * envelope is the same as the bulk read so there is one parser.\n *\n * ⚠️ A miss is `{twins: []}` and a 200, so `undefined` here means \"no twin\n * for this path\", not \"something went wrong\" -- both fall through to the\n * customer's own handler anyway, which is why neither may throw.\n */\n async function fetchOne(path: string): Promise<Twin | undefined> {\n if (!base) return undefined;\n try {\n const url = `${base}/v1/twins?path=${encodeURIComponent(normalisePath(path))}`;\n // ⚠️ Bounded as well, and for a second reason: `withDeadline` stops the\n // REQUEST waiting, but the socket stayed open for the runtime's own\n // timeout. On a cold process under an outage that is one hanging\n // connection per markdown request, in the customer's process.\n //\n // ⚠️ The deadline covers the BODY too, not just the headers. Clearing\n // it once headers arrived left a stalled body read open for the\n // runtime's own timeout -- the exact hanging connection this bound\n // exists to prevent. `refresh()` already did this correctly.\n const deadline = abortAfter(refreshTimeoutMs);\n let body: { twins?: Array<{ path?: unknown; markdown?: unknown }> };\n try {\n const response = await fetchImpl(url, {\n headers: { authorization: `Bearer ${options.serverKey}` },\n ...(deadline.signal ? { signal: deadline.signal } : {}),\n });\n if (!response.ok) return undefined;\n body = (await response.json()) as typeof body;\n } finally {\n deadline.done();\n }\n const row = Array.isArray(body.twins) ? body.twins[0] : undefined;\n if (!row || typeof row.markdown !== \"string\") return undefined;\n return { body: row.markdown };\n } catch {\n return undefined;\n }\n }\n\n return {\n lookup,\n fetchOne,\n /**\n * ⚠️ Returns a VALUE when warm and a PROMISE when cold. `TwinOptions.resolve`\n * accepts either, so a warm lookup never introduces a microtask and a cold\n * one never answers \"no twin\" just because this isolate is new.\n *\n * ⚠️ **Only call this where the client has already asked for markdown.** On\n * a cold process it waits, and the adapters route the advertise path to\n * `lookup` for exactly that reason. The wait is bounded by `coldTimeoutMs`\n * and its worst case is \"no twin\", which is a page that works normally.\n */\n resolve: (path) => {\n if (fetchedAt !== 0) return lookup(path);\n // ⚠️ Cold AND backed off: answer \"no twin\" without a socket. The cold\n // await is argued for a process that can be warmed; during an outage it\n // cannot, and paying 750ms of somebody's markdown request to learn that\n // again every time is the cost the backoff exists to remove.\n if (failedAt !== 0 && now() - failedAt < failureBackoffMs) return undefined;\n /*\n ⚠️ Cold: ask for this ONE path, bounded, and start the bulk refresh in\n the background without waiting for it. The request in hand gets an\n answer the size of one page; the process is warm for the next one.\n\n The refresh is deliberately not awaited and deliberately not abandoned\n on the deadline -- `withDeadline` stops us waiting, it does not cancel\n work other requests may be waiting on.\n */\n void refreshIfStale();\n return withDeadline(fetchOne(path));\n },\n refresh,\n refreshIfStale,\n get size() {\n return twins.size;\n },\n };\n}\n","import { prefersMarkdown } from \"./accept.js\";\n\n/**\n * The markdown twin: the decision, as a pure function.\n *\n * ── Why the decision is separated from the serving ───────────────────────────\n * Two adapters have to make the identical choice, and the choice is the part\n * with consequences -- serve the wrong thing and a human gets a text file\n * instead of a website. One function, one set of tests, two thin call sites.\n */\n\nexport interface Twin {\n body: string;\n /** Defaults to `text/markdown; charset=utf-8`. */\n contentType?: string;\n etag?: string;\n lastModified?: string;\n}\n\nexport type TwinResolver = (\n path: string,\n) => Twin | null | undefined | Promise<Twin | null | undefined>;\n\n/**\n * Serve twins from the corpus this site's own visitors wrote.\n *\n * ⚠️ **`true` is the whole configuration.** The server key and ingest URL are\n * already in `AgentHoneyConfig` for the observe half, so a corpus-backed\n * install is one word rather than the lazy singleton, the `waitUntil` and the\n * hand-wired resolver that `apps/www` had to assemble -- which, until Phase 49,\n * was the only place anyone had ever done it, because `hostedTwins` appeared in\n * exactly zero customer-facing documents.\n *\n * ⚠️ **It stays OPT-IN even though we know the key.** Turning it on changes\n * what a visitor receives under the customer's own domain, and doing that\n * because somebody took a patch upgrade is not a thing this package does.\n */\nexport type HostedTwinOptions =\n | true\n | {\n /** Defaults to the SDK's configured server key. */\n serverKey?: string;\n /** Defaults to the SDK's configured ingest URL. */\n ingestUrl?: string;\n /** How stale the in-memory set may get. Default five minutes. */\n refreshMs?: number;\n /** The ceiling on the one await a COLD process is allowed. */\n coldTimeoutMs?: number;\n /** How long a failed refresh holds off the next. Default thirty seconds. */\n failureBackoffMs?: number;\n /** The ceiling on a single refresh's network call. Default five seconds. */\n refreshTimeoutMs?: number;\n /** Injected for tests. */\n fetchImpl?: typeof fetch;\n };\n\ninterface TwinOptionsBase {\n /** `Cache-Control` for a served twin. Edge-cacheable by default. */\n cacheControl?: string;\n /** Advertise an available twin on the HTML response. Default true. */\n advertise?: boolean;\n /**\n * Serve `/llms.txt`, `/llms-full.txt` and `/install.md` from the same\n * manifest that serves the twins.\n *\n * ⚠️ Derived, not written: a hand-maintained index is wrong the first time a\n * page is added, and an index listing pages that no longer exist is worse\n * than none -- an agent spends its budget on 404s and concludes the site is\n * broken.\n */\n discovery?: import(\"./discovery.js\").DiscoveryOptions;\n}\n\n/**\n * ⚠️ **A union, so each half is sufficient alone and neither loses its type.**\n *\n * `resolve` is for a site that already HAS markdown; `hosted` is for one whose\n * twins were written by its own visitors. Supplying both is allowed and\n * `resolve` wins -- authored markdown beats harvested markdown, every time,\n * because the customer wrote one of them on purpose.\n *\n * ⚠️ This widens a published surface rather than retyping one: every existing\n * caller passes `resolve` and keeps working, which is what the append-only rule\n * requires.\n */\nexport type TwinOptions = TwinOptionsBase &\n (\n | {\n /**\n * Find the twin for a normalised path, or return null.\n *\n * ⚠️ **Registered, not derived.** Returning a twin for any path that\n * \"looks like\" it should have one produces a site where every URL\n * answers, including the typos -- caprail.dev returns a plain 404 for\n * an unregistered `.md`, and that is the behaviour to match.\n */\n resolve: TwinResolver;\n hosted?: HostedTwinOptions;\n }\n | { resolve?: TwinResolver; hosted: HostedTwinOptions }\n );\n\n/** Paths the discovery block answers, when it is configured. */\nexport const DISCOVERY_PATHS = [\"/llms.txt\", \"/llms-full.txt\", \"/install.md\"] as const;\n\nexport const DEFAULT_TWIN_CONTENT_TYPE = \"text/markdown; charset=utf-8\";\nexport const DEFAULT_TWIN_CACHE_CONTROL = \"public, max-age=3600, s-maxage=86400\";\n\nexport type TwinDecision =\n /** Serve the twin for `lookupPath`. */\n | { action: \"serve\"; lookupPath: string; reason: \"md_path\" | \"accept_header\" }\n /** Not ours to answer, but a twin may exist worth advertising. */\n | { action: \"pass\"; reason: \"not_get\" | \"no_signal\" };\n\n/**\n * Map a request onto a decision. Nothing here touches I/O.\n *\n * Two gates, and only two:\n * 1. the path ends in `.md` -- a distinct resource, no negotiation at all;\n * 2. `Accept` explicitly prefers markdown -- content negotiation, which owes\n * a `Vary: Accept`.\n *\n * ⚠️ Never the User-Agent. See `accept.ts`.\n */\nexport function decideTwin(input: {\n method: string;\n path: string;\n accept: string | null | undefined;\n}): TwinDecision {\n const method = input.method.toUpperCase();\n // A twin is a representation of a resource, so only the methods that ask for\n // one. Answering a POST with a markdown body would swallow a form submission.\n if (method !== \"GET\" && method !== \"HEAD\") {\n return { action: \"pass\", reason: \"not_get\" };\n }\n\n if (input.path.endsWith(\".md\")) {\n return { action: \"serve\", lookupPath: stripMdSuffix(input.path), reason: \"md_path\" };\n }\n\n if (prefersMarkdown(input.accept)) {\n return { action: \"serve\", lookupPath: input.path, reason: \"accept_header\" };\n }\n\n return { action: \"pass\", reason: \"no_signal\" };\n}\n\n/**\n * `/docs/intro.md` -> `/docs/intro`, and `/index.md` -> `/`.\n *\n * The root is the case worth naming: a site's home page has no slug, so its\n * twin is `/index.md` by convention -- the same convention caprail.dev uses.\n */\nexport function stripMdSuffix(path: string): string {\n const withoutSuffix = path.slice(0, -3);\n if (withoutSuffix === \"\" || withoutSuffix === \"/index\") return \"/\";\n return withoutSuffix;\n}\n\n/** `/docs/intro` -> `/docs/intro.md`, and `/` -> `/index.md`. */\nexport function twinPathFor(path: string): string {\n if (path === \"/\") return \"/index.md\";\n return `${path}.md`;\n}\n\nexport interface TwinResponse {\n status: number;\n headers: Record<string, string>;\n body: string;\n}\n\nexport function buildTwinResponse(\n twin: Twin,\n decision: Extract<TwinDecision, { action: \"serve\" }>,\n options: TwinOptions,\n): TwinResponse {\n const headers: Record<string, string> = {\n \"content-type\": twin.contentType ?? DEFAULT_TWIN_CONTENT_TYPE,\n \"cache-control\": options.cacheControl ?? DEFAULT_TWIN_CACHE_CONTROL,\n };\n\n // ⚠️ `Vary: Accept` is required ONLY when the URL can return two different\n // bodies -- which is the negotiated case, not the `.md` path. Sending it on a\n // `.md` response would needlessly fragment the edge cache for a resource that\n // has exactly one representation.\n //\n // Getting this wrong the other way is the serious one: negotiating without\n // `Vary` lets a shared cache hand one client's markdown to the next client\n // asking for HTML. That is a broken website served from a CDN, and it\n // outlives the deploy that caused it.\n if (decision.reason === \"accept_header\") {\n headers[\"vary\"] = \"Accept\";\n }\n if (twin.etag) headers[\"etag\"] = twin.etag;\n if (twin.lastModified) headers[\"last-modified\"] = twin.lastModified;\n\n return { status: 200, headers, body: twin.body };\n}\n\n/**\n * The advertisement for an available twin, as a `Link` header.\n *\n * ⚠️ **A header, not a tag injected into the HTML.** Caprail puts\n * `<link rel=\"alternate\">` in its own document head, which it can do because it\n * owns the template. A middleware cannot: injecting into the body means\n * buffering and rewriting somebody else's response, which is precisely what\n * this package refuses to do. RFC 8288 makes the header the equivalent, it\n * costs nothing, and it works for JSON and plain text as well as HTML.\n *\n * A customer who also wants the tag can add it to their own template, and\n * should -- some crawlers read one and not the other.\n */\n/**\n * Is a resolver's answer a promise?\n *\n * ⚠️ **Asked so that the ADVERTISE path can decline to wait.** `resolve` may\n * return a value or a promise, and the difference decides whether a caller is\n * allowed to use the answer: a request that has not asked for markdown must\n * never be delayed for one. Duck-typed rather than `instanceof Promise`,\n * because a customer's resolver may return a thenable from any library.\n */\nexport function isThenable<T>(value: T | Promise<T>): value is Promise<T> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\nexport function advertiseHeader(path: string): string {\n return `<${twinPathFor(path)}>; rel=\"alternate\"; type=\"text/markdown\"`;\n}\n","import type { ResolvedConfig } from \"../core/config.js\";\nimport { hostedTwins, type HostedTwins } from \"./hosted.js\";\nimport { isThenable, type Twin, type TwinOptions } from \"./twin.js\";\n\n/**\n * Where a twin comes from — and, more importantly, **what a caller is allowed\n * to wait for.**\n *\n * ── ⚠️ Why this exists at all ────────────────────────────────────────────────\n * Three adapters each called `twin.resolve(path)` and each had to know, without\n * anything in the type saying so, whether the call they were making was allowed\n * to touch the network. They did not know. `./express` called it on **every\n * passing GET** to decide whether to advertise, and deferred `next()` until it\n * settled; `./web` awaited it after the customer's handler had already built\n * the response. With a corpus-backed resolver on a cold process, both of those\n * were a visitor waiting on OUR ingest before their page rendered — which is\n * the one thing this package is built not to do.\n *\n * So the rule is expressed as two methods instead of a comment:\n *\n * - **`lookup` is synchronous and never does I/O.** Anything running on a\n * request that did not ask for markdown uses it. A cold process answers\n * `undefined`, advertises nothing, and is correct.\n * - **`resolve` may await, and only on a request that asked** — a `.md` path\n * or `Accept: text/markdown`. Bounded, and falling through to the\n * customer's own handler on a miss, a throw or the deadline.\n *\n * ⚠️ A caller cannot get this wrong by forgetting a comment any more; it gets\n * it wrong by calling the wrong method, which is visible in a diff.\n */\nexport interface TwinSource {\n lookup: (path: string) => Twin | undefined;\n resolve: (path: string) => Twin | null | undefined | Promise<Twin | null | undefined>;\n /**\n * Load the corpus in the background. **Never awaited on a request path.**\n *\n * ⚠️ Handed to whatever channel the adapter already has — `after` on Next,\n * `waitUntil` on a Worker, fire-and-forget on a long-lived Node process. It\n * is a clock comparison in the common case, so calling it per request is\n * cheap and calling it never is the bug: `resolve` refreshes only while the\n * process is cold, so a corpus that is never refreshed is **frozen at its\n * first load** — new twins never appear and withdrawals never take effect.\n */\n warm: () => Promise<void>;\n}\n\nconst NOOP_WARM = async (): Promise<void> => {};\n\n/**\n * ⚠️ Built ONCE per middleware, not per request. The corpus is the point: a\n * source rebuilt per request holds nothing and refetches everything.\n */\nexport function twinSourceFor(twin: TwinOptions, config: ResolvedConfig): TwinSource {\n const hosted = hostedFor(twin, config);\n const authored = twin.resolve;\n\n /**\n * ⚠️ **A customer's resolver is used for `lookup` only when it answers\n * synchronously.** It may be backed by a database; we cannot know. If it\n * returns a promise we decline to wait and advertise nothing, which is the\n * same trade the hosted corpus makes on a cold process — a `link` header we\n * cannot substantiate is worth less than the millisecond of a visitor's page\n * load it would cost.\n */\n const lookup = (path: string): Twin | undefined => {\n if (authored) {\n try {\n const found = authored(path);\n if (!isThenable(found) && found) return found;\n } catch {\n /* a customer's resolver throwing costs a link header, nothing else */\n }\n }\n return hosted?.lookup(path);\n };\n\n const resolve = (path: string): Twin | null | undefined | Promise<Twin | null | undefined> => {\n // ⚠️ **Authored markdown beats harvested markdown**, every time: the\n // customer wrote one of them on purpose. Hosted is the fallback, including\n // for a path their resolver does not know.\n if (!authored) return hosted ? hosted.resolve(path) : undefined;\n if (!hosted) return authored(path);\n\n const found = authored(path);\n if (isThenable(found)) return found.then((t) => t ?? hosted.resolve(path));\n return found ?? hosted.resolve(path);\n };\n\n return { lookup, resolve, warm: hosted ? () => hosted.refreshIfStale() : NOOP_WARM };\n}\n\n/**\n * ⚠️ The server key and ingest URL come from the config the observe half\n * already has, so `hosted: true` is the whole configuration. A site with no\n * server key gets no corpus rather than a broken one.\n */\nfunction hostedFor(twin: TwinOptions, config: ResolvedConfig): HostedTwins | undefined {\n if (!twin.hosted) return undefined;\n const opts = twin.hosted === true ? {} : twin.hosted;\n const serverKey = opts.serverKey ?? config.serverKey;\n if (!serverKey) return undefined;\n return hostedTwins({\n serverKey,\n ingestUrl: opts.ingestUrl ?? config.ingestUrl,\n ...(opts.refreshMs === undefined ? {} : { refreshMs: opts.refreshMs }),\n ...(opts.coldTimeoutMs === undefined ? {} : { coldTimeoutMs: opts.coldTimeoutMs }),\n ...(opts.failureBackoffMs === undefined ? {} : { failureBackoffMs: opts.failureBackoffMs }),\n ...(opts.refreshTimeoutMs === undefined ? {} : { refreshTimeoutMs: opts.refreshTimeoutMs }),\n ...(opts.fetchImpl === undefined ? {} : { fetchImpl: opts.fetchImpl }),\n });\n}\n"]}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { resolveConfig, createCollector, twinSourceFor, safe, decideTwin, buildTwinResponse, observeNetwork, runtimeName, observeResponse, newEventId, recordOrCount, SDK_VERSION, SDK_NAME } from './chunk-L22VERBM.js';
|
|
2
|
+
import { normalisePath, observeRequest, observedHost } from './chunk-F3PRHEXB.js';
|
|
3
|
+
|
|
4
|
+
// src/observe/next-router.ts
|
|
5
|
+
function nextBackground(request) {
|
|
6
|
+
const h = request.headers;
|
|
7
|
+
if (h.get("rsc") !== "1") return void 0;
|
|
8
|
+
if (h.get("next-router-prefetch") === "1" || h.get("next-router-segment-prefetch") !== null) return "prefetch";
|
|
9
|
+
const referer = h.get("referer");
|
|
10
|
+
if (!referer) return void 0;
|
|
11
|
+
try {
|
|
12
|
+
const target = new URL(request.url);
|
|
13
|
+
const from = new URL(referer);
|
|
14
|
+
const host = observedHost((name) => h.get(name) ?? void 0, target.host);
|
|
15
|
+
if (from.host !== host) return void 0;
|
|
16
|
+
target.searchParams.delete("_rsc");
|
|
17
|
+
from.searchParams.delete("_rsc");
|
|
18
|
+
return target.pathname === from.pathname && target.search === from.search ? "refresh" : void 0;
|
|
19
|
+
} catch {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/next.ts
|
|
25
|
+
function proxy(options = {}) {
|
|
26
|
+
const config = resolveConfig(options);
|
|
27
|
+
const collector = options.collector ?? createCollector(config);
|
|
28
|
+
const source = options.twin ? twinSourceFor(options.twin, config) : void 0;
|
|
29
|
+
return async (request) => {
|
|
30
|
+
const startedAt = Date.now();
|
|
31
|
+
if (source) {
|
|
32
|
+
const warming = source.warm();
|
|
33
|
+
if (options.after) safe(() => options.after?.(() => warming));
|
|
34
|
+
}
|
|
35
|
+
let served;
|
|
36
|
+
let twinResponse;
|
|
37
|
+
if (options.twin) {
|
|
38
|
+
try {
|
|
39
|
+
const url = new URL(request.url);
|
|
40
|
+
const decision = decideTwin({
|
|
41
|
+
method: request.method,
|
|
42
|
+
path: normalisePath(url.pathname),
|
|
43
|
+
accept: request.headers.get("accept")
|
|
44
|
+
});
|
|
45
|
+
if (decision.action === "serve") {
|
|
46
|
+
const found = await source.resolve(decision.lookupPath);
|
|
47
|
+
if (found) {
|
|
48
|
+
const built = buildTwinResponse(found, decision, options.twin);
|
|
49
|
+
served = {
|
|
50
|
+
decision: "served",
|
|
51
|
+
reason: decision.reason,
|
|
52
|
+
format: built.headers["content-type"] ?? "text/markdown"
|
|
53
|
+
};
|
|
54
|
+
twinResponse = new Response(
|
|
55
|
+
request.method.toUpperCase() === "HEAD" ? null : built.body,
|
|
56
|
+
{ status: built.status, headers: built.headers }
|
|
57
|
+
);
|
|
58
|
+
} else {
|
|
59
|
+
served = { decision: "fell_through", reason: "no_twin" };
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
served = { decision: "fell_through", reason: decision.reason };
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
served = { decision: "error", reason: "resolver_error" };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const record = () => {
|
|
69
|
+
safe(() => {
|
|
70
|
+
if (config.disabled) return;
|
|
71
|
+
const url = new URL(request.url);
|
|
72
|
+
const observed = observeRequest(
|
|
73
|
+
{
|
|
74
|
+
method: request.method,
|
|
75
|
+
url: url.pathname + url.search,
|
|
76
|
+
// ⚠️ NOT `url.host`: a self-hosted standalone server hands the
|
|
77
|
+
// proxy `http://0.0.0.0:3013/…`. See `observedHost`.
|
|
78
|
+
host: observedHost((name) => request.headers.get(name) ?? void 0, url.host),
|
|
79
|
+
protocol: url.protocol === "https:" ? "https" : "http",
|
|
80
|
+
userAgent: request.headers.get("user-agent") ?? void 0,
|
|
81
|
+
referer: request.headers.get("referer") ?? void 0
|
|
82
|
+
},
|
|
83
|
+
config
|
|
84
|
+
);
|
|
85
|
+
const network = observeNetwork(
|
|
86
|
+
{ header: (name) => request.headers.get(name) ?? void 0 },
|
|
87
|
+
config.clientIp
|
|
88
|
+
);
|
|
89
|
+
const event = {
|
|
90
|
+
...observed,
|
|
91
|
+
eventId: newEventId(),
|
|
92
|
+
...network ? { network } : {},
|
|
93
|
+
// ⚠️ Emitted so a post-response observation CAN be merged later.
|
|
94
|
+
// Nothing merges it today, and the docblock above says so plainly
|
|
95
|
+
// rather than letting the field imply otherwise.
|
|
96
|
+
requestId: newEventId(),
|
|
97
|
+
siteId: config.siteId,
|
|
98
|
+
observedAt: new Date(startedAt).toISOString(),
|
|
99
|
+
...served ? { serve: served } : {},
|
|
100
|
+
// ⚠️ **The response object is present ONLY when we built it.**
|
|
101
|
+
//
|
|
102
|
+
// If the twin was served, this proxy IS the responder and measured it.
|
|
103
|
+
// Otherwise the route has not run yet and there is nothing to
|
|
104
|
+
// observe — so the key is OMITTED, not set to a guess, not set to
|
|
105
|
+
// `observation: "unknown"` with a status (which the schema refuses),
|
|
106
|
+
// and not set to a latency-only object (we did not wait for the
|
|
107
|
+
// response, so we did not measure a latency either).
|
|
108
|
+
...twinResponse ? {
|
|
109
|
+
response: observeResponse({
|
|
110
|
+
status: twinResponse.status,
|
|
111
|
+
contentType: twinResponse.headers.get("content-type"),
|
|
112
|
+
contentLength: twinResponse.headers.get("content-length"),
|
|
113
|
+
latencyMs: Date.now() - startedAt,
|
|
114
|
+
observation: "measured"
|
|
115
|
+
})
|
|
116
|
+
} : {},
|
|
117
|
+
sdk: {
|
|
118
|
+
name: SDK_NAME,
|
|
119
|
+
version: SDK_VERSION,
|
|
120
|
+
// ⚠️ `next-proxy`, which is the name the WIRE SCHEMA already uses
|
|
121
|
+
// in its own docblock (`event.ts:157`). Its own name matters here
|
|
122
|
+
// more than for any other adapter: this is the one whose events
|
|
123
|
+
// legitimately carry no response, and the dashboard must not
|
|
124
|
+
// present an adapter's blind spot as a fact about the traffic.
|
|
125
|
+
adapter: "next-proxy",
|
|
126
|
+
runtime: runtimeName()
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
recordOrCount(collector, event, request.headers.get("accept"), nextBackground(request));
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
if (options.after) {
|
|
133
|
+
try {
|
|
134
|
+
options.after(() => {
|
|
135
|
+
record();
|
|
136
|
+
return collector.flush();
|
|
137
|
+
});
|
|
138
|
+
} catch {
|
|
139
|
+
record();
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
record();
|
|
143
|
+
}
|
|
144
|
+
return twinResponse;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { proxy };
|
|
149
|
+
//# sourceMappingURL=chunk-OH4H2B7O.js.map
|
|
150
|
+
//# sourceMappingURL=chunk-OH4H2B7O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/observe/next-router.ts","../src/next.ts"],"names":[],"mappings":"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","file":"chunk-OH4H2B7O.js","sourcesContent":["import { observedHost } from \"./request.js\";\n\n/**\n * Was this request the page's own background traffic, rather than a reader's?\n * (Phase 130.) Read from the headers Next's client router sets, and nothing\n * else -- the proxy never sees the answer, so these are the only facts there.\n *\n * - **prefetch**: `next-router-prefetch` or `next-router-segment-prefetch`.\n * Every `<Link>` in the viewport sends one; nobody has read anything yet.\n * - **refresh**: an RSC request (`rsc: 1`) for the very page it was sent from.\n * In Next 16 `router.refresh()` goes through the same code as a navigation\n * (`navigateToKnownRoute`) and sends the same headers, so the one fact that\n * tells them apart is the `Referer`: a refresh asks for the URL it is on, a\n * navigation for a different one. The `_rsc` cache-buster is ignored.\n *\n * ⚠️ **Absence keeps the row.** No Referer (a `no-referrer` policy), a\n * cross-origin one, or one that does not parse is `undefined` -- recorded as\n * today. Dropping a navigation we could not tell from a refresh would erase a\n * reader; keeping a refresh we could not tell costs one row.\n *\n * ⚠️ A client-side NAVIGATION is deliberately not background: it is a person\n * moving to a new page, and the assistant card's \"pages also read\" counts it.\n */\nexport function nextBackground(request: Request): \"prefetch\" | \"refresh\" | undefined {\n const h = request.headers;\n // ⚠️ `rsc: 1` FIRST, exactly as Next's own server tests it\n // (`base-server.js`, `isRSCRequestHeader`). Next ignores a prefetch header on\n // a request without it and renders the full HTML page -- so honouring the\n // header alone would let one static header read every page unrecorded\n // (security review, Phase 130).\n if (h.get(\"rsc\") !== \"1\") return undefined;\n if (h.get(\"next-router-prefetch\") === \"1\" || h.get(\"next-router-segment-prefetch\") !== null) return \"prefetch\";\n const referer = h.get(\"referer\");\n if (!referer) return undefined;\n try {\n const target = new URL(request.url);\n const from = new URL(referer);\n // ⚠️ The host the EVENT records, not `Host`: behind a proxy that rewrites\n // it -- ours, self-hosted behind cloudflared -- `Host` is the internal name\n // and the Referer never matches it.\n const host = observedHost((name) => h.get(name) ?? undefined, target.host);\n if (from.host !== host) return undefined;\n target.searchParams.delete(\"_rsc\");\n from.searchParams.delete(\"_rsc\");\n return target.pathname === from.pathname && target.search === from.search ? \"refresh\" : undefined;\n } catch {\n return undefined;\n }\n}\n","import type { RequestEvent } from \"@agenthoney/event-schema\";\nimport { createCollector, type Collector } from \"./core/collector.js\";\nimport { resolveConfig, type AgentHoneyConfig } from \"./core/config.js\";\nimport { recordOrCount } from \"./core/record-gate.js\";\nimport { safe } from \"./core/safe.js\";\nimport { observeNetwork } from \"./observe/client-ip.js\";\nimport { nextBackground } from \"./observe/next-router.js\";\nimport { observeRequest, normalisePath, observedHost } from \"./observe/request.js\";\nimport { observeResponse } from \"./observe/response.js\";\nimport { newEventId, runtimeName, SDK_NAME, SDK_VERSION } from \"./runtime.js\";\nimport { twinSourceFor } from \"./serve/source.js\";\nimport { advertiseHeader, buildTwinResponse, decideTwin, type TwinOptions } from \"./serve/twin.js\";\n\n/**\n * The Next.js adapter, for `proxy.ts` (called `middleware.ts` before Next 16).\n *\n * ── ⚠️ Why this exists rather than \"just use ./web\" ──────────────────────────\n * Because `./web` produces FALSE DATA in a Next proxy, and does it silently.\n *\n * A proxy runs **before the route**. It hands control onward by returning\n * `NextResponse.next()` — a sentinel with status 200, no real content-type and\n * no content-length. `./web` reads that sentinel and records\n * `{ status: 200, observation: \"measured\" }`, so **every event claims a\n * measured 200**, including requests the route renders as 404 or 500.\n *\n * ⚠️ **Our own schema already said this was wrong and refused it**:\n * `observation: \"unknown\"` may not carry a status, and the Next case is the one\n * where the `response` object is absent entirely — \"we never had a chance to\n * look\". Correct behaviour, specified, enforced by a `superRefine`, and until\n * now unreachable because no adapter emitted it. That reads as done, which is\n * worse than reading as missing.\n *\n * ── What this emits ──────────────────────────────────────────────────────────\n * Exactly what a proxy genuinely knows — method, path, host, user agent,\n * referrer origin, campaign — and **no `response` object at all**, unless it\n * served the twin itself, in which case it made the response and can measure it\n * honestly.\n *\n * ── ⚠️ The requestId merge is DESIGNED and NOT BUILT ─────────────────────────\n * `requestId` rides every event so a later post-response observation can be\n * merged onto it. **Nothing performs that merge today** — measured: nothing in\n * the worker or the database reads the field. Next offers no general\n * post-response hook (`onRequestError` fires only on errors; `after()` still\n * runs in the proxy, which cannot see the route's response), so the honest\n * second observation point is a wrapper around each route handler — a second\n * install step, easy to forget, and a half-installed product reports half its\n * traffic.\n *\n * So this ships the single honest observation. That is not a degraded event: a\n * proxy cannot see a response, and saying so precisely is the product's\n * differentiator rather than a shortfall.\n *\n * ── ⚠️ `after` is an OPTION, not an import ───────────────────────────────────\n * There is no `ctx.waitUntil` in a Next proxy; the equivalent is `after()` from\n * `next/server`. Importing it here — even dynamically — would put a bare\n * specifier in a package whose whole discipline is zero runtime dependencies,\n * and a dynamic `import()` slips past `check-sdk-artifact.mjs`'s regex, which\n * would be a gate quietly stopping working. So the customer passes it, exactly\n * as `observe()` already takes `waitUntil`:\n *\n * ```ts\n * import { after } from \"next/server\";\n * import { proxy } from \"@agenthoney/analytics/next\";\n *\n * export default proxy({ after });\n * export const config = { matcher: [\"/((?!_next/static).*)\"] };\n * ```\n *\n * Without it the collector falls back to its own 2s timer, which a serverless\n * deployment can freeze before it fires.\n */\n\nexport interface ProxyOptions extends AgentHoneyConfig {\n /**\n * `after` from `next/server`. Passed rather than imported — see the docblock.\n * Omitting it is supported and costs reliability on serverless.\n */\n after?: (task: () => void | Promise<void>) => void;\n /** For tests, and for a host that already has a collector. */\n collector?: Collector;\n /**\n * Serve a markdown twin from the proxy.\n *\n * ⚠️ A proxy is a GOOD place for this, unlike the observation half: it runs\n * before the route, so returning the twin short-circuits rendering entirely\n * and the agent never pays for a React tree it discards. And because the\n * proxy built that response itself, the event can honestly say `measured`.\n */\n twin?: TwinOptions;\n}\n\n/**\n * ⚠️ Returns `undefined` to continue, rather than `NextResponse.next()`.\n *\n * Next treats a void return as \"carry on\", which means this adapter never has\n * to import `next/server` at all. It also removes the exact sentinel that made\n * `./web` lie here: there is no fake 200 to accidentally observe.\n */\nexport type ProxyResult = Response | undefined;\n\nexport function proxy(options: ProxyOptions = {}): (request: Request) => Promise<ProxyResult> {\n const config = resolveConfig(options);\n const collector = options.collector ?? createCollector(config);\n // ⚠️ Built once, at wiring time — not per request, and not at module scope\n // (`hostedTwins` performs no I/O at construction, which is what makes this\n // safe in an edge runtime).\n const source = options.twin ? twinSourceFor(options.twin, config) : undefined;\n\n return async (request: Request): Promise<ProxyResult> => {\n // ⚠️ The clock starts OUTSIDE the try, exactly as in `./web`: if observation\n // setup throws, the request must still reach the route.\n const startedAt = Date.now();\n\n /*\n ⚠️ **The corpus refresh goes to `after`, the same channel `record()` uses.**\n Never awaited: a Next proxy runs in front of the route, so anything waited\n for here is added to every page of the customer's site. Without `after` it\n still runs, just without the platform keeping the invocation alive — the\n same trade the collector's own flush makes, documented in `ProxyOptions`.\n */\n if (source) {\n const warming = source.warm();\n if (options.after) safe(() => options.after?.(() => warming));\n }\n\n let served: RequestEvent[\"serve\"];\n let twinResponse: Response | undefined;\n\n // ── The serve half, before the route ─────────────────────────────────────\n // Every failure falls through to the customer's application unchanged.\n if (options.twin) {\n try {\n const url = new URL(request.url);\n const decision = decideTwin({\n method: request.method,\n path: normalisePath(url.pathname),\n accept: request.headers.get(\"accept\"),\n });\n\n if (decision.action === \"serve\") {\n // ⚠️ Awaiting is allowed here and only here: this client asked for\n // markdown. `next.ts` never advertises (see below), so there is no\n // other call site to get wrong.\n const found = await source!.resolve(decision.lookupPath);\n if (found) {\n const built = buildTwinResponse(found, decision, options.twin);\n served = {\n decision: \"served\",\n reason: decision.reason,\n format: built.headers[\"content-type\"] ?? \"text/markdown\",\n };\n twinResponse = new Response(\n request.method.toUpperCase() === \"HEAD\" ? null : built.body,\n { status: built.status, headers: built.headers },\n );\n } else {\n served = { decision: \"fell_through\", reason: \"no_twin\" };\n }\n } else {\n served = { decision: \"fell_through\", reason: decision.reason };\n }\n } catch {\n served = { decision: \"error\", reason: \"resolver_error\" };\n }\n }\n\n // ⚠️ Advertising is deliberately NOT done here. It would mean appending a\n // `link` header to a response this proxy does not have — the route builds\n // it later. `./express` advertises because it sees the real response.\n // Claiming a twin on a response we cannot touch would be a header nobody\n // receives.\n\n const record = (): void => {\n safe(() => {\n if (config.disabled) return;\n\n const url = new URL(request.url);\n const observed = observeRequest(\n {\n method: request.method,\n url: url.pathname + url.search,\n // ⚠️ NOT `url.host`: a self-hosted standalone server hands the\n // proxy `http://0.0.0.0:3013/…`. See `observedHost`.\n host: observedHost((name) => request.headers.get(name) ?? undefined, url.host),\n protocol: url.protocol === \"https:\" ? \"https\" : \"http\",\n userAgent: request.headers.get(\"user-agent\") ?? undefined,\n referer: request.headers.get(\"referer\") ?? undefined,\n },\n config,\n );\n\n // ⚠️ Headers only. A proxy holds a `Request`, which carries no socket\n // peer at all — so on a platform whose edge sets one of the headers in\n // `client-ip.ts` this yields the visitor, and on a self-hosted Next\n // behind somebody's own nginx it yields nothing until they opt into\n // `clientIp: \"forwarded\"`. Nothing is guessed in between.\n const network = observeNetwork(\n { header: (name) => request.headers.get(name) ?? undefined },\n config.clientIp,\n );\n\n const event: RequestEvent = {\n ...observed,\n eventId: newEventId(),\n ...(network ? { network } : {}),\n // ⚠️ Emitted so a post-response observation CAN be merged later.\n // Nothing merges it today, and the docblock above says so plainly\n // rather than letting the field imply otherwise.\n requestId: newEventId(),\n siteId: config.siteId,\n observedAt: new Date(startedAt).toISOString(),\n ...(served ? { serve: served } : {}),\n // ⚠️ **The response object is present ONLY when we built it.**\n //\n // If the twin was served, this proxy IS the responder and measured it.\n // Otherwise the route has not run yet and there is nothing to\n // observe — so the key is OMITTED, not set to a guess, not set to\n // `observation: \"unknown\"` with a status (which the schema refuses),\n // and not set to a latency-only object (we did not wait for the\n // response, so we did not measure a latency either).\n ...(twinResponse\n ? {\n response: observeResponse({\n status: twinResponse.status,\n contentType: twinResponse.headers.get(\"content-type\"),\n contentLength: twinResponse.headers.get(\"content-length\"),\n latencyMs: Date.now() - startedAt,\n observation: \"measured\",\n }),\n }\n : {}),\n sdk: {\n name: SDK_NAME,\n version: SDK_VERSION,\n // ⚠️ `next-proxy`, which is the name the WIRE SCHEMA already uses\n // in its own docblock (`event.ts:157`). Its own name matters here\n // more than for any other adapter: this is the one whose events\n // legitimately carry no response, and the dashboard must not\n // present an adapter's blind spot as a fact about the traffic.\n adapter: \"next-proxy\",\n runtime: runtimeName(),\n },\n };\n\n // Phase 55: stored, or counted -- see `core/record-gate.ts`.\n\n recordOrCount(collector, event, request.headers.get(\"accept\"), nextBackground(request));\n });\n };\n\n // ⚠️ `after` runs the recording once the response is on its way, so nothing\n // here is on the path of the request. Without it, `record()` is still\n // synchronous and still non-blocking — `safe()` and the bounded queue see\n // to that — it is only the eventual FLUSH that risks being frozen.\n if (options.after) {\n try {\n // ⚠️ The FLUSH rides `after` too, not just the recording. Handing it\n // only `record` left the send to the collector's 2s timer -- which on\n // a serverless function frozen after the response never fires, so\n // the event sat in memory until the isolate died. `observe()` hands\n // `collector.flush()` to `waitUntil` for the same reason.\n options.after(() => {\n record();\n return collector.flush();\n });\n } catch {\n // A hook that throws is not a reason to lose the event.\n record();\n }\n } else {\n record();\n }\n\n return twinResponse;\n };\n}\n\n/** Advertise a twin from a route handler, where the response actually exists. */\nexport { advertiseHeader };\n"]}
|