@panaversity/ksor 0.0.49 → 0.0.50

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # @panaversity/ksor
2
2
 
3
+ ## 0.0.50
4
+
5
+ ### Patch Changes
6
+
7
+ - f27f947: The three embed tuning variables now take effect when set in `.env`.
8
+ `KSOR_EMBED_TIMEOUT_S`, `KSOR_QUERY_EMBED_TIMEOUT_S` and `KSOR_EMBED_CACHE_MAX`
9
+ were read once at module load — before the CLI applies `.env` in `main()` — so
10
+ a value set there was silently ignored and the default stood. An adopter who
11
+ set `KSOR_EMBED_CACHE_MAX` to fit a small runtime, for instance, still got the
12
+ ~250 MB default cache and could OOM in production with nothing pointing at why.
13
+ The reads now happen at use. Exported shell variables were unaffected and still
14
+ are.
15
+
3
16
  ## 0.0.49
4
17
 
5
18
  ### Patch Changes
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as resolveCommand, r as verbs, t as exitCodes } from "./src-dqpI-p1a.mjs";
3
- import { A as parseViewer, B as tlsPosture, C as contentPoolMin, D as outlineHandler, E as keyRingFromEnv, F as runProbe$1, H as withPgRetry$1, I as searchHandler, L as servingPolicy$1, M as prewarmPool, N as readHandler, O as parseInstanceText$1, P as recordIsUndescribed, R as storedTextSearchConfig, S as contentPool$1, T as instancePathOf$1, U as withProbeDeadline$1, V as validateViewer, W as z$1, _ as assertGovernanceServable$1, a as GovernanceGateError$1, b as checkEmbeddingSpace$1, c as McpServer$1, d as READ_ONLY, f as READ_OUTPUT, g as TextSearchConfigMismatch, h as TRUST_TIERS$1, i as FLOOR, j as pooledEndpointFor, k as parseTrustFloor, l as MissingProviderKeyError$1, m as SchemaVersionError, n as ContentStoreError$1, o as MAX_OUTLINE_LIMIT, p as SEARCH_OUTPUT, r as EmbeddingSpaceMismatch$1, t as AudienceError$1, u as OUTLINE_OUTPUT, v as assertSchemaCompatible, w as embedQueryVlit, x as composeInstructions, y as buildShippedProvider$1, z as tallyHandlers } from "./gateway-api-8DkVCYYi-Bo0_Y1Y3.mjs";
3
+ import { A as parseViewer, B as tlsPosture, C as contentPoolMin, D as outlineHandler, E as keyRingFromEnv, F as runProbe$1, H as withPgRetry$1, I as searchHandler, L as servingPolicy$1, M as prewarmPool, N as readHandler, O as parseInstanceText$1, P as recordIsUndescribed, R as storedTextSearchConfig, S as contentPool$1, T as instancePathOf$1, U as withProbeDeadline$1, V as validateViewer, W as z$1, _ as assertGovernanceServable$1, a as GovernanceGateError$1, b as checkEmbeddingSpace$1, c as McpServer$1, d as READ_ONLY, f as READ_OUTPUT, g as TextSearchConfigMismatch, h as TRUST_TIERS$1, i as FLOOR, j as pooledEndpointFor, k as parseTrustFloor, l as MissingProviderKeyError$1, m as SchemaVersionError, n as ContentStoreError$1, o as MAX_OUTLINE_LIMIT, p as SEARCH_OUTPUT, r as EmbeddingSpaceMismatch$1, t as AudienceError$1, u as OUTLINE_OUTPUT, v as assertSchemaCompatible, w as embedQueryVlit, x as composeInstructions, y as buildShippedProvider$1, z as tallyHandlers } from "./gateway-api-D8HlLys2-Ca8OnwLn.mjs";
4
4
  import { appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { InMemoryTransport, LATEST_PROTOCOL_VERSION, createMcpHandler } from "@modelcontextprotocol/server";
@@ -17,7 +17,7 @@ import { Document, YAMLParseError, isCollection, isMap, isPair, isSeq, parseAllD
17
17
  import { parseArgs } from "node:util";
18
18
  import { readFile } from "node:fs/promises";
19
19
  import { execFileSync, spawnSync } from "node:child_process";
20
- //#region ../content-gateway/dist/main-BXNvtHqd.mjs
20
+ //#region ../content-gateway/dist/main-DMCMO8ye.mjs
21
21
  /**
22
22
  * The default registration — and the ORIGINAL of the file `ksor init` emits.
23
23
  *
@@ -4709,7 +4709,7 @@ async function withPgRetry(op, options = {}) {
4709
4709
  throw lastError;
4710
4710
  }
4711
4711
  //#endregion
4712
- //#region ../content/dist/commands-C3D7buZN.mjs
4712
+ //#region ../content/dist/commands-BNxmBRjs.mjs
4713
4713
  /**
4714
4714
  * EVAL-LOCKED constants, quarried verbatim from the oracle
4715
4715
  * (sor-agentfactory @ b554f91, config.py) — changing any of these is a
@@ -5787,11 +5787,12 @@ async function topOneScore(client, scope, queryVector) {
5787
5787
  })).rows[0]?.[0];
5788
5788
  return raw === void 0 || raw === null ? null : toNumber(raw, "score");
5789
5789
  }
5790
- const EMBED_TIMEOUT_S = envFloat("KSOR_EMBED_TIMEOUT_S", 60, 1);
5790
+ /** The per-request HTTP timeout for a document (ingest/batch) embed. */
5791
+ const EMBED_TIMEOUT_S = () => envFloat("KSOR_EMBED_TIMEOUT_S", 60, 1);
5791
5792
  /** Oracle env var: SOR_QUERY_EMBED_TIMEOUT_S. Note: query-embed.ts reads the
5792
5793
  * SAME variable with a different default (5.0) as its hard wall clock — two
5793
5794
  * deliberate reads, carried from the oracle (embedding.py:62 vs query_embed.py:44). */
5794
- const QUERY_EMBED_TIMEOUT_S = envFloat("KSOR_QUERY_EMBED_TIMEOUT_S", 10, 1);
5795
+ const QUERY_EMBED_TIMEOUT_S = () => envFloat("KSOR_QUERY_EMBED_TIMEOUT_S", 10, 1);
5795
5796
  /** The text we embed for a chunk: the readable hierarchical heading path, then the content. */
5796
5797
  function embedInput(title, headingPath, content) {
5797
5798
  const path = headingPath ? (title ? title + " > " : "") + headingPath.replaceAll("/", " > ").replaceAll("-", " ").trim() : title;
@@ -6213,8 +6214,8 @@ function buildShippedProvider(name, opts) {
6213
6214
  dim: opts.dim ?? 1536,
6214
6215
  documentTaskLabel: EMBED_TASK_DOCUMENT,
6215
6216
  queryTaskLabel: EMBED_TASK_QUERY,
6216
- documentTimeoutS: EMBED_TIMEOUT_S,
6217
- queryTimeoutS: QUERY_EMBED_TIMEOUT_S
6217
+ documentTimeoutS: EMBED_TIMEOUT_S(),
6218
+ queryTimeoutS: QUERY_EMBED_TIMEOUT_S()
6218
6219
  });
6219
6220
  }
6220
6221
  /** Written to yield ZERO ROWS, never an error, when the schema is absent. */
@@ -6,7 +6,7 @@ import { z, z as z$1 } from "zod";
6
6
  import path, { join } from "node:path";
7
7
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
8
8
  import pg from "pg";
9
- //#region ../content-gateway/dist/gateway-api-8DkVCYYi.mjs
9
+ //#region ../content-gateway/dist/gateway-api-D8HlLys2.mjs
10
10
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
11
11
  var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
12
12
  /**
@@ -8769,11 +8769,12 @@ function validate$1(ring, token, scope, nowMs = Date.now()) {
8769
8769
  reason: null
8770
8770
  };
8771
8771
  }
8772
- const EMBED_TIMEOUT_S = envFloat("KSOR_EMBED_TIMEOUT_S", 60, 1);
8772
+ /** The per-request HTTP timeout for a document (ingest/batch) embed. */
8773
+ const EMBED_TIMEOUT_S = () => envFloat("KSOR_EMBED_TIMEOUT_S", 60, 1);
8773
8774
  /** Oracle env var: SOR_QUERY_EMBED_TIMEOUT_S. Note: query-embed.ts reads the
8774
8775
  * SAME variable with a different default (5.0) as its hard wall clock — two
8775
8776
  * deliberate reads, carried from the oracle (embedding.py:62 vs query_embed.py:44). */
8776
- const QUERY_EMBED_TIMEOUT_S = envFloat("KSOR_QUERY_EMBED_TIMEOUT_S", 10, 1);
8777
+ const QUERY_EMBED_TIMEOUT_S = () => envFloat("KSOR_QUERY_EMBED_TIMEOUT_S", 10, 1);
8777
8778
  function l2Normalize(v) {
8778
8779
  const norm = Math.sqrt(v.reduce((acc, x) => acc + x * x, 0));
8779
8780
  return norm === 0 ? v : v.map((x) => x / norm);
@@ -9120,8 +9121,8 @@ function buildShippedProvider(name, opts) {
9120
9121
  dim: opts.dim ?? 1536,
9121
9122
  documentTaskLabel: EMBED_TASK_DOCUMENT,
9122
9123
  queryTaskLabel: EMBED_TASK_QUERY,
9123
- documentTimeoutS: EMBED_TIMEOUT_S,
9124
- queryTimeoutS: QUERY_EMBED_TIMEOUT_S
9124
+ documentTimeoutS: EMBED_TIMEOUT_S(),
9125
+ queryTimeoutS: QUERY_EMBED_TIMEOUT_S()
9125
9126
  });
9126
9127
  }
9127
9128
  /** Written to yield ZERO ROWS, never an error, when the schema is absent. */
@@ -9991,9 +9992,46 @@ async function outline(client, scope, options = {}) {
9991
9992
  generation: pinned ?? rows[0]?.generation ?? null
9992
9993
  };
9993
9994
  }
9994
- let cacheMax = envInt("KSOR_EMBED_CACHE_MAX", 1e4, 1);
9995
- /** Oracle env var: SOR_QUERY_EMBED_TIMEOUT_S. */
9996
- const EMBED_WALL_TIMEOUT_S = envFloat("KSOR_QUERY_EMBED_TIMEOUT_S", 5, .1);
9995
+ /**
9996
+ * Query-embedding cache, converted from the oracle (sor-agentfactory @
9997
+ * b554f91, sor_content/lib/query_embed.py): L1 in-process LRU + SINGLE-FLIGHT
9998
+ * (concurrent identical misses share ONE paid embed), keyed with
9999
+ * model + task + dim so a model or dimension bump can never serve a stale
10000
+ * vector. Whitespace collapses; case does NOT fold (folding would change the
10001
+ * embedded text).
10002
+ *
10003
+ * A tiny CIRCUIT BREAKER guards the provider: after an embed failure, further
10004
+ * misses raise immediately for a short cooldown (cache hits still serve) —
10005
+ * during an outage every request degrades to keyword-only instantly instead
10006
+ * of each unique query paying its own failed attempt against a provider that
10007
+ * is already down. The breaker is keyed per SPACE (modelId, dim), like the
10008
+ * keys: a failing provider A must not degrade a healthy provider B.
10009
+ *
10010
+ * Conversion notes (decision 6):
10011
+ * - The oracle's optional Redis L2 (fail-open both directions, TTL
10012
+ * SOR_EMBED_CACHE_TTL, the `sor:emb:*` key scheme) is DROPPED — it was
10013
+ * multi-instance infrastructure; the L1 + single-flight carry a
10014
+ * single-process deployment. It returns, if ever, with real multi-instance
10015
+ * serving — nothing here forecloses it.
10016
+ * - The oracle's waiter-shield (`asyncio.shield`) and owner-cancel handling
10017
+ * protected the shared future from one caller's cancellation. JS promises
10018
+ * are not cancellable, so sharing the promise IS the whole mechanism: no
10019
+ * caller can cancel another, and there is no owner-cancelled path to map.
10020
+ * - `asyncio.wait_for` CANCELLED the embed on timeout; a JS promise cannot be
10021
+ * cancelled, so on timeout the losing call is abandoned (its settlement is
10022
+ * still observed, so it can never surface as an unhandled rejection) and
10023
+ * runs out its own HTTP timeout in the background.
10024
+ * - The breaker clock is Date.now() (ms) rather than a monotonic clock — the
10025
+ * 10 s cooldown is coarse, and fake-timer tests need the system clock.
10026
+ */
10027
+ /** Oracle env var: SOR_EMBED_CACHE_MAX. */
10028
+ let memoizedCacheMax;
10029
+ function currentCacheMax() {
10030
+ return memoizedCacheMax ??= envInt("KSOR_EMBED_CACHE_MAX", 1e4, 1);
10031
+ }
10032
+ /** Oracle env var: SOR_QUERY_EMBED_TIMEOUT_S. Read at use, not at module load
10033
+ * — see the cache-max note above. */
10034
+ const EMBED_WALL_TIMEOUT_S = () => envFloat("KSOR_QUERY_EMBED_TIMEOUT_S", 5, .1);
9997
10035
  const cache = /* @__PURE__ */ new Map();
9998
10036
  const inflight = /* @__PURE__ */ new Map();
9999
10037
  const breakerOpenUntilByMs = /* @__PURE__ */ new Map();
@@ -10037,10 +10075,11 @@ function breakerOpenUntil(provider) {
10037
10075
  return breakerOpenUntilByMs.get(spaceKey(provider)) ?? 0;
10038
10076
  }
10039
10077
  function withWallClock(work) {
10078
+ const wallTimeoutS = EMBED_WALL_TIMEOUT_S();
10040
10079
  return new Promise((resolve, reject) => {
10041
10080
  const timer = setTimeout(() => {
10042
- reject(new QueryEmbedTimeoutError(`query embed exceeded the ${EMBED_WALL_TIMEOUT_S}s wall clock — treated as a provider failure (degrade to keyword-only)`));
10043
- }, EMBED_WALL_TIMEOUT_S * 1e3);
10081
+ reject(new QueryEmbedTimeoutError(`query embed exceeded the ${wallTimeoutS}s wall clock — treated as a provider failure (degrade to keyword-only)`));
10082
+ }, wallTimeoutS * 1e3);
10044
10083
  work.then((value) => {
10045
10084
  clearTimeout(timer);
10046
10085
  resolve(value);
@@ -10061,7 +10100,7 @@ async function embedMiss(normalized, key, provider) {
10061
10100
  const literal = vlit(vec);
10062
10101
  cache.delete(key);
10063
10102
  cache.set(key, literal);
10064
- while (cache.size > cacheMax) {
10103
+ while (cache.size > currentCacheMax()) {
10065
10104
  const oldest = cache.keys().next().value;
10066
10105
  if (oldest === void 0) break;
10067
10106
  cache.delete(oldest);
package/dist/gateway.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { D as outlineHandler, I as searchHandler, N as readHandler, P as recordIsUndescribed, W as z$1, c as McpServer$1, d as READ_ONLY, f as READ_OUTPUT, h as TRUST_TIERS, i as FLOOR, o as MAX_OUTLINE_LIMIT, p as SEARCH_OUTPUT, s as MAX_SEARCH_K, u as OUTLINE_OUTPUT, x as composeInstructions } from "./gateway-api-8DkVCYYi-Bo0_Y1Y3.mjs";
1
+ import { D as outlineHandler, I as searchHandler, N as readHandler, P as recordIsUndescribed, W as z$1, c as McpServer$1, d as READ_ONLY, f as READ_OUTPUT, h as TRUST_TIERS, i as FLOOR, o as MAX_OUTLINE_LIMIT, p as SEARCH_OUTPUT, s as MAX_SEARCH_K, u as OUTLINE_OUTPUT, x as composeInstructions } from "./gateway-api-D8HlLys2-Ca8OnwLn.mjs";
2
2
  export { FLOOR, MAX_OUTLINE_LIMIT, MAX_SEARCH_K, McpServer$1 as McpServer, OUTLINE_OUTPUT, READ_ONLY, READ_OUTPUT, SEARCH_OUTPUT, TRUST_TIERS, composeInstructions, outlineHandler, readHandler, recordIsUndescribed, searchHandler, z$1 as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panaversity/ksor",
3
- "version": "0.0.49",
3
+ "version": "0.0.50",
4
4
  "description": "Knowledge System of Record — compile governed markdown into a static site for people and an MCP server for AI agents, with citations and measured abstention.",
5
5
  "keywords": [
6
6
  "abstention",