agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
101 lines (100 loc) • 6.13 kB
TypeScript
/**
* ontology/fromSkos — a customer's SKOS concept scheme (JSON-LD, already
* parsed) becomes OUR map: `readSkos` is the pure parse, `fromSkos` joins
* the host's sources onto it and returns an `OntologySpec` for
* `defineOntology` — the ONE shape everything else reads.
*
* Pattern: reader = adapter. Two pure functions over plain JSON; no I/O, no
* registry, no second validation path — the result goes through
* `defineOntology` unchanged, which is where an id that is not
* identifier-safe, an alias that collides with an id, or a source
* nobody declared is refused.
* Role: Map. Nothing here infers: a concept is a node typed
* `skos:Concept`, its id is the last segment of its IRI, its
* meaning is its `definition`, else its `scopeNote`, else its
* `prefLabel`; `broader` is `is-a`, `related` is `related`; every
* other name and every label in another language is left where it
* was. SKOS cannot say which SOURCE holds a term or which TOOL reads
* it — the host binds those by hand in `SkosJoin.bind`; a term with
* no binding is the honest state "declared, no source holds it".
* Emits: N/A.
*
* THE OWNER'S RULING (2026-09-18): customers already have taxonomies, almost
* always as SKOS. The library takes THEIRS in and turns it into OUR map;
* readers are adapters, `defineOntology` stays canonical. OWL is out of
* scope; a Turtle reader is a follow-up; serving the map back in SKOS
* vocabulary waits for the bench.
*
* REFUSALS — every one an `Error` with a `code` (`ERR_SKOS_…`), the IRI(s)
* involved and what was expected; the reader never returns a partial map:
* ERR_SKOS_INPUT not a JSON-LD document, or a node that is not an object
* ERR_SKOS_NAMED_GRAPH a node that is itself a named graph (`{ "@id", "@graph": [...] }`) —
* its concepts would be dropped without a word; flatten the document first
* ERR_SKOS_UNTYPED a node carrying SKOS properties with no `@type`
* ERR_SKOS_NO_CONCEPTS no `skos:Concept` in the document
* ERR_SKOS_NO_LABEL a concept with no prefLabel in the asked language
* ERR_SKOS_ID an IRI whose last segment is empty
* ERR_SKOS_ID_COLLISION two concepts collapsing to one id
* ERR_SKOS_UNKNOWN_CONCEPT a broader/narrower/related target the document does not hold
* ERR_SKOS_CYCLE a cycle in `broader` (SKOS forbids it)
* ERR_SKOS_SCHEME_MISSING no id or no version, from the join or the scheme node
* ERR_SKOS_SCHEME_AMBIGUOUS several scheme nodes and the join does not settle id and version
* ERR_SKOS_BIND_UNKNOWN_TERM a `bind` key naming a term the scheme does not hold
*/
import type { OntologyEdge, OntologyNode, OntologyNodeSource, OntologySource, OntologySpec } from './types.js';
import { type JsonLdNode } from './skosJsonLd.js';
/** A JSON-LD document, already parsed: one node, an array of nodes, or `{ "@context"?, "@graph": [...] }`. */
export type SkosInput = JsonLdNode | readonly JsonLdNode[];
export type SkosErrorCode = 'ERR_SKOS_INPUT' | 'ERR_SKOS_NAMED_GRAPH' | 'ERR_SKOS_UNTYPED' | 'ERR_SKOS_NO_CONCEPTS' | 'ERR_SKOS_NO_LABEL' | 'ERR_SKOS_ID' | 'ERR_SKOS_ID_COLLISION' | 'ERR_SKOS_UNKNOWN_CONCEPT' | 'ERR_SKOS_CYCLE' | 'ERR_SKOS_SCHEME_MISSING' | 'ERR_SKOS_SCHEME_AMBIGUOUS' | 'ERR_SKOS_BIND_UNKNOWN_TERM';
/** The one error the reader throws: a `code`, the IRI(s) involved, what was expected. */
export declare class SkosError extends Error {
readonly code: SkosErrorCode;
readonly iris: readonly string[];
constructor(code: SkosErrorCode, message: string, iris?: readonly string[]);
}
/** How to read the document: which language's labels to take (default `'en'`). */
export interface ReadSkosOptions {
readonly language?: string;
}
/** One concept as read: its IRI, its derived id, and the node it becomes (no sources — SKOS has none). */
export interface SkosConcept {
readonly iri: string;
readonly id: string;
readonly prefLabel: string;
readonly node: OntologyNode;
}
/** The pure parse: the scheme's identity when the document carries it, the concepts, the edges. */
export interface SkosScheme {
readonly iri?: string;
readonly id?: string;
readonly version?: string;
readonly language: string;
readonly concepts: readonly SkosConcept[];
/** `is-a` from `broader`/`narrower` (once per pair), then `related` (once per pair, ends ordered by id), then `footprint:edge` entries — each group sorted, so node order and the broader/narrower spelling never move the hash. */
readonly edges: readonly OntologyEdge[];
}
/** What SKOS cannot say and the host binds by hand: sources, and which term is held where. */
export interface SkosJoin {
/** The map's id — else the scheme node's last IRI segment. */
readonly id?: string;
/** The map's version — else the scheme node's `dcterms:modified` / `owl:versionInfo` / `schema:version`. */
readonly version?: string;
/** Which language's labels to take (default `'en'`). */
readonly language?: string;
readonly sources: Readonly<Record<string, OntologySource>>;
/** term id → the sources holding it; a key naming a term the scheme does not hold is refused. */
readonly bind?: Readonly<Record<string, readonly OntologyNodeSource[]>>;
}
/**
* The pure parse: concepts (sorted by id), edges, and the scheme's identity
* when ONE `skos:ConceptScheme` node carries it. Refuses by code; never
* partial. Exported so a host can look before it joins, and for tests.
*/
export declare function readSkos(input: SkosInput, options?: ReadSkosOptions): SkosScheme;
/**
* The customer's scheme joined with the host's sources → an `OntologySpec`.
* The host calls `defineOntology(fromSkos(input, join))`: nothing is
* validated twice, and nothing is guessed — a term the join does not bind
* has no sources, which is the honest state.
*/
export declare function fromSkos(input: SkosInput, join: SkosJoin): OntologySpec;