agentfootprint 9.111.0 → 9.112.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/CLAUDE.md +2 -2
- package/dist/esm/lib/injection-engine/intentScorer.js +15 -4
- package/dist/esm/lib/injection-engine/intentScorer.js.map +1 -1
- package/dist/esm/lib/injection-engine/routingPolicy.d.ts +3 -1
- package/dist/esm/lib/injection-engine/routingPolicy.js +15 -3
- package/dist/esm/lib/injection-engine/routingPolicy.js.map +1 -1
- package/dist/esm/ontology/fromSkos.d.ts +100 -0
- package/dist/esm/ontology/fromSkos.js +356 -0
- package/dist/esm/ontology/fromSkos.js.map +1 -0
- package/dist/esm/ontology/index.d.ts +6 -0
- package/dist/esm/ontology/index.js +6 -0
- package/dist/esm/ontology/index.js.map +1 -1
- package/dist/esm/ontology/skosJsonLd.d.ts +101 -0
- package/dist/esm/ontology/skosJsonLd.js +219 -0
- package/dist/esm/ontology/skosJsonLd.js.map +1 -0
- package/dist/esm/ontology/toSkos.d.ts +35 -0
- package/dist/esm/ontology/toSkos.js +105 -0
- package/dist/esm/ontology/toSkos.js.map +1 -0
- package/dist/lib/injection-engine/intentScorer.js +15 -4
- package/dist/lib/injection-engine/intentScorer.js.map +1 -1
- package/dist/lib/injection-engine/routingPolicy.js +15 -3
- package/dist/lib/injection-engine/routingPolicy.js.map +1 -1
- package/dist/ontology/fromSkos.js +362 -0
- package/dist/ontology/fromSkos.js.map +1 -0
- package/dist/ontology/index.js +14 -1
- package/dist/ontology/index.js.map +1 -1
- package/dist/ontology/skosJsonLd.js +232 -0
- package/dist/ontology/skosJsonLd.js.map +1 -0
- package/dist/ontology/toSkos.js +109 -0
- package/dist/ontology/toSkos.js.map +1 -0
- package/dist/types/lib/injection-engine/intentScorer.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/routingPolicy.d.ts +3 -1
- package/dist/types/lib/injection-engine/routingPolicy.d.ts.map +1 -1
- package/dist/types/ontology/fromSkos.d.ts +101 -0
- package/dist/types/ontology/fromSkos.d.ts.map +1 -0
- package/dist/types/ontology/index.d.ts +6 -0
- package/dist/types/ontology/index.d.ts.map +1 -1
- package/dist/types/ontology/skosJsonLd.d.ts +102 -0
- package/dist/types/ontology/skosJsonLd.d.ts.map +1 -0
- package/dist/types/ontology/toSkos.d.ts +36 -0
- package/dist/types/ontology/toSkos.d.ts.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [9.112.1] - 2026-09-22
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **A near-tie no longer clings to an incumbent the scorer put out.**
|
|
13
|
+
`decideTier2` kept the incumbent on ANY near-tie, even when the scorer had
|
|
14
|
+
scored that incumbent at or below the declared floor and the tie was between
|
|
15
|
+
two new candidates — so an ambiguous new topic silently stayed on a skill the
|
|
16
|
+
scorer said the message did not match. It now offers the tied cluster as a
|
|
17
|
+
`menu` in exactly that case — the same mid-conversation menu an `unmatched`
|
|
18
|
+
turn already produced, so staying is still offered as an explicit choice; the
|
|
19
|
+
model decides in-band instead of the policy deciding silently. Unchanged: an incumbent the scorer never scored
|
|
20
|
+
(unknown, not out) still stays, and a scorer with no floor (the embedding
|
|
21
|
+
default) still stays on a contentless follow-up.
|
|
22
|
+
- **`validateIntentScores` refuses a candidate scored twice.** It stored rows
|
|
23
|
+
in a map, so a duplicate id silently replaced the earlier score (last row
|
|
24
|
+
wins, in whatever order the scorer iterated). A repeated id is now refused by
|
|
25
|
+
name beside the existing missing / foreign refusals.
|
|
26
|
+
|
|
27
|
+
## [9.112.0] - 2026-09-18
|
|
28
|
+
|
|
29
|
+
### Added — bringing your own taxonomy: SKOS in, our map out
|
|
30
|
+
|
|
31
|
+
- `fromSkos(input, join)` on `agentfootprint/ontology` reads a customer's SKOS concept scheme —
|
|
32
|
+
JSON-LD, already parsed (`@graph`, a flat array or one node; full IRIs, the `skos:` prefix, or
|
|
33
|
+
bare keys under the document's `@context`) — and returns an `OntologySpec` for `defineOntology`,
|
|
34
|
+
which stays the one shape everything reads. No inference: a concept's id is the last IRI
|
|
35
|
+
segment (lower-cased, `-`/space → `_`), its meaning `skos:definition` else `scopeNote` else the
|
|
36
|
+
prefLabel, its aliases every `altLabel`/`hiddenLabel` in the asked language (`join.language`,
|
|
37
|
+
default `'en'`) plus the prefLabel; `broader`/`narrower` → one `is-a` edge per pair, `related`
|
|
38
|
+
→ `related` once per pair; the scheme node's last segment and `dcterms:modified` /
|
|
39
|
+
`owl:versionInfo` / `schema:version` give id and version unless the join names them. What SKOS
|
|
40
|
+
cannot say — which source holds a term, which tool reads it, coverage, units — the host binds by
|
|
41
|
+
hand (`SkosJoin.sources` + `bind`); an unbound term has no sources, the honest state. Every
|
|
42
|
+
refusal is one `SkosError` with a `code` (`ERR_SKOS_…`), the IRI(s) and what was expected: no
|
|
43
|
+
label in the language, two concepts collapsing to one id, a relation to a concept the document
|
|
44
|
+
does not hold, a `broader` cycle, a missing id or version, a `bind` key the scheme does not hold.
|
|
45
|
+
- `readSkos(input, { language })` — the pure parse (concepts sorted by id, edges sorted, the
|
|
46
|
+
scheme's identity), for a host that wants to look before it joins.
|
|
47
|
+
- `toSkos(spec)` — the reverse walk to JSON-LD, with unit, sources, via, coverage and any other
|
|
48
|
+
relation in a `footprint:` namespace declared in the `@context`; `readSkos(toSkos(spec))` gives
|
|
49
|
+
the spec's terms and edges back (pinned). An export, not a serving: the model still reads
|
|
50
|
+
`ontologyPiece`.
|
|
51
|
+
- Nothing in the existing ontology moved: `define.ts`, `serve.ts`, `instruction.ts`, `score.ts`
|
|
52
|
+
and `types.ts` are untouched, and `test/ontology/fromSkos.test.ts` pins the hash and served text
|
|
53
|
+
of the reference spec to the literals captured before this release. Out of scope by ruling: a
|
|
54
|
+
Turtle parser, OWL, serving the map in SKOS vocabulary (bench first).
|
|
55
|
+
|
|
8
56
|
## [9.111.0] - 2026-09-18
|
|
9
57
|
|
|
10
58
|
### Added — the story's ask and return beats carry the tool call id
|
package/CLAUDE.md
CHANGED
|
@@ -87,7 +87,7 @@ does not exist, so the table can go out of date by OMISSION but can never lie
|
|
|
87
87
|
about what it names.
|
|
88
88
|
|
|
89
89
|
## Module map
|
|
90
|
-
Entry points — SEVENTEEN doors, and `package.json` `exports` is exhaustive, so a path not on this list does not resolve at all: `.` core API · `/providers` everything you plug a backend into (mock/anthropic/openai/bedrock/browser*, embedders, staticTools/gatedTools/skillScopedTools/mcpClient, thinking handlers, code runners) · `/memory` (defineMemory, MEMORY_TYPES, InMemoryStore, mockEmbedder, redis/agentcore/bedrockAgentMemory stores) · `/rag` index-time loaders/splitters/indexCorpus · `/cache` prompt caching (+ registerCacheStrategy; side-effectful, hence its own door) · `/observe` the whole watching story (recorders, recordRun, strategies + attach*, vendor sinks, run-autopsy finders, toSSE, status, locales) · `/events` typed event system · `/context` the injection engine (defineInjection/Skill/Fact/Instruction/Steering, decideSkill) · `/resilience` provider decorators + the reliability RULES · `/hosting` nodeHost/httpHost/standingAgent + session stores · `/security` permissions + tool credentials (it absorbed the old identity door) · `/reliability` the retained alias, kept ONLY because it is the sole home of the gate's `CircuitOpenError` · `/skill-graph` the framework-neutral routing layer · `/recipes` the declared unit of agent CONFIGURATION (defineAgentRecipe → `.recipe()`; authoring-time vocabulary, so it stays off the main barrel) · `/maps` the mount kernel's vocabulary (9.58.0: Claim<T> + the engagement lease machine + its renewal feed — pure data and pure functions, no run entry point; mounted via `.maps()`) · `/classify` the calibrated-classifier port (9.104.0: `Classifier`, `typesafe`, `mockClassifier`, `ClassifierError` — a backend that SCORES declared candidates; spent by `.findings({ judge })` and by `classifierScorer` on `/skill-graph`) · `/ontology` the declared map (9.106.0: `defineOntology`, `ontologyHash`, `ontologyPiece`, `ONTOLOGY_INSTRUCTION`, the shapes — what each term IS, how terms RELATE, which SOURCE holds a term and which registered TOOL reads it; pure data and pure functions, no run entry point, no fetch path; mounted via `.ontology()`, served as one request-only system piece per call). 9.0.0 removed sixteen older paths (`/llm-providers`, `/injection-engine`, `/tool-providers`, `/strategies`, `/observability-providers`, `/identity`, `/stream`, `/thinking`, `/status`, `/locales`, `/debug`, `/debug/finders`, `/memory-providers`, `/embedders`, `/hosting-providers`, `/observability/contextError/finders`) — it removed PATHS, not code; each name still ships through the door that absorbed it. This list is pinned against `package.json` by test/api-conformance/documented-doors.test.ts, and the removed sixteen by subpath-exports.test.ts. **Main barrel does NOT export** mock/browser*/defineMemory/defineSkill/skillGraph/mcpClient/InMemoryStore — import from the doors above.
|
|
90
|
+
Entry points — SEVENTEEN doors, and `package.json` `exports` is exhaustive, so a path not on this list does not resolve at all: `.` core API · `/providers` everything you plug a backend into (mock/anthropic/openai/bedrock/browser*, embedders, staticTools/gatedTools/skillScopedTools/mcpClient, thinking handlers, code runners) · `/memory` (defineMemory, MEMORY_TYPES, InMemoryStore, mockEmbedder, redis/agentcore/bedrockAgentMemory stores) · `/rag` index-time loaders/splitters/indexCorpus · `/cache` prompt caching (+ registerCacheStrategy; side-effectful, hence its own door) · `/observe` the whole watching story (recorders, recordRun, strategies + attach*, vendor sinks, run-autopsy finders, toSSE, status, locales) · `/events` typed event system · `/context` the injection engine (defineInjection/Skill/Fact/Instruction/Steering, decideSkill) · `/resilience` provider decorators + the reliability RULES · `/hosting` nodeHost/httpHost/standingAgent + session stores · `/security` permissions + tool credentials (it absorbed the old identity door) · `/reliability` the retained alias, kept ONLY because it is the sole home of the gate's `CircuitOpenError` · `/skill-graph` the framework-neutral routing layer · `/recipes` the declared unit of agent CONFIGURATION (defineAgentRecipe → `.recipe()`; authoring-time vocabulary, so it stays off the main barrel) · `/maps` the mount kernel's vocabulary (9.58.0: Claim<T> + the engagement lease machine + its renewal feed — pure data and pure functions, no run entry point; mounted via `.maps()`) · `/classify` the calibrated-classifier port (9.104.0: `Classifier`, `typesafe`, `mockClassifier`, `ClassifierError` — a backend that SCORES declared candidates; spent by `.findings({ judge })` and by `classifierScorer` on `/skill-graph`) · `/ontology` the declared map (9.106.0: `defineOntology`, `ontologyHash`, `ontologyPiece`, `ONTOLOGY_INSTRUCTION`, the shapes — what each term IS, how terms RELATE, which SOURCE holds a term and which registered TOOL reads it; pure data and pure functions, no run entry point, no fetch path; mounted via `.ontology()`, served as one request-only system piece per call; 9.112.0 `fromSkos`/`readSkos`/`toSkos` — the SKOS JSON-LD adapter onto the same spec, no second validation path). 9.0.0 removed sixteen older paths (`/llm-providers`, `/injection-engine`, `/tool-providers`, `/strategies`, `/observability-providers`, `/identity`, `/stream`, `/thinking`, `/status`, `/locales`, `/debug`, `/debug/finders`, `/memory-providers`, `/embedders`, `/hosting-providers`, `/observability/contextError/finders`) — it removed PATHS, not code; each name still ships through the door that absorbed it. This list is pinned against `package.json` by test/api-conformance/documented-doors.test.ts, and the removed sixteen by subpath-exports.test.ts. **Main barrel does NOT export** mock/browser*/defineMemory/defineSkill/skillGraph/mcpClient/InMemoryStore — import from the doors above.
|
|
91
91
|
|
|
92
92
|
| src/ | one job |
|
|
93
93
|
|---|---|
|
|
@@ -103,7 +103,7 @@ Entry points — SEVENTEEN doors, and `package.json` `exports` is exhaustive, so
|
|
|
103
103
|
| recorders/core/ | bridges footprintjs events → typed EventDispatcher (ContextRecorder, EmitBridge, typedEmit) — auto-attached by Agent.createExecutor; most factories also exported via `/observe` for manual wiring (EmitBridge itself stays internal) |
|
|
104
104
|
| recorders/observability/ | consumer recorders over the typed stream (RunStepRecorder, FlowchartRecorder, Status, Trace replay) + `recordRun` — THE producer of a recording `{snapshot, events, structure}` (the shape lens's `observeRecording` consumes; `structure` = `getSpec().buildTimeStructure`, which no snapshot carries). Anything that saves a run goes through it |
|
|
105
105
|
| rag/ | (8.10.0, door `/rag`) index-TIME: `DocumentLoader` adapters (text/markdown/html zero-dep, pdf via lazy `unpdf`) + `Splitter` factories + `indexCorpus` — a REAL footprintjs chart whose commit log IS the indexing report. `defineRAG` deliberately stays on the MAIN barrel (run-time wiring); this door is the half that runs once, before any agent exists |
|
|
106
|
-
| ontology/ | (9.106.0) the declared map — `defineOntology` (validate, detach, freeze, `ontologyHash` through the receipt's `stableJson`) + `serve.ts · ontologyPiece` (the ONE composer, called by `callLLM` and by `servedView.viewOf`; every line quotes the declaration under a constant header that quotes the context contract's three meanings) + `instruction.ts` (the always-on ask). Behind the `/ontology` door; `Agent.buildChart` checks every `via` tool name against `registryByName` at build; `seed` writes `AgentState.ontology` once; the grouped chart crosses the key into `sf-llm-call` under `hasOntology` |
|
|
106
|
+
| ontology/ | (9.106.0) the declared map — `defineOntology` (validate, detach, freeze, `ontologyHash` through the receipt's `stableJson`) + `serve.ts · ontologyPiece` (the ONE composer, called by `callLLM` and by `servedView.viewOf`; every line quotes the declaration under a constant header that quotes the context contract's three meanings) + `instruction.ts` (the always-on ask). Behind the `/ontology` door; `Agent.buildChart` checks every `via` tool name against `registryByName` at build; `seed` writes `AgentState.ontology` once; the grouped chart crosses the key into `sf-llm-call` under `hasOntology`. 9.112.0 `fromSkos.ts`/`toSkos.ts`/`skosJsonLd.ts` = the SKOS adapter (a customer's concept scheme → `OntologySpec` → `defineOntology`, and back); `define.ts`/`serve.ts`/`instruction.ts`/`score.ts`/`types.ts` untouched by it, pinned by test/ontology/fromSkos.test.ts |
|
|
107
107
|
| lib/ | first-party sub-libraries: injection-engine/, context-bisect/ (localizeContextBug, toBacktrackTrace + sliceToBacktrackTrace — the atui board serializers), influence-core/, trace-toolpack/ (selfExplain; 6 tools incl. variable-first `backtrack(variable, element?)`), context-ledger/ (which pieces EARNED their tokens — post-run offers/uses/outcomes bookkeeping + demote-never-starve gates `ledgerToolGate`/`ledgerEntryScorer`/`ledgerGated`; grouped-mode folds sf-llm-call inner logs, unmeterable runs → undefined; /observe), mcp/, rag/, tool-lint/ |
|
|
108
108
|
| memory/ | store/ (MemoryStore port) + pipeline presets + stages + beats/facts + causal/ (dev-only, TOP_K+search()-only) + wire/mountMemoryPipeline + retrieval/ (8.8.0: the `RetrievalStrategy` seam + `RetrievalEvidence`, the record a retrieval leaves — `topK()` is what every earlier release did unnamed) |
|
|
109
109
|
| maps/ | (9.58.0, door `/maps`) the mount kernel: claim/ (`Claim<T>` honesty primitive) + engagement/ (lease machine `advanceEngagement` + renewal feed `renewalEvidenceOf` + vocabulary). Pure data/functions; the ONE framework hook is `ctx.parkedIds` in the evaluator (the `leaseActiveIds` mirror), fed by the Evaluate stage; state rides `AgentState.mapEngagement` as a TOP-LEVEL array (the StepPointerCarrier law) |
|
|
@@ -32,19 +32,30 @@
|
|
|
32
32
|
export function validateIntentScores(scorerName, candidates, result) {
|
|
33
33
|
const byId = new Map();
|
|
34
34
|
const foreign = [];
|
|
35
|
+
// A candidate scored twice is refused like a missing or foreign one: a Map
|
|
36
|
+
// would otherwise let the LAST row win silently, and which row that is
|
|
37
|
+
// depends on the scorer's iteration order (2026-09-22 review, S8).
|
|
38
|
+
const repeated = [];
|
|
35
39
|
for (const s of result) {
|
|
36
|
-
if (candidates.some((c) => c.id === s.id))
|
|
37
|
-
byId.set(s.id, s);
|
|
38
|
-
else
|
|
40
|
+
if (!candidates.some((c) => c.id === s.id))
|
|
39
41
|
foreign.push(s.id);
|
|
42
|
+
else if (byId.has(s.id)) {
|
|
43
|
+
if (!repeated.includes(s.id))
|
|
44
|
+
repeated.push(s.id);
|
|
45
|
+
}
|
|
46
|
+
else
|
|
47
|
+
byId.set(s.id, s);
|
|
40
48
|
}
|
|
41
49
|
const missing = candidates.filter((c) => !byId.has(c.id)).map((c) => c.id);
|
|
42
|
-
if (foreign.length > 0 || missing.length > 0) {
|
|
50
|
+
if (foreign.length > 0 || missing.length > 0 || repeated.length > 0) {
|
|
43
51
|
const clauses = [
|
|
44
52
|
...(missing.length > 0 ? [`omitted ${missing.map((m) => `"${m}"`).join(', ')}`] : []),
|
|
45
53
|
...(foreign.length > 0
|
|
46
54
|
? [`named ${foreign.map((f) => `"${f}"`).join(', ')} which it was not given`]
|
|
47
55
|
: []),
|
|
56
|
+
...(repeated.length > 0
|
|
57
|
+
? [`scored ${repeated.map((r) => `"${r}"`).join(', ')} more than once`]
|
|
58
|
+
: []),
|
|
48
59
|
];
|
|
49
60
|
throw new Error(`IntentScorer '${scorerName}' returned an unusable result: it ${clauses.join(' and ')}. ` +
|
|
50
61
|
`A scorer must return one score per candidate it was given — every candidate, no ` +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intentScorer.js","sourceRoot":"","sources":["../../../../src/lib/injection-engine/intentScorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAwDH;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAkB,EAClB,UAAsC,EACtC,MAA8B;IAE9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC5C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC
|
|
1
|
+
{"version":3,"file":"intentScorer.js","sourceRoot":"","sources":["../../../../src/lib/injection-engine/intentScorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAwDH;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAkB,EAClB,UAAsC,EACtC,MAA8B;IAE9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC5C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,2EAA2E;IAC3E,uEAAuE;IACvE,mEAAmE;IACnE,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;aAC1D,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;;YAAM,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3B,CAAC;IACD,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpE,MAAM,OAAO,GAAG;YACd,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACrF,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;gBACpB,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,CAAC;gBAC7E,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;gBACrB,CAAC,CAAC,CAAC,UAAU,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC;gBACvE,CAAC,CAAC,EAAE,CAAC;SACR,CAAC;QACF,MAAM,IAAI,KAAK,CACb,iBAAiB,UAAU,qCAAqC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI;YACvF,kFAAkF;YAClF,gFAAgF;YAChF,2DAA2D,CAC9D,CAAC;IACJ,CAAC;IACD,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAE,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -103,7 +103,9 @@ export interface Tier2Numbers {
|
|
|
103
103
|
*
|
|
104
104
|
* • decisive winner ≠ incumbent → `move`;
|
|
105
105
|
* • decisive winner = incumbent, or a near-tie WITH an incumbent → `stay`
|
|
106
|
-
* (ambiguity mid-conversation = stay; the menu is not opened)
|
|
106
|
+
* (ambiguity mid-conversation = stay; the menu is not opened) — unless
|
|
107
|
+
* the scorer scored that incumbent and put it at/below the floor, when
|
|
108
|
+
* the near-tie is between new candidates only → `menu` of the tied cluster;
|
|
107
109
|
* • near-tie with NO incumbent (cold start) → `menu` of the tied cluster
|
|
108
110
|
* (every candidate within the margin of the top, capped at `menuSize`);
|
|
109
111
|
* • every candidate at/below the floor → `unmatched` (the caller offers the
|
|
@@ -65,7 +65,9 @@ function pairwiseGap(a, b) {
|
|
|
65
65
|
*
|
|
66
66
|
* • decisive winner ≠ incumbent → `move`;
|
|
67
67
|
* • decisive winner = incumbent, or a near-tie WITH an incumbent → `stay`
|
|
68
|
-
* (ambiguity mid-conversation = stay; the menu is not opened)
|
|
68
|
+
* (ambiguity mid-conversation = stay; the menu is not opened) — unless
|
|
69
|
+
* the scorer scored that incumbent and put it at/below the floor, when
|
|
70
|
+
* the near-tie is between new candidates only → `menu` of the tied cluster;
|
|
69
71
|
* • near-tie with NO incumbent (cold start) → `menu` of the tied cluster
|
|
70
72
|
* (every candidate within the margin of the top, capped at `menuSize`);
|
|
71
73
|
* • every candidate at/below the floor → `unmatched` (the caller offers the
|
|
@@ -103,8 +105,18 @@ export function decideTier2(scores, incumbentId, policy, scorer = {}) {
|
|
|
103
105
|
return { kind: 'move', to: top.id, ...numbers };
|
|
104
106
|
}
|
|
105
107
|
// Near-tie. Mid-conversation the incumbent holds (ambiguity = stay);
|
|
106
|
-
// cold start opens the menu of the tied cluster.
|
|
107
|
-
|
|
108
|
+
// cold start opens the menu of the tied cluster. ONE exception: the scorer
|
|
109
|
+
// scored the incumbent ITSELF and put it out (at/below the floor, or
|
|
110
|
+
// non-finite) — then the ambiguity is between new candidates only, and
|
|
111
|
+
// staying would cling to a skill the scorer says this message does not
|
|
112
|
+
// match, so the tied cluster is offered instead. An incumbent the scorer
|
|
113
|
+
// never scored is UNKNOWN, not out: it still holds. With no floor declared
|
|
114
|
+
// (the embedding default) every finite score is alive, so a contentless
|
|
115
|
+
// follow-up ("and yesterday?") still stays exactly as before.
|
|
116
|
+
const incumbentScoredOut = incumbentId !== undefined &&
|
|
117
|
+
safeOf.has(incumbentId) &&
|
|
118
|
+
!alive.some((r) => r.id === incumbentId);
|
|
119
|
+
if (incumbentId !== undefined && !incumbentScoredOut)
|
|
108
120
|
return { kind: 'stay', ...numbers };
|
|
109
121
|
const tied = alive
|
|
110
122
|
.filter((r) => pairwiseGap(safeOf.get(top.id), safeOf.get(r.id)) < policy.nearTieMargin)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routingPolicy.js","sourceRoot":"","sources":["../../../../src/lib/injection-engine/routingPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAIvC,qEAAqE;AACrE,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AAEpC,wEAAwE;AACxE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAY3B,0EAA0E;AAC1E,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,aAAa,EAAE,eAAe;IAC9B,QAAQ,EAAE,SAAS;CACpB,CAAC;AAEF,0DAA0D;AAC1D,MAAM,UAAU,oBAAoB,CAAC,MAA+B;IAClE,OAAO;QACL,aAAa,EAAE,MAAM,EAAE,aAAa,IAAI,eAAe;QACvD,QAAQ,EAAE,MAAM,EAAE,QAAQ,IAAI,SAAS;QACvC,GAAG,CAAC,MAAM,EAAE,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;KAC5D,CAAC;AACJ,CAAC;AAsCD,wFAAwF;AACxF,SAAS,WAAW,CAAC,CAAS,EAAE,CAAS;IACvC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAChC,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"routingPolicy.js","sourceRoot":"","sources":["../../../../src/lib/injection-engine/routingPolicy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ontology/fromSkos — a customer's SKOS concept scheme (JSON-LD, already
|
|
3
|
+
* parsed) becomes OUR map: `readSkos` is the pure parse, `fromSkos` joins
|
|
4
|
+
* the host's sources onto it and returns an `OntologySpec` for
|
|
5
|
+
* `defineOntology` — the ONE shape everything else reads.
|
|
6
|
+
*
|
|
7
|
+
* Pattern: reader = adapter. Two pure functions over plain JSON; no I/O, no
|
|
8
|
+
* registry, no second validation path — the result goes through
|
|
9
|
+
* `defineOntology` unchanged, which is where an id that is not
|
|
10
|
+
* identifier-safe, an alias that collides with an id, or a source
|
|
11
|
+
* nobody declared is refused.
|
|
12
|
+
* Role: Map. Nothing here infers: a concept is a node typed
|
|
13
|
+
* `skos:Concept`, its id is the last segment of its IRI, its
|
|
14
|
+
* meaning is its `definition`, else its `scopeNote`, else its
|
|
15
|
+
* `prefLabel`; `broader` is `is-a`, `related` is `related`; every
|
|
16
|
+
* other name and every label in another language is left where it
|
|
17
|
+
* was. SKOS cannot say which SOURCE holds a term or which TOOL reads
|
|
18
|
+
* it — the host binds those by hand in `SkosJoin.bind`; a term with
|
|
19
|
+
* no binding is the honest state "declared, no source holds it".
|
|
20
|
+
* Emits: N/A.
|
|
21
|
+
*
|
|
22
|
+
* THE OWNER'S RULING (2026-09-18): customers already have taxonomies, almost
|
|
23
|
+
* always as SKOS. The library takes THEIRS in and turns it into OUR map;
|
|
24
|
+
* readers are adapters, `defineOntology` stays canonical. OWL is out of
|
|
25
|
+
* scope; a Turtle reader is a follow-up; serving the map back in SKOS
|
|
26
|
+
* vocabulary waits for the bench.
|
|
27
|
+
*
|
|
28
|
+
* REFUSALS — every one an `Error` with a `code` (`ERR_SKOS_…`), the IRI(s)
|
|
29
|
+
* involved and what was expected; the reader never returns a partial map:
|
|
30
|
+
* ERR_SKOS_INPUT not a JSON-LD document, or a node that is not an object
|
|
31
|
+
* ERR_SKOS_NAMED_GRAPH a node that is itself a named graph (`{ "@id", "@graph": [...] }`) —
|
|
32
|
+
* its concepts would be dropped without a word; flatten the document first
|
|
33
|
+
* ERR_SKOS_UNTYPED a node carrying SKOS properties with no `@type`
|
|
34
|
+
* ERR_SKOS_NO_CONCEPTS no `skos:Concept` in the document
|
|
35
|
+
* ERR_SKOS_NO_LABEL a concept with no prefLabel in the asked language
|
|
36
|
+
* ERR_SKOS_ID an IRI whose last segment is empty
|
|
37
|
+
* ERR_SKOS_ID_COLLISION two concepts collapsing to one id
|
|
38
|
+
* ERR_SKOS_UNKNOWN_CONCEPT a broader/narrower/related target the document does not hold
|
|
39
|
+
* ERR_SKOS_CYCLE a cycle in `broader` (SKOS forbids it)
|
|
40
|
+
* ERR_SKOS_SCHEME_MISSING no id or no version, from the join or the scheme node
|
|
41
|
+
* ERR_SKOS_SCHEME_AMBIGUOUS several scheme nodes and the join does not settle id and version
|
|
42
|
+
* ERR_SKOS_BIND_UNKNOWN_TERM a `bind` key naming a term the scheme does not hold
|
|
43
|
+
*/
|
|
44
|
+
import type { OntologyEdge, OntologyNode, OntologyNodeSource, OntologySource, OntologySpec } from './types.js';
|
|
45
|
+
import { type JsonLdNode } from './skosJsonLd.js';
|
|
46
|
+
/** A JSON-LD document, already parsed: one node, an array of nodes, or `{ "@context"?, "@graph": [...] }`. */
|
|
47
|
+
export type SkosInput = JsonLdNode | readonly JsonLdNode[];
|
|
48
|
+
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';
|
|
49
|
+
/** The one error the reader throws: a `code`, the IRI(s) involved, what was expected. */
|
|
50
|
+
export declare class SkosError extends Error {
|
|
51
|
+
readonly code: SkosErrorCode;
|
|
52
|
+
readonly iris: readonly string[];
|
|
53
|
+
constructor(code: SkosErrorCode, message: string, iris?: readonly string[]);
|
|
54
|
+
}
|
|
55
|
+
/** How to read the document: which language's labels to take (default `'en'`). */
|
|
56
|
+
export interface ReadSkosOptions {
|
|
57
|
+
readonly language?: string;
|
|
58
|
+
}
|
|
59
|
+
/** One concept as read: its IRI, its derived id, and the node it becomes (no sources — SKOS has none). */
|
|
60
|
+
export interface SkosConcept {
|
|
61
|
+
readonly iri: string;
|
|
62
|
+
readonly id: string;
|
|
63
|
+
readonly prefLabel: string;
|
|
64
|
+
readonly node: OntologyNode;
|
|
65
|
+
}
|
|
66
|
+
/** The pure parse: the scheme's identity when the document carries it, the concepts, the edges. */
|
|
67
|
+
export interface SkosScheme {
|
|
68
|
+
readonly iri?: string;
|
|
69
|
+
readonly id?: string;
|
|
70
|
+
readonly version?: string;
|
|
71
|
+
readonly language: string;
|
|
72
|
+
readonly concepts: readonly SkosConcept[];
|
|
73
|
+
/** `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. */
|
|
74
|
+
readonly edges: readonly OntologyEdge[];
|
|
75
|
+
}
|
|
76
|
+
/** What SKOS cannot say and the host binds by hand: sources, and which term is held where. */
|
|
77
|
+
export interface SkosJoin {
|
|
78
|
+
/** The map's id — else the scheme node's last IRI segment. */
|
|
79
|
+
readonly id?: string;
|
|
80
|
+
/** The map's version — else the scheme node's `dcterms:modified` / `owl:versionInfo` / `schema:version`. */
|
|
81
|
+
readonly version?: string;
|
|
82
|
+
/** Which language's labels to take (default `'en'`). */
|
|
83
|
+
readonly language?: string;
|
|
84
|
+
readonly sources: Readonly<Record<string, OntologySource>>;
|
|
85
|
+
/** term id → the sources holding it; a key naming a term the scheme does not hold is refused. */
|
|
86
|
+
readonly bind?: Readonly<Record<string, readonly OntologyNodeSource[]>>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The pure parse: concepts (sorted by id), edges, and the scheme's identity
|
|
90
|
+
* when ONE `skos:ConceptScheme` node carries it. Refuses by code; never
|
|
91
|
+
* partial. Exported so a host can look before it joins, and for tests.
|
|
92
|
+
*/
|
|
93
|
+
export declare function readSkos(input: SkosInput, options?: ReadSkosOptions): SkosScheme;
|
|
94
|
+
/**
|
|
95
|
+
* The customer's scheme joined with the host's sources → an `OntologySpec`.
|
|
96
|
+
* The host calls `defineOntology(fromSkos(input, join))`: nothing is
|
|
97
|
+
* validated twice, and nothing is guessed — a term the join does not bind
|
|
98
|
+
* has no sources, which is the honest state.
|
|
99
|
+
*/
|
|
100
|
+
export declare function fromSkos(input: SkosInput, join: SkosJoin): OntologySpec;
|