@exellix/narrix-adapter-records
v2.0.0
Published
Records adapter: structured records to CNI v1.1
Readme
@exellix/narrix-adapter-records
Convert structured JSON records (assets, vulnerabilities, tickets, events, etc.) into CNI v1.1. This is the “native” NARRIX adapter: records are normalized into the same CNI shape as text/chat/docs so the pipeline has one canonical input contract.
Installation
npm install @exellix/narrix-adapter-recordsPeer / runtime: @exellix/narrix-adapters-core, @exellix/narrix-cni.
Usage
Recommended: use roleMap (and schemaToRoleMap when you have a RecordFieldSchema from e.g. getRecordSchema):
import { recordAdapter, schemaToRoleMap } from "@exellix/narrix-adapter-records";
import type { RecordAdapterInput } from "@exellix/narrix-adapter-records";
const schema = {
idField: "assetId",
displayNameField: "hostname",
factFields: [
{ path: "hostname", factKey: "hostname" },
{ path: "ip", factKey: "ipAddress" },
],
textFields: ["description"],
refFields: [],
};
const input: RecordAdapterInput = {
record: {
assetId: "host-01",
hostname: "web-01.prod",
ip: "10.0.1.50",
description: "Primary web server. See CVE-2023-4567.",
},
recordType: "asset",
roleMap: schemaToRoleMap(schema),
};
const { cni, diagnostics } = recordAdapter.toCni(input);
// cni.subject.id === "host-01"
// cni.facts: hostname, ipAddress (path evidence)
// cni.content: one text item from description (entity extraction)
// cni.references: CVE, IPs, etc. (span + path evidence)You can still pass schema directly on input (deprecated); it is converted to a role map internally.
Adapter identity
- adapterId:
narrix.adapter.record.v1 - version:
1.0.0
Input type
| Field | Type | Description |
|----------------|-----------------------------|-------------|
| record | Record<string, unknown> | Raw JSON record (non-null object). |
| recordType | string | e.g. "asset", "vulnerability", "ticket". |
| recordId? | string | Stable identifier; used as subject.id when provided. |
| roleMap? | RoleMap | Explicit role map (preferred). Use schemaToRoleMap(schema) when you have a RecordFieldSchema. |
| collectionId?| string | Used for registry lookup when roleMap is not provided. |
| schema? | RecordFieldSchema | Deprecated. Use roleMap: schemaToRoleMap(schema) instead. Still supported. |
Role map / schema shape:
idField— dot-path to ID (e.g."assetId"or"asset.id").displayNameField— dot-path to a human-friendly name.factFields—{ path, factKey, required? }[]for facts with path evidence.textFields— dot-paths to free-text fields (normalized, chunked, entity extraction).timestampField— dot-path to a timestamp (ms epoch or ISO string →meta.source.meta.ingestedAt).refFields—{ path, kind }[]for cross-references →subject.keys[kind].
Without roleMap (and without a registry or deprecated schema), the adapter auto-extracts top-level primitives as facts (strings > 100 chars are skipped and treated as content) and auto-detects long string fields (up to 3 levels, max 10) as text content.
Options
toCni(input, options?) accepts standard AdapterOptions from @exellix/narrix-adapters-core:
- chunking — e.g.
{ maxChunkChars: 4000, overlapChars: 200, strategy: "hard" }for long text fields. - limits —
maxInputChars,maxChunks,maxEntitiesfor content and entity extraction. - normalization — applied to text fields before chunking and entity extraction.
- entities — enable/disable and kinds for entity extraction (CVE, IP, hostname, URL, etc.).
- determinism —
sortDiagnostics,sortEntitiesfor stable output.
Output
- CNI v1.1:
subject,facts,content,references(extracted entities),signals: [],meta. - Subject:
type=recordType,idfromrecordId/ role mapid/ hash,displayNameandkeys(refs) when a role map is provided. - Facts: Path evidence;
confidence: 1,meta.source: "record". - Content: From role map
text(or legacytextFields) or auto-detected long strings; chunked when overmaxChunkChars. - References: Entities from text (span evidence) and from a full record string scan (path evidence); deduplicated by (kind, value) with merged evidence.
Warning codes
| Code | Meaning |
|------|--------|
| RECORD_NO_ID | No recordId or idField; subject id is hash of record. |
| RECORD_MISSING_REQUIRED_FACT | A required fact path did not resolve. |
| RECORD_AUTO_FACT_LIMIT | More than 50 top-level primitives; only first 50 used. |
| RECORD_AUTO_TEXT_FIELDS | No textFields; long strings auto-detected (paths in details). |
| RECORD_INVALID_TIMESTAMP | timestampField value could not be parsed. |
| RECORD_CIRCULAR_REFERENCE | Record has circular references; cannot hash. |
| RECORD_TEXT_TRUNCATED | Text exceeded maxInputChars and was truncated. |
| RECORD_ENTITY_SCAN_LIMIT | Record entity scan hit depth or string count limit. |
Exports
- recordAdapter —
NarrixAdapter<RecordAdapterInput>withtoCni(input, options?). - schemaToRoleMap(schema) — Convert a
RecordFieldSchemato aRoleMapfor use asinput.roleMap(e.g. when you have a schema fromgetRecordSchema). - getPath(obj, path) — Dot-path resolver (e.g.
"a.b[0].c") for use outside the adapter. - Types:
RecordAdapterInput,RecordAdapterOptions,RecordFieldSchema,FactFieldMapping,RefFieldMapping,RoleMap. - Constants:
RECORD_ADAPTER_ID,RECORD_ADAPTER_VERSION,AUTO_FACT_MAX_STRING_LENGTH,MAX_AUTO_FACTS,MAX_AUTO_TEXT_FIELDS, and the warning code constants above.
Scripts
npm run build— Compile TypeScript todist/.npm test— Build and run Node test runner (dist/test/adapter.test.js).npm run generate-expected— Build and run R1 fixture through the adapter; prints CNI JSON to stdout.
License
UNLICENSED (see repository).
