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.
Files changed (43) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/CLAUDE.md +2 -2
  3. package/dist/esm/lib/injection-engine/intentScorer.js +15 -4
  4. package/dist/esm/lib/injection-engine/intentScorer.js.map +1 -1
  5. package/dist/esm/lib/injection-engine/routingPolicy.d.ts +3 -1
  6. package/dist/esm/lib/injection-engine/routingPolicy.js +15 -3
  7. package/dist/esm/lib/injection-engine/routingPolicy.js.map +1 -1
  8. package/dist/esm/ontology/fromSkos.d.ts +100 -0
  9. package/dist/esm/ontology/fromSkos.js +356 -0
  10. package/dist/esm/ontology/fromSkos.js.map +1 -0
  11. package/dist/esm/ontology/index.d.ts +6 -0
  12. package/dist/esm/ontology/index.js +6 -0
  13. package/dist/esm/ontology/index.js.map +1 -1
  14. package/dist/esm/ontology/skosJsonLd.d.ts +101 -0
  15. package/dist/esm/ontology/skosJsonLd.js +219 -0
  16. package/dist/esm/ontology/skosJsonLd.js.map +1 -0
  17. package/dist/esm/ontology/toSkos.d.ts +35 -0
  18. package/dist/esm/ontology/toSkos.js +105 -0
  19. package/dist/esm/ontology/toSkos.js.map +1 -0
  20. package/dist/lib/injection-engine/intentScorer.js +15 -4
  21. package/dist/lib/injection-engine/intentScorer.js.map +1 -1
  22. package/dist/lib/injection-engine/routingPolicy.js +15 -3
  23. package/dist/lib/injection-engine/routingPolicy.js.map +1 -1
  24. package/dist/ontology/fromSkos.js +362 -0
  25. package/dist/ontology/fromSkos.js.map +1 -0
  26. package/dist/ontology/index.js +14 -1
  27. package/dist/ontology/index.js.map +1 -1
  28. package/dist/ontology/skosJsonLd.js +232 -0
  29. package/dist/ontology/skosJsonLd.js.map +1 -0
  30. package/dist/ontology/toSkos.js +109 -0
  31. package/dist/ontology/toSkos.js.map +1 -0
  32. package/dist/types/lib/injection-engine/intentScorer.d.ts.map +1 -1
  33. package/dist/types/lib/injection-engine/routingPolicy.d.ts +3 -1
  34. package/dist/types/lib/injection-engine/routingPolicy.d.ts.map +1 -1
  35. package/dist/types/ontology/fromSkos.d.ts +101 -0
  36. package/dist/types/ontology/fromSkos.d.ts.map +1 -0
  37. package/dist/types/ontology/index.d.ts +6 -0
  38. package/dist/types/ontology/index.d.ts.map +1 -1
  39. package/dist/types/ontology/skosJsonLd.d.ts +102 -0
  40. package/dist/types/ontology/skosJsonLd.d.ts.map +1 -0
  41. package/dist/types/ontology/toSkos.d.ts +36 -0
  42. package/dist/types/ontology/toSkos.d.ts.map +1 -0
  43. 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;;YACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1B,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,EAAE,CAAC;QAC7C,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;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"}
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
- if (incumbentId !== undefined)
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":"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"}
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;