@odla-ai/kg 0.5.0 → 0.6.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/README.md +143 -0
- package/dist/index.cjs +486 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +412 -1
- package/dist/index.d.ts +412 -1
- package/dist/index.js +486 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/ontology.ts","../src/graph/merge.ts","../src/graph/writer.ts","../src/temporal/time.ts","../src/graph/export.ts","../src/graph/resolve.ts","../src/temporal/prng.ts","../src/temporal/communities.ts","../src/temporal/lenses.ts","../src/temporal/bridges.ts","../src/graph/connectors.ts","../src/util/identity.ts","../src/enrich/facts.ts","../src/enrich/verify.ts","../src/enrich/person.ts","../src/enrich/run.ts","../src/llm/provider.ts","../src/llm/json.ts","../src/util/content.ts","../src/connectors/rss.ts","../src/connectors/url.ts","../src/connectors/gdelt.ts","../src/connectors/fred.ts","../src/connectors/edgar.ts","../src/connectors/govdata.ts","../src/connectors/web-search.ts","../src/connectors/registry.ts","../src/sources.ts","../src/state/kv.ts","../src/workflow/helpers.ts","../src/pipeline/run-items.ts"],"sourcesContent":["/**\n * @odla-ai/kg — ontology-driven knowledge-graph builder.\n *\n * Pipeline shape: Connector → RawItem[] → extract → GraphFragment →\n * writeFragment → Op[] → odla-db. The ontology is config-as-data; it compiles\n * to the odla-db schema, the writer's natural-key/merge-rule maps, and (by\n * convention) the extractor's tool schema. Consumers supply the ontology\n * instance, the extractor, the AdminDb, and a KV store — see PipelineDeps.\n */\n\n// Core shapes\nexport type { RawItem, GraphNode, GraphEdge, GraphFragment, Usage, ExtractResult } from \"./types\";\nexport { emptyUsage, addUsage } from \"./types\";\n\n// Ontology: types + compilers (ontology instances are consumer data)\nexport type { Ontology, EntitySpec, EdgeSpec, AttrSpec, AttrType, MergeRule } from \"./ontology\";\nexport { naturalKeysOf, mergeRulesOf, humanAttrsOf, toSerializedSchema } from \"./ontology\";\n\n// Graph writer + entity resolution\nexport { fragmentToOps, writeFragment, type NaturalKeys } from \"./graph/writer\";\nexport { exportFragment, type ExportSpec, type ExportEntitySpec } from \"./graph/export\";\nexport { mergeScalar, mergeAttrs } from \"./graph/merge\";\nexport { searchEntities, resolveName, canonicalizeFragment } from \"./graph/resolve\";\nexport { findConnectors } from \"./graph/connectors\";\nexport type { ConnectorLens, ConnectorConfig, Connector, ConnectorReport } from \"./graph/connectors\";\n\n// LLM enrichment (grounded research → structured facts → GraphFragment)\nexport {\n enrichCompany,\n enrichPerson,\n resolveCounties,\n personPrompt,\n personToFragment,\n validateLinks,\n PERSON_SEARCH_SYSTEM,\n PERSON_EXTRACT_SYSTEM,\n PERSON_EXTRACT_TOOL,\n normalizeVerification,\n isVerdict,\n isWritable,\n isHttpUrl,\n WRITABLE_VERDICTS,\n factsToFragment,\n locationFragment,\n countyKey,\n locatePrompt,\n LOCATE_SYSTEM,\n LOCATE_TOOL,\n FACTS_SEARCH_SYSTEM,\n FACTS_EXTRACT_SYSTEM,\n FACTS_EXTRACT_TOOL,\n} from \"./enrich\";\nexport type {\n CompanyRow,\n FactsDraft,\n EnrichModels,\n PersonEnrichment,\n PersonRow,\n PersonDraft,\n PersonLink,\n PersonLinkKind,\n PersonResult,\n Verdict,\n Verification,\n Evidence,\n} from \"./enrich\";\n\n// LLM seam (extract / search / write, adapted over @odla-ai/ai)\nexport type { Provider, ProviderFamily, ExtractCall, SearchCall, WriteCall, LlmConfig, LlmEnv } from \"./llm/provider\";\nexport { createProvider, getProvider, MODEL_TIERS } from \"./llm/provider\";\nexport { extractJson, extractJsonArray } from \"./llm/json\";\n\n// Connectors\nexport type { SourceConnector, ConnectorContext, ConnectorJob } from \"./connectors/types\";\nexport { getConnector, connectorIds, makeContext, type ContextConfig } from \"./connectors/registry\";\nexport { fetchUrl } from \"./connectors/url\";\n\n// Source scheduling (the source list itself lives with the consumer)\nexport type { SourceEntry } from \"./sources\";\nexport { enabledSources, jobsForHour, allJobs } from \"./sources\";\n\n// Operational state (seen-URL dedup, lastrun stamps, runtime source config)\nexport { Store, type KVLike } from \"./state/kv\";\n\n// Pipeline\nexport { ingestFresh, type PipelineDeps } from \"./pipeline/run-items\";\nexport {\n resilientStep,\n asStepLike,\n IO_STEP_CONFIG,\n COMMIT_STEP_CONFIG,\n LLM_STEP_CONFIG,\n type StepLike,\n type StepConfig,\n} from \"./workflow/helpers\";\n\n// Utilities\nexport { slugify, identityKey, normalizeUrl, urlHash, isLikelyPersonName } from \"./util/identity\";\nexport { fetchText, htmlToText } from \"./util/content\";\n","import type { Value } from \"@odla-ai/db\";\n\n/**\n * The output of a connector: one acquired item. Exactly one of `text` or\n * `fragment` is set — `text` goes through LLM extraction, `fragment` is already\n * ontology-typed and goes straight to the graph writer (structured feeds).\n */\nexport interface RawItem {\n url: string;\n title: string;\n date?: string;\n sourceName: string;\n sourceKind: string; // connector id: \"url\" | \"rss\" | \"edgar\" | ...\n watch?: string | null;\n text?: string;\n fragment?: GraphFragment;\n}\n\n/** A graph node = an odla-db entity in a namespace, keyed by a natural key. */\nexport interface GraphNode {\n type: string; // namespace, e.g. \"company\"\n key: string; // natural-key value (slug, urlHash, ...) — used for Lookup upsert\n attrs: Record<string, Value>; // node properties (the natural-key attr is added by the writer)\n}\n\n/** A graph edge = an odla-db `link` from one node to another by forward label. */\nexport interface GraphEdge {\n from: { type: string; key: string };\n to: { type: string; key: string };\n label: string; // forward link label, e.g. \"mentions\"\n}\n\n/** The single shape the graph writer consumes, whatever produced it. */\nexport interface GraphFragment {\n nodes: GraphNode[];\n edges: GraphEdge[];\n}\n\n/** What an extractor turns a text RawItem into: a fragment plus a prose summary. */\nexport interface ExtractResult {\n fragment: GraphFragment;\n summary: string;\n}\n\n/** Provider-agnostic LLM usage accounting. */\nexport interface Usage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\n/** Create zeroed provider-agnostic usage totals for one pipeline run. */\nexport function emptyUsage(): Usage {\n return { calls: 0, inputTokens: 0, outputTokens: 0 };\n}\n\n/** Mutate an accumulator by adding call, input-token, and output-token totals. */\nexport function addUsage(into: Usage, more: Usage): void {\n into.calls += more.calls;\n into.inputTokens += more.inputTokens;\n into.outputTokens += more.outputTokens;\n}\n","import type { SerializedAttr, SerializedEntity, SerializedLink, SerializedSchema, Cardinality } from \"@odla-ai/db\";\nimport type { NaturalKeys } from \"./graph/writer\";\n\n/** How a scalar attribute is reconciled when a node is re-discovered. */\nexport type MergeRule =\n | \"replace\" // last write wins (default)\n | \"prefer\" // first non-empty value sticks (stable facts like a name)\n | \"earliest\" // keep the smaller value (first_seen)\n | \"latest\"; // keep the larger value (last_seen)\n\nexport type AttrType = SerializedAttr[\"type\"]; // \"string\" | \"number\" | \"boolean\" | \"date\" | \"json\"\n\nexport interface AttrSpec {\n type: AttrType;\n unique?: boolean;\n indexed?: boolean;\n optional?: boolean;\n /** The pipeline NEVER writes this attr (hand-edited in a UI); omitted from every update. */\n human?: boolean;\n /** Provenance rule for re-discovery. Default \"replace\". */\n merge?: MergeRule;\n /** Shown to the LLM when this attr appears in an extraction schema. */\n desc?: string;\n}\n\nexport interface EntitySpec {\n /** Unique attr used for Lookup upsert (MERGE key). Must be `unique` in attrs. */\n naturalKey: string;\n attrs: Record<string, AttrSpec>;\n /** FTS fields for fuzzy resolution (P5). */\n searchable?: string[];\n}\n\nexport interface EdgeSpec {\n from: string; // entity type\n to: string; // entity type\n forwardLabel: string; // link label on the `from` side (what the `link` op carries)\n reverseLabel: string;\n forwardHas?: Cardinality; // default \"many\"\n reverseHas?: Cardinality; // default \"many\"\n}\n\nexport interface Ontology {\n entities: Record<string, EntitySpec>;\n edges: Record<string, EdgeSpec>; // keyed by edge name\n}\n\n/** node type → natural-key attr name (for the graph writer's Lookup refs). */\nexport function naturalKeysOf(o: Ontology): NaturalKeys {\n const m: NaturalKeys = {};\n for (const [type, e] of Object.entries(o.entities)) m[type] = e.naturalKey;\n return m;\n}\n\n/** Attr name → merge rule, for every entity type (used by the provenance writer). */\nexport function mergeRulesOf(o: Ontology): Record<string, Record<string, MergeRule>> {\n const out: Record<string, Record<string, MergeRule>> = {};\n for (const [type, e] of Object.entries(o.entities)) {\n out[type] = {};\n for (const [attr, spec] of Object.entries(e.attrs)) out[type][attr] = spec.merge ?? \"replace\";\n }\n return out;\n}\n\n/** Attr names the pipeline must never write, per entity type. */\nexport function humanAttrsOf(o: Ontology): Record<string, Set<string>> {\n const out: Record<string, Set<string>> = {};\n for (const [type, e] of Object.entries(o.entities)) {\n out[type] = new Set(Object.entries(e.attrs).filter(([, a]) => a.human).map(([n]) => n));\n }\n return out;\n}\n\n/** Compile the ontology to an odla-db SerializedSchema to POST at /app/:id/schema. */\nexport function toSerializedSchema(o: Ontology): SerializedSchema {\n const entities: Record<string, SerializedEntity> = {};\n for (const [type, e] of Object.entries(o.entities)) {\n const attrs: Record<string, SerializedAttr> = {};\n for (const [name, a] of Object.entries(e.attrs)) {\n attrs[name] = {\n type: a.type,\n unique: !!a.unique,\n indexed: !!a.indexed,\n optional: !!a.optional,\n };\n }\n entities[type] = { attrs };\n }\n const links: Record<string, SerializedLink> = {};\n for (const [name, ed] of Object.entries(o.edges)) {\n links[name] = {\n forward: { on: ed.from, has: ed.forwardHas ?? \"many\", label: ed.forwardLabel },\n reverse: { on: ed.to, has: ed.reverseHas ?? \"many\", label: ed.reverseLabel },\n };\n }\n return { entities, links };\n}\n","import type { Value } from \"@odla-ai/db\";\nimport type { MergeRule } from \"../ontology\";\n\nfunction isEmpty(v: Value | undefined): boolean {\n return v === undefined || v === null || v === \"\";\n}\n\n/**\n * Reconcile one scalar attribute when a node is re-discovered. Port of agnacl's\n * provenance rules (utils/markdown.ts). Empty incoming never clobbers; empty\n * existing is filled. Ordering rules compare stringwise (ISO dates sort right).\n */\nexport function mergeScalar(\n rule: MergeRule,\n existing: Value | undefined,\n incoming: Value | undefined,\n): Value | undefined {\n if (isEmpty(incoming)) return existing;\n if (isEmpty(existing)) return incoming;\n switch (rule) {\n case \"prefer\":\n return existing; // first non-empty value sticks\n case \"earliest\":\n return String(incoming) < String(existing) ? incoming : existing;\n case \"latest\":\n return String(incoming) > String(existing) ? incoming : existing;\n case \"replace\":\n default:\n return incoming;\n }\n}\n\n/**\n * Merge an incoming attr set into an existing one per the type's rules. `human`\n * attrs are excluded upstream (never in `incoming`); this only reconciles what\n * the pipeline owns. Returns the attrs to write (update leaves unlisted attrs intact).\n */\nexport function mergeAttrs(\n rules: Record<string, MergeRule>,\n existing: Record<string, Value>,\n incoming: Record<string, Value>,\n): Record<string, Value> {\n const out: Record<string, Value> = {};\n for (const [attr, val] of Object.entries(incoming)) {\n const merged = mergeScalar(rules[attr] ?? \"replace\", existing[attr], val);\n if (merged !== undefined) out[attr] = merged;\n }\n return out;\n}\n","import type {\n AdminDb,\n Op,\n EntityRef,\n Value,\n InstaQLQuery,\n QueryResult,\n} from \"@odla-ai/db\";\nimport type { GraphFragment } from \"../types\";\nimport {\n naturalKeysOf,\n mergeRulesOf,\n humanAttrsOf,\n type Ontology,\n} from \"../ontology\";\nimport { mergeAttrs } from \"./merge\";\n\n/** node type → unique natural-key attribute name. */\nexport type NaturalKeys = Record<string, string>;\n\n/**\n * Pure fragment → ops mapping (no read/merge). Node upserts by Lookup natural\n * key, then links — nodes always before links so `link` targets resolve.\n * Used by tests and as the building block for the provenance path below.\n */\nexport function fragmentToOps(fragment: GraphFragment, keys: NaturalKeys): Op[] {\n const ref = (type: string, key: string): EntityRef => {\n const attr = keys[type];\n if (!attr) throw new Error(`No natural key configured for node type \"${type}\"`);\n return { ns: type, attr, value: key };\n };\n const ops: Op[] = [];\n for (const n of fragment.nodes) {\n const attr = keys[n.type];\n if (!attr) throw new Error(`No natural key configured for node type \"${n.type}\"`);\n ops.push({ t: \"update\", ns: n.type, id: { ns: n.type, attr, value: n.key }, attrs: { ...n.attrs, [attr]: n.key } });\n }\n for (const e of fragment.edges) {\n ops.push({ t: \"link\", ns: e.from.type, id: ref(e.from.type, e.from.key), label: e.label, target: ref(e.to.type, e.to.key) });\n }\n return ops;\n}\n\n/**\n * Provenance-preserving write. Reads existing nodes by natural key (one batched\n * query), merges scalar attrs per the ontology's rules, drops human-owned\n * attrs, then writes merged node updates + all links in one atomic, idempotent\n * transaction. `mutationId` must be deterministic so odla-db can deduplicate\n * the write on retry; this does not make preceding reads or external effects\n * exactly-once.\n */\nexport async function writeFragment(\n db: AdminDb,\n fragment: GraphFragment,\n ontology: Ontology,\n mutationId: string,\n): Promise<{ txId: number; ops: number }> {\n const keys = naturalKeysOf(ontology);\n const rules = mergeRulesOf(ontology);\n const humans = humanAttrsOf(ontology);\n\n // 1. Collapse duplicate nodes within the fragment (same type+key), dropping human attrs.\n const byType = new Map<string, Map<string, Record<string, Value>>>();\n for (const n of fragment.nodes) {\n if (!keys[n.type]) throw new Error(`No natural key configured for node type \"${n.type}\"`);\n const human = humans[n.type] ?? new Set<string>();\n const clean: Record<string, Value> = {};\n for (const [k, v] of Object.entries(n.attrs)) if (!human.has(k)) clean[k] = v;\n const typeMap = byType.get(n.type) ?? new Map<string, Record<string, Value>>();\n const prev = typeMap.get(n.key);\n typeMap.set(n.key, prev ? mergeAttrs(rules[n.type]!, prev, clean) : clean);\n byType.set(n.type, typeMap);\n }\n\n // 2. Batch-read existing rows per type by natural key (one query, filtered per level).\n const query: InstaQLQuery = {};\n for (const [type, m] of byType) {\n query[type] = { $: { where: { [keys[type]!]: { $in: [...m.keys()] } } } };\n }\n const existing: QueryResult = Object.keys(query).length ? await db.query(query) : {};\n const priorOf = (type: string, key: string): Record<string, Value> => {\n const attr = keys[type]!;\n for (const row of existing[type] ?? []) {\n if (row[attr] === key) return row as unknown as Record<string, Value>;\n }\n return {};\n };\n\n // 3. Merged node updates first, then links.\n const ops: Op[] = [];\n for (const [type, m] of byType) {\n const attr = keys[type]!;\n for (const [key, incoming] of m) {\n const merged = mergeAttrs(rules[type]!, priorOf(type, key), incoming);\n ops.push({ t: \"update\", ns: type, id: { ns: type, attr, value: key }, attrs: { ...merged, [attr]: key } });\n }\n }\n const ref = (type: string, key: string): EntityRef => ({ ns: type, attr: keys[type]!, value: key });\n for (const e of fragment.edges) {\n ops.push({ t: \"link\", ns: e.from.type, id: ref(e.from.type, e.from.key), label: e.label, target: ref(e.to.type, e.to.key) });\n }\n\n if (ops.length === 0) return { txId: 0, ops: 0 };\n const { txId } = await db.transact(ops, { mutationId });\n return { txId, ops: ops.length };\n}\n","// UTC-only time bucketing. Date-only strings are read as UTC midnight and all\n// bucket math uses Date.UTC accessors — results never depend on the host zone.\n\n/** Supported bucketing granularities. */\nexport type TimeGrain = \"week\" | \"month\" | \"quarter\";\n\n/** One half-open time bucket: `start` inclusive, `end` exclusive (epoch ms). */\nexport interface TimeBucket {\n key: string;\n start: number;\n end: number;\n}\n\nconst DAY = 86_400_000;\nconst DATE_ONLY = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n/**\n * Parse a date-ish value to epoch ms, or null if unparseable. Accepts\n * `\"YYYY-MM-DD\"` (UTC midnight, with real calendar validation — `2026-02-31`\n * is null), full ISO strings, and finite numbers (already epoch ms).\n */\nexport function parseDateMs(v: unknown): number | null {\n if (typeof v === \"number\") return Number.isFinite(v) ? v : null;\n if (typeof v !== \"string\") return null;\n const m = DATE_ONLY.exec(v.trim());\n if (m) {\n const t = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));\n // JS rolls invalid components (\"2026-02-31\" → Mar 3); a real date\n // round-trips to the same YYYY-MM-DD.\n return new Date(t).toISOString().slice(0, 10) === m[0].trim() ? t : null;\n }\n const t = Date.parse(v);\n return Number.isNaN(t) ? null : t;\n}\n\n// ISO-8601 week: week 1 contains the year's first Thursday; weeks run Mon–Sun.\nfunction isoWeek(t: number): { year: number; week: number } {\n const d = new Date(t);\n const midnight = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());\n const dow = new Date(midnight).getUTCDay() || 7; // Mon=1 … Sun=7\n const thursday = midnight + (4 - dow) * DAY;\n const year = new Date(thursday).getUTCFullYear();\n const week = Math.floor((thursday - Date.UTC(year, 0, 1)) / DAY / 7) + 1;\n return { year, week };\n}\n\n// Monday (UTC midnight) of ISO week `week` of ISO year `year`: Jan 4 is\n// always inside week 1.\nfunction isoWeekStart(year: number, week: number): number {\n const jan4 = Date.UTC(year, 0, 4);\n const dow = new Date(jan4).getUTCDay() || 7;\n return jan4 - (dow - 1) * DAY + (week - 1) * 7 * DAY;\n}\n\nconst pad2 = (n: number) => String(n).padStart(2, \"0\");\n\n/** The bucket key containing epoch-ms `t`: `\"2026-07\"`, `\"2026-Q3\"`, or `\"2026-W29\"`. */\nexport function bucketKey(t: number, grain: TimeGrain): string {\n const d = new Date(t);\n if (grain === \"month\") return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}`;\n if (grain === \"quarter\") return `${d.getUTCFullYear()}-Q${Math.floor(d.getUTCMonth() / 3) + 1}`;\n const { year, week } = isoWeek(t);\n return `${year}-W${pad2(week)}`;\n}\n\n/**\n * Expand a bucket key back to its half-open [start, end) range. Throws on a\n * key that matches no known grain format.\n */\nexport function bucketFor(key: string): TimeBucket {\n let m = /^(\\d{4})-(\\d{2})$/.exec(key);\n if (m) {\n const [y, mo] = [Number(m[1]), Number(m[2])];\n return { key, start: Date.UTC(y, mo - 1, 1), end: Date.UTC(y, mo, 1) };\n }\n m = /^(\\d{4})-Q([1-4])$/.exec(key);\n if (m) {\n const [y, q] = [Number(m[1]), Number(m[2])];\n return { key, start: Date.UTC(y, (q - 1) * 3, 1), end: Date.UTC(y, q * 3, 1) };\n }\n m = /^(\\d{4})-W(\\d{2})$/.exec(key);\n if (m) {\n const start = isoWeekStart(Number(m[1]), Number(m[2]));\n return { key, start, end: start + 7 * DAY };\n }\n throw new Error(`not a bucket key: ${key}`);\n}\n\n/**\n * Every bucket from the one containing `from` through the one containing `to`,\n * in order. Returns [] when from > to.\n */\nexport function bucketRange(from: number, to: number, grain: TimeGrain): TimeBucket[] {\n const out: TimeBucket[] = [];\n if (from > to) return out;\n let cur = bucketFor(bucketKey(from, grain));\n const last = bucketKey(to, grain);\n for (;;) {\n out.push(cur);\n if (cur.key === last) return out;\n cur = bucketFor(bucketKey(cur.end, grain));\n }\n}\n","// Read a GraphFragment back OUT of odla-db — the inverse of the writer, and\n// the bridge from the live graph to the pure @odla-ai/kg/temporal analytics.\n// Reads are per-type offset walks with link labels as nested InstaQL keys\n// (odla-db has no cross-relation where; filter per level). Reverse labels are\n// normalized to FORWARD edges so the fragment satisfies resolvePath's\n// forward-only traversal.\nimport type { AdminDb } from \"@odla-ai/db\";\nimport type { GraphFragment, GraphNode, GraphEdge } from \"../types\";\nimport type { Ontology } from \"../ontology\";\nimport { naturalKeysOf } from \"../ontology\";\nimport { parseDateMs } from \"../temporal/time\";\n\n/** One entity type to export. */\nexport interface ExportEntitySpec {\n /** Link labels (forward or reverse, as queried in InstaQL) to pull as nested rows. */\n links?: readonly string[];\n /** Attr allowlist kept on nodes. Default: every scalar attr except `id` and links. */\n attrs?: readonly string[];\n /**\n * Date window. Filtering is client-side by default; set `indexed: true`\n * (only when the attr really is indexed) to also push a\n * `where { [dateAttr]: { $gte: from } }` to the server — odla-db silently\n * returns zero rows for `where` on unindexed attrs.\n */\n window?: { dateAttr: string; from: string; indexed?: boolean };\n /** Row cap for this type across the offset walk. Default 1000. */\n limit?: number;\n}\n\n/** What {@link exportFragment} should read. */\nexport interface ExportSpec {\n entities: Record<string, ExportEntitySpec>;\n /** Rows per page in the offset walk (1–1000). Default 200. */\n pageSize?: number;\n}\n\ninterface LinkPlan {\n label: string;\n counterpart: string;\n /** Forward: root --forwardLabel--> nested. Reverse: nested --forwardLabel--> root. */\n direction: \"forward\" | \"reverse\";\n forwardLabel: string;\n}\n\nfunction planLinks(ontology: Ontology, type: string, labels: readonly string[]): LinkPlan[] {\n return labels.map((label) => {\n for (const edge of Object.values(ontology.edges)) {\n if (edge.forwardLabel === label && edge.from === type) {\n return { label, counterpart: edge.to, direction: \"forward\" as const, forwardLabel: edge.forwardLabel };\n }\n if (edge.reverseLabel === label && edge.to === type) {\n return { label, counterpart: edge.from, direction: \"reverse\" as const, forwardLabel: edge.forwardLabel };\n }\n }\n throw new Error(`exportFragment: no ontology edge matches link \"${label}\" on \"${type}\"`);\n });\n}\n\nconst isScalar = (v: unknown) => v === null || (typeof v !== \"object\" && typeof v !== \"function\");\n\nfunction pickAttrs(row: Record<string, unknown>, spec: ExportEntitySpec, links: readonly string[]) {\n const out: GraphNode[\"attrs\"] = {};\n for (const [k, v] of Object.entries(row)) {\n if (spec.attrs ? !spec.attrs.includes(k) : k === \"id\" || links.includes(k) || !isScalar(v)) continue;\n if (isScalar(v) && v !== null) out[k] = v as GraphNode[\"attrs\"][string];\n }\n return out;\n}\n\nconst RETRYABLE_WITHOUT_WHERE = /query_scan_limit|query_too_complex/;\n\n/**\n * Export (a window of) the graph as a `GraphFragment` — nodes and forward\n * edges — ready for `extractEvents` and the rest of `@odla-ai/kg/temporal`.\n *\n * Each entity type is read with a stable offset walk (ordered by its natural\n * key) carrying its requested link labels as nested relations. Date windows\n * filter client-side; for attrs declared `indexed` the filter also runs\n * server-side, and if that indexed filter ever exceeds the scan budget\n * (`query_scan_limit`) the walk restarts without `where`.\n * Nodes dedupe by `type:key` keeping the richer attr set; output ordering is\n * deterministic (nodes by type/key, edges by from/label/to).\n */\nexport async function exportFragment(db: AdminDb, ontology: Ontology, spec: ExportSpec): Promise<GraphFragment> {\n const keys = naturalKeysOf(ontology);\n const pageSize = Math.min(1000, Math.max(1, spec.pageSize ?? 200));\n const nodes = new Map<string, GraphNode>();\n const edges = new Map<string, GraphEdge>();\n\n const putNode = (node: GraphNode) => {\n const id = `${node.type}:${node.key}`;\n const existing = nodes.get(id);\n if (!existing || Object.keys(node.attrs).length > Object.keys(existing.attrs).length) nodes.set(id, node);\n };\n\n for (const [type, entity] of Object.entries(spec.entities)) {\n const naturalKey = keys[type];\n if (!naturalKey) throw new Error(`exportFragment: no ontology entity \"${type}\"`);\n const links = planLinks(ontology, type, entity.links ?? []);\n const limit = entity.limit ?? 1000;\n const fromMs = entity.window ? parseDateMs(entity.window.from) : null;\n let useWhere = entity.window?.indexed === true;\n let offset = 0;\n let taken = 0;\n\n while (taken < limit) {\n const opts: Record<string, unknown> = {\n limit: Math.min(pageSize, limit - taken),\n offset,\n order: { [naturalKey]: \"asc\" },\n };\n if (useWhere && entity.window) opts.where = { [entity.window.dateAttr]: { $gte: entity.window.from } };\n const query = {\n [type]: { $: opts, ...Object.fromEntries(links.map((l) => [l.label, {}])) },\n };\n\n let rows: Array<Record<string, unknown>>;\n try {\n rows = ((await db.query(query)) as Record<string, unknown[]>)[type] as Array<Record<string, unknown>> ?? [];\n } catch (err) {\n if (useWhere && RETRYABLE_WITHOUT_WHERE.test(String((err as Error).message ?? err))) {\n // Unindexed attr blew the scan budget — restart this type's walk\n // without `where` (offsets aren't comparable across filters; the\n // dedupe maps make re-reads harmless) and filter client-side.\n useWhere = false;\n offset = 0;\n taken = 0;\n continue;\n }\n throw err;\n }\n\n for (const row of rows) {\n const key = row[naturalKey];\n if (key === undefined || key === null) continue; // defensive: unkeyed row\n if (entity.window && fromMs !== null) {\n const t = parseDateMs(row[entity.window.dateAttr]);\n if (t !== null && t < fromMs) continue;\n }\n const rootRef = { type, key: String(key) };\n putNode({ ...rootRef, attrs: pickAttrs(row, entity, entity.links ?? []) });\n\n for (const plan of links) {\n const nestedKeyAttr = keys[plan.counterpart];\n if (!nestedKeyAttr) throw new Error(`exportFragment: no ontology entity \"${plan.counterpart}\"`);\n // has-many relations come back as arrays, has-one as a bare object.\n const raw = row[plan.label];\n const nestedRows = Array.isArray(raw) ? raw : raw ? [raw] : [];\n for (const nested of nestedRows as Array<Record<string, unknown>>) {\n const nestedKey = nested[nestedKeyAttr];\n if (nestedKey === undefined || nestedKey === null) continue;\n const nestedRef = { type: plan.counterpart, key: String(nestedKey) };\n putNode({ ...nestedRef, attrs: pickAttrs(nested, {}, []) });\n const edge: GraphEdge =\n plan.direction === \"forward\"\n ? { from: rootRef, to: nestedRef, label: plan.forwardLabel }\n : { from: nestedRef, to: rootRef, label: plan.forwardLabel };\n edges.set(`${edge.from.type}:${edge.from.key}|${edge.label}|${edge.to.type}:${edge.to.key}`, edge);\n }\n }\n taken++;\n if (taken >= limit) break;\n }\n\n if (rows.length < pageSize) break;\n offset += rows.length;\n }\n }\n\n const byRef = (a: { type: string; key: string }, b: { type: string; key: string }) =>\n a.type < b.type ? -1 : a.type > b.type ? 1 : a.key < b.key ? -1 : a.key > b.key ? 1 : 0;\n return {\n nodes: [...nodes.values()].sort(byRef),\n edges: [...edges.values()].sort(\n (a, b) => byRef(a.from, b.from) || (a.label < b.label ? -1 : a.label > b.label ? 1 : 0) || byRef(a.to, b.to),\n ),\n };\n}\n","import type { AdminDb, Entity } from \"@odla-ai/db\";\nimport type { GraphFragment } from \"../types\";\nimport type { Ontology } from \"../ontology\";\n\n/** FTS5 search over a namespace's text fields (odla-db QueryOpts.search). */\nexport async function searchEntities(db: AdminDb, type: string, q: string, limit = 5): Promise<Entity[]> {\n if (!q.trim()) return [];\n const res = await db.query({ [type]: { $: { search: q, limit } } });\n return (res[type] ?? []) as Entity[];\n}\n\nconst norm = (s: string): string => s.toLowerCase().replace(/[^a-z0-9]+/g, \"\");\n\n/**\n * Resolve a name to an existing entity's natural key via FTS — for merging a\n * re-discovery that slugified differently. Conservative: only returns a hit when\n * the top match's name/key normalizes identically to the query.\n */\nexport async function resolveName(\n db: AdminDb,\n type: string,\n keyAttr: string,\n name: string,\n): Promise<string | null> {\n const target = norm(name);\n if (!target) return null;\n for (const h of await searchEntities(db, type, name, 3)) {\n const key = h[keyAttr];\n const hname = h[\"name\"];\n if (typeof key !== \"string\") continue;\n if ((typeof hname === \"string\" && norm(hname) === target) || norm(key) === target) return key;\n }\n return null;\n}\n\n/**\n * Canonicalize a fragment's searchable nodes against existing entities via FTS,\n * remapping node keys (and the edges that reference them) to the canonical key.\n * Used on the lower-volume discovery/enrich paths, not high-volume ingest.\n */\nexport async function canonicalizeFragment(\n db: AdminDb,\n fragment: GraphFragment,\n ontology: Ontology,\n): Promise<GraphFragment> {\n const remap = new Map<string, string>(); // `${type}:${key}` -> canonical key\n for (const n of fragment.nodes) {\n const spec = ontology.entities[n.type];\n if (!spec?.searchable?.length) continue;\n const name = typeof n.attrs.name === \"string\" ? n.attrs.name : \"\";\n if (!name) continue;\n const canon = await resolveName(db, n.type, spec.naturalKey, name);\n if (canon && canon !== n.key) remap.set(`${n.type}:${n.key}`, canon);\n }\n if (remap.size === 0) return fragment;\n\n const rk = (type: string, key: string): string => remap.get(`${type}:${key}`) ?? key;\n return {\n nodes: fragment.nodes.map((n) => ({ ...n, key: rk(n.type, n.key) })),\n edges: fragment.edges.map((e) => ({\n from: { type: e.from.type, key: rk(e.from.type, e.from.key) },\n to: { type: e.to.type, key: rk(e.to.type, e.to.key) },\n label: e.label,\n })),\n };\n}\n","// Deterministic randomness for temporal analytics. Communities and layout\n// derive ALL randomness from node identity + an explicit seed, so results are\n// pure functions of their inputs — never of Map iteration or insertion order.\n\n/**\n * FNV-1a 32-bit hash of a string. Used to derive a per-identity PRNG seed\n * (`mulberry32(hash32(id) ^ seed)`), which makes node placement independent\n * of the order nodes arrive in.\n */\nexport function hash32(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n/**\n * mulberry32: a tiny, fast, seedable PRNG returning floats in [0, 1).\n * The same seed always yields the same sequence — replayable layouts and\n * community assignments depend on this.\n */\nexport function mulberry32(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x6d2b79f5) >>> 0;\n let t = a;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Fisher–Yates shuffle into a NEW array, driven by the supplied random source\n * (pass a seeded `mulberry32` for a deterministic permutation).\n */\nexport function shuffle<T>(items: readonly T[], rand: () => number): T[] {\n const out = items.slice();\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(rand() * (i + 1));\n const tmp = out[i] as T; // i, j always in bounds\n out[i] = out[j] as T;\n out[j] = tmp;\n }\n return out;\n}\n","// Deterministic label propagation. Classic LPA needs random visit order to\n// break symmetry; here that randomness comes from one seeded shuffle stream,\n// so the same (nodes, edges, seed) always yields the same communities.\nimport { mulberry32, shuffle } from \"./prng\";\nimport type { NodeId } from \"./adjacency\";\n\n/**\n * Asynchronous label propagation with deterministic (seeded) tie-breaking:\n * - labels start as each node's own id;\n * - each round visits nodes in a seeded-shuffled order (one PRNG stream\n * reused across rounds) and adopts the label with the greatest total\n * incident edge weight; if the node's CURRENT label ties for the max it is\n * kept, otherwise a tied max is picked via the same seeded stream (a\n * lexicographic tie-break would systematically bleed communities toward\n * alphabetically small ids — verified on the two-triangles fixture);\n * - stops on a round with no change, or after `maxRounds`;\n * - finally each community's label is canonicalized to its smallest member\n * id, so the output is independent of propagation history.\n * Isolated nodes keep their own label (singleton communities).\n */\nexport function labelPropagation(\n nodes: readonly NodeId[],\n edges: ReadonlyArray<{ a: NodeId; b: NodeId; weight?: number }>,\n opts: { seed?: number; maxRounds?: number } = {},\n): Map<NodeId, string> {\n const { seed = 1, maxRounds = 20 } = opts;\n const sorted = [...nodes].sort();\n const labels = new Map<NodeId, string>(sorted.map((id) => [id, id]));\n\n const neighbors = new Map<NodeId, Array<{ id: NodeId; weight: number }>>();\n for (const e of edges) {\n if (!labels.has(e.a) || !labels.has(e.b) || e.a === e.b) continue;\n const w = e.weight ?? 1;\n let listA = neighbors.get(e.a);\n if (!listA) neighbors.set(e.a, (listA = []));\n listA.push({ id: e.b, weight: w });\n let listB = neighbors.get(e.b);\n if (!listB) neighbors.set(e.b, (listB = []));\n listB.push({ id: e.a, weight: w });\n }\n\n const rand = mulberry32(seed);\n for (let round = 0; round < maxRounds; round++) {\n const order = shuffle(sorted, rand);\n let changed = false;\n for (const id of order) {\n const incident = neighbors.get(id);\n if (!incident || incident.length === 0) continue;\n const votes = new Map<string, number>();\n for (const n of incident) {\n const label = labels.get(n.id)!;\n votes.set(label, (votes.get(label) ?? 0) + n.weight);\n }\n let best: string[] = [];\n let bestWeight = -Infinity;\n for (const [label, weight] of votes) {\n if (weight > bestWeight) {\n bestWeight = weight;\n best = [label];\n } else if (weight === bestWeight) {\n best.push(label);\n }\n }\n const current = labels.get(id)!;\n if (best.includes(current)) continue; // keep on tie — stability first\n best.sort();\n const pick = best.length === 1 ? best[0]! : best[Math.floor(rand() * best.length)]!;\n labels.set(id, pick);\n changed = true;\n }\n if (!changed) break;\n }\n\n // Canonicalize: community label = smallest member id.\n const members = new Map<string, NodeId[]>();\n for (const [id, label] of labels) {\n const list = members.get(label);\n list ? list.push(id) : members.set(label, [id]);\n }\n const canonical = new Map<string, string>();\n // Members collect in `sorted` iteration order, so ids[0] is the minimum.\n for (const [label, ids] of members) canonical.set(label, ids[0]!);\n return new Map(sorted.map((id) => [id, canonical.get(labels.get(id)!)!]));\n}\n","// Connection lenses: project a heterogeneous graph onto ONE node universe\n// (companies, people, places…) under a chosen relatedness — shared bridges\n// (people, investors), spatial proximity, temporal cohort — and blend several\n// lenses into the weighted edge set a layout/community pass consumes. Pure\n// and deterministic; the viewer re-runs these live as sliders move.\nimport type { GraphFragment } from \"../types\";\n\n/** A 2-D point in a normalized layout/map space (for the geo proximity lens). */\nexport interface XY {\n x: number;\n y: number;\n}\n\n/** One undirected weighted pair, with (capped) bridging node ids as evidence. */\nexport interface WeightedPair {\n a: string;\n b: string;\n w: number;\n via: string[];\n}\n\n/**\n * Bridge → target sets through a REIFIED relationship node (affiliation,\n * funding_round, record): every reified node with a `bridgeLabel` edge to the\n * bridge and a `targetLabel` edge to a target contributes that target to the\n * bridge's set. Ids are `type:key`.\n */\nexport function reifiedBridgeTargets(\n fragment: GraphFragment,\n opts: { reified: string; bridgeLabel: string; targetLabel: string },\n): Map<string, Set<string>> {\n const bridgesOf = new Map<string, string[]>(); // a round can have several investors\n const targetsOf = new Map<string, string[]>();\n for (const e of fragment.edges) {\n if (e.from.type !== opts.reified) continue;\n const to = `${e.to.type}:${e.to.key}`;\n if (e.label === opts.bridgeLabel) (bridgesOf.get(e.from.key) ?? bridgesOf.set(e.from.key, []).get(e.from.key))!.push(to);\n if (e.label === opts.targetLabel) (targetsOf.get(e.from.key) ?? targetsOf.set(e.from.key, []).get(e.from.key))!.push(to);\n }\n const out = new Map<string, Set<string>>();\n for (const [reifiedKey, bridges] of bridgesOf) {\n for (const bridge of bridges) {\n for (const target of targetsOf.get(reifiedKey) ?? []) {\n (out.get(bridge) ?? out.set(bridge, new Set()).get(bridge))!.add(target);\n }\n }\n }\n return out;\n}\n\n/** Direct-edge targets: `from` node id → set of `to` node ids for one label. */\nexport function directTargets(fragment: GraphFragment, label: string): Map<string, Set<string>> {\n const out = new Map<string, Set<string>>();\n for (const e of fragment.edges) {\n if (e.label !== label) continue;\n const from = `${e.from.type}:${e.from.key}`;\n (out.get(from) ?? out.set(from, new Set()).get(from))!.add(`${e.to.type}:${e.to.key}`);\n }\n return out;\n}\n\n/** Compose A→B with B→C into A→C (e.g. person→companies ∘ company→counties). */\nexport function composeTargets(\n ab: Map<string, Set<string>>,\n bc: Map<string, Set<string>>,\n): Map<string, Set<string>> {\n const out = new Map<string, Set<string>>();\n for (const [a, bs] of ab) {\n for (const b of bs) {\n for (const c of bc.get(b) ?? []) {\n (out.get(a) ?? out.set(a, new Set()).get(a))!.add(c);\n }\n }\n }\n return out;\n}\n\n/**\n * The bipartite projection: targets sharing a bridge become pairs, weight =\n * number of distinct shared bridges. `maxPerBridge` skips promiscuous hubs\n * (a bridge touching 40 targets says little about any pair). Deterministic\n * pair order (a < b, then sorted).\n */\nexport function projectBridges(\n bridges: Map<string, Set<string>>,\n opts: { maxPerBridge?: number; viaCap?: number } = {},\n): WeightedPair[] {\n const { maxPerBridge = 12, viaCap = 4 } = opts;\n const acc = new Map<string, { a: string; b: string; w: number; via: string[] }>();\n for (const [bridge, targetSet] of [...bridges.entries()].sort((x, y) => (x[0] < y[0] ? -1 : 1))) {\n const targets = [...targetSet].sort();\n if (targets.length < 2 || targets.length > maxPerBridge) continue;\n for (let i = 0; i < targets.length; i++) {\n for (let j = i + 1; j < targets.length; j++) {\n const key = `${targets[i]}|${targets[j]}`;\n const hit = acc.get(key) ?? { a: targets[i]!, b: targets[j]!, w: 0, via: [] };\n hit.w += 1;\n if (hit.via.length < viaCap) hit.via.push(bridge);\n acc.set(key, hit);\n }\n }\n }\n return [...acc.keys()].sort().map((k) => acc.get(k)!);\n}\n\n/**\n * Temporal cohort pairs: each node links to its k nearest neighbors in\n * arrival time, weight decaying with the gap (0.5 per halfLife). Nodes\n * without a date contribute nothing.\n */\nexport function timePairs(\n arrivals: Map<string, number>,\n opts: { k?: number; halfLifeDays?: number } = {},\n): WeightedPair[] {\n const { k = 4, halfLifeDays = 30 } = opts;\n const halfLife = halfLifeDays * 86_400_000;\n const rows = [...arrivals.entries()].filter(([, t]) => Number.isFinite(t)).sort((x, y) => x[1] - y[1] || (x[0] < y[0] ? -1 : 1));\n const acc = new Map<string, WeightedPair>();\n for (let i = 0; i < rows.length; i++) {\n for (let j = i + 1; j < Math.min(rows.length, i + 1 + k); j++) {\n const [idA, tA] = rows[i]!;\n const [idB, tB] = rows[j]!;\n const w = Math.pow(0.5, Math.abs(tB - tA) / halfLife);\n if (w < 0.01) break; // sorted by time — later neighbors are only farther\n const [a, b] = idA < idB ? [idA, idB] : [idB, idA];\n acc.set(`${a}|${b}`, { a, b, w, via: [] }); // i<j scan visits each pair once\n }\n }\n return [...acc.keys()].sort().map((key) => acc.get(key)!);\n}\n\n/**\n * Spatial proximity pairs: k nearest neighbors in normalized map space,\n * weight decaying with distance (0.5 per halfDist). Nodes without a\n * position contribute nothing. O(n²) — fine for the ≤1k-node universes\n * lenses operate on.\n */\nexport function geoPairs(\n positions: Map<string, XY>,\n opts: { k?: number; halfDist?: number } = {},\n): WeightedPair[] {\n const { k = 4, halfDist = 0.04 } = opts;\n const rows = [...positions.entries()].sort((x, y) => (x[0] < y[0] ? -1 : 1));\n const acc = new Map<string, WeightedPair>();\n for (let i = 0; i < rows.length; i++) {\n const dists: Array<{ j: number; d: number }> = [];\n for (let j = 0; j < rows.length; j++) {\n if (i === j) continue;\n const dx = rows[i]![1].x - rows[j]![1].x;\n const dy = rows[i]![1].y - rows[j]![1].y;\n dists.push({ j, d: Math.hypot(dx, dy) });\n }\n dists.sort((x, y) => x.d - y.d || x.j - y.j);\n for (const { j, d } of dists.slice(0, k)) {\n const w = Math.pow(0.5, d / halfDist);\n if (w < 0.01) continue;\n const [a, b] = rows[i]![0] < rows[j]![0] ? [rows[i]![0], rows[j]![0]] : [rows[j]![0], rows[i]![0]];\n const key = `${a}|${b}`;\n const prev = acc.get(key);\n if (!prev || prev.w < w) acc.set(key, { a, b, w, via: [] });\n }\n }\n return [...acc.keys()].sort().map((key) => acc.get(key)!);\n}\n\n/**\n * Blend lens edge sets: each set is normalized to a max weight of 1 (a lens\n * with big raw counts must not drown the others), scaled by its slider\n * weight, and summed per pair. Zero-weight lenses drop out entirely.\n */\nexport function blendPairs(sets: Array<{ pairs: WeightedPair[]; weight: number }>): WeightedPair[] {\n const acc = new Map<string, WeightedPair>();\n for (const { pairs, weight } of sets) {\n if (weight <= 0 || pairs.length === 0) continue;\n const max = Math.max(...pairs.map((p) => p.w));\n for (const p of pairs) {\n const key = `${p.a}|${p.b}`;\n const hit = acc.get(key) ?? { a: p.a, b: p.b, w: 0, via: [] };\n hit.w += (p.w / max) * weight;\n for (const v of p.via) if (hit.via.length < 6 && !hit.via.includes(v)) hit.via.push(v);\n acc.set(key, hit);\n }\n }\n return [...acc.keys()].sort().map((key) => acc.get(key)!);\n}\n","// Brokerage: which nodes connect otherwise-distinct groups? Scored against\n// the community partition (labelPropagation) already computed per frame:\n// a node's bridging weight is its edge weight INTO foreign communities,\n// amplified by how many distinct groups it touches. Pure and cheap (O(E))\n// — cheap enough to recompute every frame of a replay.\nimport type { NodeId } from \"./adjacency\";\n\n/** One node's brokerage: normalized score + the communities it connects. */\nexport interface Bridging {\n /** 0..1, 1 = the frame's strongest connector. */\n score: number;\n /** Communities this node touches through its edges (own community first). */\n groups: string[];\n}\n\n/**\n * Score every node's brokerage against a community partition. Edges inside a\n * community contribute nothing; an edge to a foreign community contributes\n * its weight, and the total is multiplied by the number of DISTINCT foreign\n * communities — a node wiring three groups together outranks one with the\n * same raw weight into a single neighbor group. Scores are normalized to\n * the frame's max (empty/one-community frames → all zeros).\n */\nexport function bridgingScores(\n edges: ReadonlyArray<{ a: NodeId; b: NodeId; weight?: number }>,\n labels: Map<NodeId, string>,\n): Map<NodeId, Bridging> {\n const cross = new Map<NodeId, number>();\n const foreign = new Map<NodeId, Set<string>>();\n const touch = (id: NodeId, otherLabel: string, w: number) => {\n cross.set(id, (cross.get(id) ?? 0) + w);\n (foreign.get(id) ?? foreign.set(id, new Set()).get(id))!.add(otherLabel);\n };\n for (const e of edges) {\n const la = labels.get(e.a);\n const lb = labels.get(e.b);\n if (la === undefined || lb === undefined || la === lb) continue;\n const w = e.weight ?? 1;\n touch(e.a, lb, w);\n touch(e.b, la, w);\n }\n let max = 0;\n const raw = new Map<NodeId, number>();\n for (const [id, w] of cross) {\n const r = w * foreign.get(id)!.size;\n raw.set(id, r);\n if (r > max) max = r;\n }\n const out = new Map<NodeId, Bridging>();\n for (const [id] of labels) {\n const r = raw.get(id) ?? 0;\n const own = labels.get(id)!;\n const groups = r > 0 ? [own, ...[...foreign.get(id)!].sort()] : [own];\n out.set(id, { score: max > 0 ? r / max : 0, groups });\n }\n return out;\n}\n\n/** The frame's strongest connectors, ranked (ties: id order). */\nexport function topBridges(\n bridging: Map<NodeId, Bridging>,\n n = 5,\n): Array<{ id: NodeId; score: number; groups: string[] }> {\n return [...bridging.entries()]\n .filter(([, b]) => b.score > 0)\n .sort((x, y) => y[1].score - x[1].score || (x[0] < y[0] ? -1 : 1))\n .slice(0, n)\n .map(([id, b]) => ({ id, score: Math.round(b.score * 100) / 100, groups: b.groups }));\n}\n","// Connector discovery: given a GraphFragment and a set of \"lenses\" (which\n// shared entity connects two nodes of the universe type), find the nodes that\n// bridge otherwise-distinct groups — the brokers/connectors. Pure and\n// deterministic; the headless form of what the retired galaxy viewer showed.\n//\n// A lens says \"two <universe> nodes are related when they share an <X>, where\n// X links to the universe node through reified node R via labels (bridge, of)\".\n// e.g. two companies share a PERSON via `affiliation` (of_person → of_company),\n// or share an INVESTOR via `funding_round` (raised_from → round_of).\nimport type { GraphFragment } from \"../types\";\nimport {\n blendPairs,\n bridgingScores,\n labelPropagation,\n projectBridges,\n reifiedBridgeTargets,\n topBridges,\n type WeightedPair,\n} from \"../temporal\";\n\n/** One relatedness lens over the universe nodes. */\nexport interface ConnectorLens {\n /** Reified relationship node type carrying both edges (e.g. `\"affiliation\"`). */\n reified: string;\n /** Edge label to the SHARED entity that bridges (e.g. `\"of_person\"`). */\n bridge: string;\n /** Edge label to the UNIVERSE node the shared entity connects (e.g. `\"of_company\"`). */\n of: string;\n /** Blend weight (each lens is normalized to max 1 first). Default 1. */\n weight?: number;\n}\n\n/** How to find connectors: the lenses to blend, plus limits. */\nexport interface ConnectorConfig {\n lenses: ConnectorLens[];\n /** Extra precomputed pairs to fold in (e.g. geo/time affinity). */\n extraPairs?: Array<{ pairs: WeightedPair[]; weight: number }>;\n /** How many connectors to return. Default 12. */\n top?: number;\n /** Skip promiscuous shared entities touching more than this many nodes. Default 12. */\n maxPerBridge?: number;\n}\n\n/** One bridging node: the groups it joins and the shared entities that link it out. */\nexport interface Connector {\n /** `type:key` of the connector node. */\n ref: string;\n name: string;\n /** 0..1 brokerage — 1 is the strongest connector found. */\n score: number;\n /** Representative names of the distinct groups it bridges. */\n groups: string[];\n /** Names of the shared entities (people/investors/…) linking it to other groups. */\n via: string[];\n}\n\n/** The ranked connectors plus the size of the graph they were found in. */\nexport interface ConnectorReport {\n connectors: Connector[];\n /** Distinct communities in the blended graph. */\n communities: number;\n /** Universe nodes with at least one relationship. */\n nodes: number;\n}\n\n/**\n * Rank the connectors of a graph. Builds the blended relationship graph from\n * the lenses, detects communities, scores each node's cross-community\n * brokerage, and returns the top bridges with the groups they join and the\n * shared entities behind each link. Empty/relationshipless input → empty report.\n */\nexport function findConnectors(fragment: GraphFragment, config: ConnectorConfig): ConnectorReport {\n const nameOf = new Map<string, string>();\n for (const n of fragment.nodes) {\n const label = n.attrs.name ?? n.attrs.title;\n if (typeof label === \"string\" && label) nameOf.set(`${n.type}:${n.key}`, label);\n }\n\n const sets = config.lenses.map((l) => ({\n pairs: projectBridges(reifiedBridgeTargets(fragment, { reified: l.reified, bridgeLabel: l.bridge, targetLabel: l.of }), {\n maxPerBridge: config.maxPerBridge ?? 12,\n }),\n weight: l.weight ?? 1,\n }));\n const pairs = blendPairs([...sets, ...(config.extraPairs ?? [])]);\n if (pairs.length === 0) return { connectors: [], communities: 0, nodes: 0 };\n\n const nodes = [...new Set(pairs.flatMap((p) => [p.a, p.b]))].sort();\n const edges = pairs.map((p) => ({ a: p.a, b: p.b, weight: p.w }));\n const labels = labelPropagation(nodes, edges, { seed: 1 });\n\n // Representative display name per community: an anchor member's name.\n const groupName = new Map<string, string>();\n for (const id of nodes) {\n const g = labels.get(id)!;\n if (!groupName.has(g)) groupName.set(g, nameOf.get(id) ?? id);\n }\n\n const bridging = bridgingScores(edges, labels);\n const viaOf = (id: string) => {\n const names = new Set<string>();\n for (const p of pairs) {\n if (p.a !== id && p.b !== id) continue;\n // via ids are always non-empty `type:key`, so pop() is a string.\n for (const v of p.via) names.add(nameOf.get(v) ?? v.split(\":\").pop()!);\n }\n return [...names].slice(0, 6);\n };\n\n const connectors = topBridges(bridging, config.top ?? 12).map((b): Connector => ({\n ref: b.id,\n name: nameOf.get(b.id) ?? b.id,\n score: b.score,\n groups: b.groups.map((g) => groupName.get(g)!), // every label has an anchor name\n\n via: viaOf(b.id),\n }));\n\n return { connectors, communities: new Set(labels.values()).size, nodes: nodes.length };\n}\n","/**\n * Identity + dedup helpers. The slug / urlHash a node resolves to IS its\n * natural key for Lookup-based upserts, so these must be stable and pure.\n * Ported from agnacl (utils/markdown.ts slugify, services/kv.ts normalizeUrl).\n */\n\n/** NFKD-normalize, strip accents, lowercase, non-alphanumerics → \"-\", cap 60. */\nexport function slugify(input: string): string {\n return input\n .normalize(\"NFKD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60)\n .replace(/-+$/g, \"\");\n}\n\nconst LEGAL_SUFFIXES = new Set([\"co\", \"company\", \"corp\", \"corporation\", \"inc\", \"incorporated\", \"llc\", \"llp\", \"ltd\", \"limited\", \"pllc\"]);\n\n/**\n * Collapse a name to a comparison key: accents stripped, `&` spelled out,\n * punctuation and spacing removed, and trailing legal suffixes dropped.\n *\n * Stronger than comparing {@link slugify} output, because it matches\n * \"Acme Corp.\", \"Acme, Inc.\" and \"Acme & Co\" onto the same key — the shapes the\n * same organization is actually written in across sources. Use it to decide\n * whether two records are the same entity; use `slugify` for the natural key\n * that gets stored.\n */\nexport function identityKey(value: string): string {\n const tokens = value\n .normalize(\"NFKD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLowerCase()\n .replace(/&/g, \" and \")\n .match(/[a-z0-9]+/g) ?? [];\n while (tokens.length > 1 && LEGAL_SUFFIXES.has(tokens.at(-1) ?? \"\")) {\n tokens.pop();\n if (tokens.at(-1) === \"and\") tokens.pop();\n }\n return tokens.join(\"\");\n}\n\n/** Drop tracking params, hash, and trailing slash so the same page maps to one key. */\nexport function normalizeUrl(raw: string): string {\n try {\n const u = new URL(raw);\n u.hash = \"\";\n const drop = [/^utm_/i, /^ref$/i, /^ref_/i, /^fbclid$/i, /^gclid$/i, /^mc_/i];\n for (const key of [...u.searchParams.keys()]) {\n if (drop.some((re) => re.test(key))) u.searchParams.delete(key);\n }\n let s = u.toString();\n s = s.replace(/\\/$/, \"\");\n return s;\n } catch {\n return raw.trim().replace(/#.*$/, \"\").replace(/\\/$/, \"\");\n }\n}\n\n/** Stable short key for a URL (FNV-1a 32-bit, base36). Used as the `signal` natural key. */\nexport function urlHash(raw: string): string {\n const s = normalizeUrl(raw);\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(36);\n}\n\n/**\n * Reject descriptive phrases masquerading as people (\"three craftsmen\",\n * \"the founders\"). A real name is 2-4 capitalized tokens, no digits.\n * Ported from agnacl utils/names.ts (isLikelyPersonName).\n */\nexport function isLikelyPersonName(name: string): boolean {\n const n = name.trim();\n if (!n || /\\d/.test(n)) return false;\n const parts = n.split(/\\s+/);\n if (parts.length < 2 || parts.length > 4) return false;\n return parts.every((p) => /^[A-Z][a-zA-Z'’.-]*$/.test(p));\n}\n","// Company enrichment: prompts, forced-tool schemas, and the pure fragment\n// builders that turn a model's research/structuring output into an idempotent\n// GraphFragment (company attrs + person/affiliation/funding_round/investor\n// nodes). The model's output is a proposal — everything here is capped and\n// validated before it becomes graph. This module is data + pure functions; the\n// LLM runners live in ./run.\nimport type { ExtractTool } from \"@odla-ai/ai\";\nimport { slugify } from \"../util/identity\";\nimport type { GraphFragment, GraphNode } from \"../types\";\n\n/** The minimal company row enrichment reads (attrs only — never hydrates). */\nexport interface CompanyRow {\n slug: string;\n name: string;\n city?: string;\n region?: string;\n first_seen?: string;\n founded?: string;\n fips?: string;\n}\n\n// ------------------------------------------------------------------ locate\n\n/** System prompt for the county-resolution extraction. */\nexport const LOCATE_SYSTEM =\n \"You resolve US business locations to counties. For each company you are given a city and/or a free-text region. Name the US county containing that city (for independent cities, use the city's own county-equivalent name) plus its 2-letter state. Use nulls when the location is ambiguous, outside the US, or unknown.\";\n\n/** Forced-tool schema pairing each company slug with a county name + state. */\nexport const LOCATE_TOOL: ExtractTool = {\n name: \"resolve_counties\",\n description: \"County name + state per company\",\n parameters: {\n type: \"object\",\n properties: {\n items: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n slug: { type: \"string\" },\n county: { type: [\"string\", \"null\"], description: 'county name WITHOUT the word \"County\", e.g. \"Santa Clara\"' },\n state: { type: [\"string\", \"null\"], description: \"2-letter USPS state\" },\n },\n required: [\"slug\", \"county\", \"state\"],\n },\n },\n },\n required: [\"items\"],\n },\n};\n\n/** The user prompt for a LOCATE batch. */\nexport function locatePrompt(batch: CompanyRow[]): string {\n return \"Companies:\\n\" + batch.map((c) => `- slug ${c.slug}: city=${c.city ?? \"?\"} region=${c.region ?? \"?\"}`).join(\"\\n\");\n}\n\n// ------------------------------------------------------------------- facts\n\n/** System prompt for the grounded web-search research pass. */\nexport const FACTS_SEARCH_SYSTEM =\n \"You are a research assistant with web search. Find concrete facts about the company: founding year/date, headquarters city and state, founders and key executives (names, roles), and funding (rounds, dates, amounts, investor names). Prefer official/reputable sources. Report dense factual notes; say 'not found' for anything you cannot establish. Do not speculate.\";\n\n/** System prompt for structuring the research notes into {@link FactsDraft}. */\nexport const FACTS_EXTRACT_SYSTEM =\n \"Extract structured facts from the research notes. Use null for anything the notes do not establish.\";\n\n/** Forced-tool schema for the structured company facts (founding, HQ, people, funding). */\nexport const FACTS_EXTRACT_TOOL: ExtractTool = {\n name: \"company_facts\",\n description: \"Structured company facts from research notes\",\n parameters: {\n type: \"object\",\n properties: {\n founded: { type: [\"string\", \"null\"], description: \"YYYY or YYYY-MM or YYYY-MM-DD\" },\n city: { type: [\"string\", \"null\"] },\n state: { type: [\"string\", \"null\"], description: \"2-letter USPS\" },\n people: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: { name: { type: \"string\" }, role: { type: \"string\" } },\n required: [\"name\", \"role\"],\n },\n },\n rounds: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n round: { type: [\"string\", \"null\"] },\n date: { type: [\"string\", \"null\"] },\n amount: { type: [\"string\", \"null\"] },\n investors: { type: \"array\", items: { type: \"string\" } },\n },\n required: [\"investors\"],\n },\n },\n },\n required: [\"people\", \"rounds\"],\n },\n};\n\n/** The model's structured company facts, before validation into a fragment. */\nexport interface FactsDraft {\n founded?: string | null;\n city?: string | null;\n state?: string | null;\n people: Array<{ name: string; role: string }>;\n rounds: Array<{ round?: string | null; date?: string | null; amount?: string | null; investors: string[] }>;\n}\n\nconst DATE_RE = /^\\d{4}(-\\d{2}){0,2}$/;\n\n/** Pad a year/month-precision date to a full ISO date (odla-db date attrs reject bare years). */\nconst isoDate = (v: string): string | null =>\n !DATE_RE.test(v) ? null : v.length === 4 ? `${v}-01-01` : v.length === 7 ? `${v}-01` : v;\n\n/** Loose county-name key for joining model output (\"Santa Clara\",\"CA\") to a gazetteer. */\nexport const countyKey = (name: string, st: string): string =>\n `${String(name).toLowerCase().replace(/\\bcounty\\b|\\bparish\\b|\\bborough\\b/g, \"\").replace(/[^a-z]/g, \"\")}|${String(st).toUpperCase()}`;\n\n/**\n * Turn one company's structured facts into an idempotent fragment: founded/\n * city attrs, person + dated affiliation nodes, funding_round nodes (keyed\n * `slug~e<i>` so they never collide with seed rounds), and investors. Capped\n * and validated — the draft is a proposal, not a command.\n */\nexport function factsToFragment(company: CompanyRow, draft: FactsDraft): GraphFragment {\n const nodes: GraphNode[] = [];\n const edges: GraphFragment[\"edges\"] = [];\n const attrs: Record<string, string> = {};\n const founded = draft.founded ? isoDate(draft.founded) : null;\n if (founded) attrs.founded = founded;\n if (draft.city && !company.city) attrs.city = String(draft.city).slice(0, 60);\n if (Object.keys(attrs).length > 0) {\n nodes.push({ type: \"company\", key: company.slug, attrs: { name: company.name, ...attrs } });\n }\n\n const anchorDate = attrs.founded ?? company.founded ?? company.first_seen ?? \"\";\n for (const p of (draft.people ?? []).slice(0, 5)) {\n const personSlug = slugify(p.name);\n if (!personSlug || personSlug === company.slug) continue;\n nodes.push({ type: \"person\", key: personSlug, attrs: { name: p.name } });\n const key = `${personSlug}~${company.slug}`;\n nodes.push({ type: \"affiliation\", key, attrs: { date: anchorDate, role: String(p.role).slice(0, 60) } });\n edges.push({ from: { type: \"affiliation\", key }, to: { type: \"person\", key: personSlug }, label: \"of_person\" });\n edges.push({ from: { type: \"affiliation\", key }, to: { type: \"company\", key: company.slug }, label: \"of_company\" });\n }\n\n (draft.rounds ?? []).slice(0, 4).forEach((r, i) => {\n const investors = (r.investors ?? []).map(slugify).filter(Boolean).slice(0, 6);\n const date = r.date ? isoDate(r.date) : null;\n if (investors.length === 0 && !date) return; // an undated, investor-less round says nothing\n const key = `${company.slug}~e${i}`;\n const rAttrs: Record<string, string> = { date: date ?? anchorDate };\n if (r.round) rAttrs.round = String(r.round).slice(0, 40);\n if (r.amount) rAttrs.amount = String(r.amount).slice(0, 40);\n nodes.push({ type: \"funding_round\", key, attrs: rAttrs });\n edges.push({ from: { type: \"funding_round\", key }, to: { type: \"company\", key: company.slug }, label: \"round_of\" });\n for (const [j, invSlug] of investors.entries()) {\n nodes.push({ type: \"investor\", key: invSlug, attrs: { name: (r.investors ?? [])[j]! } });\n edges.push({ from: { type: \"funding_round\", key }, to: { type: \"investor\", key: invSlug }, label: \"raised_from\" });\n }\n });\n\n return { nodes, edges };\n}\n\n/** The `located_in` write linking a company to its resolved county. */\nexport function locationFragment(slug: string, name: string, fips: string): GraphFragment {\n return {\n nodes: [{ type: \"company\", key: slug, attrs: { name, fips } }],\n edges: [{ from: { type: \"company\", key: slug }, to: { type: \"county\", key: fips }, label: \"located_in\" }],\n };\n}\n","// Verification verdicts: the vocabulary for \"did we actually find this entity?\".\n// Enrichment without one is unfalsifiable — a fragment either got written or it\n// didn't, and nothing recorded whether the model was sure, guessing, or looking\n// at a different person with the same name. This module is pure: the verdict is\n// a claim to be scored, and everything here validates it rather than trusting it.\n\n/**\n * What a research pass concluded about the entity it was asked to find.\n *\n * `wrong_entity` is deliberately distinct from `not_found`: a pass that\n * confidently describes a *different* person with the same name is a much worse\n * failure than one that admits it found nothing, and a benchmark that collapses\n * the two cannot see it.\n */\nexport type Verdict = \"confirmed\" | \"probable\" | \"ambiguous\" | \"not_found\" | \"wrong_entity\";\n\n/** Verdicts that permit writing enriched facts to the graph. */\nexport const WRITABLE_VERDICTS: readonly Verdict[] = [\"confirmed\", \"probable\"];\n\nconst ALL: readonly Verdict[] = [\"confirmed\", \"probable\", \"ambiguous\", \"not_found\", \"wrong_entity\"];\n\n/** One supporting claim tied to the source that backs it. */\nexport interface Evidence {\n /** What the source establishes, in the model's words. */\n claim: string;\n /** The URL the claim came from. */\n sourceUrl: string;\n /** When the source was read, as an ISO date. */\n retrievedAt?: string;\n}\n\n/** A verdict plus the confidence and evidence offered for it. */\nexport interface Verification {\n verdict: Verdict;\n /** The model's stated confidence, clamped to 0..1. Scored for calibration, never trusted. */\n confidence: number;\n evidence: Evidence[];\n}\n\n/** Whether a value is one of the closed verdict members. */\nexport function isVerdict(value: unknown): value is Verdict {\n return typeof value === \"string\" && (ALL as readonly string[]).includes(value);\n}\n\n/** Whether a verdict permits writing the enriched facts it came with. */\nexport function isWritable(verdict: Verdict): boolean {\n return WRITABLE_VERDICTS.includes(verdict);\n}\n\n/**\n * Normalize a model's self-reported verification into the closed shape.\n *\n * Fails safe in both directions: an unrecognized verdict becomes `ambiguous`\n * (never `confirmed`), a missing or out-of-range confidence is clamped, and\n * evidence without a usable http(s) source URL is dropped — an unsourced claim\n * is exactly the thing this package exists to stop treating as a fact.\n */\nexport function normalizeVerification(draft: unknown, retrievedAt?: string): Verification {\n const d = (draft ?? {}) as Record<string, unknown>;\n const verdict = isVerdict(d.verdict) ? d.verdict : \"ambiguous\";\n const raw = typeof d.confidence === \"number\" && Number.isFinite(d.confidence) ? d.confidence : 0;\n const confidence = Math.min(1, Math.max(0, raw));\n const evidence: Evidence[] = [];\n for (const item of Array.isArray(d.evidence) ? d.evidence.slice(0, 12) : []) {\n const e = (item ?? {}) as Record<string, unknown>;\n const sourceUrl = typeof e.sourceUrl === \"string\" ? e.sourceUrl.trim() : \"\";\n const claim = typeof e.claim === \"string\" ? e.claim.trim() : \"\";\n if (!claim || !isHttpUrl(sourceUrl)) continue;\n evidence.push({ claim: claim.slice(0, 300), sourceUrl, ...(retrievedAt ? { retrievedAt } : {}) });\n }\n // A confirmation nobody can check is not a confirmation.\n if (evidence.length === 0 && isWritable(verdict)) return { verdict: \"ambiguous\", confidence, evidence };\n return { verdict, confidence, evidence };\n}\n\n/** Whether a string is an absolute http(s) URL — the only kind evidence may cite. */\nexport function isHttpUrl(value: string): boolean {\n try {\n const u = new URL(value);\n return u.protocol === \"https:\" || u.protocol === \"http:\";\n } catch {\n return false;\n }\n}\n","// Person enrichment: prompts, the forced-tool schema, and the pure builder that\n// turns a research pass into a verdict plus an idempotent GraphFragment.\n//\n// Scope is deliberate. These are real named individuals, so the pass looks only\n// for a *public professional* footprint and contact points the person or their\n// employer published — never a home address, personal phone, or personal email.\n// Everything the model returns is a proposal: validated, capped, and gated on\n// the verdict before any of it becomes graph.\nimport type { ExtractTool } from \"@odla-ai/ai\";\nimport { identityKey, slugify } from \"../util/identity\";\nimport type { GraphFragment, GraphNode } from \"../types\";\nimport { isWritable, normalizeVerification, type Verification } from \"./verify\";\n\n/** The minimal person row enrichment reads. */\nexport interface PersonRow {\n slug: string;\n name: string;\n /** Natural key of a company the person is already linked to — the strongest disambiguator. */\n companySlug?: string;\n companyName?: string;\n role?: string;\n city?: string;\n region?: string;\n}\n\n/** Where a person publishes. Kinds with one canonical host are validated against it. */\nexport type PersonLinkKind = \"website\" | \"blog\" | \"substack\" | \"linkedin\" | \"x\" | \"bluesky\" | \"github\" | \"other\";\n\nconst LINK_KINDS: readonly PersonLinkKind[] = [\"website\", \"blog\", \"substack\", \"linkedin\", \"x\", \"bluesky\", \"github\", \"other\"];\n\n/** Kinds whose URL must live on a known host — the ones models most often invent. */\nconst CANONICAL_HOSTS: Partial<Record<PersonLinkKind, readonly string[]>> = {\n linkedin: [\"linkedin.com\"],\n github: [\"github.com\"],\n x: [\"x.com\", \"twitter.com\"],\n bluesky: [\"bsky.app\"],\n};\n\n/** Hosts that only ever appear in a model's imagination. */\nconst PLACEHOLDER_HOSTS = [\"example.com\", \"example.org\", \"example.net\", \"yourdomain.com\", \"domain.com\"];\n\n/** System prompt for the grounded person research pass. */\nexport const PERSON_SEARCH_SYSTEM =\n \"You are a research assistant with web search, confirming the identity of a specific named person and finding their PUBLIC PROFESSIONAL presence. \" +\n \"Look for: a personal website, blog, or Substack; LinkedIn, X/Bluesky, or GitHub profiles; their current employer and role; and a business email or phone that they or their employer have published openly (a company contact page, a conference bio). \" +\n \"Do NOT look for or report home addresses, personal phone numbers, or personal email addresses. \" +\n \"Also report disambiguating facts: city or region, alma mater, and other companies named alongside them. \" +\n \"Names are not unique. If the results describe more than one person with this name and you cannot tell which is meant, say so explicitly. If you find a different person with the same name, say that too. \" +\n \"Cite the URL for every claim. Never guess a profile URL from a name pattern — report only URLs you actually saw. If you found nothing, say so plainly.\";\n\n/** System prompt for structuring the research notes into {@link PersonDraft}. */\nexport const PERSON_EXTRACT_SYSTEM =\n \"Extract the structured profile from the research notes. Use null for anything the notes do not establish. \" +\n \"Set verdict to 'confirmed' only when the notes identify this exact person beyond doubt; 'probable' when the evidence is strong but circumstantial; \" +\n \"'ambiguous' when several same-named people cannot be told apart; 'not_found' when nothing was established; 'wrong_entity' when the notes describe a different person with the same name. \" +\n \"Every claim in evidence must carry the source URL it came from.\";\n\n/** Forced-tool schema for a person's verified public profile. */\nexport const PERSON_EXTRACT_TOOL: ExtractTool = {\n name: \"person_profile\",\n description: \"Verified identity plus public professional profile for one person\",\n parameters: {\n type: \"object\",\n properties: {\n verdict: { type: \"string\", enum: [\"confirmed\", \"probable\", \"ambiguous\", \"not_found\", \"wrong_entity\"] },\n confidence: { type: \"number\", description: \"0..1 — how sure you are this is the right person\" },\n links: {\n type: \"array\",\n description: \"Only URLs actually seen in the results; never a guessed profile pattern.\",\n items: {\n type: \"object\",\n properties: {\n kind: { type: \"string\", enum: [...LINK_KINDS] },\n url: { type: \"string\" },\n },\n required: [\"kind\", \"url\"],\n },\n },\n employer: { type: [\"string\", \"null\"], description: \"Current employer name\" },\n role: { type: [\"string\", \"null\"], description: \"Current title at that employer\" },\n businessEmail: { type: [\"string\", \"null\"], description: \"Publicly published work email only\" },\n businessPhone: { type: [\"string\", \"null\"], description: \"Publicly published work phone only\" },\n city: { type: [\"string\", \"null\"] },\n region: { type: [\"string\", \"null\"], description: \"State, province, or country\" },\n almaMater: { type: [\"string\", \"null\"] },\n otherCompanies: { type: \"array\", items: { type: \"string\" }, description: \"Companies named alongside them\" },\n evidence: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: { claim: { type: \"string\" }, sourceUrl: { type: \"string\" } },\n required: [\"claim\", \"sourceUrl\"],\n },\n },\n },\n required: [\"verdict\", \"confidence\", \"links\", \"evidence\"],\n },\n};\n\n/** The model's structured person profile, before validation into a fragment. */\nexport interface PersonDraft {\n verdict?: string;\n confidence?: number;\n links?: Array<{ kind?: string; url?: string }>;\n employer?: string | null;\n role?: string | null;\n businessEmail?: string | null;\n businessPhone?: string | null;\n city?: string | null;\n region?: string | null;\n almaMater?: string | null;\n otherCompanies?: string[];\n evidence?: Array<{ claim?: string; sourceUrl?: string }>;\n}\n\n/** A validated person link. */\nexport interface PersonLink {\n kind: PersonLinkKind;\n url: string;\n}\n\n/** The validated result of one person enrichment pass. */\nexport interface PersonResult {\n verification: Verification;\n links: PersonLink[];\n fragment: GraphFragment;\n}\n\n/** The user prompt for a person research pass. */\nexport function personPrompt(person: PersonRow): string {\n const at = person.companyName ? ` (${person.role ? `${person.role}, ` : \"\"}${person.companyName})` : \"\";\n const where = [person.city, person.region].filter(Boolean).join(\", \");\n return `Person: ${person.name}${at}${where ? `\\nKnown location: ${where}` : \"\"}\\n\\nConfirm this is a real, identifiable person and find their public professional presence.`;\n}\n\n/**\n * Keep only links that are plausibly real: absolute https URLs, on the canonical\n * host when the kind has one, and never a placeholder domain. Models invent\n * `linkedin.com/in/first-last` from a name constantly; a kind/host mismatch is\n * the cheapest way to catch it before it becomes a stored \"fact\".\n */\nexport function validateLinks(raw: PersonDraft[\"links\"], limit = 8): PersonLink[] {\n const out: PersonLink[] = [];\n const seen = new Set<string>();\n for (const item of (raw ?? []).slice(0, 24)) {\n const kind = (LINK_KINDS as readonly string[]).includes(String(item?.kind)) ? (item!.kind as PersonLinkKind) : \"other\";\n const url = String(item?.url ?? \"\").trim();\n const parsed = httpsUrl(url);\n if (!parsed) continue;\n const host = parsed.hostname.toLowerCase().replace(/^www\\./, \"\");\n if (PLACEHOLDER_HOSTS.includes(host)) continue;\n const required = CANONICAL_HOSTS[kind];\n if (required && !required.some((h) => host === h || host.endsWith(`.${h}`))) continue;\n if (seen.has(url)) continue;\n seen.add(url);\n out.push({ kind, url });\n if (out.length >= limit) break;\n }\n return out;\n}\n\n/**\n * Turn one person's structured draft into a verdict plus an idempotent fragment.\n *\n * The fragment is empty unless the verdict is writable — an `ambiguous`,\n * `not_found`, or `wrong_entity` pass contributes no graph, however confident\n * the model sounded. The verdict and its evidence are still returned, because\n * \"this lane confidently found the wrong person\" is exactly what a benchmark\n * needs to record.\n */\nexport function personToFragment(person: PersonRow, draft: PersonDraft, retrievedAt?: string): PersonResult {\n const verification = normalizeVerification(draft, retrievedAt);\n const links = validateLinks(draft.links);\n if (!isWritable(verification.verdict)) return { verification, links, fragment: { nodes: [], edges: [] } };\n\n const attrs: Record<string, string> = { name: person.name };\n for (const link of links) if (!attrs[link.kind]) attrs[link.kind] = link.url;\n const city = str(draft.city, 60);\n if (city && !person.city) attrs.city = city;\n const region = str(draft.region, 60);\n if (region && !person.region) attrs.region = region;\n const email = str(draft.businessEmail, 120);\n if (email && /^[^@\\s]+@[^@\\s.]+\\.[^@\\s]+$/.test(email)) attrs.businessEmail = email;\n const phone = str(draft.businessPhone, 40);\n if (phone) attrs.businessPhone = phone;\n const almaMater = str(draft.almaMater, 120);\n if (almaMater) attrs.almaMater = almaMater;\n\n const nodes: GraphNode[] = [{ type: \"person\", key: person.slug, attrs }];\n const edges: GraphFragment[\"edges\"] = [];\n // Only an employer the research established gets written — reasserting the\n // seed row's own company would be echoing the input back as a finding.\n const employer = str(draft.employer, 120);\n // Reuse the known company's key only when it is the *same* company. A pass\n // that found a different employer must not be filed under the seed's slug.\n const known = person.companySlug && person.companyName && identityKey(person.companyName) === identityKey(employer);\n const companySlug = employer ? (known ? person.companySlug! : slugify(employer)) : \"\";\n if (employer && companySlug) {\n const key = `${person.slug}~${companySlug}`;\n nodes.push({ type: \"company\", key: companySlug, attrs: { name: known ? person.companyName! : employer } });\n nodes.push({ type: \"affiliation\", key, attrs: { role: str(draft.role, 60) || person.role || \"\" } });\n edges.push({ from: { type: \"affiliation\", key }, to: { type: \"person\", key: person.slug }, label: \"of_person\" });\n edges.push({ from: { type: \"affiliation\", key }, to: { type: \"company\", key: companySlug }, label: \"of_company\" });\n }\n return { verification, links, fragment: { nodes, edges } };\n}\n\nfunction str(value: unknown, max: number): string {\n return typeof value === \"string\" ? value.trim().slice(0, max) : \"\";\n}\n\n/** Parse a link, accepting https only — a profile served over plaintext is not one we store. */\nfunction httpsUrl(value: string): URL | null {\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" ? url : null;\n } catch {\n return null;\n }\n}\n","// The enrichment runners: Env-agnostic functions that take an @odla-ai/ai\n// `Ai` (bring-your-own provider/keys — the caller decides which lane, e.g. a\n// cheap vaulted key vs the platform provider) and produce graph fragments.\n// Callers persist the fragments (via writeFragment) and own batching,\n// concurrency, and cooldowns.\nimport type { Ai, SearchCitation } from \"@odla-ai/ai\";\nimport type { GraphFragment } from \"../types\";\nimport {\n PERSON_EXTRACT_SYSTEM,\n PERSON_EXTRACT_TOOL,\n PERSON_SEARCH_SYSTEM,\n personPrompt,\n personToFragment,\n type PersonDraft,\n type PersonResult,\n type PersonRow,\n} from \"./person\";\nimport {\n FACTS_EXTRACT_SYSTEM,\n FACTS_EXTRACT_TOOL,\n FACTS_SEARCH_SYSTEM,\n LOCATE_SYSTEM,\n LOCATE_TOOL,\n countyKey,\n factsToFragment,\n locatePrompt,\n type CompanyRow,\n type FactsDraft,\n} from \"./facts\";\n\n/** Which model serves each verb (search must be a web-grounded model). */\nexport interface EnrichModels {\n /** Web-search-grounded research model. */\n search: string;\n /** Structured-extraction (forced tool-call) model. */\n extract: string;\n}\n\n/**\n * Enrich one company: a grounded web search for its facts, then a structured\n * extraction into a validated {@link factsToFragment}. Retries the extraction\n * once (reasoning models sometimes skip a forced tool call). Returns the\n * fragment (possibly empty — nothing established); the caller writes it.\n */\nexport async function enrichCompany(ai: Ai, models: EnrichModels, company: CompanyRow): Promise<GraphFragment> {\n const where = `${company.city ? `, ${company.city}` : \"\"}${company.region ? ` (${company.region})` : \"\"}`;\n const { text } = await ai.search({\n model: models.search,\n system: FACTS_SEARCH_SYSTEM,\n user: `Company: ${company.name}${where}. Research its founding, headquarters, key people, and funding.`,\n maxTokens: 900,\n });\n const extractOnce = () =>\n ai.extract<FactsDraft>({\n model: models.extract,\n system: FACTS_EXTRACT_SYSTEM,\n user: `Company: ${company.name}\\n\\nNotes:\\n${text}`,\n tool: FACTS_EXTRACT_TOOL,\n maxTokens: 4000, // reasoning headroom before the forced tool call\n });\n const { value } = await extractOnce().catch(extractOnce);\n return factsToFragment(company, value);\n}\n\n/** One person enrichment pass, with the sources the provider actually cited. */\nexport interface PersonEnrichment extends PersonResult {\n /** Sources the search model attributed. Empty when the provider grounded nothing. */\n citations: SearchCitation[];\n /** The research prose the extraction was taken from — useful for adjudication. */\n notes: string;\n}\n\n/**\n * Enrich one person: a grounded web search for their public professional\n * presence, then a structured extraction into a validated verdict + fragment.\n *\n * Unlike {@link enrichCompany}, this always returns a verdict — including\n * `not_found` and `wrong_entity` — because \"the lane confidently identified the\n * wrong person\" is a result worth recording, not an error. The fragment is empty\n * unless the verdict permits writing.\n */\nexport async function enrichPerson(ai: Ai, models: EnrichModels, person: PersonRow, retrievedAt?: string): Promise<PersonEnrichment> {\n const { text, citations } = await ai.search({\n model: models.search,\n system: PERSON_SEARCH_SYSTEM,\n user: personPrompt(person),\n maxTokens: 1200,\n });\n const extractOnce = () =>\n ai.extract<PersonDraft>({\n model: models.extract,\n system: PERSON_EXTRACT_SYSTEM,\n user: `Person: ${person.name}\\n\\nNotes:\\n${text}`,\n tool: PERSON_EXTRACT_TOOL,\n maxTokens: 4000, // reasoning headroom before the forced tool call\n });\n const { value } = await extractOnce().catch(extractOnce);\n return { ...personToFragment(person, value, retrievedAt), citations, notes: text };\n}\n\n/**\n * Resolve a batch of companies' free-text locations to county FIPS codes via a\n * single structured extraction, joined against a caller-supplied gazetteer\n * (`countyKey(name, state)` → FIPS). Only confident, gazetteer-matched\n * resolutions are returned.\n */\nexport async function resolveCounties(\n ai: Ai,\n model: string,\n rows: CompanyRow[],\n gazetteer: Map<string, string>,\n): Promise<Array<{ slug: string; fips: string }>> {\n const { value } = await ai.extract<{ items: Array<{ slug: string; county: string | null; state: string | null }> }>({\n model,\n system: LOCATE_SYSTEM,\n user: locatePrompt(rows),\n tool: LOCATE_TOOL,\n maxTokens: 6000, // reasoning models spend tokens before the tool call\n });\n const out: Array<{ slug: string; fips: string }> = [];\n for (const item of value.items ?? []) {\n const fips = item.county && item.state ? gazetteer.get(countyKey(item.county, item.state)) : undefined;\n if (fips) out.push({ slug: item.slug, fips });\n }\n return out;\n}\n","import { init, extractText, type Ai } from \"@odla-ai/ai\";\nimport type { Usage } from \"../types\";\n\n/**\n * The LLM seam, now a thin adapter over @odla-ai/ai (which was itself\n * generalized from this package's original Provider). Three verbs stay:\n * extract (forced structured tool call), search (web-search-enabled text),\n * write (plain generation). Callers never see vendor or oracle types.\n */\n\nexport type ProviderFamily = \"claude\" | \"openai\";\n\n/** Role → model tiers per family — config as data. `cheap` is for gate-style\n * calls, `extract` for structured extraction (and prose writing), `search`\n * for web-search-enabled calls. */\nexport const MODEL_TIERS: Record<ProviderFamily, { cheap: string; extract: string; search: string }> = {\n claude: { cheap: \"claude-haiku-4-5\", extract: \"claude-sonnet-5\", search: \"claude-sonnet-5\" },\n openai: { cheap: \"gpt-5-nano\", extract: \"gpt-5-mini\", search: \"gpt-5-search-api\" },\n};\n\n/** A forced structured tool call: schema in, validated object out. */\nexport interface ExtractCall {\n system: string;\n user: string;\n tool: { name: string; description?: string; parameters: Record<string, unknown> };\n model?: string;\n maxTokens?: number;\n}\n\n/** A web-search-enabled call: the model searches and returns text (caller tolerant-parses). */\nexport interface SearchCall {\n system: string;\n user: string;\n model?: string;\n maxTokens?: number;\n}\n\n/** A plain text-generation call (no tools, no search) — e.g. digest prose. */\nexport interface WriteCall {\n system: string;\n user: string;\n model?: string;\n maxTokens?: number;\n}\n\nexport interface Provider {\n id: ProviderFamily;\n /** The underlying @odla-ai/ai client — escape hatch for advanced use. */\n ai: Ai;\n extract<T = unknown>(c: ExtractCall): Promise<{ value: T; usage: Usage }>;\n search(c: SearchCall): Promise<{ text: string; usage: Usage }>;\n write(c: WriteCall): Promise<{ text: string; usage: Usage }>;\n}\n\nexport interface LlmConfig {\n provider: ProviderFamily;\n anthropicKey?: string;\n openaiKey?: string;\n /** Inject a pre-built Ai (tests, custom catalog); keys are ignored when set. */\n ai?: Ai;\n}\n\nfunction toUsage(u: { inputTokens: number; outputTokens: number }): Usage {\n return { calls: 1, inputTokens: u.inputTokens, outputTokens: u.outputTokens };\n}\n\n/** The library primitive: an explicit config in, the three-verb seam out. */\nexport function createProvider(cfg: LlmConfig): Provider {\n const tiers = MODEL_TIERS[cfg.provider];\n const ai = cfg.ai ?? init({ keys: { anthropic: cfg.anthropicKey, openai: cfg.openaiKey } });\n return {\n id: cfg.provider,\n ai,\n async extract<T = unknown>(c: ExtractCall) {\n const { value, usage } = await ai.extract<T>({\n model: c.model ?? tiers.extract,\n system: c.system,\n user: c.user,\n tool: c.tool,\n maxTokens: c.maxTokens,\n });\n return { value, usage: toUsage(usage) };\n },\n async search(c: SearchCall) {\n const { text, usage } = await ai.search({\n model: c.model ?? tiers.search,\n system: c.system,\n user: c.user,\n maxTokens: c.maxTokens,\n });\n return { text, usage: toUsage(usage) };\n },\n async write(c: WriteCall) {\n const res = await ai.chat({\n model: c.model ?? tiers.extract,\n system: c.system,\n messages: [{ role: \"user\", content: c.user }],\n maxTokens: c.maxTokens ?? 2048,\n });\n return { text: extractText(res.content), usage: toUsage(res.usage) };\n },\n };\n}\n\n/** The env vars the provider factory reads — a structural slice, not the\n * worker Env, so any host (worker, script, test) can supply plain strings. */\nexport interface LlmEnv {\n LLM_PROVIDER?: string; // \"claude\" | \"openai\"\n ANTHROPIC_API_KEY?: string;\n OPENAI_API_KEY?: string;\n}\n\n/** Build the configured provider from host env, failing before use when its key is missing. */\nexport function getProvider(env: LlmEnv): Provider {\n const which: ProviderFamily = (env.LLM_PROVIDER || \"claude\").toLowerCase() === \"openai\" ? \"openai\" : \"claude\";\n if (which === \"openai\" && !env.OPENAI_API_KEY) throw new Error(\"LLM_PROVIDER=openai but OPENAI_API_KEY is unset\");\n if (which === \"claude\" && !env.ANTHROPIC_API_KEY) throw new Error(\"LLM_PROVIDER=claude but ANTHROPIC_API_KEY is unset\");\n return createProvider({ provider: which, anthropicKey: env.ANTHROPIC_API_KEY, openaiKey: env.OPENAI_API_KEY });\n}\n","/**\n * Tolerant JSON extraction from LLM prose — web-search models return JSON inside\n * explanatory text. Ported from agnacl services/enrichment.ts.\n */\n\nexport function extractJson<T = unknown>(text: string): T | null {\n const m = text.match(/\\{[\\s\\S]*\\}/);\n if (!m) return null;\n try {\n return JSON.parse(m[0]) as T;\n } catch {\n return null;\n }\n}\n\n/** Tolerantly parse the first JSON-array-shaped span in model prose, or return an empty array. */\nexport function extractJsonArray<T = unknown>(text: string): T[] {\n const m = text.match(/\\[[\\s\\S]*\\]/);\n if (!m) return [];\n try {\n const v = JSON.parse(m[0]);\n return Array.isArray(v) ? (v as T[]) : [];\n } catch {\n return [];\n }\n}\n","/**\n * Dependency-free HTML → text extraction (Workers-isolate friendly: no DOM/jsdom).\n * Ported from agnacl services/content-extractor.ts.\n */\n\nconst MAX_CHARS = 8000;\n\n/** Strip scripts/styles/tags and collapse whitespace; cap length. */\nexport function htmlToText(html: string, maxChars = MAX_CHARS): string {\n let t = html;\n t = t.replace(/<script[\\s\\S]*?<\\/script>/gi, \" \");\n t = t.replace(/<style[\\s\\S]*?<\\/style>/gi, \" \");\n t = t.replace(/<!--[\\s\\S]*?-->/g, \" \");\n t = t.replace(/<\\/(p|div|section|article|li|h[1-6]|br)>/gi, \"\\n\");\n t = t.replace(/<[^>]+>/g, \" \");\n t = decodeEntities(t);\n t = t.replace(/[ \\t\\f\\r]+/g, \" \").replace(/\\n\\s*\\n\\s*/g, \"\\n\\n\").trim();\n return t.slice(0, maxChars);\n}\n\nfunction decodeEntities(s: string): string {\n return s\n .replace(/ /gi, \" \")\n .replace(/&/gi, \"&\")\n .replace(/</gi, \"<\")\n .replace(/>/gi, \">\")\n .replace(/"/gi, '\"')\n .replace(/'|'/gi, \"'\")\n .replace(/&#(\\d+);/g, (_, d) => String.fromCharCode(Number(d)))\n .replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCharCode(parseInt(h, 16)));\n}\n\n/** Fetch a URL and return readable text. Best-effort: returns \"\" on any failure. */\nexport async function fetchText(url: string, maxChars = MAX_CHARS): Promise<string> {\n try {\n const res = await fetch(url, {\n headers: {\n \"user-agent\": \"odla-kg/0.1 (+https://github.com/cory/odla-kg)\",\n accept: \"text/html,application/xhtml+xml\",\n },\n });\n if (!res.ok) return \"\";\n const ct = res.headers.get(\"content-type\") || \"\";\n const body = await res.text();\n if (ct.includes(\"html\") || /<\\w+[\\s>]/.test(body.slice(0, 200))) {\n return htmlToText(body, maxChars);\n }\n return body.slice(0, maxChars);\n } catch {\n return \"\";\n }\n}\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\nimport { htmlToText } from \"../util/content\";\n\n/** Dependency-free RSS/Atom parsing, ported from agnacl services/rss.ts. */\nconst UA = \"Mozilla/5.0 (compatible; odla-kg/0.1)\";\n\nfunction extractTag(xml: string, tag: string): string {\n const cdata = xml.match(new RegExp(`<${tag}[^>]*>\\\\s*<!\\\\[CDATA\\\\[([\\\\s\\\\S]*?)\\\\]\\\\]>\\\\s*</${tag}>`, \"i\"));\n if (cdata) return cdata[1]!.trim();\n const m = xml.match(new RegExp(`<${tag}[^>]*>([\\\\s\\\\S]*?)</${tag}>`, \"i\"));\n return m ? m[1]!.trim() : \"\";\n}\n\nfunction extractAttr(xml: string, tag: string, attr: string): string {\n const m = xml.match(new RegExp(`<${tag}[^>]*\\\\s${attr}=[\"']([^\"']*)[\"']`, \"i\"));\n return m ? m[1]!.trim() : \"\";\n}\n\nfunction splitItems(xml: string): string[] {\n return xml.match(/<item[\\s>][\\s\\S]*?<\\/item>/gi) || xml.match(/<entry[\\s>][\\s\\S]*?<\\/entry>/gi) || [];\n}\n\ninterface RssCfg {\n url: string;\n name: string;\n watch?: string | null;\n}\n\nexport const rssConnector: SourceConnector<RssCfg> = {\n id: \"rss\",\n capabilities: { fetch: true, structured: false },\n\n parseConfig(raw): RssCfg {\n const r = raw as Record<string, unknown>;\n if (typeof r.url !== \"string\" || !r.url) throw new Error(\"rss connector requires a `url`\");\n return { url: r.url, name: String(r.name ?? r.url), watch: (r.watch as string | null) ?? null };\n },\n\n async fetch(cfg, ctx): Promise<RawItem[]> {\n let xml: string;\n try {\n const res = await fetch(cfg.url, { headers: { \"User-Agent\": UA } });\n if (!res.ok) {\n if (res.status === 429 || res.status === 403 || res.status >= 500) return [];\n throw new Error(`feed ${cfg.url} → ${res.status}`);\n }\n xml = await res.text();\n } catch (e) {\n console.log(`rss fetch failed (non-blocking): ${cfg.url}: ${e}`);\n return [];\n }\n\n const out: RawItem[] = [];\n for (const raw of splitItems(xml)) {\n const title = extractTag(raw, \"title\");\n let url = extractTag(raw, \"link\") || extractAttr(raw, \"link\", \"href\");\n if (!title || !url) continue;\n url = url.trim();\n const date =\n extractTag(raw, \"pubDate\") || extractTag(raw, \"published\") || extractTag(raw, \"updated\") || undefined;\n const content =\n extractTag(raw, \"content:encoded\") ||\n extractTag(raw, \"content\") ||\n extractTag(raw, \"description\") ||\n extractTag(raw, \"summary\") ||\n \"\";\n let text = htmlToText(content);\n // Thin feed body → fetch the article page.\n if (text.length < 200) {\n const page = await ctx.fetchText(url);\n if (page.length > text.length) text = page;\n }\n out.push({ url, title, date, sourceName: cfg.name, sourceKind: \"rss\", watch: cfg.watch, text });\n }\n return out;\n },\n};\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector } from \"./types\";\nimport { fetchText } from \"../util/content\";\n\n/** Fetch one URL → a text-bearing RawItem. Used both directly (/trigger) and as a connector. */\nexport async function fetchUrl(url: string): Promise<RawItem> {\n const text = await fetchText(url);\n const firstLine = text.split(\"\\n\").find((l) => l.trim().length > 0) ?? \"\";\n let host = url;\n try {\n host = new URL(url).hostname;\n } catch {\n /* keep raw url as the source label */\n }\n return { url, title: (firstLine || url).slice(0, 200).trim(), sourceName: host, sourceKind: \"url\", text };\n}\n\ninterface UrlCfg {\n url: string;\n watch?: string | null;\n}\n\nexport const urlConnector: SourceConnector<UrlCfg> = {\n id: \"url\",\n capabilities: { fetch: true, structured: false },\n parseConfig(raw): UrlCfg {\n const r = raw as Record<string, unknown>;\n if (typeof r.url !== \"string\" || !r.url) throw new Error(\"url connector requires a `url`\");\n return { url: r.url, watch: (r.watch as string | null) ?? null };\n },\n async fetch(cfg): Promise<RawItem[]> {\n const item = await fetchUrl(cfg.url);\n if (cfg.watch !== undefined) item.watch = cfg.watch;\n return [item];\n },\n};\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\n\n/** GDELT DOC 2.0 standing-query connector (text). Ported from agnacl services/gdelt.ts. */\nconst UA = \"Mozilla/5.0 (compatible; odla-kg/0.1)\";\nconst MAX_ITEMS = 15;\n\ninterface GdeltCfg {\n query: string;\n name: string;\n watch?: string | null;\n}\n\nfunction isoFromSeendate(s: string): string | undefined {\n const m = s.match(/^(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})Z?$/);\n return m ? `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}Z` : undefined;\n}\n\nexport const gdeltConnector: SourceConnector<GdeltCfg> = {\n id: \"gdelt\",\n capabilities: { fetch: true, structured: false },\n\n parseConfig(raw): GdeltCfg {\n const r = raw as Record<string, unknown>;\n if (typeof r.query !== \"string\" || !r.query) throw new Error(\"gdelt connector requires a `query`\");\n return { query: r.query, name: String(r.name ?? `GDELT: ${r.query}`), watch: (r.watch as string | null) ?? null };\n },\n\n async fetch(cfg, ctx): Promise<RawItem[]> {\n const api =\n `https://api.gdeltproject.org/api/v2/doc/doc?query=${encodeURIComponent(cfg.query)}` +\n `&mode=artlist&format=json&maxrecords=40×pan=2d`;\n\n let articles: Array<{ url?: string; title?: string; seendate?: string; language?: string }> = [];\n try {\n // GDELT is a best-effort external source; without a deadline a slow or wedged endpoint hangs\n // the whole workflow run. Bound it and let the catch below treat a timeout like any other miss.\n const res = await fetch(api, { headers: { \"User-Agent\": UA }, signal: AbortSignal.timeout(15_000) });\n if (!res.ok) return [];\n const text = await res.text(); // GDELT sometimes returns text errors with 200\n try {\n articles = (JSON.parse(text) as { articles?: typeof articles }).articles ?? [];\n } catch {\n return [];\n }\n } catch (e) {\n console.log(`gdelt fetch failed (non-blocking): ${e}`);\n return [];\n }\n\n const seen = new Set<string>();\n const picked = articles\n .filter((a) => a?.url && a?.title && a.language === \"English\" && !seen.has(a.url!) && (seen.add(a.url!), true))\n .slice(0, MAX_ITEMS);\n\n const out: RawItem[] = [];\n for (const a of picked) {\n const body = await ctx.fetchText(a.url!); // GDELT gives no body; fetch the article\n if (body.length < 200) continue;\n out.push({\n url: a.url!,\n title: a.title!,\n date: isoFromSeendate(a.seendate ?? \"\"),\n sourceName: cfg.name,\n sourceKind: \"gdelt\",\n watch: cfg.watch,\n text: body,\n });\n }\n return out;\n },\n};\n","import type { Value } from \"@odla-ai/db\";\nimport type { RawItem, GraphFragment } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\nimport { slugify } from \"../util/identity\";\n\n/**\n * FRED economic indicators — a STRUCTURED connector: it maps series directly to\n * graph nodes (indicator + region) and emits RawItem.fragment, so the ingest\n * workflow writes it without any LLM extraction. Ported from agnacl services/fred.ts.\n */\ninterface FredCfg {\n series: Array<{ id: string; label: string; unit?: string }>;\n region?: string;\n regionName?: string;\n}\n\nasync function fetchSeries(\n seriesId: string,\n apiKey: string,\n start: string,\n): Promise<Array<{ date: string; value: number }>> {\n const url =\n `https://api.stlouisfed.org/fred/series/observations?series_id=${encodeURIComponent(seriesId)}` +\n `&api_key=${encodeURIComponent(apiKey)}&file_type=json&observation_start=${encodeURIComponent(start)}`;\n try {\n const res = await fetch(url);\n if (!res.ok) return [];\n const data = (await res.json()) as { observations?: Array<{ date?: string; value?: string }> };\n const out: Array<{ date: string; value: number }> = [];\n for (const o of data.observations ?? []) {\n if (!o?.date || o.value === undefined || o.value === \".\") continue;\n const v = Number(o.value);\n if (Number.isFinite(v)) out.push({ date: o.date, value: v });\n }\n return out;\n } catch (e) {\n console.log(`fred fetch failed for ${seriesId} (non-blocking): ${e}`);\n return [];\n }\n}\n\nexport const fredConnector: SourceConnector<FredCfg> = {\n id: \"fred\",\n capabilities: { fetch: true, structured: true },\n\n parseConfig(raw): FredCfg {\n const r = raw as Record<string, unknown>;\n const series = Array.isArray(r.series) ? (r.series as FredCfg[\"series\"]) : [];\n if (!series.length) throw new Error(\"fred connector requires a non-empty `series`\");\n return { series, region: r.region as string | undefined, regionName: r.regionName as string | undefined };\n },\n\n async fetch(cfg, ctx): Promise<RawItem[]> {\n const apiKey = ctx.secret(\"FRED_API_KEY\");\n if (!apiKey) {\n console.log(\"fred: FRED_API_KEY unset — skipping\");\n return [];\n }\n const start = new Date(ctx.now.getTime() - 400 * 24 * 3600 * 1000).toISOString().slice(0, 10);\n const out: RawItem[] = [];\n\n for (const s of cfg.series) {\n const obs = await fetchSeries(s.id, apiKey, start);\n if (!obs.length) continue;\n const latest = obs[obs.length - 1]!;\n const prior = obs.length > 1 ? obs[obs.length - 2] : undefined;\n\n const attrs: Record<string, Value> = { label: s.label, latest: String(latest.value), as_of: latest.date };\n if (prior) attrs.prior = String(prior.value);\n if (s.unit) attrs.unit = s.unit;\n\n const fragment: GraphFragment = { nodes: [{ type: \"indicator\", key: s.id, attrs }], edges: [] };\n if (cfg.region) {\n const rslug = slugify(cfg.region);\n if (rslug) {\n fragment.nodes.push({ type: \"region\", key: rslug, attrs: { name: cfg.regionName ?? cfg.region } });\n fragment.edges.push({ from: { type: \"region\", key: rslug }, to: { type: \"indicator\", key: s.id }, label: \"has_indicator\" });\n }\n }\n\n out.push({\n url: `https://fred.stlouisfed.org/series/${s.id}#${latest.date}`,\n title: `${s.label}: ${latest.value} (${latest.date})`,\n date: latest.date,\n sourceName: \"FRED\",\n sourceKind: \"fred\",\n fragment,\n });\n }\n return out;\n },\n};\n","import type { Value } from \"@odla-ai/db\";\nimport type { RawItem, GraphFragment } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\nimport { slugify, isLikelyPersonName } from \"../util/identity\";\n\n/**\n * SEC EDGAR Form D — a STRUCTURED connector (RawItem.fragment, no LLM). A lean\n * port of agnacl services/edgar.ts: first page of Form D filings for the given\n * states, each mapped to company + funding_round (+ people, region). SEC\n * requires an identifying User-Agent (env.EDGAR_USER_AGENT) on every request.\n */\nconst SEARCH_BASE = \"https://efts.sec.gov/LATEST/search-index\";\nconst MAX_FILINGS = 10;\n\ninterface EdgarCfg {\n locationCodes: string[];\n cityFilter?: string[];\n days?: number;\n}\n\nconst cikTrim = (cik: string): string => cik.replace(/^0+/, \"\") || cik;\nconst indexUrl = (cik: string, adsh: string): string =>\n `https://www.sec.gov/Archives/edgar/data/${cikTrim(cik)}/${adsh.replace(/-/g, \"\")}/${adsh}-index.htm`;\n\nfunction tagText(scope: string, tag: string): string {\n const m = scope.match(new RegExp(`<${tag}[^>]*>\\\\s*([\\\\s\\\\S]*?)\\\\s*</${tag}>`, \"i\"));\n return m ? m[1]!.trim() : \"\";\n}\n\ninterface EdgarHit {\n _source?: {\n adsh?: string;\n ciks?: string[];\n display_names?: string[];\n file_date?: string;\n biz_states?: string[];\n biz_locations?: string[];\n };\n}\n\nexport const edgarConnector: SourceConnector<EdgarCfg> = {\n id: \"edgar\",\n capabilities: { fetch: true, structured: true },\n\n parseConfig(raw): EdgarCfg {\n const r = raw as Record<string, unknown>;\n const codes = Array.isArray(r.locationCodes) ? (r.locationCodes as string[]) : [];\n if (!codes.length) throw new Error('edgar connector requires `locationCodes` (e.g. [\"UT\"])');\n return {\n locationCodes: codes,\n cityFilter: Array.isArray(r.cityFilter) ? (r.cityFilter as string[]) : undefined,\n days: typeof r.days === \"number\" ? r.days : 7,\n };\n },\n\n async fetch(cfg, ctx): Promise<RawItem[]> {\n const ua = ctx.secret(\"EDGAR_USER_AGENT\");\n if (!ua) {\n console.log(\"edgar: EDGAR_USER_AGENT unset — skipping\");\n return [];\n }\n const enddt = ctx.now.toISOString().slice(0, 10);\n const startdt = new Date(ctx.now.getTime() - (cfg.days ?? 7) * 24 * 3600 * 1000).toISOString().slice(0, 10);\n const searchUrl =\n `${SEARCH_BASE}?q=&forms=D&locationCodes=${encodeURIComponent(cfg.locationCodes.join(\",\"))}` +\n `&startdt=${startdt}&enddt=${enddt}`;\n\n let hits: EdgarHit[] = [];\n try {\n const res = await fetch(searchUrl, { headers: { \"User-Agent\": ua } });\n if (!res.ok) return [];\n hits = ((await res.json()) as { hits?: { hits?: EdgarHit[] } }).hits?.hits ?? [];\n } catch (e) {\n console.log(`edgar search failed (non-blocking): ${e}`);\n return [];\n }\n\n const out: RawItem[] = [];\n const seen = new Set<string>();\n for (const hit of hits.slice(0, MAX_FILINGS)) {\n const src = hit._source ?? {};\n const adsh = src.adsh ?? \"\";\n if (!adsh || seen.has(adsh)) continue;\n seen.add(adsh);\n const cik = src.ciks?.[0] ?? \"\";\n const name = (src.display_names?.[0] ?? \"\").replace(/\\s*\\(CIK\\s*\\d+\\)\\s*$/i, \"\").trim();\n if (!name || !cik) continue;\n\n const loc = src.biz_locations?.[0] ?? \"\";\n let city = loc.trim();\n let state = src.biz_states?.[0] ?? \"\";\n const comma = loc.lastIndexOf(\",\");\n if (comma >= 0) {\n city = loc.slice(0, comma).trim();\n state = loc.slice(comma + 1).trim() || state;\n }\n if (cfg.cityFilter?.length && !cfg.cityFilter.some((c) => c.toLowerCase() === city.toLowerCase())) continue;\n\n const url = indexUrl(cik, adsh);\n const fileDate = src.file_date ?? \"\";\n\n // Filing detail (offering amount, industry, related persons)\n let industry = \"\";\n let offering = \"\";\n const persons: Array<{ name: string; roles: string[] }> = [];\n try {\n const detailUrl = `https://www.sec.gov/Archives/edgar/data/${cikTrim(cik)}/${adsh.replace(/-/g, \"\")}/primary_doc.xml`;\n const dres = await fetch(detailUrl, { headers: { \"User-Agent\": ua } });\n if (dres.ok) {\n const xml = await dres.text();\n industry = tagText(xml, \"industryGroupType\");\n offering = tagText(xml, \"totalOfferingAmount\");\n for (const block of xml.match(/<relatedPersonInfo>[\\s\\S]*?<\\/relatedPersonInfo>/gi) ?? []) {\n const pname = [tagText(block, \"firstName\"), tagText(block, \"lastName\")]\n .filter((p) => p && p !== \"-\")\n .join(\" \")\n .trim();\n if (!pname) continue;\n const roles: string[] = [];\n const relRe = /<relationship>\\s*([\\s\\S]*?)\\s*<\\/relationship>/gi;\n let m: RegExpExecArray | null;\n while ((m = relRe.exec(block)) !== null) {\n const r = m[1]!.trim();\n if (r && !roles.includes(r)) roles.push(r);\n }\n persons.push({ name: pname, roles });\n }\n }\n } catch {\n /* fall back to search-hit metadata */\n }\n\n const cslug = slugify(name);\n if (!cslug) continue;\n const f: GraphFragment = { nodes: [], edges: [] };\n const cattrs: Record<string, Value> = { name };\n if (city) cattrs.city = city;\n if (state) cattrs.region = state;\n if (industry) cattrs.sector = industry;\n if (fileDate) {\n cattrs.first_seen = fileDate;\n cattrs.last_seen = fileDate;\n }\n f.nodes.push({ type: \"company\", key: cslug, attrs: cattrs });\n\n if (state) {\n const rslug = slugify(state);\n if (rslug) {\n f.nodes.push({ type: \"region\", key: rslug, attrs: { name: state } });\n f.edges.push({ from: { type: \"company\", key: cslug }, to: { type: \"region\", key: rslug }, label: \"located_in\" });\n }\n }\n\n const rkey = `${cslug}:form-d:${fileDate}`;\n const rattrs: Record<string, Value> = { round: \"Form D\", source_url: url };\n if (offering) rattrs.amount = offering;\n if (fileDate) rattrs.date = fileDate;\n f.nodes.push({ type: \"funding_round\", key: rkey, attrs: rattrs });\n f.edges.push({ from: { type: \"funding_round\", key: rkey }, to: { type: \"company\", key: cslug }, label: \"round_of\" });\n\n for (const p of persons) {\n if (!isLikelyPersonName(p.name)) continue;\n const pslug = slugify(p.name);\n if (!pslug) continue;\n const pattrs: Record<string, Value> = { name: p.name };\n if (p.roles.length) pattrs.role = p.roles.join(\", \");\n f.nodes.push({ type: \"person\", key: pslug, attrs: pattrs });\n f.edges.push({ from: { type: \"person\", key: pslug }, to: { type: \"company\", key: cslug }, label: \"works_at\" });\n }\n\n out.push({\n url,\n title: `Form D: ${name}${offering ? ` — ${offering}` : \"\"}`,\n date: fileDate || undefined,\n sourceName: \"SEC EDGAR\",\n sourceKind: \"edgar\",\n fragment: f,\n });\n }\n return out;\n },\n};\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\n\n/**\n * TEMPLATE — a government / open-data connector.\n *\n * This exists to demonstrate the extensibility contract: adding a new source is\n * a self-contained module implementing SourceConnector + ONE line in the\n * registry + config entries in config/sources.ts. The cron, ingest workflow,\n * extractor, and graph writer are untouched — they only ever speak RawItem /\n * GraphFragment.\n *\n * To make it real, fetch your dataset and return either:\n * - text-bearing RawItems ({ ...meta, text }) → the LLM extracts a graph\n * - fragment-bearing RawItems ({ ...meta, fragment }) → straight to the writer\n * (map records to ontology nodes/edges yourself — see fred.ts / edgar.ts).\n * Returns [] until implemented, so wiring it up is harmless.\n */\ninterface GovDataCfg {\n dataset: string;\n watch?: string | null;\n}\n\nexport const govDataConnector: SourceConnector<GovDataCfg> = {\n id: \"gov-data\",\n capabilities: { fetch: true, structured: true },\n\n parseConfig(raw): GovDataCfg {\n const r = raw as Record<string, unknown>;\n return { dataset: String(r.dataset ?? \"\"), watch: (r.watch as string | null) ?? null };\n },\n\n async fetch(cfg, _ctx: ConnectorContext): Promise<RawItem[]> {\n console.log(`gov-data: template connector for dataset \"${cfg.dataset}\" — implement fetch() to emit RawItems`);\n return [];\n },\n};\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\nimport { urlHash } from \"../util/identity\";\n\n/**\n * LLM-native web search — a DISCOVERY connector. Each standing query is run\n * through the provider's web-search tool; the prose result becomes a text\n * RawItem that flows through the normal extractor (so discovered companies land\n * in the graph the same way feed items do). Discovery-only: fetch() is a no-op.\n */\ninterface WebSearchCfg {\n queries: string[];\n name: string;\n watch?: string | null;\n}\n\nconst SWEEP_SYSTEM =\n \"You are a research scout building a knowledge graph of entrepreneurs, startups, and business trends. Search the web and report concrete, real companies and the people leading them, with specifics (what they do, where, who founded them). Tie every claim to something you actually found — never invent names, numbers, or companies. If a search turns up nothing solid, say so plainly.\";\n\nexport const webSearchConnector: SourceConnector<WebSearchCfg> = {\n id: \"web-search\",\n capabilities: { fetch: false, discover: true, structured: false },\n\n parseConfig(raw): WebSearchCfg {\n const r = raw as Record<string, unknown>;\n const queries = Array.isArray(r.queries)\n ? (r.queries.filter((q) => typeof q === \"string\") as string[])\n : typeof r.query === \"string\"\n ? [r.query]\n : [];\n if (!queries.length) throw new Error(\"web-search connector requires `queries` (or a `query`)\");\n return { queries, name: String(r.name ?? \"web-search\"), watch: (r.watch as string | null) ?? null };\n },\n\n async fetch(): Promise<RawItem[]> {\n return []; // discovery-only\n },\n\n async discover(cfg, ctx: ConnectorContext): Promise<RawItem[]> {\n const provider = ctx.provider();\n const today = ctx.now.toISOString().slice(0, 10);\n const out: RawItem[] = [];\n for (const q of cfg.queries) {\n const { text } = await provider.search({\n system: SWEEP_SYSTEM,\n user: `Search task: ${q}\\nToday's date: ${today}\\n\\nReport what you find, naming specific companies and the real people leading them.`,\n });\n if (text.trim().length < 80) continue;\n out.push({\n url: `search://web-search/${urlHash(q)}#${today}`, // synthetic; changes daily so re-sweeps aren't deduped away\n title: q,\n date: today,\n sourceName: cfg.name,\n sourceKind: \"web-search\",\n watch: cfg.watch,\n text,\n });\n }\n return out;\n },\n};\n","import type { SourceConnector, ConnectorContext } from \"./types\";\nimport type { Ontology } from \"../ontology\";\nimport type { Provider } from \"../llm/provider\";\nimport { fetchText } from \"../util/content\";\nimport { rssConnector } from \"./rss\";\nimport { urlConnector } from \"./url\";\nimport { gdeltConnector } from \"./gdelt\";\nimport { fredConnector } from \"./fred\";\nimport { edgarConnector } from \"./edgar\";\nimport { govDataConnector } from \"./govdata\";\nimport { webSearchConnector } from \"./web-search\";\n\n/**\n * The connector registry. Adding a source (RSS, a government feed, a new API)\n * is: implement SourceConnector in a module, add one line here, add config\n * entries to config/sources.ts. The cron, workflow, extractor, and writer are\n * untouched — they only speak RawItem / GraphFragment.\n */\nconst REGISTRY = new Map<string, SourceConnector>([\n [rssConnector.id, rssConnector as SourceConnector],\n [urlConnector.id, urlConnector as SourceConnector],\n [gdeltConnector.id, gdeltConnector as SourceConnector],\n [fredConnector.id, fredConnector as SourceConnector], // structured (RawItem.fragment)\n [edgarConnector.id, edgarConnector as SourceConnector], // structured (RawItem.fragment)\n [webSearchConnector.id, webSearchConnector as SourceConnector], // discovery (web search)\n [govDataConnector.id, govDataConnector as SourceConnector], // template — zero core change to add\n]);\n\n/** Resolve a built-in source connector by its stable registry id. */\nexport function getConnector(id: string): SourceConnector | undefined {\n return REGISTRY.get(id);\n}\n\n/** List registered connector ids in deterministic insertion order. */\nexport function connectorIds(): string[] {\n return [...REGISTRY.keys()];\n}\n\n/** Everything a ConnectorContext needs, supplied by the host (worker, script, test). */\nexport interface ContextConfig {\n ontology: Ontology;\n provider: () => Provider;\n /** Named secrets (API keys, user agents) — e.g. the worker Env, or a plain object. */\n secrets?: Record<string, string | undefined>;\n now?: Date;\n}\n\n/** Create a host-supplied connector context with lazy provider construction. */\nexport function makeContext(cfg: ContextConfig): ConnectorContext {\n let provider: Provider | undefined; // lazy: only discovery connectors need it\n return {\n now: cfg.now ?? new Date(),\n fetchText: (u) => fetchText(u),\n secret: (name) => cfg.secrets?.[name],\n ontology: cfg.ontology,\n provider: () => (provider ??= cfg.provider()),\n };\n}\n","import type { ConnectorJob } from \"./connectors/types\";\n\n/**\n * Sources are DATA, not code. Each entry names a connector (`kind`) + its config\n * + scheduling. Add a feed → add an entry; no redeploy of the pipeline. P5 moves\n * this into KV behind a /config admin endpoint so edits don't need a deploy at all.\n */\nexport interface SourceEntry {\n kind: string; // connector id (registry key)\n name: string;\n enabled?: boolean; // default true\n cadenceHours?: number; // default 1 — runs when UTC hour % cadence === 0\n watch?: string | null;\n // connector-specific config (rss: url; more in P3):\n url?: string;\n query?: string;\n [key: string]: unknown;\n}\n\n/** Return source entries not explicitly disabled, preserving configured order. */\nexport function enabledSources(sources: SourceEntry[]): SourceEntry[] {\n return sources.filter((s) => s.enabled !== false);\n}\n\nfunction toJob(s: SourceEntry): ConnectorJob {\n return { kind: s.kind, name: s.name, watch: s.watch ?? null, cfg: s };\n}\n\n/** Enabled sources whose cadence lands on this UTC hour. */\nexport function jobsForHour(sources: SourceEntry[], hour: number): ConnectorJob[] {\n return enabledSources(sources)\n .filter((s) => hour % Math.max(1, Math.floor(s.cadenceHours ?? 1)) === 0)\n .map(toJob);\n}\n\n/** Every enabled source, ignoring cadence (manual /trigger?kind=sources). */\nexport function allJobs(sources: SourceEntry[]): ConnectorJob[] {\n return enabledSources(sources).map(toJob);\n}\n","import type { RawItem } from \"../types\";\nimport type { SourceEntry } from \"../sources\";\nimport { urlHash } from \"../util/identity\";\n\nconst SEEN_TTL_S = 120 * 24 * 60 * 60; // 120 days\nconst ENRICHED_TTL_S = 30 * 24 * 60 * 60; // 30-day enrichment cooldown\nconst TEMPORAL_TTL_S = 120 * 24 * 60 * 60; // dated temporal-report history copies\n\n/**\n * The slice of a KV store the pipeline needs. Cloudflare's KVNamespace\n * satisfies it structurally; tests pass an in-memory map. Keeping the library\n * off Workers types is what lets it publish as a plain isomorphic package.\n */\nexport interface KVLike {\n get(key: string): Promise<string | null>;\n put(key: string, value: string, opts?: { expirationTtl?: number }): Promise<void>;\n}\n\n/**\n * Operational state in KV (never the graph itself — that lives in odla-db).\n * Ported/trimmed from agnacl services/kv.ts: seen-URL dedup + lastrun stamps.\n */\nexport class Store {\n constructor(private kv: KVLike) {}\n\n private seenKey(url: string): string {\n return `seen:${urlHash(url)}`;\n }\n\n async hasSeen(url: string): Promise<boolean> {\n return (await this.kv.get(this.seenKey(url))) !== null;\n }\n\n async markSeen(url: string): Promise<void> {\n await this.kv.put(this.seenKey(url), \"1\", { expirationTtl: SEEN_TTL_S });\n }\n\n /** Drop items whose URL we've already processed (checked BEFORE any LLM work). */\n async filterNew(items: RawItem[]): Promise<RawItem[]> {\n const flags = await Promise.all(items.map((it) => this.hasSeen(it.url)));\n return items.filter((_, i) => !flags[i]);\n }\n\n async setLastRun(kind: string, at = new Date().toISOString()): Promise<void> {\n await this.kv.put(`lastrun:${kind}`, at);\n }\n\n async getLastRun(kind: string): Promise<string | null> {\n return this.kv.get(`lastrun:${kind}`);\n }\n\n /** Per-entity enrichment cooldown so we don't re-search the same node constantly. */\n async isEnrichCooling(key: string): Promise<boolean> {\n return (await this.kv.get(`enriched:${key}`)) !== null;\n }\n\n async markEnriched(key: string): Promise<void> {\n await this.kv.put(`enriched:${key}`, new Date().toISOString(), { expirationTtl: ENRICHED_TTL_S });\n }\n\n /** Runtime source config (edit without redeploy). null = fall back to the static SOURCES. */\n async getConfigSources(): Promise<SourceEntry[] | null> {\n const raw = await this.kv.get(\"config:sources\");\n if (!raw) return null;\n try {\n const v = JSON.parse(raw);\n return Array.isArray(v) ? (v as SourceEntry[]) : null;\n } catch {\n return null;\n }\n }\n\n async setConfigSources(list: SourceEntry[]): Promise<void> {\n await this.kv.put(\"config:sources\", JSON.stringify(list));\n }\n\n /** Cache the latest temporal report (`temporal:latest`) plus a dated history copy. */\n async setTemporalReport(report: unknown, date = new Date().toISOString().slice(0, 10)): Promise<void> {\n const raw = JSON.stringify(report);\n await this.kv.put(\"temporal:latest\", raw);\n await this.kv.put(`temporal:${date}`, raw, { expirationTtl: TEMPORAL_TTL_S });\n }\n\n /** The cached latest temporal report, or null when absent/unparseable. */\n async getTemporalReport<T = unknown>(): Promise<T | null> {\n const raw = await this.kv.get(\"temporal:latest\");\n if (!raw) return null;\n try {\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n }\n}\n","/**\n * Cloudflare Workflow hardening — ported verbatim from agnacl\n * (utils/workflow-helpers.ts). resilientStep adds structured error logging\n * without changing retry semantics; the step configs replace the engine's slow\n * default retry with fast exponential backoff.\n */\n\nexport async function resilientStep<T>(stepName: string, fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n const stack = err instanceof Error ? err.stack : undefined;\n console.error(`[STEP_ERROR] ${stepName}: ${msg}`);\n if (stack) console.error(`[STEP_STACK] ${stepName}: ${stack}`);\n throw err;\n }\n}\n\n/** I/O-bound steps: KV reads, feed fetches. */\nexport const IO_STEP_CONFIG = {\n retries: { limit: 3, delay: \"5 seconds\" as const, backoff: \"exponential\" as const },\n timeout: \"2 minutes\" as const,\n};\n\n/** Graph write steps. */\nexport const COMMIT_STEP_CONFIG = {\n retries: { limit: 3, delay: \"3 seconds\" as const, backoff: \"exponential\" as const },\n timeout: \"90 seconds\" as const,\n};\n\n/** LLM generation steps (can be slow). */\nexport const LLM_STEP_CONFIG = {\n retries: { limit: 2, delay: \"5 seconds\" as const, backoff: \"exponential\" as const },\n timeout: \"3 minutes\" as const,\n};\n\n/** Retry/timeout shape of the step configs above. */\nexport interface StepConfig {\n retries?: { limit: number; delay: string; backoff?: string };\n timeout?: string;\n}\n\n/**\n * Structural view of Cloudflare's WorkflowStep. WorkflowStep.do constrains its\n * result to Rpc.Serializable<T>, which cannot terminate (TS2589) on the\n * recursive Value type now that the @odla/* workspace packages give\n * @odla-ai/db's re-exports real definitions. A live WorkflowStep satisfies\n * this shape at runtime; widen through asStepLike at the workflow boundary.\n */\nexport interface StepLike {\n do<T>(name: string, fn: () => Promise<T>): Promise<T>;\n do<T>(name: string, config: StepConfig, fn: () => Promise<T>): Promise<T>;\n}\n\n/** Widen a WorkflowStep to StepLike (type-level only, no runtime cost). */\nexport function asStepLike(step: unknown): StepLike {\n return step as StepLike;\n}\n","import { LLM_STEP_CONFIG, type StepLike, resilientStep } from \"../workflow/helpers\";\nimport type { ExtractResult, RawItem } from \"../types\";\nimport type { AdminDb } from \"@odla-ai/db\";\nimport type { Store } from \"../state/kv\";\nimport type { Provider } from \"../llm/provider\";\nimport type { Ontology } from \"../ontology\";\nimport { writeFragment } from \"../graph/writer\";\nimport { canonicalizeFragment } from \"../graph/resolve\";\nimport { urlHash } from \"../util/identity\";\n\n/** Everything the shared ingest loop needs, supplied by the host. The\n * extractor is the ontology's domain craft (e.g. the OSINT extractor); the\n * library stays ontology-agnostic. */\nexport interface PipelineDeps {\n db: AdminDb;\n provider: Provider;\n ontology: Ontology;\n extract: (provider: Provider, item: RawItem) => Promise<ExtractResult | null>;\n store: Store;\n}\n\n/**\n * Shared per-item processing: each RawItem is its own step (non-blocking,\n * replay-cached). Structured items (fragment) skip the LLM; text items extract.\n * `resolve` FTS-canonicalizes new nodes against existing ones (discovery path).\n * Used by both the ingest (fetch) and discover (web-search) workflows.\n */\nexport async function ingestFresh(\n step: StepLike,\n deps: PipelineDeps,\n fresh: RawItem[],\n kind: string,\n resolve = false,\n): Promise<void> {\n const { db, provider, ontology, extract, store } = deps;\n if (fresh.length === 0) {\n await store.setLastRun(kind);\n return;\n }\n\n for (const item of fresh) {\n const id = urlHash(item.url);\n await step.do(`item-${id}`, LLM_STEP_CONFIG, () =>\n resilientStep(`item-${id}`, async () => {\n try {\n let fragment = item.fragment;\n if (!fragment) {\n const r = await extract(provider, item);\n if (!r) {\n await store.markSeen(item.url);\n return { skipped: true };\n }\n fragment = r.fragment;\n }\n if (resolve) fragment = await canonicalizeFragment(db, fragment, ontology);\n const { txId, ops } = await writeFragment(db, fragment, ontology, `ingest:${id}:v1`);\n await store.markSeen(item.url);\n return { txId, ops };\n } catch (e) {\n console.log(`ingest item failed (non-blocking): ${item.url}: ${e instanceof Error ? e.message : e}`);\n return { error: true };\n }\n }),\n );\n }\n\n await step.do(`stamp-${kind}`, () =>\n resilientStep(`stamp-${kind}`, async () => {\n await store.setLastRun(kind);\n return { ok: true };\n }),\n );\n}\n"],"mappings":"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","names":["t","UA"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/ontology.ts","../src/graph/merge.ts","../src/graph/writer.ts","../src/temporal/time.ts","../src/graph/export.ts","../src/graph/resolve.ts","../src/temporal/prng.ts","../src/temporal/communities.ts","../src/temporal/lenses.ts","../src/temporal/bridges.ts","../src/graph/connectors.ts","../src/util/identity.ts","../src/enrich/facts.ts","../src/enrich/verify.ts","../src/enrich/person.ts","../src/enrich/grade.ts","../src/enrich/run.ts","../src/enrich/match.ts","../src/enrich/strategy.ts","../src/enrich/strategies/org-site.ts","../src/enrich/strategies/research.ts","../src/enrich/strategies/targeted-profile.ts","../src/identity/person.ts","../src/identity/identify.ts","../src/llm/provider.ts","../src/llm/json.ts","../src/util/content.ts","../src/connectors/rss.ts","../src/connectors/url.ts","../src/connectors/gdelt.ts","../src/connectors/fred.ts","../src/connectors/edgar.ts","../src/connectors/govdata.ts","../src/connectors/web-search.ts","../src/connectors/registry.ts","../src/sources.ts","../src/state/kv.ts","../src/workflow/helpers.ts","../src/pipeline/run-items.ts"],"sourcesContent":["/**\n * @odla-ai/kg — ontology-driven knowledge-graph builder.\n *\n * Pipeline shape: Connector → RawItem[] → extract → GraphFragment →\n * writeFragment → Op[] → odla-db. The ontology is config-as-data; it compiles\n * to the odla-db schema, the writer's natural-key/merge-rule maps, and (by\n * convention) the extractor's tool schema. Consumers supply the ontology\n * instance, the extractor, the AdminDb, and a KV store — see PipelineDeps.\n */\n\n// Core shapes\nexport type { RawItem, GraphNode, GraphEdge, GraphFragment, Usage, ExtractResult } from \"./types\";\nexport { emptyUsage, addUsage } from \"./types\";\n\n// Ontology: types + compilers (ontology instances are consumer data)\nexport type { Ontology, EntitySpec, EdgeSpec, AttrSpec, AttrType, MergeRule } from \"./ontology\";\nexport { naturalKeysOf, mergeRulesOf, humanAttrsOf, toSerializedSchema } from \"./ontology\";\n\n// Graph writer + entity resolution\nexport { fragmentToOps, writeFragment, type NaturalKeys } from \"./graph/writer\";\nexport { exportFragment, type ExportSpec, type ExportEntitySpec } from \"./graph/export\";\nexport { mergeScalar, mergeAttrs } from \"./graph/merge\";\nexport { searchEntities, resolveName, canonicalizeFragment } from \"./graph/resolve\";\nexport { findConnectors } from \"./graph/connectors\";\nexport type { ConnectorLens, ConnectorConfig, Connector, ConnectorReport } from \"./graph/connectors\";\n\n// LLM enrichment (grounded research → structured facts → GraphFragment)\nexport {\n enrichCompany,\n enrichPerson,\n resolveCounties,\n personPrompt,\n personToFragment,\n validateLinks,\n PERSON_SEARCH_SYSTEM,\n PERSON_EXTRACT_SYSTEM,\n PERSON_EXTRACT_TOOL,\n normalizeVerification,\n isVerdict,\n isWritable,\n isHttpUrl,\n WRITABLE_VERDICTS,\n gradeSources,\n independentHosts,\n isActionable,\n DEFAULT_STANDARD,\n ACTIONABLE,\n factsToFragment,\n locationFragment,\n countyKey,\n locatePrompt,\n LOCATE_SYSTEM,\n LOCATE_TOOL,\n FACTS_SEARCH_SYSTEM,\n FACTS_EXTRACT_SYSTEM,\n FACTS_EXTRACT_TOOL,\n investigatePerson,\n researchStrategy,\n orgSiteStrategy,\n targetedProfileStrategy,\n classifySource,\n isAttributed,\n candidatesFrom,\n targetedQuery,\n candidatePages,\n judgeOrgPage,\n peopleLinks,\n rootOf,\n TEAM_PATHS,\n fold,\n mentionsName,\n snippetAround,\n stripMarkup,\n} from \"./enrich\";\nexport type {\n Strategy,\n StrategyContext,\n Finding,\n Investigation,\n PageReader,\n Candidate,\n OrgHit,\n CompanyRow,\n FactsDraft,\n EnrichModels,\n PersonEnrichment,\n PersonRow,\n PersonDraft,\n PersonLink,\n PersonLinkKind,\n PersonResult,\n Verdict,\n Verification,\n Evidence,\n VerifiedSource,\n SourceClass,\n Corroboration,\n Grade,\n GradeReport,\n GradeStandard,\n} from \"./enrich\";\n\n// Person identity (opaque keys + an alias index, so a name is not an identity)\nexport {\n aliasesFor,\n applyPersonIdentity,\n identifyPerson,\n isMintedKey,\n mintPersonKey,\n nameAlias,\n profileAlias,\n resolvePersonKey,\n} from \"./identity\";\nexport type { PersonIdentity, PersonIdentityConfig } from \"./identity\";\n\n// LLM seam (extract / search / write, adapted over @odla-ai/ai)\nexport type { Provider, ProviderFamily, ExtractCall, SearchCall, WriteCall, LlmConfig, LlmEnv } from \"./llm/provider\";\nexport { createProvider, getProvider, MODEL_TIERS } from \"./llm/provider\";\nexport { extractJson, extractJsonArray } from \"./llm/json\";\n\n// Connectors\nexport type { SourceConnector, ConnectorContext, ConnectorJob } from \"./connectors/types\";\nexport { getConnector, connectorIds, makeContext, type ContextConfig } from \"./connectors/registry\";\nexport { fetchUrl } from \"./connectors/url\";\n\n// Source scheduling (the source list itself lives with the consumer)\nexport type { SourceEntry } from \"./sources\";\nexport { enabledSources, jobsForHour, allJobs } from \"./sources\";\n\n// Operational state (seen-URL dedup, lastrun stamps, runtime source config)\nexport { Store, type KVLike } from \"./state/kv\";\n\n// Pipeline\nexport { ingestFresh, type PipelineDeps } from \"./pipeline/run-items\";\nexport {\n resilientStep,\n asStepLike,\n IO_STEP_CONFIG,\n COMMIT_STEP_CONFIG,\n LLM_STEP_CONFIG,\n type StepLike,\n type StepConfig,\n} from \"./workflow/helpers\";\n\n// Utilities\nexport { slugify, identityKey, normalizeUrl, urlHash, isLikelyPersonName } from \"./util/identity\";\nexport { fetchText, htmlToText } from \"./util/content\";\n","import type { Value } from \"@odla-ai/db\";\n\n/**\n * The output of a connector: one acquired item. Exactly one of `text` or\n * `fragment` is set — `text` goes through LLM extraction, `fragment` is already\n * ontology-typed and goes straight to the graph writer (structured feeds).\n */\nexport interface RawItem {\n url: string;\n title: string;\n date?: string;\n sourceName: string;\n sourceKind: string; // connector id: \"url\" | \"rss\" | \"edgar\" | ...\n watch?: string | null;\n text?: string;\n fragment?: GraphFragment;\n}\n\n/** A graph node = an odla-db entity in a namespace, keyed by a natural key. */\nexport interface GraphNode {\n type: string; // namespace, e.g. \"company\"\n key: string; // natural-key value (slug, urlHash, ...) — used for Lookup upsert\n attrs: Record<string, Value>; // node properties (the natural-key attr is added by the writer)\n}\n\n/** A graph edge = an odla-db `link` from one node to another by forward label. */\nexport interface GraphEdge {\n from: { type: string; key: string };\n to: { type: string; key: string };\n label: string; // forward link label, e.g. \"mentions\"\n}\n\n/** The single shape the graph writer consumes, whatever produced it. */\nexport interface GraphFragment {\n nodes: GraphNode[];\n edges: GraphEdge[];\n}\n\n/** What an extractor turns a text RawItem into: a fragment plus a prose summary. */\nexport interface ExtractResult {\n fragment: GraphFragment;\n summary: string;\n}\n\n/** Provider-agnostic LLM usage accounting. */\nexport interface Usage {\n calls: number;\n inputTokens: number;\n outputTokens: number;\n}\n\n/** Create zeroed provider-agnostic usage totals for one pipeline run. */\nexport function emptyUsage(): Usage {\n return { calls: 0, inputTokens: 0, outputTokens: 0 };\n}\n\n/** Mutate an accumulator by adding call, input-token, and output-token totals. */\nexport function addUsage(into: Usage, more: Usage): void {\n into.calls += more.calls;\n into.inputTokens += more.inputTokens;\n into.outputTokens += more.outputTokens;\n}\n","import type { SerializedAttr, SerializedEntity, SerializedLink, SerializedSchema, Cardinality } from \"@odla-ai/db\";\nimport type { NaturalKeys } from \"./graph/writer\";\n\n/** How a scalar attribute is reconciled when a node is re-discovered. */\nexport type MergeRule =\n | \"replace\" // last write wins (default)\n | \"prefer\" // first non-empty value sticks (stable facts like a name)\n | \"earliest\" // keep the smaller value (first_seen)\n | \"latest\"; // keep the larger value (last_seen)\n\nexport type AttrType = SerializedAttr[\"type\"]; // \"string\" | \"number\" | \"boolean\" | \"date\" | \"json\"\n\nexport interface AttrSpec {\n type: AttrType;\n unique?: boolean;\n indexed?: boolean;\n optional?: boolean;\n /** The pipeline NEVER writes this attr (hand-edited in a UI); omitted from every update. */\n human?: boolean;\n /** Provenance rule for re-discovery. Default \"replace\". */\n merge?: MergeRule;\n /** Shown to the LLM when this attr appears in an extraction schema. */\n desc?: string;\n}\n\nexport interface EntitySpec {\n /** Unique attr used for Lookup upsert (MERGE key). Must be `unique` in attrs. */\n naturalKey: string;\n attrs: Record<string, AttrSpec>;\n /** FTS fields for fuzzy resolution (P5). */\n searchable?: string[];\n}\n\nexport interface EdgeSpec {\n from: string; // entity type\n to: string; // entity type\n forwardLabel: string; // link label on the `from` side (what the `link` op carries)\n reverseLabel: string;\n forwardHas?: Cardinality; // default \"many\"\n reverseHas?: Cardinality; // default \"many\"\n}\n\nexport interface Ontology {\n entities: Record<string, EntitySpec>;\n edges: Record<string, EdgeSpec>; // keyed by edge name\n}\n\n/** node type → natural-key attr name (for the graph writer's Lookup refs). */\nexport function naturalKeysOf(o: Ontology): NaturalKeys {\n const m: NaturalKeys = {};\n for (const [type, e] of Object.entries(o.entities)) m[type] = e.naturalKey;\n return m;\n}\n\n/** Attr name → merge rule, for every entity type (used by the provenance writer). */\nexport function mergeRulesOf(o: Ontology): Record<string, Record<string, MergeRule>> {\n const out: Record<string, Record<string, MergeRule>> = {};\n for (const [type, e] of Object.entries(o.entities)) {\n out[type] = {};\n for (const [attr, spec] of Object.entries(e.attrs)) out[type][attr] = spec.merge ?? \"replace\";\n }\n return out;\n}\n\n/** Attr names the pipeline must never write, per entity type. */\nexport function humanAttrsOf(o: Ontology): Record<string, Set<string>> {\n const out: Record<string, Set<string>> = {};\n for (const [type, e] of Object.entries(o.entities)) {\n out[type] = new Set(Object.entries(e.attrs).filter(([, a]) => a.human).map(([n]) => n));\n }\n return out;\n}\n\n/** Compile the ontology to an odla-db SerializedSchema to POST at /app/:id/schema. */\nexport function toSerializedSchema(o: Ontology): SerializedSchema {\n const entities: Record<string, SerializedEntity> = {};\n for (const [type, e] of Object.entries(o.entities)) {\n const attrs: Record<string, SerializedAttr> = {};\n for (const [name, a] of Object.entries(e.attrs)) {\n attrs[name] = {\n type: a.type,\n unique: !!a.unique,\n indexed: !!a.indexed,\n optional: !!a.optional,\n };\n }\n entities[type] = { attrs };\n }\n const links: Record<string, SerializedLink> = {};\n for (const [name, ed] of Object.entries(o.edges)) {\n links[name] = {\n forward: { on: ed.from, has: ed.forwardHas ?? \"many\", label: ed.forwardLabel },\n reverse: { on: ed.to, has: ed.reverseHas ?? \"many\", label: ed.reverseLabel },\n };\n }\n return { entities, links };\n}\n","import type { Value } from \"@odla-ai/db\";\nimport type { MergeRule } from \"../ontology\";\n\nfunction isEmpty(v: Value | undefined): boolean {\n return v === undefined || v === null || v === \"\";\n}\n\n/**\n * Reconcile one scalar attribute when a node is re-discovered. Port of agnacl's\n * provenance rules (utils/markdown.ts). Empty incoming never clobbers; empty\n * existing is filled. Ordering rules compare stringwise (ISO dates sort right).\n */\nexport function mergeScalar(\n rule: MergeRule,\n existing: Value | undefined,\n incoming: Value | undefined,\n): Value | undefined {\n if (isEmpty(incoming)) return existing;\n if (isEmpty(existing)) return incoming;\n switch (rule) {\n case \"prefer\":\n return existing; // first non-empty value sticks\n case \"earliest\":\n return String(incoming) < String(existing) ? incoming : existing;\n case \"latest\":\n return String(incoming) > String(existing) ? incoming : existing;\n case \"replace\":\n default:\n return incoming;\n }\n}\n\n/**\n * Merge an incoming attr set into an existing one per the type's rules. `human`\n * attrs are excluded upstream (never in `incoming`); this only reconciles what\n * the pipeline owns. Returns the attrs to write (update leaves unlisted attrs intact).\n */\nexport function mergeAttrs(\n rules: Record<string, MergeRule>,\n existing: Record<string, Value>,\n incoming: Record<string, Value>,\n): Record<string, Value> {\n const out: Record<string, Value> = {};\n for (const [attr, val] of Object.entries(incoming)) {\n const merged = mergeScalar(rules[attr] ?? \"replace\", existing[attr], val);\n if (merged !== undefined) out[attr] = merged;\n }\n return out;\n}\n","import type {\n AdminDb,\n Op,\n EntityRef,\n Value,\n InstaQLQuery,\n QueryResult,\n} from \"@odla-ai/db\";\nimport type { GraphFragment } from \"../types\";\nimport {\n naturalKeysOf,\n mergeRulesOf,\n humanAttrsOf,\n type Ontology,\n} from \"../ontology\";\nimport { mergeAttrs } from \"./merge\";\n\n/** node type → unique natural-key attribute name. */\nexport type NaturalKeys = Record<string, string>;\n\n/**\n * Pure fragment → ops mapping (no read/merge). Node upserts by Lookup natural\n * key, then links — nodes always before links so `link` targets resolve.\n * Used by tests and as the building block for the provenance path below.\n */\nexport function fragmentToOps(fragment: GraphFragment, keys: NaturalKeys): Op[] {\n const ref = (type: string, key: string): EntityRef => {\n const attr = keys[type];\n if (!attr) throw new Error(`No natural key configured for node type \"${type}\"`);\n return { ns: type, attr, value: key };\n };\n const ops: Op[] = [];\n for (const n of fragment.nodes) {\n const attr = keys[n.type];\n if (!attr) throw new Error(`No natural key configured for node type \"${n.type}\"`);\n ops.push({ t: \"update\", ns: n.type, id: { ns: n.type, attr, value: n.key }, attrs: { ...n.attrs, [attr]: n.key } });\n }\n for (const e of fragment.edges) {\n ops.push({ t: \"link\", ns: e.from.type, id: ref(e.from.type, e.from.key), label: e.label, target: ref(e.to.type, e.to.key) });\n }\n return ops;\n}\n\n/**\n * Provenance-preserving write. Reads existing nodes by natural key (one batched\n * query), merges scalar attrs per the ontology's rules, drops human-owned\n * attrs, then writes merged node updates + all links in one atomic, idempotent\n * transaction. `mutationId` must be deterministic so odla-db can deduplicate\n * the write on retry; this does not make preceding reads or external effects\n * exactly-once.\n */\nexport async function writeFragment(\n db: AdminDb,\n fragment: GraphFragment,\n ontology: Ontology,\n mutationId: string,\n): Promise<{ txId: number; ops: number }> {\n const keys = naturalKeysOf(ontology);\n const rules = mergeRulesOf(ontology);\n const humans = humanAttrsOf(ontology);\n\n // 1. Collapse duplicate nodes within the fragment (same type+key), dropping human attrs.\n const byType = new Map<string, Map<string, Record<string, Value>>>();\n for (const n of fragment.nodes) {\n if (!keys[n.type]) throw new Error(`No natural key configured for node type \"${n.type}\"`);\n const human = humans[n.type] ?? new Set<string>();\n const clean: Record<string, Value> = {};\n for (const [k, v] of Object.entries(n.attrs)) if (!human.has(k)) clean[k] = v;\n const typeMap = byType.get(n.type) ?? new Map<string, Record<string, Value>>();\n const prev = typeMap.get(n.key);\n typeMap.set(n.key, prev ? mergeAttrs(rules[n.type]!, prev, clean) : clean);\n byType.set(n.type, typeMap);\n }\n\n // 2. Batch-read existing rows per type by natural key (one query, filtered per level).\n const query: InstaQLQuery = {};\n for (const [type, m] of byType) {\n query[type] = { $: { where: { [keys[type]!]: { $in: [...m.keys()] } } } };\n }\n const existing: QueryResult = Object.keys(query).length ? await db.query(query) : {};\n const priorOf = (type: string, key: string): Record<string, Value> => {\n const attr = keys[type]!;\n for (const row of existing[type] ?? []) {\n if (row[attr] === key) return row as unknown as Record<string, Value>;\n }\n return {};\n };\n\n // 3. Merged node updates first, then links.\n const ops: Op[] = [];\n for (const [type, m] of byType) {\n const attr = keys[type]!;\n for (const [key, incoming] of m) {\n const merged = mergeAttrs(rules[type]!, priorOf(type, key), incoming);\n ops.push({ t: \"update\", ns: type, id: { ns: type, attr, value: key }, attrs: { ...merged, [attr]: key } });\n }\n }\n const ref = (type: string, key: string): EntityRef => ({ ns: type, attr: keys[type]!, value: key });\n for (const e of fragment.edges) {\n ops.push({ t: \"link\", ns: e.from.type, id: ref(e.from.type, e.from.key), label: e.label, target: ref(e.to.type, e.to.key) });\n }\n\n if (ops.length === 0) return { txId: 0, ops: 0 };\n const { txId } = await db.transact(ops, { mutationId });\n return { txId, ops: ops.length };\n}\n","// UTC-only time bucketing. Date-only strings are read as UTC midnight and all\n// bucket math uses Date.UTC accessors — results never depend on the host zone.\n\n/** Supported bucketing granularities. */\nexport type TimeGrain = \"week\" | \"month\" | \"quarter\";\n\n/** One half-open time bucket: `start` inclusive, `end` exclusive (epoch ms). */\nexport interface TimeBucket {\n key: string;\n start: number;\n end: number;\n}\n\nconst DAY = 86_400_000;\nconst DATE_ONLY = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n/**\n * Parse a date-ish value to epoch ms, or null if unparseable. Accepts\n * `\"YYYY-MM-DD\"` (UTC midnight, with real calendar validation — `2026-02-31`\n * is null), full ISO strings, and finite numbers (already epoch ms).\n */\nexport function parseDateMs(v: unknown): number | null {\n if (typeof v === \"number\") return Number.isFinite(v) ? v : null;\n if (typeof v !== \"string\") return null;\n const m = DATE_ONLY.exec(v.trim());\n if (m) {\n const t = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));\n // JS rolls invalid components (\"2026-02-31\" → Mar 3); a real date\n // round-trips to the same YYYY-MM-DD.\n return new Date(t).toISOString().slice(0, 10) === m[0].trim() ? t : null;\n }\n const t = Date.parse(v);\n return Number.isNaN(t) ? null : t;\n}\n\n// ISO-8601 week: week 1 contains the year's first Thursday; weeks run Mon–Sun.\nfunction isoWeek(t: number): { year: number; week: number } {\n const d = new Date(t);\n const midnight = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());\n const dow = new Date(midnight).getUTCDay() || 7; // Mon=1 … Sun=7\n const thursday = midnight + (4 - dow) * DAY;\n const year = new Date(thursday).getUTCFullYear();\n const week = Math.floor((thursday - Date.UTC(year, 0, 1)) / DAY / 7) + 1;\n return { year, week };\n}\n\n// Monday (UTC midnight) of ISO week `week` of ISO year `year`: Jan 4 is\n// always inside week 1.\nfunction isoWeekStart(year: number, week: number): number {\n const jan4 = Date.UTC(year, 0, 4);\n const dow = new Date(jan4).getUTCDay() || 7;\n return jan4 - (dow - 1) * DAY + (week - 1) * 7 * DAY;\n}\n\nconst pad2 = (n: number) => String(n).padStart(2, \"0\");\n\n/** The bucket key containing epoch-ms `t`: `\"2026-07\"`, `\"2026-Q3\"`, or `\"2026-W29\"`. */\nexport function bucketKey(t: number, grain: TimeGrain): string {\n const d = new Date(t);\n if (grain === \"month\") return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}`;\n if (grain === \"quarter\") return `${d.getUTCFullYear()}-Q${Math.floor(d.getUTCMonth() / 3) + 1}`;\n const { year, week } = isoWeek(t);\n return `${year}-W${pad2(week)}`;\n}\n\n/**\n * Expand a bucket key back to its half-open [start, end) range. Throws on a\n * key that matches no known grain format.\n */\nexport function bucketFor(key: string): TimeBucket {\n let m = /^(\\d{4})-(\\d{2})$/.exec(key);\n if (m) {\n const [y, mo] = [Number(m[1]), Number(m[2])];\n return { key, start: Date.UTC(y, mo - 1, 1), end: Date.UTC(y, mo, 1) };\n }\n m = /^(\\d{4})-Q([1-4])$/.exec(key);\n if (m) {\n const [y, q] = [Number(m[1]), Number(m[2])];\n return { key, start: Date.UTC(y, (q - 1) * 3, 1), end: Date.UTC(y, q * 3, 1) };\n }\n m = /^(\\d{4})-W(\\d{2})$/.exec(key);\n if (m) {\n const start = isoWeekStart(Number(m[1]), Number(m[2]));\n return { key, start, end: start + 7 * DAY };\n }\n throw new Error(`not a bucket key: ${key}`);\n}\n\n/**\n * Every bucket from the one containing `from` through the one containing `to`,\n * in order. Returns [] when from > to.\n */\nexport function bucketRange(from: number, to: number, grain: TimeGrain): TimeBucket[] {\n const out: TimeBucket[] = [];\n if (from > to) return out;\n let cur = bucketFor(bucketKey(from, grain));\n const last = bucketKey(to, grain);\n for (;;) {\n out.push(cur);\n if (cur.key === last) return out;\n cur = bucketFor(bucketKey(cur.end, grain));\n }\n}\n","// Read a GraphFragment back OUT of odla-db — the inverse of the writer, and\n// the bridge from the live graph to the pure @odla-ai/kg/temporal analytics.\n// Reads are per-type offset walks with link labels as nested InstaQL keys\n// (odla-db has no cross-relation where; filter per level). Reverse labels are\n// normalized to FORWARD edges so the fragment satisfies resolvePath's\n// forward-only traversal.\nimport type { AdminDb } from \"@odla-ai/db\";\nimport type { GraphFragment, GraphNode, GraphEdge } from \"../types\";\nimport type { Ontology } from \"../ontology\";\nimport { naturalKeysOf } from \"../ontology\";\nimport { parseDateMs } from \"../temporal/time\";\n\n/** One entity type to export. */\nexport interface ExportEntitySpec {\n /** Link labels (forward or reverse, as queried in InstaQL) to pull as nested rows. */\n links?: readonly string[];\n /** Attr allowlist kept on nodes. Default: every scalar attr except `id` and links. */\n attrs?: readonly string[];\n /**\n * Date window. Filtering is client-side by default; set `indexed: true`\n * (only when the attr really is indexed) to also push a\n * `where { [dateAttr]: { $gte: from } }` to the server — odla-db silently\n * returns zero rows for `where` on unindexed attrs.\n */\n window?: { dateAttr: string; from: string; indexed?: boolean };\n /** Row cap for this type across the offset walk. Default 1000. */\n limit?: number;\n}\n\n/** What {@link exportFragment} should read. */\nexport interface ExportSpec {\n entities: Record<string, ExportEntitySpec>;\n /** Rows per page in the offset walk (1–1000). Default 200. */\n pageSize?: number;\n}\n\ninterface LinkPlan {\n label: string;\n counterpart: string;\n /** Forward: root --forwardLabel--> nested. Reverse: nested --forwardLabel--> root. */\n direction: \"forward\" | \"reverse\";\n forwardLabel: string;\n}\n\nfunction planLinks(ontology: Ontology, type: string, labels: readonly string[]): LinkPlan[] {\n return labels.map((label) => {\n for (const edge of Object.values(ontology.edges)) {\n if (edge.forwardLabel === label && edge.from === type) {\n return { label, counterpart: edge.to, direction: \"forward\" as const, forwardLabel: edge.forwardLabel };\n }\n if (edge.reverseLabel === label && edge.to === type) {\n return { label, counterpart: edge.from, direction: \"reverse\" as const, forwardLabel: edge.forwardLabel };\n }\n }\n throw new Error(`exportFragment: no ontology edge matches link \"${label}\" on \"${type}\"`);\n });\n}\n\nconst isScalar = (v: unknown) => v === null || (typeof v !== \"object\" && typeof v !== \"function\");\n\nfunction pickAttrs(row: Record<string, unknown>, spec: ExportEntitySpec, links: readonly string[]) {\n const out: GraphNode[\"attrs\"] = {};\n for (const [k, v] of Object.entries(row)) {\n if (spec.attrs ? !spec.attrs.includes(k) : k === \"id\" || links.includes(k) || !isScalar(v)) continue;\n if (isScalar(v) && v !== null) out[k] = v as GraphNode[\"attrs\"][string];\n }\n return out;\n}\n\nconst RETRYABLE_WITHOUT_WHERE = /query_scan_limit|query_too_complex/;\n\n/**\n * Export (a window of) the graph as a `GraphFragment` — nodes and forward\n * edges — ready for `extractEvents` and the rest of `@odla-ai/kg/temporal`.\n *\n * Each entity type is read with a stable offset walk (ordered by its natural\n * key) carrying its requested link labels as nested relations. Date windows\n * filter client-side; for attrs declared `indexed` the filter also runs\n * server-side, and if that indexed filter ever exceeds the scan budget\n * (`query_scan_limit`) the walk restarts without `where`.\n * Nodes dedupe by `type:key` keeping the richer attr set; output ordering is\n * deterministic (nodes by type/key, edges by from/label/to).\n */\nexport async function exportFragment(db: AdminDb, ontology: Ontology, spec: ExportSpec): Promise<GraphFragment> {\n const keys = naturalKeysOf(ontology);\n const pageSize = Math.min(1000, Math.max(1, spec.pageSize ?? 200));\n const nodes = new Map<string, GraphNode>();\n const edges = new Map<string, GraphEdge>();\n\n const putNode = (node: GraphNode) => {\n const id = `${node.type}:${node.key}`;\n const existing = nodes.get(id);\n if (!existing || Object.keys(node.attrs).length > Object.keys(existing.attrs).length) nodes.set(id, node);\n };\n\n for (const [type, entity] of Object.entries(spec.entities)) {\n const naturalKey = keys[type];\n if (!naturalKey) throw new Error(`exportFragment: no ontology entity \"${type}\"`);\n const links = planLinks(ontology, type, entity.links ?? []);\n const limit = entity.limit ?? 1000;\n const fromMs = entity.window ? parseDateMs(entity.window.from) : null;\n let useWhere = entity.window?.indexed === true;\n let offset = 0;\n let taken = 0;\n\n while (taken < limit) {\n const opts: Record<string, unknown> = {\n limit: Math.min(pageSize, limit - taken),\n offset,\n order: { [naturalKey]: \"asc\" },\n };\n if (useWhere && entity.window) opts.where = { [entity.window.dateAttr]: { $gte: entity.window.from } };\n const query = {\n [type]: { $: opts, ...Object.fromEntries(links.map((l) => [l.label, {}])) },\n };\n\n let rows: Array<Record<string, unknown>>;\n try {\n rows = ((await db.query(query)) as Record<string, unknown[]>)[type] as Array<Record<string, unknown>> ?? [];\n } catch (err) {\n if (useWhere && RETRYABLE_WITHOUT_WHERE.test(String((err as Error).message ?? err))) {\n // Unindexed attr blew the scan budget — restart this type's walk\n // without `where` (offsets aren't comparable across filters; the\n // dedupe maps make re-reads harmless) and filter client-side.\n useWhere = false;\n offset = 0;\n taken = 0;\n continue;\n }\n throw err;\n }\n\n for (const row of rows) {\n const key = row[naturalKey];\n if (key === undefined || key === null) continue; // defensive: unkeyed row\n if (entity.window && fromMs !== null) {\n const t = parseDateMs(row[entity.window.dateAttr]);\n if (t !== null && t < fromMs) continue;\n }\n const rootRef = { type, key: String(key) };\n putNode({ ...rootRef, attrs: pickAttrs(row, entity, entity.links ?? []) });\n\n for (const plan of links) {\n const nestedKeyAttr = keys[plan.counterpart];\n if (!nestedKeyAttr) throw new Error(`exportFragment: no ontology entity \"${plan.counterpart}\"`);\n // has-many relations come back as arrays, has-one as a bare object.\n const raw = row[plan.label];\n const nestedRows = Array.isArray(raw) ? raw : raw ? [raw] : [];\n for (const nested of nestedRows as Array<Record<string, unknown>>) {\n const nestedKey = nested[nestedKeyAttr];\n if (nestedKey === undefined || nestedKey === null) continue;\n const nestedRef = { type: plan.counterpart, key: String(nestedKey) };\n putNode({ ...nestedRef, attrs: pickAttrs(nested, {}, []) });\n const edge: GraphEdge =\n plan.direction === \"forward\"\n ? { from: rootRef, to: nestedRef, label: plan.forwardLabel }\n : { from: nestedRef, to: rootRef, label: plan.forwardLabel };\n edges.set(`${edge.from.type}:${edge.from.key}|${edge.label}|${edge.to.type}:${edge.to.key}`, edge);\n }\n }\n taken++;\n if (taken >= limit) break;\n }\n\n if (rows.length < pageSize) break;\n offset += rows.length;\n }\n }\n\n const byRef = (a: { type: string; key: string }, b: { type: string; key: string }) =>\n a.type < b.type ? -1 : a.type > b.type ? 1 : a.key < b.key ? -1 : a.key > b.key ? 1 : 0;\n return {\n nodes: [...nodes.values()].sort(byRef),\n edges: [...edges.values()].sort(\n (a, b) => byRef(a.from, b.from) || (a.label < b.label ? -1 : a.label > b.label ? 1 : 0) || byRef(a.to, b.to),\n ),\n };\n}\n","import type { AdminDb, Entity } from \"@odla-ai/db\";\nimport type { GraphFragment } from \"../types\";\nimport type { Ontology } from \"../ontology\";\n\n/** FTS5 search over a namespace's text fields (odla-db QueryOpts.search). */\nexport async function searchEntities(db: AdminDb, type: string, q: string, limit = 5): Promise<Entity[]> {\n if (!q.trim()) return [];\n const res = await db.query({ [type]: { $: { search: q, limit } } });\n return (res[type] ?? []) as Entity[];\n}\n\nconst norm = (s: string): string => s.toLowerCase().replace(/[^a-z0-9]+/g, \"\");\n\n/**\n * Resolve a name to an existing entity's natural key via FTS — for merging a\n * re-discovery that slugified differently. Conservative: only returns a hit when\n * the top match's name/key normalizes identically to the query.\n */\nexport async function resolveName(\n db: AdminDb,\n type: string,\n keyAttr: string,\n name: string,\n): Promise<string | null> {\n const target = norm(name);\n if (!target) return null;\n for (const h of await searchEntities(db, type, name, 3)) {\n const key = h[keyAttr];\n const hname = h[\"name\"];\n if (typeof key !== \"string\") continue;\n if ((typeof hname === \"string\" && norm(hname) === target) || norm(key) === target) return key;\n }\n return null;\n}\n\n/**\n * Canonicalize a fragment's searchable nodes against existing entities via FTS,\n * remapping node keys (and the edges that reference them) to the canonical key.\n * Used on the lower-volume discovery/enrich paths, not high-volume ingest.\n */\nexport async function canonicalizeFragment(\n db: AdminDb,\n fragment: GraphFragment,\n ontology: Ontology,\n): Promise<GraphFragment> {\n const remap = new Map<string, string>(); // `${type}:${key}` -> canonical key\n for (const n of fragment.nodes) {\n const spec = ontology.entities[n.type];\n if (!spec?.searchable?.length) continue;\n const name = typeof n.attrs.name === \"string\" ? n.attrs.name : \"\";\n if (!name) continue;\n const canon = await resolveName(db, n.type, spec.naturalKey, name);\n if (canon && canon !== n.key) remap.set(`${n.type}:${n.key}`, canon);\n }\n if (remap.size === 0) return fragment;\n\n const rk = (type: string, key: string): string => remap.get(`${type}:${key}`) ?? key;\n return {\n nodes: fragment.nodes.map((n) => ({ ...n, key: rk(n.type, n.key) })),\n edges: fragment.edges.map((e) => ({\n from: { type: e.from.type, key: rk(e.from.type, e.from.key) },\n to: { type: e.to.type, key: rk(e.to.type, e.to.key) },\n label: e.label,\n })),\n };\n}\n","// Deterministic randomness for temporal analytics. Communities and layout\n// derive ALL randomness from node identity + an explicit seed, so results are\n// pure functions of their inputs — never of Map iteration or insertion order.\n\n/**\n * FNV-1a 32-bit hash of a string. Used to derive a per-identity PRNG seed\n * (`mulberry32(hash32(id) ^ seed)`), which makes node placement independent\n * of the order nodes arrive in.\n */\nexport function hash32(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n/**\n * mulberry32: a tiny, fast, seedable PRNG returning floats in [0, 1).\n * The same seed always yields the same sequence — replayable layouts and\n * community assignments depend on this.\n */\nexport function mulberry32(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x6d2b79f5) >>> 0;\n let t = a;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Fisher–Yates shuffle into a NEW array, driven by the supplied random source\n * (pass a seeded `mulberry32` for a deterministic permutation).\n */\nexport function shuffle<T>(items: readonly T[], rand: () => number): T[] {\n const out = items.slice();\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(rand() * (i + 1));\n const tmp = out[i] as T; // i, j always in bounds\n out[i] = out[j] as T;\n out[j] = tmp;\n }\n return out;\n}\n","// Deterministic label propagation. Classic LPA needs random visit order to\n// break symmetry; here that randomness comes from one seeded shuffle stream,\n// so the same (nodes, edges, seed) always yields the same communities.\nimport { mulberry32, shuffle } from \"./prng\";\nimport type { NodeId } from \"./adjacency\";\n\n/**\n * Asynchronous label propagation with deterministic (seeded) tie-breaking:\n * - labels start as each node's own id;\n * - each round visits nodes in a seeded-shuffled order (one PRNG stream\n * reused across rounds) and adopts the label with the greatest total\n * incident edge weight; if the node's CURRENT label ties for the max it is\n * kept, otherwise a tied max is picked via the same seeded stream (a\n * lexicographic tie-break would systematically bleed communities toward\n * alphabetically small ids — verified on the two-triangles fixture);\n * - stops on a round with no change, or after `maxRounds`;\n * - finally each community's label is canonicalized to its smallest member\n * id, so the output is independent of propagation history.\n * Isolated nodes keep their own label (singleton communities).\n */\nexport function labelPropagation(\n nodes: readonly NodeId[],\n edges: ReadonlyArray<{ a: NodeId; b: NodeId; weight?: number }>,\n opts: { seed?: number; maxRounds?: number } = {},\n): Map<NodeId, string> {\n const { seed = 1, maxRounds = 20 } = opts;\n const sorted = [...nodes].sort();\n const labels = new Map<NodeId, string>(sorted.map((id) => [id, id]));\n\n const neighbors = new Map<NodeId, Array<{ id: NodeId; weight: number }>>();\n for (const e of edges) {\n if (!labels.has(e.a) || !labels.has(e.b) || e.a === e.b) continue;\n const w = e.weight ?? 1;\n let listA = neighbors.get(e.a);\n if (!listA) neighbors.set(e.a, (listA = []));\n listA.push({ id: e.b, weight: w });\n let listB = neighbors.get(e.b);\n if (!listB) neighbors.set(e.b, (listB = []));\n listB.push({ id: e.a, weight: w });\n }\n\n const rand = mulberry32(seed);\n for (let round = 0; round < maxRounds; round++) {\n const order = shuffle(sorted, rand);\n let changed = false;\n for (const id of order) {\n const incident = neighbors.get(id);\n if (!incident || incident.length === 0) continue;\n const votes = new Map<string, number>();\n for (const n of incident) {\n const label = labels.get(n.id)!;\n votes.set(label, (votes.get(label) ?? 0) + n.weight);\n }\n let best: string[] = [];\n let bestWeight = -Infinity;\n for (const [label, weight] of votes) {\n if (weight > bestWeight) {\n bestWeight = weight;\n best = [label];\n } else if (weight === bestWeight) {\n best.push(label);\n }\n }\n const current = labels.get(id)!;\n if (best.includes(current)) continue; // keep on tie — stability first\n best.sort();\n const pick = best.length === 1 ? best[0]! : best[Math.floor(rand() * best.length)]!;\n labels.set(id, pick);\n changed = true;\n }\n if (!changed) break;\n }\n\n // Canonicalize: community label = smallest member id.\n const members = new Map<string, NodeId[]>();\n for (const [id, label] of labels) {\n const list = members.get(label);\n list ? list.push(id) : members.set(label, [id]);\n }\n const canonical = new Map<string, string>();\n // Members collect in `sorted` iteration order, so ids[0] is the minimum.\n for (const [label, ids] of members) canonical.set(label, ids[0]!);\n return new Map(sorted.map((id) => [id, canonical.get(labels.get(id)!)!]));\n}\n","// Connection lenses: project a heterogeneous graph onto ONE node universe\n// (companies, people, places…) under a chosen relatedness — shared bridges\n// (people, investors), spatial proximity, temporal cohort — and blend several\n// lenses into the weighted edge set a layout/community pass consumes. Pure\n// and deterministic; the viewer re-runs these live as sliders move.\nimport type { GraphFragment } from \"../types\";\n\n/** A 2-D point in a normalized layout/map space (for the geo proximity lens). */\nexport interface XY {\n x: number;\n y: number;\n}\n\n/** One undirected weighted pair, with (capped) bridging node ids as evidence. */\nexport interface WeightedPair {\n a: string;\n b: string;\n w: number;\n via: string[];\n}\n\n/**\n * Bridge → target sets through a REIFIED relationship node (affiliation,\n * funding_round, record): every reified node with a `bridgeLabel` edge to the\n * bridge and a `targetLabel` edge to a target contributes that target to the\n * bridge's set. Ids are `type:key`.\n */\nexport function reifiedBridgeTargets(\n fragment: GraphFragment,\n opts: { reified: string; bridgeLabel: string; targetLabel: string },\n): Map<string, Set<string>> {\n const bridgesOf = new Map<string, string[]>(); // a round can have several investors\n const targetsOf = new Map<string, string[]>();\n for (const e of fragment.edges) {\n if (e.from.type !== opts.reified) continue;\n const to = `${e.to.type}:${e.to.key}`;\n if (e.label === opts.bridgeLabel) (bridgesOf.get(e.from.key) ?? bridgesOf.set(e.from.key, []).get(e.from.key))!.push(to);\n if (e.label === opts.targetLabel) (targetsOf.get(e.from.key) ?? targetsOf.set(e.from.key, []).get(e.from.key))!.push(to);\n }\n const out = new Map<string, Set<string>>();\n for (const [reifiedKey, bridges] of bridgesOf) {\n for (const bridge of bridges) {\n for (const target of targetsOf.get(reifiedKey) ?? []) {\n (out.get(bridge) ?? out.set(bridge, new Set()).get(bridge))!.add(target);\n }\n }\n }\n return out;\n}\n\n/** Direct-edge targets: `from` node id → set of `to` node ids for one label. */\nexport function directTargets(fragment: GraphFragment, label: string): Map<string, Set<string>> {\n const out = new Map<string, Set<string>>();\n for (const e of fragment.edges) {\n if (e.label !== label) continue;\n const from = `${e.from.type}:${e.from.key}`;\n (out.get(from) ?? out.set(from, new Set()).get(from))!.add(`${e.to.type}:${e.to.key}`);\n }\n return out;\n}\n\n/** Compose A→B with B→C into A→C (e.g. person→companies ∘ company→counties). */\nexport function composeTargets(\n ab: Map<string, Set<string>>,\n bc: Map<string, Set<string>>,\n): Map<string, Set<string>> {\n const out = new Map<string, Set<string>>();\n for (const [a, bs] of ab) {\n for (const b of bs) {\n for (const c of bc.get(b) ?? []) {\n (out.get(a) ?? out.set(a, new Set()).get(a))!.add(c);\n }\n }\n }\n return out;\n}\n\n/**\n * The bipartite projection: targets sharing a bridge become pairs, weight =\n * number of distinct shared bridges. `maxPerBridge` skips promiscuous hubs\n * (a bridge touching 40 targets says little about any pair). Deterministic\n * pair order (a < b, then sorted).\n */\nexport function projectBridges(\n bridges: Map<string, Set<string>>,\n opts: { maxPerBridge?: number; viaCap?: number } = {},\n): WeightedPair[] {\n const { maxPerBridge = 12, viaCap = 4 } = opts;\n const acc = new Map<string, { a: string; b: string; w: number; via: string[] }>();\n for (const [bridge, targetSet] of [...bridges.entries()].sort((x, y) => (x[0] < y[0] ? -1 : 1))) {\n const targets = [...targetSet].sort();\n if (targets.length < 2 || targets.length > maxPerBridge) continue;\n for (let i = 0; i < targets.length; i++) {\n for (let j = i + 1; j < targets.length; j++) {\n const key = `${targets[i]}|${targets[j]}`;\n const hit = acc.get(key) ?? { a: targets[i]!, b: targets[j]!, w: 0, via: [] };\n hit.w += 1;\n if (hit.via.length < viaCap) hit.via.push(bridge);\n acc.set(key, hit);\n }\n }\n }\n return [...acc.keys()].sort().map((k) => acc.get(k)!);\n}\n\n/**\n * Temporal cohort pairs: each node links to its k nearest neighbors in\n * arrival time, weight decaying with the gap (0.5 per halfLife). Nodes\n * without a date contribute nothing.\n */\nexport function timePairs(\n arrivals: Map<string, number>,\n opts: { k?: number; halfLifeDays?: number } = {},\n): WeightedPair[] {\n const { k = 4, halfLifeDays = 30 } = opts;\n const halfLife = halfLifeDays * 86_400_000;\n const rows = [...arrivals.entries()].filter(([, t]) => Number.isFinite(t)).sort((x, y) => x[1] - y[1] || (x[0] < y[0] ? -1 : 1));\n const acc = new Map<string, WeightedPair>();\n for (let i = 0; i < rows.length; i++) {\n for (let j = i + 1; j < Math.min(rows.length, i + 1 + k); j++) {\n const [idA, tA] = rows[i]!;\n const [idB, tB] = rows[j]!;\n const w = Math.pow(0.5, Math.abs(tB - tA) / halfLife);\n if (w < 0.01) break; // sorted by time — later neighbors are only farther\n const [a, b] = idA < idB ? [idA, idB] : [idB, idA];\n acc.set(`${a}|${b}`, { a, b, w, via: [] }); // i<j scan visits each pair once\n }\n }\n return [...acc.keys()].sort().map((key) => acc.get(key)!);\n}\n\n/**\n * Spatial proximity pairs: k nearest neighbors in normalized map space,\n * weight decaying with distance (0.5 per halfDist). Nodes without a\n * position contribute nothing. O(n²) — fine for the ≤1k-node universes\n * lenses operate on.\n */\nexport function geoPairs(\n positions: Map<string, XY>,\n opts: { k?: number; halfDist?: number } = {},\n): WeightedPair[] {\n const { k = 4, halfDist = 0.04 } = opts;\n const rows = [...positions.entries()].sort((x, y) => (x[0] < y[0] ? -1 : 1));\n const acc = new Map<string, WeightedPair>();\n for (let i = 0; i < rows.length; i++) {\n const dists: Array<{ j: number; d: number }> = [];\n for (let j = 0; j < rows.length; j++) {\n if (i === j) continue;\n const dx = rows[i]![1].x - rows[j]![1].x;\n const dy = rows[i]![1].y - rows[j]![1].y;\n dists.push({ j, d: Math.hypot(dx, dy) });\n }\n dists.sort((x, y) => x.d - y.d || x.j - y.j);\n for (const { j, d } of dists.slice(0, k)) {\n const w = Math.pow(0.5, d / halfDist);\n if (w < 0.01) continue;\n const [a, b] = rows[i]![0] < rows[j]![0] ? [rows[i]![0], rows[j]![0]] : [rows[j]![0], rows[i]![0]];\n const key = `${a}|${b}`;\n const prev = acc.get(key);\n if (!prev || prev.w < w) acc.set(key, { a, b, w, via: [] });\n }\n }\n return [...acc.keys()].sort().map((key) => acc.get(key)!);\n}\n\n/**\n * Blend lens edge sets: each set is normalized to a max weight of 1 (a lens\n * with big raw counts must not drown the others), scaled by its slider\n * weight, and summed per pair. Zero-weight lenses drop out entirely.\n */\nexport function blendPairs(sets: Array<{ pairs: WeightedPair[]; weight: number }>): WeightedPair[] {\n const acc = new Map<string, WeightedPair>();\n for (const { pairs, weight } of sets) {\n if (weight <= 0 || pairs.length === 0) continue;\n const max = Math.max(...pairs.map((p) => p.w));\n for (const p of pairs) {\n const key = `${p.a}|${p.b}`;\n const hit = acc.get(key) ?? { a: p.a, b: p.b, w: 0, via: [] };\n hit.w += (p.w / max) * weight;\n for (const v of p.via) if (hit.via.length < 6 && !hit.via.includes(v)) hit.via.push(v);\n acc.set(key, hit);\n }\n }\n return [...acc.keys()].sort().map((key) => acc.get(key)!);\n}\n","// Brokerage: which nodes connect otherwise-distinct groups? Scored against\n// the community partition (labelPropagation) already computed per frame:\n// a node's bridging weight is its edge weight INTO foreign communities,\n// amplified by how many distinct groups it touches. Pure and cheap (O(E))\n// — cheap enough to recompute every frame of a replay.\nimport type { NodeId } from \"./adjacency\";\n\n/** One node's brokerage: normalized score + the communities it connects. */\nexport interface Bridging {\n /** 0..1, 1 = the frame's strongest connector. */\n score: number;\n /** Communities this node touches through its edges (own community first). */\n groups: string[];\n}\n\n/**\n * Score every node's brokerage against a community partition. Edges inside a\n * community contribute nothing; an edge to a foreign community contributes\n * its weight, and the total is multiplied by the number of DISTINCT foreign\n * communities — a node wiring three groups together outranks one with the\n * same raw weight into a single neighbor group. Scores are normalized to\n * the frame's max (empty/one-community frames → all zeros).\n */\nexport function bridgingScores(\n edges: ReadonlyArray<{ a: NodeId; b: NodeId; weight?: number }>,\n labels: Map<NodeId, string>,\n): Map<NodeId, Bridging> {\n const cross = new Map<NodeId, number>();\n const foreign = new Map<NodeId, Set<string>>();\n const touch = (id: NodeId, otherLabel: string, w: number) => {\n cross.set(id, (cross.get(id) ?? 0) + w);\n (foreign.get(id) ?? foreign.set(id, new Set()).get(id))!.add(otherLabel);\n };\n for (const e of edges) {\n const la = labels.get(e.a);\n const lb = labels.get(e.b);\n if (la === undefined || lb === undefined || la === lb) continue;\n const w = e.weight ?? 1;\n touch(e.a, lb, w);\n touch(e.b, la, w);\n }\n let max = 0;\n const raw = new Map<NodeId, number>();\n for (const [id, w] of cross) {\n const r = w * foreign.get(id)!.size;\n raw.set(id, r);\n if (r > max) max = r;\n }\n const out = new Map<NodeId, Bridging>();\n for (const [id] of labels) {\n const r = raw.get(id) ?? 0;\n const own = labels.get(id)!;\n const groups = r > 0 ? [own, ...[...foreign.get(id)!].sort()] : [own];\n out.set(id, { score: max > 0 ? r / max : 0, groups });\n }\n return out;\n}\n\n/** The frame's strongest connectors, ranked (ties: id order). */\nexport function topBridges(\n bridging: Map<NodeId, Bridging>,\n n = 5,\n): Array<{ id: NodeId; score: number; groups: string[] }> {\n return [...bridging.entries()]\n .filter(([, b]) => b.score > 0)\n .sort((x, y) => y[1].score - x[1].score || (x[0] < y[0] ? -1 : 1))\n .slice(0, n)\n .map(([id, b]) => ({ id, score: Math.round(b.score * 100) / 100, groups: b.groups }));\n}\n","// Connector discovery: given a GraphFragment and a set of \"lenses\" (which\n// shared entity connects two nodes of the universe type), find the nodes that\n// bridge otherwise-distinct groups — the brokers/connectors. Pure and\n// deterministic; the headless form of what the retired galaxy viewer showed.\n//\n// A lens says \"two <universe> nodes are related when they share an <X>, where\n// X links to the universe node through reified node R via labels (bridge, of)\".\n// e.g. two companies share a PERSON via `affiliation` (of_person → of_company),\n// or share an INVESTOR via `funding_round` (raised_from → round_of).\nimport type { GraphFragment } from \"../types\";\nimport {\n blendPairs,\n bridgingScores,\n labelPropagation,\n projectBridges,\n reifiedBridgeTargets,\n topBridges,\n type WeightedPair,\n} from \"../temporal\";\n\n/** One relatedness lens over the universe nodes. */\nexport interface ConnectorLens {\n /** Reified relationship node type carrying both edges (e.g. `\"affiliation\"`). */\n reified: string;\n /** Edge label to the SHARED entity that bridges (e.g. `\"of_person\"`). */\n bridge: string;\n /** Edge label to the UNIVERSE node the shared entity connects (e.g. `\"of_company\"`). */\n of: string;\n /** Blend weight (each lens is normalized to max 1 first). Default 1. */\n weight?: number;\n}\n\n/** How to find connectors: the lenses to blend, plus limits. */\nexport interface ConnectorConfig {\n lenses: ConnectorLens[];\n /** Extra precomputed pairs to fold in (e.g. geo/time affinity). */\n extraPairs?: Array<{ pairs: WeightedPair[]; weight: number }>;\n /** How many connectors to return. Default 12. */\n top?: number;\n /** Skip promiscuous shared entities touching more than this many nodes. Default 12. */\n maxPerBridge?: number;\n}\n\n/** One bridging node: the groups it joins and the shared entities that link it out. */\nexport interface Connector {\n /** `type:key` of the connector node. */\n ref: string;\n name: string;\n /** 0..1 brokerage — 1 is the strongest connector found. */\n score: number;\n /** Representative names of the distinct groups it bridges. */\n groups: string[];\n /** Names of the shared entities (people/investors/…) linking it to other groups. */\n via: string[];\n}\n\n/** The ranked connectors plus the size of the graph they were found in. */\nexport interface ConnectorReport {\n connectors: Connector[];\n /** Distinct communities in the blended graph. */\n communities: number;\n /** Universe nodes with at least one relationship. */\n nodes: number;\n}\n\n/**\n * Rank the connectors of a graph. Builds the blended relationship graph from\n * the lenses, detects communities, scores each node's cross-community\n * brokerage, and returns the top bridges with the groups they join and the\n * shared entities behind each link. Empty/relationshipless input → empty report.\n */\nexport function findConnectors(fragment: GraphFragment, config: ConnectorConfig): ConnectorReport {\n const nameOf = new Map<string, string>();\n for (const n of fragment.nodes) {\n const label = n.attrs.name ?? n.attrs.title;\n if (typeof label === \"string\" && label) nameOf.set(`${n.type}:${n.key}`, label);\n }\n\n const sets = config.lenses.map((l) => ({\n pairs: projectBridges(reifiedBridgeTargets(fragment, { reified: l.reified, bridgeLabel: l.bridge, targetLabel: l.of }), {\n maxPerBridge: config.maxPerBridge ?? 12,\n }),\n weight: l.weight ?? 1,\n }));\n const pairs = blendPairs([...sets, ...(config.extraPairs ?? [])]);\n if (pairs.length === 0) return { connectors: [], communities: 0, nodes: 0 };\n\n const nodes = [...new Set(pairs.flatMap((p) => [p.a, p.b]))].sort();\n const edges = pairs.map((p) => ({ a: p.a, b: p.b, weight: p.w }));\n const labels = labelPropagation(nodes, edges, { seed: 1 });\n\n // Representative display name per community: an anchor member's name.\n const groupName = new Map<string, string>();\n for (const id of nodes) {\n const g = labels.get(id)!;\n if (!groupName.has(g)) groupName.set(g, nameOf.get(id) ?? id);\n }\n\n const bridging = bridgingScores(edges, labels);\n const viaOf = (id: string) => {\n const names = new Set<string>();\n for (const p of pairs) {\n if (p.a !== id && p.b !== id) continue;\n // via ids are always non-empty `type:key`, so pop() is a string.\n for (const v of p.via) names.add(nameOf.get(v) ?? v.split(\":\").pop()!);\n }\n return [...names].slice(0, 6);\n };\n\n const connectors = topBridges(bridging, config.top ?? 12).map((b): Connector => ({\n ref: b.id,\n name: nameOf.get(b.id) ?? b.id,\n score: b.score,\n groups: b.groups.map((g) => groupName.get(g)!), // every label has an anchor name\n\n via: viaOf(b.id),\n }));\n\n return { connectors, communities: new Set(labels.values()).size, nodes: nodes.length };\n}\n","/**\n * Identity + dedup helpers. The slug / urlHash a node resolves to IS its\n * natural key for Lookup-based upserts, so these must be stable and pure.\n * Ported from agnacl (utils/markdown.ts slugify, services/kv.ts normalizeUrl).\n */\n\n/** NFKD-normalize, strip accents, lowercase, non-alphanumerics → \"-\", cap 60. */\nexport function slugify(input: string): string {\n return input\n .normalize(\"NFKD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60)\n .replace(/-+$/g, \"\");\n}\n\nconst LEGAL_SUFFIXES = new Set([\"co\", \"company\", \"corp\", \"corporation\", \"inc\", \"incorporated\", \"llc\", \"llp\", \"ltd\", \"limited\", \"pllc\"]);\n\n/**\n * Collapse a name to a comparison key: accents stripped, `&` spelled out,\n * punctuation and spacing removed, and trailing legal suffixes dropped.\n *\n * Stronger than comparing {@link slugify} output, because it matches\n * \"Acme Corp.\", \"Acme, Inc.\" and \"Acme & Co\" onto the same key — the shapes the\n * same organization is actually written in across sources. Use it to decide\n * whether two records are the same entity; use `slugify` for the natural key\n * that gets stored.\n */\nexport function identityKey(value: string): string {\n const tokens = value\n .normalize(\"NFKD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLowerCase()\n .replace(/&/g, \" and \")\n .match(/[a-z0-9]+/g) ?? [];\n while (tokens.length > 1 && LEGAL_SUFFIXES.has(tokens.at(-1) ?? \"\")) {\n tokens.pop();\n if (tokens.at(-1) === \"and\") tokens.pop();\n }\n return tokens.join(\"\");\n}\n\n/** Drop tracking params, hash, and trailing slash so the same page maps to one key. */\nexport function normalizeUrl(raw: string): string {\n try {\n const u = new URL(raw);\n u.hash = \"\";\n const drop = [/^utm_/i, /^ref$/i, /^ref_/i, /^fbclid$/i, /^gclid$/i, /^mc_/i];\n for (const key of [...u.searchParams.keys()]) {\n if (drop.some((re) => re.test(key))) u.searchParams.delete(key);\n }\n let s = u.toString();\n s = s.replace(/\\/$/, \"\");\n return s;\n } catch {\n return raw.trim().replace(/#.*$/, \"\").replace(/\\/$/, \"\");\n }\n}\n\n/** Stable short key for a URL (FNV-1a 32-bit, base36). Used as the `signal` natural key. */\nexport function urlHash(raw: string): string {\n const s = normalizeUrl(raw);\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(36);\n}\n\n/**\n * Reject descriptive phrases masquerading as people (\"three craftsmen\",\n * \"the founders\"). A real name is 2-4 capitalized tokens, no digits.\n * Ported from agnacl utils/names.ts (isLikelyPersonName).\n */\nexport function isLikelyPersonName(name: string): boolean {\n const n = name.trim();\n if (!n || /\\d/.test(n)) return false;\n const parts = n.split(/\\s+/);\n if (parts.length < 2 || parts.length > 4) return false;\n return parts.every((p) => /^[A-Z][a-zA-Z'’.-]*$/.test(p));\n}\n","// Company enrichment: prompts, forced-tool schemas, and the pure fragment\n// builders that turn a model's research/structuring output into an idempotent\n// GraphFragment (company attrs + person/affiliation/funding_round/investor\n// nodes). The model's output is a proposal — everything here is capped and\n// validated before it becomes graph. This module is data + pure functions; the\n// LLM runners live in ./run.\nimport type { ExtractTool } from \"@odla-ai/ai\";\nimport { slugify } from \"../util/identity\";\nimport type { GraphFragment, GraphNode } from \"../types\";\n\n/** The minimal company row enrichment reads (attrs only — never hydrates). */\nexport interface CompanyRow {\n slug: string;\n name: string;\n city?: string;\n region?: string;\n first_seen?: string;\n founded?: string;\n fips?: string;\n}\n\n// ------------------------------------------------------------------ locate\n\n/** System prompt for the county-resolution extraction. */\nexport const LOCATE_SYSTEM =\n \"You resolve US business locations to counties. For each company you are given a city and/or a free-text region. Name the US county containing that city (for independent cities, use the city's own county-equivalent name) plus its 2-letter state. Use nulls when the location is ambiguous, outside the US, or unknown.\";\n\n/** Forced-tool schema pairing each company slug with a county name + state. */\nexport const LOCATE_TOOL: ExtractTool = {\n name: \"resolve_counties\",\n description: \"County name + state per company\",\n parameters: {\n type: \"object\",\n properties: {\n items: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n slug: { type: \"string\" },\n county: { type: [\"string\", \"null\"], description: 'county name WITHOUT the word \"County\", e.g. \"Santa Clara\"' },\n state: { type: [\"string\", \"null\"], description: \"2-letter USPS state\" },\n },\n required: [\"slug\", \"county\", \"state\"],\n },\n },\n },\n required: [\"items\"],\n },\n};\n\n/** The user prompt for a LOCATE batch. */\nexport function locatePrompt(batch: CompanyRow[]): string {\n return \"Companies:\\n\" + batch.map((c) => `- slug ${c.slug}: city=${c.city ?? \"?\"} region=${c.region ?? \"?\"}`).join(\"\\n\");\n}\n\n// ------------------------------------------------------------------- facts\n\n/** System prompt for the grounded web-search research pass. */\nexport const FACTS_SEARCH_SYSTEM =\n \"You are a research assistant with web search. Find concrete facts about the company: founding year/date, headquarters city and state, founders and key executives (names, roles), and funding (rounds, dates, amounts, investor names). Prefer official/reputable sources. Report dense factual notes; say 'not found' for anything you cannot establish. Do not speculate.\";\n\n/** System prompt for structuring the research notes into {@link FactsDraft}. */\nexport const FACTS_EXTRACT_SYSTEM =\n \"Extract structured facts from the research notes. Use null for anything the notes do not establish.\";\n\n/** Forced-tool schema for the structured company facts (founding, HQ, people, funding). */\nexport const FACTS_EXTRACT_TOOL: ExtractTool = {\n name: \"company_facts\",\n description: \"Structured company facts from research notes\",\n parameters: {\n type: \"object\",\n properties: {\n founded: { type: [\"string\", \"null\"], description: \"YYYY or YYYY-MM or YYYY-MM-DD\" },\n city: { type: [\"string\", \"null\"] },\n state: { type: [\"string\", \"null\"], description: \"2-letter USPS\" },\n people: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: { name: { type: \"string\" }, role: { type: \"string\" } },\n required: [\"name\", \"role\"],\n },\n },\n rounds: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n round: { type: [\"string\", \"null\"] },\n date: { type: [\"string\", \"null\"] },\n amount: { type: [\"string\", \"null\"] },\n investors: { type: \"array\", items: { type: \"string\" } },\n },\n required: [\"investors\"],\n },\n },\n },\n required: [\"people\", \"rounds\"],\n },\n};\n\n/** The model's structured company facts, before validation into a fragment. */\nexport interface FactsDraft {\n founded?: string | null;\n city?: string | null;\n state?: string | null;\n people: Array<{ name: string; role: string }>;\n rounds: Array<{ round?: string | null; date?: string | null; amount?: string | null; investors: string[] }>;\n}\n\nconst DATE_RE = /^\\d{4}(-\\d{2}){0,2}$/;\n\n/** Pad a year/month-precision date to a full ISO date (odla-db date attrs reject bare years). */\nconst isoDate = (v: string): string | null =>\n !DATE_RE.test(v) ? null : v.length === 4 ? `${v}-01-01` : v.length === 7 ? `${v}-01` : v;\n\n/** Loose county-name key for joining model output (\"Santa Clara\",\"CA\") to a gazetteer. */\nexport const countyKey = (name: string, st: string): string =>\n `${String(name).toLowerCase().replace(/\\bcounty\\b|\\bparish\\b|\\bborough\\b/g, \"\").replace(/[^a-z]/g, \"\")}|${String(st).toUpperCase()}`;\n\n/**\n * Turn one company's structured facts into an idempotent fragment: founded/\n * city attrs, person + dated affiliation nodes, funding_round nodes (keyed\n * `slug~e<i>` so they never collide with seed rounds), and investors. Capped\n * and validated — the draft is a proposal, not a command.\n */\nexport function factsToFragment(company: CompanyRow, draft: FactsDraft): GraphFragment {\n const nodes: GraphNode[] = [];\n const edges: GraphFragment[\"edges\"] = [];\n const attrs: Record<string, string> = {};\n const founded = draft.founded ? isoDate(draft.founded) : null;\n if (founded) attrs.founded = founded;\n if (draft.city && !company.city) attrs.city = String(draft.city).slice(0, 60);\n if (Object.keys(attrs).length > 0) {\n nodes.push({ type: \"company\", key: company.slug, attrs: { name: company.name, ...attrs } });\n }\n\n const anchorDate = attrs.founded ?? company.founded ?? company.first_seen ?? \"\";\n for (const p of (draft.people ?? []).slice(0, 5)) {\n const personSlug = slugify(p.name);\n if (!personSlug || personSlug === company.slug) continue;\n nodes.push({ type: \"person\", key: personSlug, attrs: { name: p.name } });\n const key = `${personSlug}~${company.slug}`;\n nodes.push({ type: \"affiliation\", key, attrs: { date: anchorDate, role: String(p.role).slice(0, 60) } });\n edges.push({ from: { type: \"affiliation\", key }, to: { type: \"person\", key: personSlug }, label: \"of_person\" });\n edges.push({ from: { type: \"affiliation\", key }, to: { type: \"company\", key: company.slug }, label: \"of_company\" });\n }\n\n (draft.rounds ?? []).slice(0, 4).forEach((r, i) => {\n const investors = (r.investors ?? []).map(slugify).filter(Boolean).slice(0, 6);\n const date = r.date ? isoDate(r.date) : null;\n if (investors.length === 0 && !date) return; // an undated, investor-less round says nothing\n const key = `${company.slug}~e${i}`;\n const rAttrs: Record<string, string> = { date: date ?? anchorDate };\n if (r.round) rAttrs.round = String(r.round).slice(0, 40);\n if (r.amount) rAttrs.amount = String(r.amount).slice(0, 40);\n nodes.push({ type: \"funding_round\", key, attrs: rAttrs });\n edges.push({ from: { type: \"funding_round\", key }, to: { type: \"company\", key: company.slug }, label: \"round_of\" });\n for (const [j, invSlug] of investors.entries()) {\n nodes.push({ type: \"investor\", key: invSlug, attrs: { name: (r.investors ?? [])[j]! } });\n edges.push({ from: { type: \"funding_round\", key }, to: { type: \"investor\", key: invSlug }, label: \"raised_from\" });\n }\n });\n\n return { nodes, edges };\n}\n\n/** The `located_in` write linking a company to its resolved county. */\nexport function locationFragment(slug: string, name: string, fips: string): GraphFragment {\n return {\n nodes: [{ type: \"company\", key: slug, attrs: { name, fips } }],\n edges: [{ from: { type: \"company\", key: slug }, to: { type: \"county\", key: fips }, label: \"located_in\" }],\n };\n}\n","// Verification verdicts: the vocabulary for \"did we actually find this entity?\".\n// Enrichment without one is unfalsifiable — a fragment either got written or it\n// didn't, and nothing recorded whether the model was sure, guessing, or looking\n// at a different person with the same name. This module is pure: the verdict is\n// a claim to be scored, and everything here validates it rather than trusting it.\n\n/**\n * What a research pass concluded about the entity it was asked to find.\n *\n * `wrong_entity` is deliberately distinct from `not_found`: a pass that\n * confidently describes a *different* person with the same name is a much worse\n * failure than one that admits it found nothing, and a benchmark that collapses\n * the two cannot see it.\n */\nexport type Verdict = \"confirmed\" | \"probable\" | \"ambiguous\" | \"not_found\" | \"wrong_entity\";\n\n/** Verdicts that permit writing enriched facts to the graph. */\nexport const WRITABLE_VERDICTS: readonly Verdict[] = [\"confirmed\", \"probable\"];\n\nconst ALL: readonly Verdict[] = [\"confirmed\", \"probable\", \"ambiguous\", \"not_found\", \"wrong_entity\"];\n\n/** One supporting claim tied to the source that backs it. */\nexport interface Evidence {\n /** What the source establishes, in the model's words. */\n claim: string;\n /** The URL the claim came from. */\n sourceUrl: string;\n /** When the source was read, as an ISO date. */\n retrievedAt?: string;\n}\n\n/** A verdict plus the confidence and evidence offered for it. */\nexport interface Verification {\n verdict: Verdict;\n /** The model's stated confidence, clamped to 0..1. Scored for calibration, never trusted. */\n confidence: number;\n evidence: Evidence[];\n}\n\n/** Whether a value is one of the closed verdict members. */\nexport function isVerdict(value: unknown): value is Verdict {\n return typeof value === \"string\" && (ALL as readonly string[]).includes(value);\n}\n\n/** Whether a verdict permits writing the enriched facts it came with. */\nexport function isWritable(verdict: Verdict): boolean {\n return WRITABLE_VERDICTS.includes(verdict);\n}\n\n/**\n * Normalize a model's self-reported verification into the closed shape.\n *\n * Fails safe in both directions: an unrecognized verdict becomes `ambiguous`\n * (never `confirmed`), a missing or out-of-range confidence is clamped, and\n * evidence without a usable http(s) source URL is dropped — an unsourced claim\n * is exactly the thing this package exists to stop treating as a fact.\n */\nexport function normalizeVerification(draft: unknown, retrievedAt?: string): Verification {\n const d = (draft ?? {}) as Record<string, unknown>;\n const verdict = isVerdict(d.verdict) ? d.verdict : \"ambiguous\";\n const raw = typeof d.confidence === \"number\" && Number.isFinite(d.confidence) ? d.confidence : 0;\n const confidence = Math.min(1, Math.max(0, raw));\n const evidence: Evidence[] = [];\n for (const item of Array.isArray(d.evidence) ? d.evidence.slice(0, 12) : []) {\n const e = (item ?? {}) as Record<string, unknown>;\n const sourceUrl = typeof e.sourceUrl === \"string\" ? e.sourceUrl.trim() : \"\";\n const claim = typeof e.claim === \"string\" ? e.claim.trim() : \"\";\n if (!claim || !isHttpUrl(sourceUrl)) continue;\n evidence.push({ claim: claim.slice(0, 300), sourceUrl, ...(retrievedAt ? { retrievedAt } : {}) });\n }\n // A confirmation nobody can check is not a confirmation.\n if (evidence.length === 0 && isWritable(verdict)) return { verdict: \"ambiguous\", confidence, evidence };\n return { verdict, confidence, evidence };\n}\n\n/** Whether a string is an absolute http(s) URL — the only kind evidence may cite. */\nexport function isHttpUrl(value: string): boolean {\n try {\n const u = new URL(value);\n return u.protocol === \"https:\" || u.protocol === \"http:\";\n } catch {\n return false;\n }\n}\n","// Person enrichment: prompts, the forced-tool schema, and the pure builder that\n// turns a research pass into a verdict plus an idempotent GraphFragment.\n//\n// Scope is deliberate. These are real named individuals, so the pass looks only\n// for a *public professional* footprint and contact points the person or their\n// employer published — never a home address, personal phone, or personal email.\n// Everything the model returns is a proposal: validated, capped, and gated on\n// the verdict before any of it becomes graph.\nimport type { ExtractTool } from \"@odla-ai/ai\";\nimport { identityKey, slugify } from \"../util/identity\";\nimport type { GraphFragment, GraphNode } from \"../types\";\nimport { isWritable, normalizeVerification, type Verification } from \"./verify\";\n\n/** The minimal person row enrichment reads. */\nexport interface PersonRow {\n slug: string;\n name: string;\n /** Natural key of a company the person is already linked to — the strongest disambiguator. */\n companySlug?: string;\n companyName?: string;\n /** The employer's website. What makes first-party evidence reachable at all. */\n companyWebsite?: string;\n role?: string;\n city?: string;\n region?: string;\n}\n\n/** Where a person publishes. Kinds with one canonical host are validated against it. */\nexport type PersonLinkKind = \"website\" | \"blog\" | \"substack\" | \"linkedin\" | \"x\" | \"bluesky\" | \"github\" | \"other\";\n\nconst LINK_KINDS: readonly PersonLinkKind[] = [\"website\", \"blog\", \"substack\", \"linkedin\", \"x\", \"bluesky\", \"github\", \"other\"];\n\n/** Kinds whose URL must live on a known host — the ones models most often invent. */\nconst CANONICAL_HOSTS: Partial<Record<PersonLinkKind, readonly string[]>> = {\n linkedin: [\"linkedin.com\"],\n github: [\"github.com\"],\n x: [\"x.com\", \"twitter.com\"],\n bluesky: [\"bsky.app\"],\n};\n\n/** Hosts that only ever appear in a model's imagination. */\nconst PLACEHOLDER_HOSTS = [\"example.com\", \"example.org\", \"example.net\", \"yourdomain.com\", \"domain.com\"];\n\n/** System prompt for the grounded person research pass. */\nexport const PERSON_SEARCH_SYSTEM =\n \"You are a research assistant with web search, confirming the identity of a specific named person and finding their PUBLIC PROFESSIONAL presence. \" +\n \"Look for: a personal website, blog, or Substack; LinkedIn, X/Bluesky, or GitHub profiles; their current employer and role; and a business email or phone that they or their employer have published openly (a company contact page, a conference bio). \" +\n \"Do NOT look for or report home addresses, personal phone numbers, or personal email addresses. \" +\n \"Also report disambiguating facts: city or region, alma mater, and other companies named alongside them. \" +\n \"Names are not unique. If the results describe more than one person with this name and you cannot tell which is meant, say so explicitly. If you find a different person with the same name, say that too. \" +\n \"Cite the URL for every claim. Never guess a profile URL from a name pattern — report only URLs you actually saw. If you found nothing, say so plainly.\";\n\n/** System prompt for structuring the research notes into {@link PersonDraft}. */\nexport const PERSON_EXTRACT_SYSTEM =\n \"Extract the structured profile from the research notes. Use null for anything the notes do not establish. \" +\n \"Set verdict to 'confirmed' only when the notes identify this exact person beyond doubt; 'probable' when the evidence is strong but circumstantial; \" +\n \"'ambiguous' when several same-named people cannot be told apart; 'not_found' when nothing was established; 'wrong_entity' when the notes describe a different person with the same name. \" +\n \"Every claim in evidence must carry the source URL it came from.\";\n\n/** Forced-tool schema for a person's verified public profile. */\nexport const PERSON_EXTRACT_TOOL: ExtractTool = {\n name: \"person_profile\",\n description: \"Verified identity plus public professional profile for one person\",\n parameters: {\n type: \"object\",\n properties: {\n verdict: { type: \"string\", enum: [\"confirmed\", \"probable\", \"ambiguous\", \"not_found\", \"wrong_entity\"] },\n confidence: { type: \"number\", description: \"0..1 — how sure you are this is the right person\" },\n links: {\n type: \"array\",\n description: \"Only URLs actually seen in the results; never a guessed profile pattern.\",\n items: {\n type: \"object\",\n properties: {\n kind: { type: \"string\", enum: [...LINK_KINDS] },\n url: { type: \"string\" },\n },\n required: [\"kind\", \"url\"],\n },\n },\n employer: { type: [\"string\", \"null\"], description: \"Current employer name\" },\n role: { type: [\"string\", \"null\"], description: \"Current title at that employer\" },\n businessEmail: { type: [\"string\", \"null\"], description: \"Publicly published work email only\" },\n businessPhone: { type: [\"string\", \"null\"], description: \"Publicly published work phone only\" },\n city: { type: [\"string\", \"null\"] },\n region: { type: [\"string\", \"null\"], description: \"State, province, or country\" },\n almaMater: { type: [\"string\", \"null\"] },\n otherCompanies: { type: \"array\", items: { type: \"string\" }, description: \"Companies named alongside them\" },\n evidence: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: { claim: { type: \"string\" }, sourceUrl: { type: \"string\" } },\n required: [\"claim\", \"sourceUrl\"],\n },\n },\n },\n required: [\"verdict\", \"confidence\", \"links\", \"evidence\"],\n },\n};\n\n/** The model's structured person profile, before validation into a fragment. */\nexport interface PersonDraft {\n verdict?: string;\n confidence?: number;\n links?: Array<{ kind?: string; url?: string }>;\n employer?: string | null;\n role?: string | null;\n businessEmail?: string | null;\n businessPhone?: string | null;\n city?: string | null;\n region?: string | null;\n almaMater?: string | null;\n otherCompanies?: string[];\n evidence?: Array<{ claim?: string; sourceUrl?: string }>;\n}\n\n/** A validated person link. */\nexport interface PersonLink {\n kind: PersonLinkKind;\n url: string;\n}\n\n/** The validated result of one person enrichment pass. */\nexport interface PersonResult {\n verification: Verification;\n links: PersonLink[];\n fragment: GraphFragment;\n}\n\n/** The user prompt for a person research pass. */\nexport function personPrompt(person: PersonRow): string {\n const at = person.companyName ? ` (${person.role ? `${person.role}, ` : \"\"}${person.companyName})` : \"\";\n const where = [person.city, person.region].filter(Boolean).join(\", \");\n return `Person: ${person.name}${at}${where ? `\\nKnown location: ${where}` : \"\"}\\n\\nConfirm this is a real, identifiable person and find their public professional presence.`;\n}\n\n/**\n * Keep only links that are plausibly real: absolute https URLs, on the canonical\n * host when the kind has one, and never a placeholder domain. Models invent\n * `linkedin.com/in/first-last` from a name constantly; a kind/host mismatch is\n * the cheapest way to catch it before it becomes a stored \"fact\".\n */\nexport function validateLinks(raw: PersonDraft[\"links\"], limit = 8): PersonLink[] {\n const out: PersonLink[] = [];\n const seen = new Set<string>();\n for (const item of (raw ?? []).slice(0, 24)) {\n const kind = (LINK_KINDS as readonly string[]).includes(String(item?.kind)) ? (item!.kind as PersonLinkKind) : \"other\";\n const url = String(item?.url ?? \"\").trim();\n const parsed = httpsUrl(url);\n if (!parsed) continue;\n const host = parsed.hostname.toLowerCase().replace(/^www\\./, \"\");\n if (PLACEHOLDER_HOSTS.includes(host)) continue;\n const required = CANONICAL_HOSTS[kind];\n if (required && !required.some((h) => host === h || host.endsWith(`.${h}`))) continue;\n if (seen.has(url)) continue;\n seen.add(url);\n out.push({ kind, url });\n if (out.length >= limit) break;\n }\n return out;\n}\n\n/**\n * Turn one person's structured draft into a verdict plus an idempotent fragment.\n *\n * The fragment is empty unless the verdict is writable — an `ambiguous`,\n * `not_found`, or `wrong_entity` pass contributes no graph, however confident\n * the model sounded. The verdict and its evidence are still returned, because\n * \"this lane confidently found the wrong person\" is exactly what a benchmark\n * needs to record.\n */\nexport function personToFragment(person: PersonRow, draft: PersonDraft, retrievedAt?: string): PersonResult {\n const verification = normalizeVerification(draft, retrievedAt);\n const links = validateLinks(draft.links);\n if (!isWritable(verification.verdict)) return { verification, links, fragment: { nodes: [], edges: [] } };\n\n const attrs: Record<string, string> = { name: person.name };\n for (const link of links) if (!attrs[link.kind]) attrs[link.kind] = link.url;\n const city = str(draft.city, 60);\n if (city && !person.city) attrs.city = city;\n const region = str(draft.region, 60);\n if (region && !person.region) attrs.region = region;\n const email = str(draft.businessEmail, 120);\n if (email && /^[^@\\s]+@[^@\\s.]+\\.[^@\\s]+$/.test(email)) attrs.businessEmail = email;\n const phone = str(draft.businessPhone, 40);\n if (phone) attrs.businessPhone = phone;\n const almaMater = str(draft.almaMater, 120);\n if (almaMater) attrs.almaMater = almaMater;\n\n const nodes: GraphNode[] = [{ type: \"person\", key: person.slug, attrs }];\n const edges: GraphFragment[\"edges\"] = [];\n // Only an employer the research established gets written — reasserting the\n // seed row's own company would be echoing the input back as a finding.\n const employer = str(draft.employer, 120);\n // Reuse the known company's key only when it is the *same* company. A pass\n // that found a different employer must not be filed under the seed's slug.\n const known = person.companySlug && person.companyName && identityKey(person.companyName) === identityKey(employer);\n const companySlug = employer ? (known ? person.companySlug! : slugify(employer)) : \"\";\n if (employer && companySlug) {\n const key = `${person.slug}~${companySlug}`;\n nodes.push({ type: \"company\", key: companySlug, attrs: { name: known ? person.companyName! : employer } });\n nodes.push({ type: \"affiliation\", key, attrs: { role: str(draft.role, 60) || person.role || \"\" } });\n edges.push({ from: { type: \"affiliation\", key }, to: { type: \"person\", key: person.slug }, label: \"of_person\" });\n edges.push({ from: { type: \"affiliation\", key }, to: { type: \"company\", key: companySlug }, label: \"of_company\" });\n }\n return { verification, links, fragment: { nodes, edges } };\n}\n\nfunction str(value: unknown, max: number): string {\n return typeof value === \"string\" ? value.trim().slice(0, max) : \"\";\n}\n\n/** Parse a link, accepting https only — a profile served over plaintext is not one we store. */\nfunction httpsUrl(value: string): URL | null {\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" ? url : null;\n } catch {\n return null;\n }\n}\n","// Grading: how sure we are that an entity was correctly identified, derived\n// from the sources that were actually checked.\n//\n// The alternative — asking the model how confident it is — was measured and\n// does not work. Across real runs every lane confirmed between one and three of\n// four people who do not exist, and did so with high stated confidence. A\n// number a model writes about its own certainty is not evidence.\n//\n// So a grade is computed from what sources corroborated, and the sources are\n// what gets stored. That ordering matters: changing the standard then re-scores\n// stored evidence instead of re-running inference, which is the difference\n// between a free re-grade and paying for the whole corpus again.\n\n/** Where a source sits on the scale that decides how much it counts. */\nexport type SourceClass =\n /** The organization's own site: team page, staff directory, press release.\n * The subject cannot assert this about themselves, which is what makes it\n * the strongest ordinary evidence. */\n | \"first-party\"\n /** A registry that assigns identifiers: ORCID, SEC EDGAR, a licensing board. */\n | \"authoritative\"\n /** The person's own profile or site. Real, but self-asserted. */\n | \"self-asserted\"\n /** Press, directories, anything else. */\n | \"third-party\";\n\n/** What reading a source established about the subject. */\nexport type Corroboration =\n /** Named the subject and tied them to their organization. */\n | \"confirmed\"\n /** Named the subject, but nothing tied it to their work. */\n | \"name-only\"\n /** Loaded, and mentioned neither. */\n | \"absent\"\n /** Could not be read: blocked, gone, or refused. */\n | \"unreachable\"\n /** Not checked yet. */\n | \"unchecked\";\n\n/** One dated, classified source and what it corroborated. */\nexport interface VerifiedSource {\n sourceUrl: string;\n sourceClass: SourceClass;\n /** Which facts this source supported: \"employer\", \"title\", \"location\", … */\n facts: string[];\n /** When it was read. Every fact is a fact *as of* a date. */\n verifiedOn: string;\n corroboration: Corroboration;\n}\n\n/** How well an identity is established. */\nexport type Grade =\n /** Enough independent sources, anchored, agreeing, with no contradiction. */\n | \"high\"\n /** Corroborated, but thinly or only by sources the subject controls. */\n | \"medium\"\n /** One source, or a contradiction, or nothing that settles it. */\n | \"unresolved\"\n /**\n * Nothing could be read at all.\n *\n * Deliberately distinct from `unresolved`. In a real run 60 of 93 claimed\n * URLs came back unreachable, almost all login walls. Scoring those as \"not\n * established\" blames a lane for a wall it cannot walk through; scoring them\n * as established would be a lie. Neither is true, so this says so.\n */\n | \"unverifiable\";\n\n/** The bar a `high` grade has to clear. */\nexport interface GradeStandard {\n /** Distinct hosts that must corroborate. */\n minimumSources: number;\n /** Distinct facts that must be corroborated across them. */\n minimumFacts: number;\n /** Classes that can anchor a `high` grade. */\n anchors: readonly SourceClass[];\n /** Whether `medium` also requires an anchor. Measured: two third-party\n * sources agreeing about someone who does not exist is a thing that\n * happens, so anything acted on should usually require one. */\n anchorRequiredForMedium: boolean;\n}\n\n/** Two independent sources, one first-party or authoritative, three facts. */\nexport const DEFAULT_STANDARD: GradeStandard = {\n minimumSources: 2,\n minimumFacts: 3,\n anchors: [\"first-party\", \"authoritative\"],\n anchorRequiredForMedium: false,\n};\n\n/** What the grade was based on, so a reviewer can disagree with it. */\nexport interface GradeReport {\n grade: Grade;\n independentSources: number;\n factsCorroborated: string[];\n hasAnchor: boolean;\n contradictions: string[];\n reason: string;\n}\n\n/** Two sources are independent when they are not the same site. */\nexport function independentHosts(sources: readonly VerifiedSource[]): string[] {\n const hosts = new Set<string>();\n for (const source of sources) {\n try {\n hosts.add(new URL(source.sourceUrl).hostname.toLowerCase().replace(/^www\\./, \"\"));\n } catch {\n // An unparseable source cannot count toward independence.\n }\n }\n return [...hosts];\n}\n\n/**\n * Grade an identity from the sources that were checked.\n *\n * Only sources that corroborated something count toward the grade. One that was\n * read and did NOT name the subject is a contradiction; one that could not be\n * read is neither, which is the whole reason `unverifiable` exists.\n */\nexport function gradeSources(\n sources: readonly VerifiedSource[],\n standard: GradeStandard = DEFAULT_STANDARD,\n): GradeReport {\n const supporting = sources.filter((s) => s.corroboration === \"confirmed\" || s.corroboration === \"name-only\");\n const contradicting = sources.filter((s) => s.corroboration === \"absent\");\n const unreadable = sources.filter((s) => s.corroboration === \"unreachable\" || s.corroboration === \"unchecked\");\n\n const facts = [...new Set(supporting.flatMap((s) => s.facts))];\n const hosts = independentHosts(supporting);\n const hasAnchor = supporting.some((s) => standard.anchors.includes(s.sourceClass));\n const base = {\n independentSources: hosts.length,\n factsCorroborated: facts,\n hasAnchor,\n contradictions: contradicting.map((s) => s.sourceUrl),\n };\n\n if (supporting.length === 0) {\n return unreadable.length > 0 && contradicting.length === 0\n ? { ...base, grade: \"unverifiable\", reason: `no source could be read (${unreadable.length} unreachable)` }\n : { ...base, grade: \"unresolved\", reason: \"no source corroborated the subject\" };\n }\n if (base.contradictions.length > 0) {\n return { ...base, grade: \"unresolved\", reason: `${base.contradictions.length} source(s) named someone else` };\n }\n if (hosts.length >= standard.minimumSources && hasAnchor && facts.length >= standard.minimumFacts) {\n return { ...base, grade: \"high\", reason: `${hosts.length} independent sources, anchored, agreeing on ${facts.length} facts` };\n }\n if (hosts.length >= standard.minimumSources && (hasAnchor || !standard.anchorRequiredForMedium)) {\n return {\n ...base,\n grade: \"medium\",\n reason: hasAnchor ? `${hosts.length} sources but only ${facts.length} corroborated facts` : `${hosts.length} sources, none first-party or authoritative`,\n };\n }\n return { ...base, grade: \"unresolved\", reason: hosts.length < standard.minimumSources ? \"only one independent source\" : \"no anchoring source\" };\n}\n\n/** Grades that justify acting on the identity. */\nexport const ACTIONABLE: readonly Grade[] = [\"high\", \"medium\"];\n\n/** Whether a grade is good enough to act on. */\nexport function isActionable(value: Grade): boolean {\n return ACTIONABLE.includes(value);\n}\n","// The enrichment runners: Env-agnostic functions that take an @odla-ai/ai\n// `Ai` (bring-your-own provider/keys — the caller decides which lane, e.g. a\n// cheap vaulted key vs the platform provider) and produce graph fragments.\n// Callers persist the fragments (via writeFragment) and own batching,\n// concurrency, and cooldowns.\nimport type { Ai, SearchCitation } from \"@odla-ai/ai\";\nimport type { GraphFragment } from \"../types\";\nimport {\n PERSON_EXTRACT_SYSTEM,\n PERSON_EXTRACT_TOOL,\n PERSON_SEARCH_SYSTEM,\n personPrompt,\n personToFragment,\n type PersonDraft,\n type PersonResult,\n type PersonRow,\n} from \"./person\";\nimport {\n FACTS_EXTRACT_SYSTEM,\n FACTS_EXTRACT_TOOL,\n FACTS_SEARCH_SYSTEM,\n LOCATE_SYSTEM,\n LOCATE_TOOL,\n countyKey,\n factsToFragment,\n locatePrompt,\n type CompanyRow,\n type FactsDraft,\n} from \"./facts\";\n\n/** Which model serves each verb (search must be a web-grounded model). */\nexport interface EnrichModels {\n /** Web-search-grounded research model. */\n search: string;\n /** Structured-extraction (forced tool-call) model. */\n extract: string;\n}\n\n/**\n * Enrich one company: a grounded web search for its facts, then a structured\n * extraction into a validated {@link factsToFragment}. Retries the extraction\n * once (reasoning models sometimes skip a forced tool call). Returns the\n * fragment (possibly empty — nothing established); the caller writes it.\n */\nexport async function enrichCompany(ai: Ai, models: EnrichModels, company: CompanyRow): Promise<GraphFragment> {\n const where = `${company.city ? `, ${company.city}` : \"\"}${company.region ? ` (${company.region})` : \"\"}`;\n const { text } = await ai.search({\n model: models.search,\n system: FACTS_SEARCH_SYSTEM,\n user: `Company: ${company.name}${where}. Research its founding, headquarters, key people, and funding.`,\n maxTokens: 900,\n });\n const extractOnce = () =>\n ai.extract<FactsDraft>({\n model: models.extract,\n system: FACTS_EXTRACT_SYSTEM,\n user: `Company: ${company.name}\\n\\nNotes:\\n${text}`,\n tool: FACTS_EXTRACT_TOOL,\n maxTokens: 4000, // reasoning headroom before the forced tool call\n });\n const { value } = await extractOnce().catch(extractOnce);\n return factsToFragment(company, value);\n}\n\n/** One person enrichment pass, with the sources the provider actually cited. */\nexport interface PersonEnrichment extends PersonResult {\n /** Sources the search model attributed. Empty when the provider grounded nothing. */\n citations: SearchCitation[];\n /** The research prose the extraction was taken from — useful for adjudication. */\n notes: string;\n}\n\n/**\n * Enrich one person: a grounded web search for their public professional\n * presence, then a structured extraction into a validated verdict + fragment.\n *\n * Unlike {@link enrichCompany}, this always returns a verdict — including\n * `not_found` and `wrong_entity` — because \"the lane confidently identified the\n * wrong person\" is a result worth recording, not an error. The fragment is empty\n * unless the verdict permits writing.\n */\nexport async function enrichPerson(ai: Ai, models: EnrichModels, person: PersonRow, retrievedAt?: string): Promise<PersonEnrichment> {\n const { text, citations } = await ai.search({\n model: models.search,\n system: PERSON_SEARCH_SYSTEM,\n user: personPrompt(person),\n maxTokens: 1200,\n });\n const extractOnce = () =>\n ai.extract<PersonDraft>({\n model: models.extract,\n system: PERSON_EXTRACT_SYSTEM,\n user: `Person: ${person.name}\\n\\nNotes:\\n${text}`,\n tool: PERSON_EXTRACT_TOOL,\n maxTokens: 4000, // reasoning headroom before the forced tool call\n });\n const { value } = await extractOnce().catch(extractOnce);\n return { ...personToFragment(person, value, retrievedAt), citations, notes: text };\n}\n\n/**\n * Resolve a batch of companies' free-text locations to county FIPS codes via a\n * single structured extraction, joined against a caller-supplied gazetteer\n * (`countyKey(name, state)` → FIPS). Only confident, gazetteer-matched\n * resolutions are returned.\n */\nexport async function resolveCounties(\n ai: Ai,\n model: string,\n rows: CompanyRow[],\n gazetteer: Map<string, string>,\n): Promise<Array<{ slug: string; fips: string }>> {\n const { value } = await ai.extract<{ items: Array<{ slug: string; county: string | null; state: string | null }> }>({\n model,\n system: LOCATE_SYSTEM,\n user: locatePrompt(rows),\n tool: LOCATE_TOOL,\n maxTokens: 6000, // reasoning models spend tokens before the tool call\n });\n const out: Array<{ slug: string; fips: string }> = [];\n for (const item of value.items ?? []) {\n const fips = item.county && item.state ? gazetteer.get(countyKey(item.county, item.state)) : undefined;\n if (fips) out.push({ slug: item.slug, fips });\n }\n return out;\n}\n","// Does this page name this person?\n//\n// Every evidence-gathering strategy eventually reduces to that question, and\n// getting it wrong in either direction is expensive: too loose and a staff\n// directory corroborates anyone whose name tokens appear on it, too strict and\n// a real hit is missed because the page wrote a middle initial.\n\n/** Normalize for containment testing: fold case, accents, and punctuation. */\nexport function fold(value: string): string {\n return value\n .normalize(\"NFKD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \" \")\n .trim();\n}\n\n/**\n * Whether a folded haystack names a person.\n *\n * Requires every name token, and requires them to sit within a couple of words\n * of each other, which is how names are actually written: \"Abbey Chrystal\",\n * \"Chrystal, Abbey\", and \"Abbey R. Chrystal\" all qualify.\n *\n * Proximity is the load-bearing part, and it was added because of a measured\n * failure rather than in anticipation of one. Requiring only that the tokens\n * appear somewhere on the page matched \"Abbey Chrystal\" against a staff\n * directory that listed \"Abbey Fornier\" and \"Dale Chrystal\" — on a team page\n * most name tokens appear somewhere, so a document-wide match corroborates\n * almost anybody.\n *\n * Single-token names are rejected outright: matching \"Deere\" against arbitrary\n * page text would corroborate almost anything.\n *\n * @param haystack Page text, already passed through {@link fold}.\n * @param name The person's name, in any casing or accenting.\n */\nexport function mentionsName(haystack: string, name: string): boolean {\n const tokens = fold(name).split(\" \").filter((t) => t.length > 2);\n if (tokens.length < 2) return false;\n\n const words = haystack.split(\" \");\n const positions = new Map<string, number[]>();\n for (const [i, word] of words.entries()) {\n if (tokens.includes(word)) positions.set(word, [...(positions.get(word) ?? []), i]);\n }\n if (tokens.some((t) => !positions.has(t))) return false;\n\n // Allow about one filler word per gap — a middle initial, a comma-separated\n // inversion — but no more, which is what separates a name from a list.\n const span = tokens.length;\n const anchors = positions.get(tokens[0]!) ?? [];\n return anchors.some((anchor) =>\n tokens.every((token) => (positions.get(token) ?? []).some((at) => Math.abs(at - anchor) <= span)),\n );\n}\n\n/**\n * Pull a window of text around the person's name, as evidence a reviewer can\n * read without re-fetching the page.\n */\nexport function snippetAround(text: string, name: string, width = 240): string {\n const hay = fold(text);\n const first = fold(name).split(\" \")[0] ?? \"\";\n const at = hay.indexOf(first);\n if (at < 0) return \"\";\n const start = Math.max(0, at - width / 2);\n return text.slice(start, start + width).replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * Reduce HTML to readable text.\n *\n * Script and style bodies are dropped first: a page's inline JSON often repeats\n * unrelated names, and matching against it corroborates the wrong person.\n */\nexport function stripMarkup(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, \" \")\n .replace(/<style[\\s\\S]*?<\\/style>/gi, \" \")\n .replace(/<[^>]+>/g, \" \")\n .replace(/ /gi, \" \")\n .replace(/&/gi, \"&\")\n .replace(/\\s+/g, \" \")\n .slice(0, 200_000);\n}\n","// Enrichment as independent strategies feeding one grader.\n//\n// The single-call shape (`enrichPerson`: search, extract, believe the verdict)\n// is the wrong primitive, and the benchmark that led here says why. Across real\n// runs, a research pass found 14 of 18 real people but also \"confirmed\" one of\n// four people who do not exist. Walking the employer's own site found only 5 of\n// 18 — but confirmed none of the four, because a company's website does not\n// contain someone who does not work there.\n//\n// The two fail in opposite directions. Composed, they reached the `high`\n// standard — two independent sources, one of them first-party — on 16 of 18,\n// with none of the four fabricated people getting there. Neither reached that\n// alone. So the useful unit is not a better prompt; it is several strategies\n// that fail differently, and a grader that only believes what more than one of\n// them saw.\nimport type { Ai } from \"@odla-ai/ai\";\nimport type { PersonLink, PersonRow } from \"./person\";\nimport type { EnrichModels } from \"./run\";\nimport { DEFAULT_STANDARD, gradeSources, type Grade, type GradeReport, type GradeStandard, type VerifiedSource } from \"./grade\";\n\n/**\n * Read a URL and return its body, or null if it could not be read.\n *\n * Supplied by the caller rather than implemented here, and deliberately so.\n * Fetched pages are attacker-influenced: a strategy reads whatever a third\n * party chose to serve. Making the reader an explicit dependency means the\n * caller decides what boundary it crosses — in odla that is\n * `@odla-ai/camel`'s artifact ingress, which caps bytes, screens redirects for\n * SSRF, and labels the body Unsafe. kg does not reach for the network itself,\n * so it can never quietly bypass that.\n */\nexport type PageReader = (url: string) => Promise<string | null>;\n\n/** What a strategy is given to work with. */\nexport interface StrategyContext {\n person: PersonRow;\n /** The date this evidence is being gathered on. Every fact is a fact *as of* a date. */\n retrievedAt: string;\n /** Present for strategies that call a model. */\n ai?: Ai;\n models?: EnrichModels;\n /** Present for strategies that read pages. */\n readPage?: PageReader;\n}\n\n/** What one strategy established. */\nexport interface Finding {\n strategy: string;\n /** Dated, classified sources — the input the grade is computed from. */\n sources: VerifiedSource[];\n /** Profile and site URLs this strategy proposes. */\n links: PersonLink[];\n /** Human-readable account of what happened, for adjudication. */\n notes: string;\n /** Set when the strategy failed outright, which is not the same as finding nothing. */\n error?: string;\n}\n\n/**\n * One way of gathering evidence about a person.\n *\n * A strategy never decides whether the person was found. It reports what it\n * saw and how strong the sources were; {@link investigatePerson} grades. That\n * split is the point — it means adding a strategy cannot inflate a verdict, and\n * it means the standard can be raised later and re-applied to stored evidence\n * without paying for the inference again.\n */\nexport interface Strategy {\n /** Stable identifier, recorded on findings so results stay attributable. */\n name: string;\n /** Whether this strategy can run at all for this subject and context. */\n applicable(context: StrategyContext): boolean;\n run(context: StrategyContext): Promise<Finding>;\n}\n\n/** The result of investigating one person with a set of strategies. */\nexport interface Investigation {\n person: PersonRow;\n /** How well the identity is established, per the standard used. */\n grade: Grade;\n /** Why, in enough detail to disagree with. */\n report: GradeReport;\n /** Every source gathered, deduplicated by URL. */\n sources: VerifiedSource[];\n /** Every link proposed, deduplicated by URL. */\n links: PersonLink[];\n /** Per-strategy detail, including strategies that ran and found nothing. */\n findings: Finding[];\n /** Strategies skipped because they were not applicable. */\n skipped: string[];\n}\n\n/**\n * Run every applicable strategy and grade what they found together.\n *\n * Strategies run concurrently and are isolated from each other: one that throws\n * becomes a `Finding` carrying its error rather than failing the investigation.\n * A search provider being down should cost you that strategy's evidence, not\n * the first-party page another strategy already read.\n *\n * @param context The person, the date, and whichever capabilities are available.\n * @param strategies Which strategies to attempt, in any order.\n * @param standard The bar a `high` grade must clear. Defaults to\n * {@link DEFAULT_STANDARD}: two independent sources, one anchoring, three facts.\n */\nexport async function investigatePerson(\n context: StrategyContext,\n strategies: readonly Strategy[],\n standard: GradeStandard = DEFAULT_STANDARD,\n): Promise<Investigation> {\n const skipped: string[] = [];\n const runnable: Strategy[] = [];\n for (const strategy of strategies) {\n if (strategy.applicable(context)) runnable.push(strategy);\n else skipped.push(strategy.name);\n }\n\n const findings = await Promise.all(\n runnable.map(async (strategy): Promise<Finding> => {\n try {\n return await strategy.run(context);\n } catch (err) {\n return {\n strategy: strategy.name,\n sources: [],\n links: [],\n notes: \"\",\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }),\n );\n\n const sources = dedupeBy(findings.flatMap((f) => f.sources), (s) => s.sourceUrl);\n const links = dedupeBy(findings.flatMap((f) => f.links), (l) => l.url);\n const report = gradeSources(sources, standard);\n return { person: context.person, grade: report.grade, report, sources, links, findings, skipped };\n}\n\n/** Keep the first of each key, preserving order. */\nfunction dedupeBy<T>(items: readonly T[], key: (item: T) => string): T[] {\n const seen = new Map<string, T>();\n for (const item of items) {\n const at = key(item);\n if (!seen.has(at)) seen.set(at, item);\n }\n return [...seen.values()];\n}\n","// Ask the employer's own website.\n//\n// The strongest ordinary evidence that someone holds a role at an organization\n// is the organization saying so. A team page, staff directory, or leadership\n// bio is first-party, usually dated, and cannot be self-asserted by the subject\n// — which is exactly what a LinkedIn profile cannot offer.\n//\n// It is also the only strategy here with no model and no search provider in it,\n// so its cost does not scale with token pricing at all.\nimport { fold, mentionsName, snippetAround, stripMarkup } from \"../match\";\nimport type { VerifiedSource } from \"../grade\";\nimport type { Finding, Strategy, StrategyContext } from \"../strategy\";\n\n/** Paths an organization is most likely to describe its people on. */\nexport const TEAM_PATHS: readonly string[] = [\n \"/team\", \"/about\", \"/about-us\", \"/our-team\", \"/leadership\", \"/people\",\n \"/staff\", \"/who-we-are\", \"/company/team\", \"/press\", \"/news\",\n];\n\n/** Words that mark a link as leading to people rather than to product. */\nconst PEOPLE_WORDS = [\n \"team\", \"about\", \"leadership\", \"staff\", \"people\", \"who we are\", \"our story\",\n \"founders\", \"management\", \"board\", \"press\", \"news\", \"advisors\", \"bios\",\n];\n\n/** The site root, which is the only URL on a domain guaranteed to exist. */\nexport function rootOf(website: string): string | null {\n try {\n return new URL(website.startsWith(\"http\") ? website : `https://${website}`).origin;\n } catch {\n return null;\n }\n}\n\n/**\n * Guessed people-page paths. A fallback only.\n *\n * Guessing produced 67 refusals against 23 successful reads in a real run: most\n * sites do not use these paths, and a 404 teaches nothing about whether the\n * person works there. Following the site's own links is the primary route; this\n * is what remains when a homepage offers no usable navigation.\n */\nexport function candidatePages(website: string, limit = 4): string[] {\n const origin = rootOf(website);\n if (!origin) return [];\n return TEAM_PATHS.map((p) => `${origin}${p}`).slice(0, limit);\n}\n\n/**\n * Extract the links a homepage itself offers toward its people pages.\n *\n * Reading the site's own navigation beats guessing at it on both counts: it is\n * far more likely to resolve, and it lands on the page the organization\n * actually maintains. Same-origin only — an off-site link is not first-party\n * evidence, which is the entire reason this strategy carries weight.\n */\nexport function peopleLinks(html: string, origin: string, limit = 5): string[] {\n const found = new Map<string, number>();\n const anchor = /<a\\b[^>]*href=[\"']([^\"']+)[\"'][^>]*>([\\s\\S]{0,200}?)<\\/a>/gi;\n for (const match of html.matchAll(anchor)) {\n let url: URL;\n try {\n url = new URL(match[1] ?? \"\", origin);\n } catch {\n continue;\n }\n if (url.origin !== origin) continue;\n const text = (match[2] ?? \"\").replace(/<[^>]+>/g, \" \").toLowerCase();\n // Rank by which people-word matched, so /team outranks /news.\n const rank = PEOPLE_WORDS.findIndex((w) => `${text} ${url.pathname.toLowerCase()}`.includes(w));\n if (rank < 0) continue;\n const clean = `${url.origin}${url.pathname}`.replace(/\\/$/, \"\");\n if (clean === origin) continue;\n const prior = found.get(clean);\n if (prior === undefined || rank < prior) found.set(clean, rank);\n }\n return [...found.entries()].sort((a, b) => a[1] - b[1]).slice(0, limit).map(([url]) => url);\n}\n\n/** What one organization page said about the person. */\nexport interface OrgHit {\n url: string;\n named: boolean;\n /** Text around the name, for the reviewer. */\n snippet: string;\n}\n\n/**\n * Decide whether an organization page establishes the person.\n *\n * Deliberately strict about the name and silent about the title: a team page\n * naming the person is the finding, and inferring their exact title from\n * surrounding prose would manufacture a fact the page never stated plainly.\n */\nexport function judgeOrgPage(name: string, url: string, text: string): OrgHit {\n const named = mentionsName(fold(text), name);\n return { url, named, snippet: named ? snippetAround(text, name) : \"\" };\n}\n\n/**\n * Walk the employer's site looking for the person.\n *\n * Requires `companyWebsite` on the person and a `readPage`; without either it\n * reports itself inapplicable rather than pretending to have checked.\n */\nexport const orgSiteStrategy: Strategy = {\n name: \"org-site\",\n\n applicable(context: StrategyContext): boolean {\n return Boolean(context.readPage && rootOf(context.person.companyWebsite ?? \"\"));\n },\n\n async run(context: StrategyContext): Promise<Finding> {\n const { person, readPage, retrievedAt } = context;\n const origin = rootOf(person.companyWebsite ?? \"\");\n if (!origin || !readPage) {\n return { strategy: \"org-site\", sources: [], links: [], notes: \"no employer website to read\" };\n }\n\n const hits: OrgHit[] = [];\n let read = 0;\n /** Read a page, judge it, and return its markup so links can be followed. */\n const visit = async (url: string): Promise<string | null> => {\n const html = await readPage(url);\n if (html === null) return null;\n read += 1;\n const hit = judgeOrgPage(person.name, url, stripMarkup(html));\n if (hit.named) hits.push(hit);\n return html;\n };\n\n // The homepage is read once and kept: it is both the first place to check\n // and the source of the navigation used to find the rest.\n const home = await visit(origin);\n const followable = home && hits.length === 0 ? peopleLinks(home, origin) : [];\n const fallback = read <= 1 ? candidatePages(person.companyWebsite ?? \"\") : [];\n for (const url of [...followable, ...fallback]) {\n if (hits.length > 0) break;\n await visit(url);\n }\n\n // Only hits become sources. A company page that loaded without naming the\n // person is NOT evidence against them — most pages on a company's site have\n // no reason to list any given employee. Recording those as `absent` would\n // make the grader read them as contradictions and drag down identities that\n // other strategies had properly established, which is a worse error than\n // the silence it would be trying to capture. The count goes in the notes.\n const sources: VerifiedSource[] = hits.map((hit) => ({\n sourceUrl: hit.url,\n sourceClass: \"first-party\",\n facts: [\"employer\", \"affiliation\"],\n verifiedOn: retrievedAt,\n corroboration: \"confirmed\",\n }));\n\n return {\n strategy: \"org-site\",\n sources,\n links: [],\n notes: hits[0]\n ? `named on ${hits[0].url}\\n\\n${hits[0].snippet}`\n : read > 0\n ? `read ${read} page(s) on the employer's site; none named them`\n : \"no page on the employer's site could be read\",\n };\n },\n};\n","// The grounded research pass, as a strategy.\n//\n// This is {@link enrichPerson} — a web-grounded search followed by a structured\n// extraction — with one deliberate change: its verdict does not decide anything.\n// The pass reports what it found and where; the grader decides what that is\n// worth. That change is the whole reason this layer exists. Measured over real\n// runs, research found 14 of 18 real people and also \"confirmed\" one of four\n// people who do not exist, at high stated confidence. It is the best recall\n// available and it cannot be trusted on its own.\n//\n// So every source it produces enters at `name-only` at best — a source that\n// named the person but has not been read to confirm the affiliation. Reaching\n// `high` needs an anchoring source from somewhere else.\nimport type { SearchCitation } from \"@odla-ai/ai\";\nimport type { Corroboration, SourceClass, VerifiedSource } from \"../grade\";\nimport { enrichPerson } from \"../run\";\nimport type { Finding, Strategy, StrategyContext } from \"../strategy\";\n\n/** Hosts a person publishes about themselves on. */\nconst SELF_ASSERTED_HOSTS = [\"linkedin.com\", \"x.com\", \"twitter.com\", \"bsky.app\", \"github.com\", \"substack.com\"];\n\n/** Bare host, lowercased, without `www.`. */\nfunction hostOf(url: string): string {\n try {\n return new URL(url).hostname.toLowerCase().replace(/^www\\./, \"\");\n } catch {\n return \"\";\n }\n}\n\n/**\n * Class a cited source.\n *\n * The employer's own domain is first-party even when a search found it rather\n * than a crawl — what makes evidence first-party is who published it, not how\n * it was reached.\n */\nexport function classifySource(url: string, companyWebsite?: string): SourceClass {\n const host = hostOf(url);\n if (!host) return \"third-party\";\n const company = companyWebsite ? hostOf(companyWebsite.startsWith(\"http\") ? companyWebsite : `https://${companyWebsite}`) : \"\";\n if (company && (host === company || host.endsWith(`.${company}`))) return \"first-party\";\n if (SELF_ASSERTED_HOSTS.some((h) => host === h || host.endsWith(`.${h}`))) return \"self-asserted\";\n return \"third-party\";\n}\n\n/**\n * Whether a provider actually grounded a claim in this source, as opposed to\n * merely consulting it.\n *\n * A citation carrying cited text or a text span is attributed: the provider is\n * saying *this sentence came from here*. One carrying only a URL means the\n * source was visited. The difference matters enough to grade differently —\n * consulted-but-unquoted sources are the ones that pad a bibliography without\n * supporting anything in it.\n */\nexport function isAttributed(citation: SearchCitation): boolean {\n return Boolean(citation.citedText || citation.startIndex !== undefined || citation.endIndex !== undefined);\n}\n\n/**\n * Research a person with a grounded search, and report the sources rather than\n * the conclusion.\n */\nexport const researchStrategy: Strategy = {\n name: \"research\",\n\n applicable(context: StrategyContext): boolean {\n return Boolean(context.ai && context.models?.search && context.models?.extract);\n },\n\n async run(context: StrategyContext): Promise<Finding> {\n const { ai, models, person, retrievedAt } = context;\n if (!ai || !models) return { strategy: \"research\", sources: [], links: [], notes: \"no models available\" };\n\n const result = await enrichPerson(ai, models, person, retrievedAt);\n\n // Sources come from the citation set, not from the model's evidence list:\n // a URL the provider recorded is one it actually retrieved, whereas a URL\n // written into a tool call is one the model produced — and models produce\n // plausible URLs for people who do not exist.\n const sources: VerifiedSource[] = result.citations.map((citation): VerifiedSource => {\n const corroboration: Corroboration = isAttributed(citation) ? \"name-only\" : \"unchecked\";\n return {\n sourceUrl: citation.url,\n sourceClass: classifySource(citation.url, person.companyWebsite),\n facts: [\"identity\"],\n verifiedOn: retrievedAt,\n corroboration,\n };\n });\n\n return {\n strategy: \"research\",\n sources,\n links: result.links,\n notes: `[${result.verification.verdict}] ${result.notes}`,\n };\n },\n};\n","// One narrow query, judged on how many distinct profiles come back.\n//\n// Instead of asking a model to research someone, ask the search index a single\n// question — `\"Abbey Chrystal\" \"Rhizos Viticulture\" linkedin` — and read the\n// answer off the citation set. One distinct profile is the useful answer.\n// Several means the name is ambiguous and any research pass that picked one was\n// guessing. None means not found.\n//\n// The verdict is arithmetic over citations with no model judgment in it, which\n// is what makes this a fair contrast to an expensive research pass rather than\n// a cheaper version of one. It only became possible once `ai.search()` returned\n// citations at all: the citation set IS the signal.\nimport type { SearchCitation } from \"@odla-ai/ai\";\nimport type { PersonRow, PersonLink, PersonLinkKind } from \"../person\";\nimport type { VerifiedSource } from \"../grade\";\nimport type { Finding, Strategy, StrategyContext } from \"../strategy\";\n\n/** Hosts whose profile paths identify exactly one person. */\nconst PROFILE_HOSTS: Record<string, RegExp> = {\n \"linkedin.com\": /\\/in\\/[^/?#]+/i,\n \"github.com\": /^\\/[^/?#]+\\/?$/i,\n \"bsky.app\": /\\/profile\\/[^/?#]+/i,\n \"x.com\": /^\\/[^/?#]+\\/?$/i,\n \"twitter.com\": /^\\/[^/?#]+\\/?$/i,\n};\n\n/** Map a profile host onto the link kind it represents. */\nfunction kindOf(host: string): PersonLinkKind {\n if (host.startsWith(\"linkedin\")) return \"linkedin\";\n if (host.startsWith(\"github\")) return \"github\";\n if (host.startsWith(\"bsky\")) return \"bluesky\";\n if (host.startsWith(\"x.\") || host.startsWith(\"twitter\")) return \"x\";\n return \"other\";\n}\n\n/** Build the narrow query. Quoting both names is what makes the count meaningful. */\nexport function targetedQuery(person: PersonRow, platform = \"linkedin\"): string {\n const company = person.companyName ? ` \"${person.companyName}\"` : \"\";\n return `\"${person.name}\"${company} ${platform}`;\n}\n\n/** One candidate profile the search turned up. */\nexport interface Candidate {\n url: string;\n host: string;\n /** The profile handle, which tells two results apart from one result cited twice. */\n handle: string;\n}\n\n/**\n * Reduce a citation set to distinct candidate profiles.\n *\n * Deduplicates by (host, handle) rather than by URL. The same profile arrives\n * as `linkedin.com/in/x`, `www.linkedin.com/in/x/`, and again with tracking\n * suffixes; counting those as three candidates turns every confident answer\n * into a false ambiguity.\n */\nexport function candidatesFrom(citations: readonly SearchCitation[]): Candidate[] {\n const seen = new Map<string, Candidate>();\n for (const citation of citations) {\n let url: URL;\n try {\n url = new URL(citation.url);\n } catch {\n continue;\n }\n const host = url.hostname.toLowerCase().replace(/^www\\./, \"\").replace(/^[a-z]{2}\\./, \"\");\n const match = PROFILE_HOSTS[host]?.exec(url.pathname);\n if (!match) continue;\n const handle = match[0].replace(/\\/+$/, \"\").toLowerCase();\n if (!seen.has(`${host}${handle}`)) seen.set(`${host}${handle}`, { url: citation.url, host, handle });\n }\n return [...seen.values()];\n}\n\n/**\n * Look up one person by a single narrow query.\n *\n * Emits a source only when exactly one distinct profile came back. Emitting one\n * per candidate when the name is ambiguous would manufacture precisely the\n * confidence this strategy exists to withhold — and because the profile is the\n * person's own, it is classed `self-asserted`, which under the default standard\n * cannot anchor a `high` grade by itself.\n */\nexport const targetedProfileStrategy: Strategy = {\n name: \"targeted-profile\",\n\n applicable(context: StrategyContext): boolean {\n return Boolean(context.ai && context.models?.search);\n },\n\n async run(context: StrategyContext): Promise<Finding> {\n const { ai, models, person, retrievedAt } = context;\n if (!ai || !models) return { strategy: \"targeted-profile\", sources: [], links: [], notes: \"no search model\" };\n\n const query = targetedQuery(person);\n const { citations } = await ai.search({\n model: models.search,\n system:\n \"Search for the exact query given and report what you find. \" +\n \"Do not guess a profile URL that did not appear in the results.\",\n user: query,\n maxTokens: 600,\n });\n const candidates = candidatesFrom(citations);\n\n if (candidates.length !== 1) {\n return {\n strategy: \"targeted-profile\",\n sources: [],\n links: [],\n notes: candidates.length === 0\n ? `${query}\\n\\nno profile matched`\n : `${query}\\n\\n${candidates.length} distinct profiles matched — the name is ambiguous`,\n };\n }\n\n const only = candidates[0]!;\n const link: PersonLink = { kind: kindOf(only.host), url: only.url };\n const source: VerifiedSource = {\n sourceUrl: only.url,\n sourceClass: \"self-asserted\",\n facts: [\"profile\"],\n verifiedOn: retrievedAt,\n corroboration: \"confirmed\",\n };\n return {\n strategy: \"targeted-profile\",\n sources: [source],\n links: [link],\n notes: `${query}\\n\\nexactly one distinct profile matched the name and company`,\n };\n },\n};\n","// A person's key should not be their name.\n//\n// kg has been writing person nodes keyed on `slugify(name)`, which fails in\n// both directions. Two people called John Smith at two companies collapse into\n// one node, silently merging their employers, links, and evidence. And a person\n// whose name is written differently by the next source — a married name, an\n// accent restored, \"Bob\" where the last pass saw \"Robert\" — mints a second node\n// that nothing will ever reconcile.\n//\n// The fix is to mint an opaque key once and index the ways a person is\n// recognized *alongside* it. The name stops being the identity and becomes one\n// of several aliases pointing at it.\n//\n// Which alias resolves matters. A public profile — a LinkedIn handle, a GitHub\n// login, an ORCID — belongs to the person and survives them changing jobs, so\n// those are tried first. Name-plus-employer is the bootstrap for someone with\n// no profile found yet: it distinguishes namesakes, which is the failure that\n// actually bites, but it does not survive a job change on its own. That is why\n// a person picks up profile aliases as soon as any pass finds one, and why the\n// two kinds are ordered rather than pooled.\nimport type { AdminDb } from \"@odla-ai/db\";\nimport type { GraphFragment } from \"../types\";\nimport { identityKey } from \"../util/identity\";\nimport type { PersonLink, PersonRow } from \"../enrich/person\";\n\n/** Link kinds that identify one person durably, in descending strength. */\nconst DURABLE_KINDS: readonly PersonLink[\"kind\"][] = [\"linkedin\", \"github\", \"bluesky\", \"x\"];\n\n/**\n * How an ontology stores the alias index.\n *\n * kg does not own your ontology — you declare the entity, kg reads and writes\n * it through these names. The alias entity needs a unique natural key and one\n * attribute holding the person's key.\n */\nexport interface PersonIdentityConfig {\n /** Entity type indexing aliases, e.g. `\"person_alias\"`. */\n aliasType: string;\n /** Unique natural-key attr on the alias entity. Default `\"alias\"`. */\n aliasKeyAttr?: string;\n /** Attr on the alias entity holding the person's key. Default `\"personKey\"`. */\n personKeyAttr?: string;\n /** Entity type of the person the alias points at. Default `\"person\"`. */\n personType?: string;\n /** Edge label from alias to person. Default `\"of_person\"`. */\n edgeLabel?: string;\n}\n\n/** Config with defaults applied. */\nfunction settled(config: PersonIdentityConfig) {\n return {\n aliasType: config.aliasType,\n aliasKeyAttr: config.aliasKeyAttr ?? \"alias\",\n personKeyAttr: config.personKeyAttr ?? \"personKey\",\n personType: config.personType ?? \"person\",\n edgeLabel: config.edgeLabel ?? \"of_person\",\n };\n}\n\n/**\n * Mint an opaque person key.\n *\n * Random rather than derived, and that is the entire point. Any key computed\n * from the facts — a hash of name plus employer — collides two namesakes at one\n * company and mints a second identity the moment someone changes job. A key\n * that means nothing cannot be wrong about anything.\n *\n * @param random Injectable for tests; defaults to `crypto.randomUUID`.\n */\nexport function mintPersonKey(random: () => string = () => crypto.randomUUID()): string {\n return `p_${random()}`;\n}\n\n/** Whether a key was minted by {@link mintPersonKey} rather than derived from a name. */\nexport function isMintedKey(key: string): boolean {\n return key.startsWith(\"p_\");\n}\n\n/**\n * The alias for a public profile: durable, because it belongs to the person\n * rather than to their current job.\n */\nexport function profileAlias(kind: PersonLink[\"kind\"], url: string): string | null {\n try {\n const parsed = new URL(url);\n const handle = parsed.pathname.replace(/\\/+$/, \"\").toLowerCase();\n return handle && handle !== \"/\" ? `${kind}:${handle}` : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The bootstrap alias for someone with no profile found yet.\n *\n * Employer is part of the key on purpose: without it every namesake resolves to\n * one person, which is the bug this module exists to close. With it, a job\n * change produces a *new* alias rather than a wrong match — the person keeps\n * their key, and the old alias keeps pointing at them.\n *\n * Returns null without an employer. A bare name is not an identity, and\n * indexing one would reintroduce the collision by the back door.\n */\nexport function nameAlias(name: string, companyName?: string): string | null {\n const person = identityKey(name);\n const company = companyName ? identityKey(companyName) : \"\";\n return person && company ? `name:${person}~${company}` : null;\n}\n\n/**\n * Every alias a person is recognizable by, strongest first.\n *\n * Order is what makes resolution correct rather than merely convenient: a\n * profile match is trusted over a name-plus-employer match, so someone who\n * changed jobs still resolves to themselves.\n */\nexport function aliasesFor(person: PersonRow, links: readonly PersonLink[] = []): string[] {\n const out: string[] = [];\n for (const kind of DURABLE_KINDS) {\n for (const link of links.filter((l) => l.kind === kind)) {\n const alias = profileAlias(kind, link.url);\n if (alias && !out.includes(alias)) out.push(alias);\n }\n }\n const byName = nameAlias(person.name, person.companyName);\n if (byName) out.push(byName);\n return out;\n}\n\n/**\n * Resolve aliases to an existing person key.\n *\n * One batched read, then the *caller's* order decides — the first alias with a\n * hit wins, so a profile match beats a name match even when the name row was\n * written first. Returns null when nothing matches, which means mint.\n */\nexport async function resolvePersonKey(\n db: AdminDb,\n config: PersonIdentityConfig,\n aliases: readonly string[],\n): Promise<string | null> {\n const { aliasType, aliasKeyAttr, personKeyAttr } = settled(config);\n if (aliases.length === 0) return null;\n\n const result = await db.query({\n [aliasType]: { $: { where: { [aliasKeyAttr]: { $in: [...aliases] } } } },\n });\n const found = new Map<string, string>();\n for (const row of result[aliasType] ?? []) {\n const alias = (row as Record<string, unknown>)[aliasKeyAttr];\n const personKey = (row as Record<string, unknown>)[personKeyAttr];\n if (typeof alias === \"string\" && typeof personKey === \"string\" && personKey) found.set(alias, personKey);\n }\n for (const alias of aliases) {\n const hit = found.get(alias);\n if (hit) return hit;\n }\n return null;\n}\n\n/**\n * Rewrite a fragment onto a minted person key and index the aliases that lead\n * back to it.\n *\n * Everything referencing the old key moves with it — edges included — so an\n * affiliation written against the name-derived key does not end up orphaned.\n * The alias rows are what make the *next* pass idempotent: without them a\n * minted key would be re-minted every run, which is worse than the name key it\n * replaced.\n */\nexport function applyPersonIdentity(\n fragment: GraphFragment,\n from: string,\n to: string,\n aliases: readonly string[],\n config: PersonIdentityConfig,\n): GraphFragment {\n const { aliasType, aliasKeyAttr, personKeyAttr, personType, edgeLabel } = settled(config);\n const rekey = (type: string, key: string): string => (type === personType && key === from ? to : key);\n\n const nodes = fragment.nodes.map((n) => ({ ...n, key: rekey(n.type, n.key) }));\n const edges = fragment.edges.map((e) => ({\n from: { type: e.from.type, key: rekey(e.from.type, e.from.key) },\n to: { type: e.to.type, key: rekey(e.to.type, e.to.key) },\n label: e.label,\n }));\n\n for (const alias of [...new Set(aliases)]) {\n nodes.push({ type: aliasType, key: alias, attrs: { [personKeyAttr]: to } });\n edges.push({ from: { type: aliasType, key: alias }, to: { type: personType, key: to }, label: edgeLabel });\n }\n return { nodes, edges };\n}\n","// Resolve-or-mint, in one call.\n//\n// The order is the whole contract: look the person up by every alias we know\n// them by, and only mint when nothing matched. Minting first and reconciling\n// later is how a graph ends up with four nodes for one person.\nimport type { AdminDb } from \"@odla-ai/db\";\nimport type { GraphFragment } from \"../types\";\nimport type { PersonLink, PersonRow } from \"../enrich/person\";\nimport {\n aliasesFor,\n applyPersonIdentity,\n mintPersonKey,\n resolvePersonKey,\n type PersonIdentityConfig,\n} from \"./person\";\n\n/** What identifying a person settled on. */\nexport interface PersonIdentity {\n /** The opaque key the person is stored under. */\n personKey: string;\n /** True when nobody matched and a fresh key was minted. */\n minted: boolean;\n /** The aliases now indexed against that key, strongest first. */\n aliases: string[];\n /** The fragment, rekeyed onto `personKey` with the alias rows appended. */\n fragment: GraphFragment;\n}\n\n/**\n * Give a person a stable opaque key, reusing the one they already have.\n *\n * Aliases found by this pass are indexed too, not just the one that resolved.\n * That is how someone graduates off the fragile name-plus-employer alias: the\n * first pass that finds their LinkedIn attaches it to the key they already\n * have, and every later pass — including after they change jobs — resolves on\n * the profile instead.\n *\n * @param fragment A fragment keyed on `person.slug`, as `personToFragment`\n * produces. Its person node and every edge touching it are rekeyed.\n */\nexport async function identifyPerson(\n db: AdminDb,\n config: PersonIdentityConfig,\n person: PersonRow,\n links: readonly PersonLink[],\n fragment: GraphFragment,\n): Promise<PersonIdentity> {\n const aliases = aliasesFor(person, links);\n const existing = await resolvePersonKey(db, config, aliases);\n const personKey = existing ?? mintPersonKey();\n return {\n personKey,\n minted: existing === null,\n aliases,\n fragment: applyPersonIdentity(fragment, person.slug, personKey, aliases, config),\n };\n}\n","import { init, extractText, type Ai } from \"@odla-ai/ai\";\nimport type { Usage } from \"../types\";\n\n/**\n * The LLM seam, now a thin adapter over @odla-ai/ai (which was itself\n * generalized from this package's original Provider). Three verbs stay:\n * extract (forced structured tool call), search (web-search-enabled text),\n * write (plain generation). Callers never see vendor or oracle types.\n */\n\nexport type ProviderFamily = \"claude\" | \"openai\";\n\n/** Role → model tiers per family — config as data. `cheap` is for gate-style\n * calls, `extract` for structured extraction (and prose writing), `search`\n * for web-search-enabled calls. */\nexport const MODEL_TIERS: Record<ProviderFamily, { cheap: string; extract: string; search: string }> = {\n claude: { cheap: \"claude-haiku-4-5\", extract: \"claude-sonnet-5\", search: \"claude-sonnet-5\" },\n openai: { cheap: \"gpt-5-nano\", extract: \"gpt-5-mini\", search: \"gpt-5-search-api\" },\n};\n\n/** A forced structured tool call: schema in, validated object out. */\nexport interface ExtractCall {\n system: string;\n user: string;\n tool: { name: string; description?: string; parameters: Record<string, unknown> };\n model?: string;\n maxTokens?: number;\n}\n\n/** A web-search-enabled call: the model searches and returns text (caller tolerant-parses). */\nexport interface SearchCall {\n system: string;\n user: string;\n model?: string;\n maxTokens?: number;\n}\n\n/** A plain text-generation call (no tools, no search) — e.g. digest prose. */\nexport interface WriteCall {\n system: string;\n user: string;\n model?: string;\n maxTokens?: number;\n}\n\nexport interface Provider {\n id: ProviderFamily;\n /** The underlying @odla-ai/ai client — escape hatch for advanced use. */\n ai: Ai;\n extract<T = unknown>(c: ExtractCall): Promise<{ value: T; usage: Usage }>;\n search(c: SearchCall): Promise<{ text: string; usage: Usage }>;\n write(c: WriteCall): Promise<{ text: string; usage: Usage }>;\n}\n\nexport interface LlmConfig {\n provider: ProviderFamily;\n anthropicKey?: string;\n openaiKey?: string;\n /** Inject a pre-built Ai (tests, custom catalog); keys are ignored when set. */\n ai?: Ai;\n}\n\nfunction toUsage(u: { inputTokens: number; outputTokens: number }): Usage {\n return { calls: 1, inputTokens: u.inputTokens, outputTokens: u.outputTokens };\n}\n\n/** The library primitive: an explicit config in, the three-verb seam out. */\nexport function createProvider(cfg: LlmConfig): Provider {\n const tiers = MODEL_TIERS[cfg.provider];\n const ai = cfg.ai ?? init({ keys: { anthropic: cfg.anthropicKey, openai: cfg.openaiKey } });\n return {\n id: cfg.provider,\n ai,\n async extract<T = unknown>(c: ExtractCall) {\n const { value, usage } = await ai.extract<T>({\n model: c.model ?? tiers.extract,\n system: c.system,\n user: c.user,\n tool: c.tool,\n maxTokens: c.maxTokens,\n });\n return { value, usage: toUsage(usage) };\n },\n async search(c: SearchCall) {\n const { text, usage } = await ai.search({\n model: c.model ?? tiers.search,\n system: c.system,\n user: c.user,\n maxTokens: c.maxTokens,\n });\n return { text, usage: toUsage(usage) };\n },\n async write(c: WriteCall) {\n const res = await ai.chat({\n model: c.model ?? tiers.extract,\n system: c.system,\n messages: [{ role: \"user\", content: c.user }],\n maxTokens: c.maxTokens ?? 2048,\n });\n return { text: extractText(res.content), usage: toUsage(res.usage) };\n },\n };\n}\n\n/** The env vars the provider factory reads — a structural slice, not the\n * worker Env, so any host (worker, script, test) can supply plain strings. */\nexport interface LlmEnv {\n LLM_PROVIDER?: string; // \"claude\" | \"openai\"\n ANTHROPIC_API_KEY?: string;\n OPENAI_API_KEY?: string;\n}\n\n/** Build the configured provider from host env, failing before use when its key is missing. */\nexport function getProvider(env: LlmEnv): Provider {\n const which: ProviderFamily = (env.LLM_PROVIDER || \"claude\").toLowerCase() === \"openai\" ? \"openai\" : \"claude\";\n if (which === \"openai\" && !env.OPENAI_API_KEY) throw new Error(\"LLM_PROVIDER=openai but OPENAI_API_KEY is unset\");\n if (which === \"claude\" && !env.ANTHROPIC_API_KEY) throw new Error(\"LLM_PROVIDER=claude but ANTHROPIC_API_KEY is unset\");\n return createProvider({ provider: which, anthropicKey: env.ANTHROPIC_API_KEY, openaiKey: env.OPENAI_API_KEY });\n}\n","/**\n * Tolerant JSON extraction from LLM prose — web-search models return JSON inside\n * explanatory text. Ported from agnacl services/enrichment.ts.\n */\n\nexport function extractJson<T = unknown>(text: string): T | null {\n const m = text.match(/\\{[\\s\\S]*\\}/);\n if (!m) return null;\n try {\n return JSON.parse(m[0]) as T;\n } catch {\n return null;\n }\n}\n\n/** Tolerantly parse the first JSON-array-shaped span in model prose, or return an empty array. */\nexport function extractJsonArray<T = unknown>(text: string): T[] {\n const m = text.match(/\\[[\\s\\S]*\\]/);\n if (!m) return [];\n try {\n const v = JSON.parse(m[0]);\n return Array.isArray(v) ? (v as T[]) : [];\n } catch {\n return [];\n }\n}\n","/**\n * Dependency-free HTML → text extraction (Workers-isolate friendly: no DOM/jsdom).\n * Ported from agnacl services/content-extractor.ts.\n */\n\nconst MAX_CHARS = 8000;\n\n/** Strip scripts/styles/tags and collapse whitespace; cap length. */\nexport function htmlToText(html: string, maxChars = MAX_CHARS): string {\n let t = html;\n t = t.replace(/<script[\\s\\S]*?<\\/script>/gi, \" \");\n t = t.replace(/<style[\\s\\S]*?<\\/style>/gi, \" \");\n t = t.replace(/<!--[\\s\\S]*?-->/g, \" \");\n t = t.replace(/<\\/(p|div|section|article|li|h[1-6]|br)>/gi, \"\\n\");\n t = t.replace(/<[^>]+>/g, \" \");\n t = decodeEntities(t);\n t = t.replace(/[ \\t\\f\\r]+/g, \" \").replace(/\\n\\s*\\n\\s*/g, \"\\n\\n\").trim();\n return t.slice(0, maxChars);\n}\n\nfunction decodeEntities(s: string): string {\n return s\n .replace(/ /gi, \" \")\n .replace(/&/gi, \"&\")\n .replace(/</gi, \"<\")\n .replace(/>/gi, \">\")\n .replace(/"/gi, '\"')\n .replace(/'|'/gi, \"'\")\n .replace(/&#(\\d+);/g, (_, d) => String.fromCharCode(Number(d)))\n .replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCharCode(parseInt(h, 16)));\n}\n\n/** Fetch a URL and return readable text. Best-effort: returns \"\" on any failure. */\nexport async function fetchText(url: string, maxChars = MAX_CHARS): Promise<string> {\n try {\n const res = await fetch(url, {\n headers: {\n \"user-agent\": \"odla-kg/0.1 (+https://github.com/cory/odla-kg)\",\n accept: \"text/html,application/xhtml+xml\",\n },\n });\n if (!res.ok) return \"\";\n const ct = res.headers.get(\"content-type\") || \"\";\n const body = await res.text();\n if (ct.includes(\"html\") || /<\\w+[\\s>]/.test(body.slice(0, 200))) {\n return htmlToText(body, maxChars);\n }\n return body.slice(0, maxChars);\n } catch {\n return \"\";\n }\n}\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\nimport { htmlToText } from \"../util/content\";\n\n/** Dependency-free RSS/Atom parsing, ported from agnacl services/rss.ts. */\nconst UA = \"Mozilla/5.0 (compatible; odla-kg/0.1)\";\n\nfunction extractTag(xml: string, tag: string): string {\n const cdata = xml.match(new RegExp(`<${tag}[^>]*>\\\\s*<!\\\\[CDATA\\\\[([\\\\s\\\\S]*?)\\\\]\\\\]>\\\\s*</${tag}>`, \"i\"));\n if (cdata) return cdata[1]!.trim();\n const m = xml.match(new RegExp(`<${tag}[^>]*>([\\\\s\\\\S]*?)</${tag}>`, \"i\"));\n return m ? m[1]!.trim() : \"\";\n}\n\nfunction extractAttr(xml: string, tag: string, attr: string): string {\n const m = xml.match(new RegExp(`<${tag}[^>]*\\\\s${attr}=[\"']([^\"']*)[\"']`, \"i\"));\n return m ? m[1]!.trim() : \"\";\n}\n\nfunction splitItems(xml: string): string[] {\n return xml.match(/<item[\\s>][\\s\\S]*?<\\/item>/gi) || xml.match(/<entry[\\s>][\\s\\S]*?<\\/entry>/gi) || [];\n}\n\ninterface RssCfg {\n url: string;\n name: string;\n watch?: string | null;\n}\n\nexport const rssConnector: SourceConnector<RssCfg> = {\n id: \"rss\",\n capabilities: { fetch: true, structured: false },\n\n parseConfig(raw): RssCfg {\n const r = raw as Record<string, unknown>;\n if (typeof r.url !== \"string\" || !r.url) throw new Error(\"rss connector requires a `url`\");\n return { url: r.url, name: String(r.name ?? r.url), watch: (r.watch as string | null) ?? null };\n },\n\n async fetch(cfg, ctx): Promise<RawItem[]> {\n let xml: string;\n try {\n const res = await fetch(cfg.url, { headers: { \"User-Agent\": UA } });\n if (!res.ok) {\n if (res.status === 429 || res.status === 403 || res.status >= 500) return [];\n throw new Error(`feed ${cfg.url} → ${res.status}`);\n }\n xml = await res.text();\n } catch (e) {\n console.log(`rss fetch failed (non-blocking): ${cfg.url}: ${e}`);\n return [];\n }\n\n const out: RawItem[] = [];\n for (const raw of splitItems(xml)) {\n const title = extractTag(raw, \"title\");\n let url = extractTag(raw, \"link\") || extractAttr(raw, \"link\", \"href\");\n if (!title || !url) continue;\n url = url.trim();\n const date =\n extractTag(raw, \"pubDate\") || extractTag(raw, \"published\") || extractTag(raw, \"updated\") || undefined;\n const content =\n extractTag(raw, \"content:encoded\") ||\n extractTag(raw, \"content\") ||\n extractTag(raw, \"description\") ||\n extractTag(raw, \"summary\") ||\n \"\";\n let text = htmlToText(content);\n // Thin feed body → fetch the article page.\n if (text.length < 200) {\n const page = await ctx.fetchText(url);\n if (page.length > text.length) text = page;\n }\n out.push({ url, title, date, sourceName: cfg.name, sourceKind: \"rss\", watch: cfg.watch, text });\n }\n return out;\n },\n};\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector } from \"./types\";\nimport { fetchText } from \"../util/content\";\n\n/** Fetch one URL → a text-bearing RawItem. Used both directly (/trigger) and as a connector. */\nexport async function fetchUrl(url: string): Promise<RawItem> {\n const text = await fetchText(url);\n const firstLine = text.split(\"\\n\").find((l) => l.trim().length > 0) ?? \"\";\n let host = url;\n try {\n host = new URL(url).hostname;\n } catch {\n /* keep raw url as the source label */\n }\n return { url, title: (firstLine || url).slice(0, 200).trim(), sourceName: host, sourceKind: \"url\", text };\n}\n\ninterface UrlCfg {\n url: string;\n watch?: string | null;\n}\n\nexport const urlConnector: SourceConnector<UrlCfg> = {\n id: \"url\",\n capabilities: { fetch: true, structured: false },\n parseConfig(raw): UrlCfg {\n const r = raw as Record<string, unknown>;\n if (typeof r.url !== \"string\" || !r.url) throw new Error(\"url connector requires a `url`\");\n return { url: r.url, watch: (r.watch as string | null) ?? null };\n },\n async fetch(cfg): Promise<RawItem[]> {\n const item = await fetchUrl(cfg.url);\n if (cfg.watch !== undefined) item.watch = cfg.watch;\n return [item];\n },\n};\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\n\n/** GDELT DOC 2.0 standing-query connector (text). Ported from agnacl services/gdelt.ts. */\nconst UA = \"Mozilla/5.0 (compatible; odla-kg/0.1)\";\nconst MAX_ITEMS = 15;\n\ninterface GdeltCfg {\n query: string;\n name: string;\n watch?: string | null;\n}\n\nfunction isoFromSeendate(s: string): string | undefined {\n const m = s.match(/^(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})Z?$/);\n return m ? `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}Z` : undefined;\n}\n\nexport const gdeltConnector: SourceConnector<GdeltCfg> = {\n id: \"gdelt\",\n capabilities: { fetch: true, structured: false },\n\n parseConfig(raw): GdeltCfg {\n const r = raw as Record<string, unknown>;\n if (typeof r.query !== \"string\" || !r.query) throw new Error(\"gdelt connector requires a `query`\");\n return { query: r.query, name: String(r.name ?? `GDELT: ${r.query}`), watch: (r.watch as string | null) ?? null };\n },\n\n async fetch(cfg, ctx): Promise<RawItem[]> {\n const api =\n `https://api.gdeltproject.org/api/v2/doc/doc?query=${encodeURIComponent(cfg.query)}` +\n `&mode=artlist&format=json&maxrecords=40×pan=2d`;\n\n let articles: Array<{ url?: string; title?: string; seendate?: string; language?: string }> = [];\n try {\n // GDELT is a best-effort external source; without a deadline a slow or wedged endpoint hangs\n // the whole workflow run. Bound it and let the catch below treat a timeout like any other miss.\n const res = await fetch(api, { headers: { \"User-Agent\": UA }, signal: AbortSignal.timeout(15_000) });\n if (!res.ok) return [];\n const text = await res.text(); // GDELT sometimes returns text errors with 200\n try {\n articles = (JSON.parse(text) as { articles?: typeof articles }).articles ?? [];\n } catch {\n return [];\n }\n } catch (e) {\n console.log(`gdelt fetch failed (non-blocking): ${e}`);\n return [];\n }\n\n const seen = new Set<string>();\n const picked = articles\n .filter((a) => a?.url && a?.title && a.language === \"English\" && !seen.has(a.url!) && (seen.add(a.url!), true))\n .slice(0, MAX_ITEMS);\n\n const out: RawItem[] = [];\n for (const a of picked) {\n const body = await ctx.fetchText(a.url!); // GDELT gives no body; fetch the article\n if (body.length < 200) continue;\n out.push({\n url: a.url!,\n title: a.title!,\n date: isoFromSeendate(a.seendate ?? \"\"),\n sourceName: cfg.name,\n sourceKind: \"gdelt\",\n watch: cfg.watch,\n text: body,\n });\n }\n return out;\n },\n};\n","import type { Value } from \"@odla-ai/db\";\nimport type { RawItem, GraphFragment } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\nimport { slugify } from \"../util/identity\";\n\n/**\n * FRED economic indicators — a STRUCTURED connector: it maps series directly to\n * graph nodes (indicator + region) and emits RawItem.fragment, so the ingest\n * workflow writes it without any LLM extraction. Ported from agnacl services/fred.ts.\n */\ninterface FredCfg {\n series: Array<{ id: string; label: string; unit?: string }>;\n region?: string;\n regionName?: string;\n}\n\nasync function fetchSeries(\n seriesId: string,\n apiKey: string,\n start: string,\n): Promise<Array<{ date: string; value: number }>> {\n const url =\n `https://api.stlouisfed.org/fred/series/observations?series_id=${encodeURIComponent(seriesId)}` +\n `&api_key=${encodeURIComponent(apiKey)}&file_type=json&observation_start=${encodeURIComponent(start)}`;\n try {\n const res = await fetch(url);\n if (!res.ok) return [];\n const data = (await res.json()) as { observations?: Array<{ date?: string; value?: string }> };\n const out: Array<{ date: string; value: number }> = [];\n for (const o of data.observations ?? []) {\n if (!o?.date || o.value === undefined || o.value === \".\") continue;\n const v = Number(o.value);\n if (Number.isFinite(v)) out.push({ date: o.date, value: v });\n }\n return out;\n } catch (e) {\n console.log(`fred fetch failed for ${seriesId} (non-blocking): ${e}`);\n return [];\n }\n}\n\nexport const fredConnector: SourceConnector<FredCfg> = {\n id: \"fred\",\n capabilities: { fetch: true, structured: true },\n\n parseConfig(raw): FredCfg {\n const r = raw as Record<string, unknown>;\n const series = Array.isArray(r.series) ? (r.series as FredCfg[\"series\"]) : [];\n if (!series.length) throw new Error(\"fred connector requires a non-empty `series`\");\n return { series, region: r.region as string | undefined, regionName: r.regionName as string | undefined };\n },\n\n async fetch(cfg, ctx): Promise<RawItem[]> {\n const apiKey = ctx.secret(\"FRED_API_KEY\");\n if (!apiKey) {\n console.log(\"fred: FRED_API_KEY unset — skipping\");\n return [];\n }\n const start = new Date(ctx.now.getTime() - 400 * 24 * 3600 * 1000).toISOString().slice(0, 10);\n const out: RawItem[] = [];\n\n for (const s of cfg.series) {\n const obs = await fetchSeries(s.id, apiKey, start);\n if (!obs.length) continue;\n const latest = obs[obs.length - 1]!;\n const prior = obs.length > 1 ? obs[obs.length - 2] : undefined;\n\n const attrs: Record<string, Value> = { label: s.label, latest: String(latest.value), as_of: latest.date };\n if (prior) attrs.prior = String(prior.value);\n if (s.unit) attrs.unit = s.unit;\n\n const fragment: GraphFragment = { nodes: [{ type: \"indicator\", key: s.id, attrs }], edges: [] };\n if (cfg.region) {\n const rslug = slugify(cfg.region);\n if (rslug) {\n fragment.nodes.push({ type: \"region\", key: rslug, attrs: { name: cfg.regionName ?? cfg.region } });\n fragment.edges.push({ from: { type: \"region\", key: rslug }, to: { type: \"indicator\", key: s.id }, label: \"has_indicator\" });\n }\n }\n\n out.push({\n url: `https://fred.stlouisfed.org/series/${s.id}#${latest.date}`,\n title: `${s.label}: ${latest.value} (${latest.date})`,\n date: latest.date,\n sourceName: \"FRED\",\n sourceKind: \"fred\",\n fragment,\n });\n }\n return out;\n },\n};\n","import type { Value } from \"@odla-ai/db\";\nimport type { RawItem, GraphFragment } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\nimport { slugify, isLikelyPersonName } from \"../util/identity\";\n\n/**\n * SEC EDGAR Form D — a STRUCTURED connector (RawItem.fragment, no LLM). A lean\n * port of agnacl services/edgar.ts: first page of Form D filings for the given\n * states, each mapped to company + funding_round (+ people, region). SEC\n * requires an identifying User-Agent (env.EDGAR_USER_AGENT) on every request.\n */\nconst SEARCH_BASE = \"https://efts.sec.gov/LATEST/search-index\";\nconst MAX_FILINGS = 10;\n\ninterface EdgarCfg {\n locationCodes: string[];\n cityFilter?: string[];\n days?: number;\n}\n\nconst cikTrim = (cik: string): string => cik.replace(/^0+/, \"\") || cik;\nconst indexUrl = (cik: string, adsh: string): string =>\n `https://www.sec.gov/Archives/edgar/data/${cikTrim(cik)}/${adsh.replace(/-/g, \"\")}/${adsh}-index.htm`;\n\nfunction tagText(scope: string, tag: string): string {\n const m = scope.match(new RegExp(`<${tag}[^>]*>\\\\s*([\\\\s\\\\S]*?)\\\\s*</${tag}>`, \"i\"));\n return m ? m[1]!.trim() : \"\";\n}\n\ninterface EdgarHit {\n _source?: {\n adsh?: string;\n ciks?: string[];\n display_names?: string[];\n file_date?: string;\n biz_states?: string[];\n biz_locations?: string[];\n };\n}\n\nexport const edgarConnector: SourceConnector<EdgarCfg> = {\n id: \"edgar\",\n capabilities: { fetch: true, structured: true },\n\n parseConfig(raw): EdgarCfg {\n const r = raw as Record<string, unknown>;\n const codes = Array.isArray(r.locationCodes) ? (r.locationCodes as string[]) : [];\n if (!codes.length) throw new Error('edgar connector requires `locationCodes` (e.g. [\"UT\"])');\n return {\n locationCodes: codes,\n cityFilter: Array.isArray(r.cityFilter) ? (r.cityFilter as string[]) : undefined,\n days: typeof r.days === \"number\" ? r.days : 7,\n };\n },\n\n async fetch(cfg, ctx): Promise<RawItem[]> {\n const ua = ctx.secret(\"EDGAR_USER_AGENT\");\n if (!ua) {\n console.log(\"edgar: EDGAR_USER_AGENT unset — skipping\");\n return [];\n }\n const enddt = ctx.now.toISOString().slice(0, 10);\n const startdt = new Date(ctx.now.getTime() - (cfg.days ?? 7) * 24 * 3600 * 1000).toISOString().slice(0, 10);\n const searchUrl =\n `${SEARCH_BASE}?q=&forms=D&locationCodes=${encodeURIComponent(cfg.locationCodes.join(\",\"))}` +\n `&startdt=${startdt}&enddt=${enddt}`;\n\n let hits: EdgarHit[] = [];\n try {\n const res = await fetch(searchUrl, { headers: { \"User-Agent\": ua } });\n if (!res.ok) return [];\n hits = ((await res.json()) as { hits?: { hits?: EdgarHit[] } }).hits?.hits ?? [];\n } catch (e) {\n console.log(`edgar search failed (non-blocking): ${e}`);\n return [];\n }\n\n const out: RawItem[] = [];\n const seen = new Set<string>();\n for (const hit of hits.slice(0, MAX_FILINGS)) {\n const src = hit._source ?? {};\n const adsh = src.adsh ?? \"\";\n if (!adsh || seen.has(adsh)) continue;\n seen.add(adsh);\n const cik = src.ciks?.[0] ?? \"\";\n const name = (src.display_names?.[0] ?? \"\").replace(/\\s*\\(CIK\\s*\\d+\\)\\s*$/i, \"\").trim();\n if (!name || !cik) continue;\n\n const loc = src.biz_locations?.[0] ?? \"\";\n let city = loc.trim();\n let state = src.biz_states?.[0] ?? \"\";\n const comma = loc.lastIndexOf(\",\");\n if (comma >= 0) {\n city = loc.slice(0, comma).trim();\n state = loc.slice(comma + 1).trim() || state;\n }\n if (cfg.cityFilter?.length && !cfg.cityFilter.some((c) => c.toLowerCase() === city.toLowerCase())) continue;\n\n const url = indexUrl(cik, adsh);\n const fileDate = src.file_date ?? \"\";\n\n // Filing detail (offering amount, industry, related persons)\n let industry = \"\";\n let offering = \"\";\n const persons: Array<{ name: string; roles: string[] }> = [];\n try {\n const detailUrl = `https://www.sec.gov/Archives/edgar/data/${cikTrim(cik)}/${adsh.replace(/-/g, \"\")}/primary_doc.xml`;\n const dres = await fetch(detailUrl, { headers: { \"User-Agent\": ua } });\n if (dres.ok) {\n const xml = await dres.text();\n industry = tagText(xml, \"industryGroupType\");\n offering = tagText(xml, \"totalOfferingAmount\");\n for (const block of xml.match(/<relatedPersonInfo>[\\s\\S]*?<\\/relatedPersonInfo>/gi) ?? []) {\n const pname = [tagText(block, \"firstName\"), tagText(block, \"lastName\")]\n .filter((p) => p && p !== \"-\")\n .join(\" \")\n .trim();\n if (!pname) continue;\n const roles: string[] = [];\n const relRe = /<relationship>\\s*([\\s\\S]*?)\\s*<\\/relationship>/gi;\n let m: RegExpExecArray | null;\n while ((m = relRe.exec(block)) !== null) {\n const r = m[1]!.trim();\n if (r && !roles.includes(r)) roles.push(r);\n }\n persons.push({ name: pname, roles });\n }\n }\n } catch {\n /* fall back to search-hit metadata */\n }\n\n const cslug = slugify(name);\n if (!cslug) continue;\n const f: GraphFragment = { nodes: [], edges: [] };\n const cattrs: Record<string, Value> = { name };\n if (city) cattrs.city = city;\n if (state) cattrs.region = state;\n if (industry) cattrs.sector = industry;\n if (fileDate) {\n cattrs.first_seen = fileDate;\n cattrs.last_seen = fileDate;\n }\n f.nodes.push({ type: \"company\", key: cslug, attrs: cattrs });\n\n if (state) {\n const rslug = slugify(state);\n if (rslug) {\n f.nodes.push({ type: \"region\", key: rslug, attrs: { name: state } });\n f.edges.push({ from: { type: \"company\", key: cslug }, to: { type: \"region\", key: rslug }, label: \"located_in\" });\n }\n }\n\n const rkey = `${cslug}:form-d:${fileDate}`;\n const rattrs: Record<string, Value> = { round: \"Form D\", source_url: url };\n if (offering) rattrs.amount = offering;\n if (fileDate) rattrs.date = fileDate;\n f.nodes.push({ type: \"funding_round\", key: rkey, attrs: rattrs });\n f.edges.push({ from: { type: \"funding_round\", key: rkey }, to: { type: \"company\", key: cslug }, label: \"round_of\" });\n\n for (const p of persons) {\n if (!isLikelyPersonName(p.name)) continue;\n const pslug = slugify(p.name);\n if (!pslug) continue;\n const pattrs: Record<string, Value> = { name: p.name };\n if (p.roles.length) pattrs.role = p.roles.join(\", \");\n f.nodes.push({ type: \"person\", key: pslug, attrs: pattrs });\n f.edges.push({ from: { type: \"person\", key: pslug }, to: { type: \"company\", key: cslug }, label: \"works_at\" });\n }\n\n out.push({\n url,\n title: `Form D: ${name}${offering ? ` — ${offering}` : \"\"}`,\n date: fileDate || undefined,\n sourceName: \"SEC EDGAR\",\n sourceKind: \"edgar\",\n fragment: f,\n });\n }\n return out;\n },\n};\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\n\n/**\n * TEMPLATE — a government / open-data connector.\n *\n * This exists to demonstrate the extensibility contract: adding a new source is\n * a self-contained module implementing SourceConnector + ONE line in the\n * registry + config entries in config/sources.ts. The cron, ingest workflow,\n * extractor, and graph writer are untouched — they only ever speak RawItem /\n * GraphFragment.\n *\n * To make it real, fetch your dataset and return either:\n * - text-bearing RawItems ({ ...meta, text }) → the LLM extracts a graph\n * - fragment-bearing RawItems ({ ...meta, fragment }) → straight to the writer\n * (map records to ontology nodes/edges yourself — see fred.ts / edgar.ts).\n * Returns [] until implemented, so wiring it up is harmless.\n */\ninterface GovDataCfg {\n dataset: string;\n watch?: string | null;\n}\n\nexport const govDataConnector: SourceConnector<GovDataCfg> = {\n id: \"gov-data\",\n capabilities: { fetch: true, structured: true },\n\n parseConfig(raw): GovDataCfg {\n const r = raw as Record<string, unknown>;\n return { dataset: String(r.dataset ?? \"\"), watch: (r.watch as string | null) ?? null };\n },\n\n async fetch(cfg, _ctx: ConnectorContext): Promise<RawItem[]> {\n console.log(`gov-data: template connector for dataset \"${cfg.dataset}\" — implement fetch() to emit RawItems`);\n return [];\n },\n};\n","import type { RawItem } from \"../types\";\nimport type { SourceConnector, ConnectorContext } from \"./types\";\nimport { urlHash } from \"../util/identity\";\n\n/**\n * LLM-native web search — a DISCOVERY connector. Each standing query is run\n * through the provider's web-search tool; the prose result becomes a text\n * RawItem that flows through the normal extractor (so discovered companies land\n * in the graph the same way feed items do). Discovery-only: fetch() is a no-op.\n */\ninterface WebSearchCfg {\n queries: string[];\n name: string;\n watch?: string | null;\n}\n\nconst SWEEP_SYSTEM =\n \"You are a research scout building a knowledge graph of entrepreneurs, startups, and business trends. Search the web and report concrete, real companies and the people leading them, with specifics (what they do, where, who founded them). Tie every claim to something you actually found — never invent names, numbers, or companies. If a search turns up nothing solid, say so plainly.\";\n\nexport const webSearchConnector: SourceConnector<WebSearchCfg> = {\n id: \"web-search\",\n capabilities: { fetch: false, discover: true, structured: false },\n\n parseConfig(raw): WebSearchCfg {\n const r = raw as Record<string, unknown>;\n const queries = Array.isArray(r.queries)\n ? (r.queries.filter((q) => typeof q === \"string\") as string[])\n : typeof r.query === \"string\"\n ? [r.query]\n : [];\n if (!queries.length) throw new Error(\"web-search connector requires `queries` (or a `query`)\");\n return { queries, name: String(r.name ?? \"web-search\"), watch: (r.watch as string | null) ?? null };\n },\n\n async fetch(): Promise<RawItem[]> {\n return []; // discovery-only\n },\n\n async discover(cfg, ctx: ConnectorContext): Promise<RawItem[]> {\n const provider = ctx.provider();\n const today = ctx.now.toISOString().slice(0, 10);\n const out: RawItem[] = [];\n for (const q of cfg.queries) {\n const { text } = await provider.search({\n system: SWEEP_SYSTEM,\n user: `Search task: ${q}\\nToday's date: ${today}\\n\\nReport what you find, naming specific companies and the real people leading them.`,\n });\n if (text.trim().length < 80) continue;\n out.push({\n url: `search://web-search/${urlHash(q)}#${today}`, // synthetic; changes daily so re-sweeps aren't deduped away\n title: q,\n date: today,\n sourceName: cfg.name,\n sourceKind: \"web-search\",\n watch: cfg.watch,\n text,\n });\n }\n return out;\n },\n};\n","import type { SourceConnector, ConnectorContext } from \"./types\";\nimport type { Ontology } from \"../ontology\";\nimport type { Provider } from \"../llm/provider\";\nimport { fetchText } from \"../util/content\";\nimport { rssConnector } from \"./rss\";\nimport { urlConnector } from \"./url\";\nimport { gdeltConnector } from \"./gdelt\";\nimport { fredConnector } from \"./fred\";\nimport { edgarConnector } from \"./edgar\";\nimport { govDataConnector } from \"./govdata\";\nimport { webSearchConnector } from \"./web-search\";\n\n/**\n * The connector registry. Adding a source (RSS, a government feed, a new API)\n * is: implement SourceConnector in a module, add one line here, add config\n * entries to config/sources.ts. The cron, workflow, extractor, and writer are\n * untouched — they only speak RawItem / GraphFragment.\n */\nconst REGISTRY = new Map<string, SourceConnector>([\n [rssConnector.id, rssConnector as SourceConnector],\n [urlConnector.id, urlConnector as SourceConnector],\n [gdeltConnector.id, gdeltConnector as SourceConnector],\n [fredConnector.id, fredConnector as SourceConnector], // structured (RawItem.fragment)\n [edgarConnector.id, edgarConnector as SourceConnector], // structured (RawItem.fragment)\n [webSearchConnector.id, webSearchConnector as SourceConnector], // discovery (web search)\n [govDataConnector.id, govDataConnector as SourceConnector], // template — zero core change to add\n]);\n\n/** Resolve a built-in source connector by its stable registry id. */\nexport function getConnector(id: string): SourceConnector | undefined {\n return REGISTRY.get(id);\n}\n\n/** List registered connector ids in deterministic insertion order. */\nexport function connectorIds(): string[] {\n return [...REGISTRY.keys()];\n}\n\n/** Everything a ConnectorContext needs, supplied by the host (worker, script, test). */\nexport interface ContextConfig {\n ontology: Ontology;\n provider: () => Provider;\n /** Named secrets (API keys, user agents) — e.g. the worker Env, or a plain object. */\n secrets?: Record<string, string | undefined>;\n now?: Date;\n}\n\n/** Create a host-supplied connector context with lazy provider construction. */\nexport function makeContext(cfg: ContextConfig): ConnectorContext {\n let provider: Provider | undefined; // lazy: only discovery connectors need it\n return {\n now: cfg.now ?? new Date(),\n fetchText: (u) => fetchText(u),\n secret: (name) => cfg.secrets?.[name],\n ontology: cfg.ontology,\n provider: () => (provider ??= cfg.provider()),\n };\n}\n","import type { ConnectorJob } from \"./connectors/types\";\n\n/**\n * Sources are DATA, not code. Each entry names a connector (`kind`) + its config\n * + scheduling. Add a feed → add an entry; no redeploy of the pipeline. P5 moves\n * this into KV behind a /config admin endpoint so edits don't need a deploy at all.\n */\nexport interface SourceEntry {\n kind: string; // connector id (registry key)\n name: string;\n enabled?: boolean; // default true\n cadenceHours?: number; // default 1 — runs when UTC hour % cadence === 0\n watch?: string | null;\n // connector-specific config (rss: url; more in P3):\n url?: string;\n query?: string;\n [key: string]: unknown;\n}\n\n/** Return source entries not explicitly disabled, preserving configured order. */\nexport function enabledSources(sources: SourceEntry[]): SourceEntry[] {\n return sources.filter((s) => s.enabled !== false);\n}\n\nfunction toJob(s: SourceEntry): ConnectorJob {\n return { kind: s.kind, name: s.name, watch: s.watch ?? null, cfg: s };\n}\n\n/** Enabled sources whose cadence lands on this UTC hour. */\nexport function jobsForHour(sources: SourceEntry[], hour: number): ConnectorJob[] {\n return enabledSources(sources)\n .filter((s) => hour % Math.max(1, Math.floor(s.cadenceHours ?? 1)) === 0)\n .map(toJob);\n}\n\n/** Every enabled source, ignoring cadence (manual /trigger?kind=sources). */\nexport function allJobs(sources: SourceEntry[]): ConnectorJob[] {\n return enabledSources(sources).map(toJob);\n}\n","import type { RawItem } from \"../types\";\nimport type { SourceEntry } from \"../sources\";\nimport { urlHash } from \"../util/identity\";\n\nconst SEEN_TTL_S = 120 * 24 * 60 * 60; // 120 days\nconst ENRICHED_TTL_S = 30 * 24 * 60 * 60; // 30-day enrichment cooldown\nconst TEMPORAL_TTL_S = 120 * 24 * 60 * 60; // dated temporal-report history copies\n\n/**\n * The slice of a KV store the pipeline needs. Cloudflare's KVNamespace\n * satisfies it structurally; tests pass an in-memory map. Keeping the library\n * off Workers types is what lets it publish as a plain isomorphic package.\n */\nexport interface KVLike {\n get(key: string): Promise<string | null>;\n put(key: string, value: string, opts?: { expirationTtl?: number }): Promise<void>;\n}\n\n/**\n * Operational state in KV (never the graph itself — that lives in odla-db).\n * Ported/trimmed from agnacl services/kv.ts: seen-URL dedup + lastrun stamps.\n */\nexport class Store {\n constructor(private kv: KVLike) {}\n\n private seenKey(url: string): string {\n return `seen:${urlHash(url)}`;\n }\n\n async hasSeen(url: string): Promise<boolean> {\n return (await this.kv.get(this.seenKey(url))) !== null;\n }\n\n async markSeen(url: string): Promise<void> {\n await this.kv.put(this.seenKey(url), \"1\", { expirationTtl: SEEN_TTL_S });\n }\n\n /** Drop items whose URL we've already processed (checked BEFORE any LLM work). */\n async filterNew(items: RawItem[]): Promise<RawItem[]> {\n const flags = await Promise.all(items.map((it) => this.hasSeen(it.url)));\n return items.filter((_, i) => !flags[i]);\n }\n\n async setLastRun(kind: string, at = new Date().toISOString()): Promise<void> {\n await this.kv.put(`lastrun:${kind}`, at);\n }\n\n async getLastRun(kind: string): Promise<string | null> {\n return this.kv.get(`lastrun:${kind}`);\n }\n\n /** Per-entity enrichment cooldown so we don't re-search the same node constantly. */\n async isEnrichCooling(key: string): Promise<boolean> {\n return (await this.kv.get(`enriched:${key}`)) !== null;\n }\n\n async markEnriched(key: string): Promise<void> {\n await this.kv.put(`enriched:${key}`, new Date().toISOString(), { expirationTtl: ENRICHED_TTL_S });\n }\n\n /** Runtime source config (edit without redeploy). null = fall back to the static SOURCES. */\n async getConfigSources(): Promise<SourceEntry[] | null> {\n const raw = await this.kv.get(\"config:sources\");\n if (!raw) return null;\n try {\n const v = JSON.parse(raw);\n return Array.isArray(v) ? (v as SourceEntry[]) : null;\n } catch {\n return null;\n }\n }\n\n async setConfigSources(list: SourceEntry[]): Promise<void> {\n await this.kv.put(\"config:sources\", JSON.stringify(list));\n }\n\n /** Cache the latest temporal report (`temporal:latest`) plus a dated history copy. */\n async setTemporalReport(report: unknown, date = new Date().toISOString().slice(0, 10)): Promise<void> {\n const raw = JSON.stringify(report);\n await this.kv.put(\"temporal:latest\", raw);\n await this.kv.put(`temporal:${date}`, raw, { expirationTtl: TEMPORAL_TTL_S });\n }\n\n /** The cached latest temporal report, or null when absent/unparseable. */\n async getTemporalReport<T = unknown>(): Promise<T | null> {\n const raw = await this.kv.get(\"temporal:latest\");\n if (!raw) return null;\n try {\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n }\n}\n","/**\n * Cloudflare Workflow hardening — ported verbatim from agnacl\n * (utils/workflow-helpers.ts). resilientStep adds structured error logging\n * without changing retry semantics; the step configs replace the engine's slow\n * default retry with fast exponential backoff.\n */\n\nexport async function resilientStep<T>(stepName: string, fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n const stack = err instanceof Error ? err.stack : undefined;\n console.error(`[STEP_ERROR] ${stepName}: ${msg}`);\n if (stack) console.error(`[STEP_STACK] ${stepName}: ${stack}`);\n throw err;\n }\n}\n\n/** I/O-bound steps: KV reads, feed fetches. */\nexport const IO_STEP_CONFIG = {\n retries: { limit: 3, delay: \"5 seconds\" as const, backoff: \"exponential\" as const },\n timeout: \"2 minutes\" as const,\n};\n\n/** Graph write steps. */\nexport const COMMIT_STEP_CONFIG = {\n retries: { limit: 3, delay: \"3 seconds\" as const, backoff: \"exponential\" as const },\n timeout: \"90 seconds\" as const,\n};\n\n/** LLM generation steps (can be slow). */\nexport const LLM_STEP_CONFIG = {\n retries: { limit: 2, delay: \"5 seconds\" as const, backoff: \"exponential\" as const },\n timeout: \"3 minutes\" as const,\n};\n\n/** Retry/timeout shape of the step configs above. */\nexport interface StepConfig {\n retries?: { limit: number; delay: string; backoff?: string };\n timeout?: string;\n}\n\n/**\n * Structural view of Cloudflare's WorkflowStep. WorkflowStep.do constrains its\n * result to Rpc.Serializable<T>, which cannot terminate (TS2589) on the\n * recursive Value type now that the @odla/* workspace packages give\n * @odla-ai/db's re-exports real definitions. A live WorkflowStep satisfies\n * this shape at runtime; widen through asStepLike at the workflow boundary.\n */\nexport interface StepLike {\n do<T>(name: string, fn: () => Promise<T>): Promise<T>;\n do<T>(name: string, config: StepConfig, fn: () => Promise<T>): Promise<T>;\n}\n\n/** Widen a WorkflowStep to StepLike (type-level only, no runtime cost). */\nexport function asStepLike(step: unknown): StepLike {\n return step as StepLike;\n}\n","import { LLM_STEP_CONFIG, type StepLike, resilientStep } from \"../workflow/helpers\";\nimport type { ExtractResult, RawItem } from \"../types\";\nimport type { AdminDb } from \"@odla-ai/db\";\nimport type { Store } from \"../state/kv\";\nimport type { Provider } from \"../llm/provider\";\nimport type { Ontology } from \"../ontology\";\nimport { writeFragment } from \"../graph/writer\";\nimport { canonicalizeFragment } from \"../graph/resolve\";\nimport { urlHash } from \"../util/identity\";\n\n/** Everything the shared ingest loop needs, supplied by the host. The\n * extractor is the ontology's domain craft (e.g. the OSINT extractor); the\n * library stays ontology-agnostic. */\nexport interface PipelineDeps {\n db: AdminDb;\n provider: Provider;\n ontology: Ontology;\n extract: (provider: Provider, item: RawItem) => Promise<ExtractResult | null>;\n store: Store;\n}\n\n/**\n * Shared per-item processing: each RawItem is its own step (non-blocking,\n * replay-cached). Structured items (fragment) skip the LLM; text items extract.\n * `resolve` FTS-canonicalizes new nodes against existing ones (discovery path).\n * Used by both the ingest (fetch) and discover (web-search) workflows.\n */\nexport async function ingestFresh(\n step: StepLike,\n deps: PipelineDeps,\n fresh: RawItem[],\n kind: string,\n resolve = false,\n): Promise<void> {\n const { db, provider, ontology, extract, store } = deps;\n if (fresh.length === 0) {\n await store.setLastRun(kind);\n return;\n }\n\n for (const item of fresh) {\n const id = urlHash(item.url);\n await step.do(`item-${id}`, LLM_STEP_CONFIG, () =>\n resilientStep(`item-${id}`, async () => {\n try {\n let fragment = item.fragment;\n if (!fragment) {\n const r = await extract(provider, item);\n if (!r) {\n await store.markSeen(item.url);\n return { skipped: true };\n }\n fragment = r.fragment;\n }\n if (resolve) fragment = await canonicalizeFragment(db, fragment, ontology);\n const { txId, ops } = await writeFragment(db, fragment, ontology, `ingest:${id}:v1`);\n await store.markSeen(item.url);\n return { txId, ops };\n } catch (e) {\n console.log(`ingest item failed (non-blocking): ${item.url}: ${e instanceof Error ? e.message : e}`);\n return { error: true };\n }\n }),\n );\n }\n\n await step.do(`stamp-${kind}`, () =>\n resilientStep(`stamp-${kind}`, async () => {\n await store.setLastRun(kind);\n return { ok: true };\n }),\n );\n}\n"],"mappings":"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","names":["t","UA"]}
|