@panaversity/ksor 0.0.53 → 0.0.55
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 +328 -0
- package/README.md +30 -0
- package/dist/checker/check-main.mjs +5 -2
- package/dist/cli.mjs +319 -60
- package/dist/{gateway-api-Ck1l_Sgs-B-_wMYLB.mjs → gateway-api-C0vL3oOK-D24n786A.mjs} +221 -20
- package/dist/gateway.d.mts +2 -2
- package/dist/gateway.mjs +1 -1
- package/docs/deploying.md +48 -9
- package/docs/ingesting.md +30 -8
- package/package.json +1 -1
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +3 -0
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +3 -0
- package/templates/scaffold/AGENTS.md +42 -5
- package/templates/scaffold/README.md +79 -13
- package/templates/scaffold/env.example +6 -1
- package/templates/scaffold/gitignore +4 -3
- package/templates/scaffold/mcp.json +13 -0
- package/templates/scaffold/system/site/lib/lock.ts +8 -1
- package/templates/scaffold/system/site/lib/people-rule.ts +56 -0
- package/templates/scaffold/system/site/lib/people.ts +5 -24
- package/templates/scaffold/system/site/lib/stage-knowledge.ts +2 -0
- package/templates/scaffold/system/site/record/load.ts +11 -1
- package/templates/scaffold/system/site/record/lock.ts +12 -0
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
|
|
3
|
+
import { A as parseViewer, B as tallyHandlers, C as contentPoolMin, D as outlineHandler, E as keyRingFromEnv, F as recordIsUndescribed, G as z$1, H as validateViewer, I as runProbe$1, L as searchHandler, M as prewarmPool, N as providerKeyEnv$1, O as parseInstanceText$1, P as readHandler, R as servingPolicy$1, S as contentPool$1, T as instancePathOf$1, U as withPgRetry$1, V as tlsPosture, W as withProbeDeadline$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 storedTextSearchConfig } from "./gateway-api-C0vL3oOK-D24n786A.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-
|
|
20
|
+
//#region ../content-gateway/dist/main-BoKhKcP0.mjs
|
|
21
21
|
/**
|
|
22
22
|
* The default registration — and the ORIGINAL of the file `ksor init` emits.
|
|
23
23
|
*
|
|
@@ -1008,7 +1008,7 @@ async function compose(rawInstancePath, version) {
|
|
|
1008
1008
|
let provider;
|
|
1009
1009
|
try {
|
|
1010
1010
|
provider = buildShippedProvider$1(instance.embeddingProvider, {
|
|
1011
|
-
apiKey: process.env["
|
|
1011
|
+
apiKey: process.env[providerKeyEnv$1(instance.embeddingProvider) ?? ""] ?? null,
|
|
1012
1012
|
modelId: instance.embeddingModel,
|
|
1013
1013
|
dim: instance.embeddingDim
|
|
1014
1014
|
});
|
|
@@ -1533,7 +1533,7 @@ async function main$1(version = GATEWAY_VERSION) {
|
|
|
1533
1533
|
}
|
|
1534
1534
|
}
|
|
1535
1535
|
//#endregion
|
|
1536
|
-
//#region ../content/dist/check-
|
|
1536
|
+
//#region ../content/dist/check-6fB_kR4G.mjs
|
|
1537
1537
|
/**
|
|
1538
1538
|
* One reader for the control files beside the bundle (`.ksor/*.yaml`): the
|
|
1539
1539
|
* same parser posture as a concept's frontmatter — one document, unique keys,
|
|
@@ -2825,6 +2825,7 @@ const LEDGER_HEADER$1 = "# The takedown ledger (record spec §5): append-only, w
|
|
|
2825
2825
|
const CONTROL_FILES = [
|
|
2826
2826
|
"instance.md",
|
|
2827
2827
|
".ksor/governance.yaml",
|
|
2828
|
+
".ksor/people.yaml",
|
|
2828
2829
|
".ksor/takedowns.yaml"
|
|
2829
2830
|
];
|
|
2830
2831
|
/** Files the operating system writes behind the author's back: ignored, never reported. */
|
|
@@ -4709,7 +4710,7 @@ async function withPgRetry(op, options = {}) {
|
|
|
4709
4710
|
throw lastError;
|
|
4710
4711
|
}
|
|
4711
4712
|
//#endregion
|
|
4712
|
-
//#region ../content/dist/commands-
|
|
4713
|
+
//#region ../content/dist/commands-CO9-bQnb.mjs
|
|
4713
4714
|
/**
|
|
4714
4715
|
* EVAL-LOCKED constants, quarried verbatim from the oracle
|
|
4715
4716
|
* (sor-agentfactory @ b554f91, config.py) — changing any of these is a
|
|
@@ -5823,13 +5824,6 @@ const INGEST_RETRY = {
|
|
|
5823
5824
|
initialS: 1,
|
|
5824
5825
|
maxS: 30
|
|
5825
5826
|
};
|
|
5826
|
-
/** Fail-fast for the query path: 2 attempts, sub-second backoff, no 429 retry — worst case a
|
|
5827
|
-
* query embed costs ~1 s before the caller degrades, never a 15 s stall on the read. */
|
|
5828
|
-
const QUERY_RETRY = {
|
|
5829
|
-
attempts: 2,
|
|
5830
|
-
initialS: .2,
|
|
5831
|
-
maxS: .5
|
|
5832
|
-
};
|
|
5833
5827
|
function sleepS(seconds) {
|
|
5834
5828
|
return new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
|
|
5835
5829
|
}
|
|
@@ -5855,17 +5849,6 @@ async function embedIntent(texts, opts) {
|
|
|
5855
5849
|
return contract(texts, await withRetry(INGEST_RETRY, (exc) => provider.isRetryable(exc), () => provider.reset(), () => provider.embed(texts, { intent })), provider.dim);
|
|
5856
5850
|
}
|
|
5857
5851
|
/**
|
|
5858
|
-
* The READ plane's door (query-embed, intent "query"). Same contract;
|
|
5859
|
-
* FAIL-FAST retry (no 429, 2 attempts): a rate limit degrades the search to
|
|
5860
|
-
* keyword-only in under a second instead of stalling the read ~15 s. Async
|
|
5861
|
-
* like everything here — the oracle's `a` prefix marked Python's async
|
|
5862
|
-
* flavor and is kept only for greppability against the Python.
|
|
5863
|
-
*/
|
|
5864
|
-
async function aembedIntent(texts, opts) {
|
|
5865
|
-
const { provider, intent } = opts;
|
|
5866
|
-
return contract(texts, await withRetry(QUERY_RETRY, (exc) => provider.isRetryableQuery(exc), () => provider.reset(), () => provider.embed(texts, { intent })), provider.dim);
|
|
5867
|
-
}
|
|
5868
|
-
/**
|
|
5869
5852
|
* The deterministic, key-free fake provider — a KSOR ADDITION (no oracle
|
|
5870
5853
|
* counterpart; decision 6 conversion note). It exists so the DB tier and CI
|
|
5871
5854
|
* can exercise ingest + retrieval end to end without a vendor key: same text
|
|
@@ -5937,7 +5920,7 @@ var FakeEmbeddingProvider = class {
|
|
|
5937
5920
|
}
|
|
5938
5921
|
reset() {}
|
|
5939
5922
|
};
|
|
5940
|
-
const DEFAULT_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
5923
|
+
const DEFAULT_BASE$1 = "https://generativelanguage.googleapis.com/v1beta";
|
|
5941
5924
|
/**
|
|
5942
5925
|
* An HTTP-shaped failure carrying the status the retry classifier reads.
|
|
5943
5926
|
*
|
|
@@ -5955,7 +5938,7 @@ var GeminiHttpError = class extends Error {
|
|
|
5955
5938
|
};
|
|
5956
5939
|
/** One POST, with the key in a HEADER — never the query string, which is logged. */
|
|
5957
5940
|
async function post(opts, apiKey, path, body, timeoutMs) {
|
|
5958
|
-
const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl ?? DEFAULT_BASE}${path}`, {
|
|
5941
|
+
const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl ?? DEFAULT_BASE$1}${path}`, {
|
|
5959
5942
|
method: "POST",
|
|
5960
5943
|
headers: {
|
|
5961
5944
|
"x-goog-api-key": apiKey,
|
|
@@ -6025,33 +6008,33 @@ function geminiRestTextClient(apiKey, opts = {}) {
|
|
|
6025
6008
|
* Python-SDK failure mode with no JS equivalent; `reset()`
|
|
6026
6009
|
* keeps its drop-never-close contract regardless.
|
|
6027
6010
|
*/
|
|
6028
|
-
function httpStatusOf(exc) {
|
|
6011
|
+
function httpStatusOf$1(exc) {
|
|
6029
6012
|
if (!(exc instanceof Error)) return void 0;
|
|
6030
6013
|
const status = exc.status;
|
|
6031
6014
|
return typeof status === "number" ? status : void 0;
|
|
6032
6015
|
}
|
|
6033
|
-
function isTransportBlip(exc, depth = 0) {
|
|
6016
|
+
function isTransportBlip$1(exc, depth = 0) {
|
|
6034
6017
|
if (depth > 5 || !(exc instanceof Error)) return false;
|
|
6035
6018
|
if (exc.name === "AbortError" || exc.name === "TimeoutError") return true;
|
|
6036
6019
|
const code = exc.code;
|
|
6037
6020
|
if (typeof code === "string" && /^E[A-Z0-9_]+$/.test(code)) return true;
|
|
6038
6021
|
if (exc instanceof TypeError && exc.message.includes("fetch failed")) return true;
|
|
6039
|
-
return isTransportBlip(exc.cause, depth + 1);
|
|
6022
|
+
return isTransportBlip$1(exc.cause, depth + 1);
|
|
6040
6023
|
}
|
|
6041
6024
|
/** The Gemini taxonomy for the INGEST plane: transport blips, 5xx, AND 429 —
|
|
6042
6025
|
* batch work is resumable and has no user waiting. */
|
|
6043
|
-
function isRetryable(exc) {
|
|
6044
|
-
if (isTransportBlip(exc)) return true;
|
|
6045
|
-
const status = httpStatusOf(exc);
|
|
6026
|
+
function isRetryable$1(exc) {
|
|
6027
|
+
if (isTransportBlip$1(exc)) return true;
|
|
6028
|
+
const status = httpStatusOf$1(exc);
|
|
6046
6029
|
if (status === void 0) return false;
|
|
6047
6030
|
return status >= 500 && status <= 599 || status === 429;
|
|
6048
6031
|
}
|
|
6049
6032
|
/** The READ path's predicate: transport blips + 5xx only — NEVER 429. A
|
|
6050
6033
|
* rate-limited project stays rate-limited on the next second; the correct
|
|
6051
6034
|
* move is to degrade to keyword-only NOW, not stall the read behind backoff. */
|
|
6052
|
-
function isRetryableQuery(exc) {
|
|
6053
|
-
if (isTransportBlip(exc)) return true;
|
|
6054
|
-
const status = httpStatusOf(exc);
|
|
6035
|
+
function isRetryableQuery$1(exc) {
|
|
6036
|
+
if (isTransportBlip$1(exc)) return true;
|
|
6037
|
+
const status = httpStatusOf$1(exc);
|
|
6055
6038
|
return status !== void 0 && status >= 500 && status <= 599;
|
|
6056
6039
|
}
|
|
6057
6040
|
/**
|
|
@@ -6102,10 +6085,10 @@ var GeminiEmbeddingProvider = class {
|
|
|
6102
6085
|
})).embeddings ?? []).map((e) => [...e.values ?? []]);
|
|
6103
6086
|
}
|
|
6104
6087
|
isRetryable(exc) {
|
|
6105
|
-
return isRetryable(exc);
|
|
6088
|
+
return isRetryable$1(exc);
|
|
6106
6089
|
}
|
|
6107
6090
|
isRetryableQuery(exc) {
|
|
6108
|
-
return isRetryableQuery(exc);
|
|
6091
|
+
return isRetryableQuery$1(exc);
|
|
6109
6092
|
}
|
|
6110
6093
|
};
|
|
6111
6094
|
/**
|
|
@@ -6118,7 +6101,7 @@ var GeminiTextGenerator = class {
|
|
|
6118
6101
|
clientFactory;
|
|
6119
6102
|
client = null;
|
|
6120
6103
|
constructor(opts) {
|
|
6121
|
-
this.model = opts.model ?? "gemini-
|
|
6104
|
+
this.model = opts.model ?? "gemini-3.7-flash";
|
|
6122
6105
|
this.clientFactory = opts.clientFactory ?? (() => geminiRestTextClient(opts.apiKey));
|
|
6123
6106
|
}
|
|
6124
6107
|
getClient() {
|
|
@@ -6139,9 +6122,174 @@ var GeminiTextGenerator = class {
|
|
|
6139
6122
|
}
|
|
6140
6123
|
})).text ?? "";
|
|
6141
6124
|
}
|
|
6125
|
+
isRetryable(exc) {
|
|
6126
|
+
return isRetryable$1(exc);
|
|
6127
|
+
}
|
|
6128
|
+
};
|
|
6129
|
+
/**
|
|
6130
|
+
* OpenAI's embedding endpoint, over `fetch` — no SDK.
|
|
6131
|
+
*
|
|
6132
|
+
* The same call decision 12's 2026-08-22 revision made for Gemini, for the same
|
|
6133
|
+
* reason: this is ONE HTTP call behind a structurally-typed slice, and a vendor
|
|
6134
|
+
* SDK would put megabytes and a transitive dependency tree into every
|
|
6135
|
+
* `ksor init` that never embeds anything. If a provider ever needs an SDK, the
|
|
6136
|
+
* seam takes one through `clientFactory`; nothing here forecloses that.
|
|
6137
|
+
*
|
|
6138
|
+
* WHAT DIFFERS FROM GEMINI, and it is worth knowing before choosing:
|
|
6139
|
+
*
|
|
6140
|
+
* Gemini embeds ASYMMETRICALLY — `RETRIEVAL_DOCUMENT` and `RETRIEVAL_QUERY`
|
|
6141
|
+
* produce different vectors for the same text, and the plane's intent picks
|
|
6142
|
+
* which. OpenAI has no task type: a query and a document are embedded
|
|
6143
|
+
* identically. The seam anticipates exactly this — "a provider whose two
|
|
6144
|
+
* vendor labels are equal can never mis-route a plane"
|
|
6145
|
+
* (`lib/embedding.ts`) — so both labels are the empty string and the intent
|
|
6146
|
+
* reaches this transport and is deliberately ignored.
|
|
6147
|
+
*
|
|
6148
|
+
* `dimensions` is supported on `text-embedding-3-*` only. Asking an older
|
|
6149
|
+
* model for a dimension is an error there, not a silent full-width vector,
|
|
6150
|
+
* which is the failure the framework's own width check would catch anyway.
|
|
6151
|
+
*/
|
|
6152
|
+
/**
|
|
6153
|
+
* An HTTP-shaped failure carrying the status the retry classifier reads — and
|
|
6154
|
+
* the vendor's own error `type`, which the status alone does not distinguish.
|
|
6155
|
+
*
|
|
6156
|
+
* OpenAI answers a spent balance with **429**, the same status as a rate limit:
|
|
6157
|
+
* `{"error":{"type":"insufficient_quota","code":"credit_balance_exhausted"}}`
|
|
6158
|
+
* (observed live, 2026-09-01). One clears by waiting and one never will, so a
|
|
6159
|
+
* classifier reading only the status retries a billing problem five times with
|
|
6160
|
+
* exponential backoff and then reports it — slowly, and as if it had been
|
|
6161
|
+
* transient.
|
|
6162
|
+
*/
|
|
6163
|
+
var OpenAiHttpError = class extends Error {
|
|
6164
|
+
status;
|
|
6165
|
+
/** The vendor's `error.type`, when it sends one. */
|
|
6166
|
+
kind;
|
|
6167
|
+
constructor(status, detail, kind = null) {
|
|
6168
|
+
super(`OpenAI API error ${status}: ${detail}`);
|
|
6169
|
+
this.name = "OpenAiHttpError";
|
|
6170
|
+
this.status = status;
|
|
6171
|
+
this.kind = kind;
|
|
6172
|
+
}
|
|
6173
|
+
};
|
|
6174
|
+
const DEFAULT_BASE = "https://api.openai.com/v1";
|
|
6175
|
+
function openAiRestEmbedClient(apiKey, opts = {}) {
|
|
6176
|
+
return { async embed(params) {
|
|
6177
|
+
const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl ?? DEFAULT_BASE}/embeddings`, {
|
|
6178
|
+
method: "POST",
|
|
6179
|
+
headers: {
|
|
6180
|
+
authorization: `Bearer ${apiKey}`,
|
|
6181
|
+
"content-type": "application/json"
|
|
6182
|
+
},
|
|
6183
|
+
body: JSON.stringify({
|
|
6184
|
+
model: params.model,
|
|
6185
|
+
input: [...params.input],
|
|
6186
|
+
dimensions: params.dimensions
|
|
6187
|
+
}),
|
|
6188
|
+
signal: AbortSignal.timeout(params.timeoutMs)
|
|
6189
|
+
});
|
|
6190
|
+
const text = await res.text();
|
|
6191
|
+
if (!res.ok) {
|
|
6192
|
+
let detail = text.slice(0, 300);
|
|
6193
|
+
let kind = null;
|
|
6194
|
+
try {
|
|
6195
|
+
const err = JSON.parse(text).error;
|
|
6196
|
+
if (typeof err?.message === "string") detail = err.message;
|
|
6197
|
+
if (typeof err?.type === "string") kind = err.type;
|
|
6198
|
+
} catch {}
|
|
6199
|
+
throw new OpenAiHttpError(res.status, detail, kind);
|
|
6200
|
+
}
|
|
6201
|
+
const data = [...JSON.parse(text).data ?? []];
|
|
6202
|
+
data.sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
|
|
6203
|
+
return { embeddings: data.map((d) => ({ values: d.embedding })) };
|
|
6204
|
+
} };
|
|
6205
|
+
}
|
|
6206
|
+
/** True for a transport blip with no HTTP status of its own. */
|
|
6207
|
+
function isTransportBlip(exc) {
|
|
6208
|
+
if (exc instanceof OpenAiHttpError) return false;
|
|
6209
|
+
const name = exc?.name;
|
|
6210
|
+
return name === "AbortError" || name === "TimeoutError" || name === "TypeError";
|
|
6211
|
+
}
|
|
6212
|
+
function httpStatusOf(exc) {
|
|
6213
|
+
return exc instanceof OpenAiHttpError ? exc.status : void 0;
|
|
6214
|
+
}
|
|
6215
|
+
/**
|
|
6216
|
+
* The INGEST plane's taxonomy: transport blips, 5xx, AND 429 — batch work is
|
|
6217
|
+
* resumable and has nobody waiting. Deliberately the same shape as the Gemini
|
|
6218
|
+
* adapter's, because the two planes are a property of ksor, not of a vendor.
|
|
6219
|
+
*/
|
|
6220
|
+
function isRetryable(exc) {
|
|
6221
|
+
if (isTransportBlip(exc)) return true;
|
|
6222
|
+
if (exc instanceof OpenAiHttpError && exc.kind === "insufficient_quota") return false;
|
|
6223
|
+
const status = httpStatusOf(exc);
|
|
6224
|
+
if (status === void 0) return false;
|
|
6225
|
+
return status >= 500 && status <= 599 || status === 429;
|
|
6226
|
+
}
|
|
6227
|
+
/**
|
|
6228
|
+
* The READ plane's: transport blips + 5xx only, NEVER 429. A rate-limited
|
|
6229
|
+
* project stays rate-limited on the next second, so a search degrades to
|
|
6230
|
+
* keyword-only now rather than stalling a reader behind backoff.
|
|
6231
|
+
*/
|
|
6232
|
+
/**
|
|
6233
|
+
* An ACCOUNT-level failure: no amount of waiting and no other passage changes
|
|
6234
|
+
* it. The drain must abort on this rather than quarantine, because the chunk
|
|
6235
|
+
* it happened to be holding is not what is wrong — see `ingest/worker.ts`.
|
|
6236
|
+
*/
|
|
6237
|
+
function isFatal(exc) {
|
|
6238
|
+
return exc instanceof OpenAiHttpError && exc.kind === "insufficient_quota";
|
|
6239
|
+
}
|
|
6240
|
+
function isRetryableQuery(exc) {
|
|
6241
|
+
if (isTransportBlip(exc)) return true;
|
|
6242
|
+
const status = httpStatusOf(exc);
|
|
6243
|
+
return status !== void 0 && status >= 500 && status <= 599;
|
|
6244
|
+
}
|
|
6245
|
+
var OpenAiEmbeddingProvider = class {
|
|
6246
|
+
providerId = "openai";
|
|
6247
|
+
modelId;
|
|
6248
|
+
dim;
|
|
6249
|
+
documentTaskLabel;
|
|
6250
|
+
queryTaskLabel;
|
|
6251
|
+
documentTimeoutMs;
|
|
6252
|
+
queryTimeoutMs;
|
|
6253
|
+
clientFactory;
|
|
6254
|
+
client = null;
|
|
6255
|
+
constructor(opts) {
|
|
6256
|
+
this.modelId = opts.modelId;
|
|
6257
|
+
this.dim = opts.dim;
|
|
6258
|
+
this.documentTaskLabel = opts.documentTaskLabel;
|
|
6259
|
+
this.queryTaskLabel = opts.queryTaskLabel;
|
|
6260
|
+
this.documentTimeoutMs = Math.trunc(opts.documentTimeoutS * 1e3);
|
|
6261
|
+
this.queryTimeoutMs = Math.trunc(opts.queryTimeoutS * 1e3);
|
|
6262
|
+
this.clientFactory = opts.clientFactory ?? (() => openAiRestEmbedClient(opts.apiKey));
|
|
6263
|
+
}
|
|
6264
|
+
get recipe() {
|
|
6265
|
+
return `${this.modelId}/d${this.dim}/${this.documentTaskLabel}`;
|
|
6266
|
+
}
|
|
6267
|
+
getClient() {
|
|
6268
|
+
this.client ??= this.clientFactory();
|
|
6269
|
+
return this.client;
|
|
6270
|
+
}
|
|
6271
|
+
/** DROP the client reference, never close it: in-flight calls finish on their
|
|
6272
|
+
* own reference and the next call rebuilds lazily. Idempotent. */
|
|
6273
|
+
reset() {
|
|
6274
|
+
this.client = null;
|
|
6275
|
+
}
|
|
6276
|
+
async embed(texts, opts) {
|
|
6277
|
+
return (await this.getClient().embed({
|
|
6278
|
+
model: this.modelId,
|
|
6279
|
+
input: texts,
|
|
6280
|
+
dimensions: this.dim,
|
|
6281
|
+
timeoutMs: opts.intent === "document" ? this.documentTimeoutMs : this.queryTimeoutMs
|
|
6282
|
+
})).embeddings.map((e) => [...e.values ?? []]);
|
|
6283
|
+
}
|
|
6142
6284
|
isRetryable(exc) {
|
|
6143
6285
|
return isRetryable(exc);
|
|
6144
6286
|
}
|
|
6287
|
+
isRetryableQuery(exc) {
|
|
6288
|
+
return isRetryableQuery(exc);
|
|
6289
|
+
}
|
|
6290
|
+
isFatal(exc) {
|
|
6291
|
+
return isFatal(exc);
|
|
6292
|
+
}
|
|
6145
6293
|
};
|
|
6146
6294
|
/**
|
|
6147
6295
|
* The embedding-provider registry — a plain object, NOT any discovery
|
|
@@ -6168,20 +6316,49 @@ var GeminiTextGenerator = class {
|
|
|
6168
6316
|
*/
|
|
6169
6317
|
var MissingProviderKeyError = class extends Error {
|
|
6170
6318
|
providerName;
|
|
6171
|
-
|
|
6172
|
-
|
|
6319
|
+
keyEnv;
|
|
6320
|
+
/**
|
|
6321
|
+
* `keyEnv` is not decoration. The message named the PROVIDER and nothing
|
|
6322
|
+
* else, so an operator whose `ksor serve` exited 3 on an OpenAI record was
|
|
6323
|
+
* told "provider openai needs an API key" and left to guess which variable —
|
|
6324
|
+
* while `ksor serve --help`, `env.example` and `docs/deploying.md` all named
|
|
6325
|
+
* `GEMINI_API_KEY`, which the door does not read (review, 2026-09-01). The
|
|
6326
|
+
* registry row already held the answer; this is it reaching the operator.
|
|
6327
|
+
*/
|
|
6328
|
+
constructor(providerName, keyEnv = null) {
|
|
6329
|
+
super(`embedding provider ${JSON.stringify(providerName)} needs an API key and none was supplied` + (keyEnv === null ? "" : ` — set ${keyEnv}`));
|
|
6173
6330
|
this.name = "MissingProviderKeyError";
|
|
6174
6331
|
this.providerName = providerName;
|
|
6332
|
+
this.keyEnv = keyEnv;
|
|
6175
6333
|
}
|
|
6176
6334
|
};
|
|
6177
6335
|
const PROVIDERS = {
|
|
6178
6336
|
gemini: {
|
|
6179
6337
|
build: (opts) => new GeminiEmbeddingProvider(opts),
|
|
6180
|
-
needsApiKey: true
|
|
6338
|
+
needsApiKey: true,
|
|
6339
|
+
keyEnv: "GEMINI_API_KEY",
|
|
6340
|
+
taskLabels: {
|
|
6341
|
+
document: EMBED_TASK_DOCUMENT,
|
|
6342
|
+
query: EMBED_TASK_QUERY
|
|
6343
|
+
}
|
|
6344
|
+
},
|
|
6345
|
+
openai: {
|
|
6346
|
+
build: (opts) => new OpenAiEmbeddingProvider(opts),
|
|
6347
|
+
needsApiKey: true,
|
|
6348
|
+
keyEnv: "OPENAI_API_KEY",
|
|
6349
|
+
taskLabels: {
|
|
6350
|
+
document: "",
|
|
6351
|
+
query: ""
|
|
6352
|
+
}
|
|
6181
6353
|
},
|
|
6182
6354
|
fake: {
|
|
6183
6355
|
build: (opts) => new FakeEmbeddingProvider(opts),
|
|
6184
|
-
needsApiKey: false
|
|
6356
|
+
needsApiKey: false,
|
|
6357
|
+
keyEnv: null,
|
|
6358
|
+
taskLabels: {
|
|
6359
|
+
document: EMBED_TASK_DOCUMENT,
|
|
6360
|
+
query: EMBED_TASK_QUERY
|
|
6361
|
+
}
|
|
6185
6362
|
}
|
|
6186
6363
|
};
|
|
6187
6364
|
function entryFor(name) {
|
|
@@ -6196,6 +6373,13 @@ function providerNeedsApiKey(name) {
|
|
|
6196
6373
|
return entryFor(name).needsApiKey;
|
|
6197
6374
|
}
|
|
6198
6375
|
/**
|
|
6376
|
+
* The environment variable this provider's key comes from, or null when it
|
|
6377
|
+
* needs none. Unknown name → the same loud error as building it.
|
|
6378
|
+
*/
|
|
6379
|
+
function providerKeyEnv(name) {
|
|
6380
|
+
return entryFor(name).keyEnv;
|
|
6381
|
+
}
|
|
6382
|
+
/**
|
|
6199
6383
|
* The port door: the named provider bound to the DECLARED embedding space and
|
|
6200
6384
|
* the framework's timeout knobs. `modelId`/`dim` omitted = the shipped
|
|
6201
6385
|
* config space (EMBED_MODEL / EMBED_DIM, eval-locked); an instance may
|
|
@@ -6207,13 +6391,13 @@ function providerNeedsApiKey(name) {
|
|
|
6207
6391
|
*/
|
|
6208
6392
|
function buildShippedProvider(name, opts) {
|
|
6209
6393
|
const entry = entryFor(name);
|
|
6210
|
-
if (entry.needsApiKey && !opts.apiKey) throw new MissingProviderKeyError(name);
|
|
6394
|
+
if (entry.needsApiKey && !opts.apiKey) throw new MissingProviderKeyError(name, entry.keyEnv);
|
|
6211
6395
|
return entry.build({
|
|
6212
6396
|
apiKey: opts.apiKey ?? "",
|
|
6213
6397
|
modelId: opts.modelId ?? "gemini-embedding-001",
|
|
6214
6398
|
dim: opts.dim ?? 1536,
|
|
6215
|
-
documentTaskLabel:
|
|
6216
|
-
queryTaskLabel:
|
|
6399
|
+
documentTaskLabel: entry.taskLabels.document,
|
|
6400
|
+
queryTaskLabel: entry.taskLabels.query,
|
|
6217
6401
|
documentTimeoutS: EMBED_TIMEOUT_S(),
|
|
6218
6402
|
queryTimeoutS: QUERY_EMBED_TIMEOUT_S()
|
|
6219
6403
|
});
|
|
@@ -6711,7 +6895,7 @@ function calibrationScope(viewer) {
|
|
|
6711
6895
|
async function scoreQueries(pool, scope, gucs, provider, queries, inCorpus) {
|
|
6712
6896
|
const out = [];
|
|
6713
6897
|
for (const query of queries) {
|
|
6714
|
-
const [vector] = await
|
|
6898
|
+
const [vector] = await embedIntent([query], {
|
|
6715
6899
|
provider,
|
|
6716
6900
|
intent: "query"
|
|
6717
6901
|
});
|
|
@@ -9113,6 +9297,7 @@ async function drain(pending, io) {
|
|
|
9113
9297
|
literals = await io.embedBatch(batch.map(([, text]) => text));
|
|
9114
9298
|
} catch (exc) {
|
|
9115
9299
|
if (io.isRetryable(exc)) throw exc;
|
|
9300
|
+
if (io.isFatal?.(exc) === true) throw exc;
|
|
9116
9301
|
if (batch.length === 1) {
|
|
9117
9302
|
await io.markFailed(failureReason(exc), batch[0][0]);
|
|
9118
9303
|
failed += 1;
|
|
@@ -9664,7 +9849,8 @@ async function buildGeneration(pool, instance, options) {
|
|
|
9664
9849
|
markFailed: (reason, chunkId) => runIngest(pool, tenant, async (c) => {
|
|
9665
9850
|
await c.query(FAIL_SQL, [reason, chunkId]);
|
|
9666
9851
|
}),
|
|
9667
|
-
isRetryable: (exc) => provider.isRetryable(exc)
|
|
9852
|
+
isRetryable: (exc) => provider.isRetryable(exc),
|
|
9853
|
+
isFatal: (exc) => provider.isFatal?.(exc) === true
|
|
9668
9854
|
});
|
|
9669
9855
|
log(`embedded ${embedded}, failed ${failed}`);
|
|
9670
9856
|
const fin = await runIngest(pool, tenant, async (c) => {
|
|
@@ -9743,6 +9929,43 @@ function pct(fraction) {
|
|
|
9743
9929
|
return `${(fraction * 100).toFixed(0)}%`;
|
|
9744
9930
|
}
|
|
9745
9931
|
/**
|
|
9932
|
+
* What to do when calibration is refused for quota — named, not left as the
|
|
9933
|
+
* vendor's sentence.
|
|
9934
|
+
*
|
|
9935
|
+
* Both of these were hit walking a real free-tier key, and they are DIFFERENT
|
|
9936
|
+
* failures with different remedies, which is why the raw error is not enough:
|
|
9937
|
+
*
|
|
9938
|
+
* generate_content …/gemini-3.7-flash, limit 5 the SYNTHESIZED door writes
|
|
9939
|
+
* one probe question per
|
|
9940
|
+
* sampled passage, and a free
|
|
9941
|
+
* key allows a handful of
|
|
9942
|
+
* generations a minute. No
|
|
9943
|
+
* amount of waiting fixes a
|
|
9944
|
+
* corpus of any size; the
|
|
9945
|
+
* answer is the zero-LLM door.
|
|
9946
|
+
* global_embed_content_requests_per_minute the EMBEDDING endpoint, which
|
|
9947
|
+
* both doors use. Transient:
|
|
9948
|
+
* an ingest immediately before
|
|
9949
|
+
* a calibration spends the same
|
|
9950
|
+
* per-minute budget.
|
|
9951
|
+
*
|
|
9952
|
+
* Product principle 4: a failure states what is wrong, why the rule exists, and
|
|
9953
|
+
* how to fix it. The vendor's message states only the first.
|
|
9954
|
+
*/
|
|
9955
|
+
/** The generation quota, which no wait resolves on a free key. */
|
|
9956
|
+
const GENERATION = /generate_content|generativelanguage\.googleapis\.com\/generate/i;
|
|
9957
|
+
/** The embedding quota, which clears on its own. */
|
|
9958
|
+
const EMBEDDING = /embed_content|global_embed/i;
|
|
9959
|
+
/**
|
|
9960
|
+
* The remedy for a quota refusal, or null when the failure is not one this
|
|
9961
|
+
* knows — in which case the vendor's own message stands, unembellished.
|
|
9962
|
+
*/
|
|
9963
|
+
function quotaRemedy(message) {
|
|
9964
|
+
if (GENERATION.test(message)) return "the SYNTHESIZED door writes one probe question per sampled passage with an LLM, and a free-tier key allows only a few generations a minute — a bigger corpus makes this worse, not better.\n fix: calibrate with zero LLM — write your in-corpus questions one per line and pass\n --queries-file PATH. The floor is measured the same way; only the questions differ,\n and the door is recorded beside the number so the two are never compared.";
|
|
9965
|
+
if (EMBEDDING.test(message)) return "the EMBEDDING endpoint is limited per minute, and both doors use it — an ingest immediately before this spends the same budget.\n fix: wait about a minute and run it again. Nothing was written; calibration only reads.";
|
|
9966
|
+
return null;
|
|
9967
|
+
}
|
|
9968
|
+
/**
|
|
9746
9969
|
* How close to the floor an answer has to be to count as marginal.
|
|
9747
9970
|
*
|
|
9748
9971
|
* 0.01 is not a tuned threshold — it is the size of the decision in this
|
|
@@ -10175,8 +10398,9 @@ function composeProvider(instance) {
|
|
|
10175
10398
|
try {
|
|
10176
10399
|
let apiKey = null;
|
|
10177
10400
|
if (providerNeedsApiKey(instance.embeddingProvider)) {
|
|
10178
|
-
|
|
10179
|
-
|
|
10401
|
+
const keyEnv = providerKeyEnv(instance.embeddingProvider) ?? "";
|
|
10402
|
+
apiKey = process.env[keyEnv] || null;
|
|
10403
|
+
if (apiKey === null) return fail$1(ENVIRONMENT, `${keyEnv} is required (the instance's embedding provider ${JSON.stringify(instance.embeddingProvider)} needs a key)\n fix: export ${keyEnv}=... and rerun`);
|
|
10180
10404
|
}
|
|
10181
10405
|
return buildShippedProvider(instance.embeddingProvider, {
|
|
10182
10406
|
apiKey,
|
|
@@ -10433,11 +10657,11 @@ async function calibrateCommand(args) {
|
|
|
10433
10657
|
let textGenerator = null;
|
|
10434
10658
|
if (queries === null) {
|
|
10435
10659
|
const apiKey = process.env["GEMINI_API_KEY"];
|
|
10436
|
-
if (apiKey === void 0 || apiKey === "") return refuse$2("bad-args", "the synthesized door needs GEMINI_API_KEY (it writes one probe question per sampled passage) — or calibrate with zero LLM: --queries-file PATH (one in-corpus question per line)");
|
|
10660
|
+
if (apiKey === void 0 || apiKey === "") return refuse$2("bad-args", "the synthesized door needs GEMINI_API_KEY (it writes one probe question per sampled passage) — or calibrate with zero LLM: --queries-file PATH (one in-corpus question per line).\n note: this is the TEXT generator, not the embedding provider. A record on `embedding.provider: openai` still embeds with OPENAI_API_KEY; only question synthesis is Gemini-only today, and --queries-file avoids it entirely");
|
|
10437
10661
|
textGenerator = new GeminiTextGenerator({ apiKey });
|
|
10438
10662
|
}
|
|
10439
10663
|
const ooc = values["ooc-file"] === void 0 ? null : parseQueriesFile(readFileSync(values["ooc-file"], "utf8"));
|
|
10440
|
-
const report = await withPool(dsn, async (pool) => runCalibration(pool, {
|
|
10664
|
+
const report = await withQuotaRemedy(async () => withPool(dsn, async (pool) => runCalibration(pool, {
|
|
10441
10665
|
tenantId: instance.tenantId,
|
|
10442
10666
|
corpusId: instance.corpusId,
|
|
10443
10667
|
viewer: await widestViewer(pool, instance),
|
|
@@ -10448,12 +10672,31 @@ async function calibrateCommand(args) {
|
|
|
10448
10672
|
oocProbes: ooc,
|
|
10449
10673
|
perNode: values["per-node"] === void 0 ? void 0 : intFlag("--per-node", values["per-node"]),
|
|
10450
10674
|
minChars: values["min-chars"] === void 0 ? void 0 : intFlag("--min-chars", values["min-chars"])
|
|
10451
|
-
}));
|
|
10675
|
+
})));
|
|
10452
10676
|
process.stdout.write(renderReport(report, GATE_PREDICATE_DIGEST) + "\n");
|
|
10453
10677
|
const advice = overlapAdvice(report);
|
|
10454
10678
|
if (advice !== null) process.stdout.write(advice);
|
|
10455
10679
|
return 0;
|
|
10456
10680
|
}
|
|
10681
|
+
/**
|
|
10682
|
+
* Run a calibration, turning a quota refusal into the remedy for THAT quota.
|
|
10683
|
+
*
|
|
10684
|
+
* Both failures reach here as the vendor's own sentence, which states what is
|
|
10685
|
+
* wrong and neither why nor how to fix it — and the two need opposite answers
|
|
10686
|
+
* (change door vs wait a minute). Anything `quotaRemedy` does not recognise is
|
|
10687
|
+
* re-thrown untouched: inventing advice for an error nobody has read is worse
|
|
10688
|
+
* than passing the vendor's through.
|
|
10689
|
+
*/
|
|
10690
|
+
async function withQuotaRemedy(op) {
|
|
10691
|
+
try {
|
|
10692
|
+
return await op();
|
|
10693
|
+
} catch (exc) {
|
|
10694
|
+
const message = exc instanceof Error ? exc.message : String(exc);
|
|
10695
|
+
const remedy = quotaRemedy(message);
|
|
10696
|
+
if (remedy === null) throw exc;
|
|
10697
|
+
throw Object.assign(/* @__PURE__ */ new Error(`${message}\n why: ${remedy}`), { slug: "ksor-calibrate-quota" });
|
|
10698
|
+
}
|
|
10699
|
+
}
|
|
10457
10700
|
/** How many days of traffic one --check reads. Bounded so a busy record cannot make it expensive. */
|
|
10458
10701
|
const DRIFT_DEFAULT_DAYS = 30;
|
|
10459
10702
|
/**
|
|
@@ -10796,7 +11039,7 @@ async function runContentCli(argv) {
|
|
|
10796
11039
|
}
|
|
10797
11040
|
}
|
|
10798
11041
|
//#endregion
|
|
10799
|
-
//#region ../content/dist/record-
|
|
11042
|
+
//#region ../content/dist/record-Cxw0SUG3.mjs
|
|
10800
11043
|
/**
|
|
10801
11044
|
* May `surface` publish `doc` at instant `at`? `drafts` is the build's drafts switch,
|
|
10802
11045
|
* which admits drafts to HUMAN surfaces only.
|
|
@@ -10842,6 +11085,7 @@ const lockSchema = z.object({
|
|
|
10842
11085
|
drafts: z.enum(["hidden", "shown"]),
|
|
10843
11086
|
instance_sha256: hex64,
|
|
10844
11087
|
policy_sha256: hex64,
|
|
11088
|
+
people_sha256: hex64,
|
|
10845
11089
|
ledger_sha256: hex64,
|
|
10846
11090
|
ledger_entries: z.array(z.object({
|
|
10847
11091
|
id: z.string().min(1),
|
|
@@ -10914,6 +11158,7 @@ function buildIdOf(inputs) {
|
|
|
10914
11158
|
indexes: [...inputs.indexes].map((i) => [i.path, i.sha256]).sort((a, b) => compare(a[0] ?? "", b[0] ?? "")),
|
|
10915
11159
|
instance_sha256: inputs.instance_sha256,
|
|
10916
11160
|
policy_sha256: inputs.policy_sha256,
|
|
11161
|
+
people_sha256: inputs.people_sha256,
|
|
10917
11162
|
ledger_sha256: inputs.ledger_sha256,
|
|
10918
11163
|
ksor_version: inputs.ksor_version,
|
|
10919
11164
|
drafts: inputs.drafts
|
|
@@ -10960,6 +11205,7 @@ function composeLock(input) {
|
|
|
10960
11205
|
}));
|
|
10961
11206
|
const instance_sha256 = sha256Hex(input.instanceText);
|
|
10962
11207
|
const policy_sha256 = sha256Hex(input.policyText);
|
|
11208
|
+
const people_sha256 = sha256Hex(input.peopleText ?? "");
|
|
10963
11209
|
const ledger_sha256 = sha256Hex(input.ledgerText ?? "");
|
|
10964
11210
|
return {
|
|
10965
11211
|
format: 1,
|
|
@@ -10970,6 +11216,7 @@ function composeLock(input) {
|
|
|
10970
11216
|
indexes,
|
|
10971
11217
|
instance_sha256,
|
|
10972
11218
|
policy_sha256,
|
|
11219
|
+
people_sha256,
|
|
10973
11220
|
ledger_sha256,
|
|
10974
11221
|
ksor_version: input.ksorVersion,
|
|
10975
11222
|
drafts: input.drafts
|
|
@@ -10982,6 +11229,7 @@ function composeLock(input) {
|
|
|
10982
11229
|
drafts: input.drafts,
|
|
10983
11230
|
instance_sha256,
|
|
10984
11231
|
policy_sha256,
|
|
11232
|
+
people_sha256,
|
|
10985
11233
|
ledger_sha256,
|
|
10986
11234
|
ledger_entries: [...input.ledgerEntries].sort((a, b) => compare(a.id, b.id)),
|
|
10987
11235
|
audiences: {
|
|
@@ -11144,6 +11392,7 @@ const INPUTS = [
|
|
|
11144
11392
|
"knowledge",
|
|
11145
11393
|
"instance.md",
|
|
11146
11394
|
".ksor/governance.yaml",
|
|
11395
|
+
".ksor/people.yaml",
|
|
11147
11396
|
".ksor/takedowns.yaml"
|
|
11148
11397
|
];
|
|
11149
11398
|
/**
|
|
@@ -11154,7 +11403,11 @@ const INPUTS = [
|
|
|
11154
11403
|
* clone is invisible to every surface, so the build says so by name.
|
|
11155
11404
|
*/
|
|
11156
11405
|
function ignoredGovernance(root) {
|
|
11157
|
-
const paths = [
|
|
11406
|
+
const paths = [
|
|
11407
|
+
".ksor/governance.yaml",
|
|
11408
|
+
".ksor/people.yaml",
|
|
11409
|
+
".ksor/takedowns.yaml"
|
|
11410
|
+
].filter((rel) => existsSync(join(root, rel)));
|
|
11158
11411
|
if (paths.length === 0) return [];
|
|
11159
11412
|
const out = git(root, [
|
|
11160
11413
|
"check-ignore",
|
|
@@ -11344,8 +11597,8 @@ function runBuild(args, cwd, io, options) {
|
|
|
11344
11597
|
const facts = gitFacts(root);
|
|
11345
11598
|
if (facts.repository && facts.historicLedger === null && !parsed.allowUnverifiable) return refuse$1(io, "ksor-ledger-unverifiable", facts.historyUnreadable === "shallow" ? "this is a shallow clone: the takedown ledger is append-only, and without history a deleted entry cannot be told from one that never existed" : "git could not read the takedown ledger's history (`git log -- .ksor/takedowns.yaml` failed, and this is not a shallow clone): the ledger is append-only, and without history a deleted entry cannot be told from one that never existed", facts.historyUnreadable === "shallow" ? "fetch full history (`git fetch --unshallow`; in CI, `fetch-depth: 0`), or pass --allow-unverifiable-ledger to build anyway" : "check that `git log` works in this checkout, or pass --allow-unverifiable-ledger to build anyway");
|
|
11346
11599
|
const ignored = facts.repository ? ignoredGovernance(root) : [];
|
|
11347
|
-
if (ignored.length > 0) return refuse$1(io, "ksor-governance-ignored", `git ignores ${ignored.join(" and ")}, so ${ignored.length === 1 ? "it is" : "they are"} in no commit — the policy and the takedown ledger ARE the record, and a clone (your CI, your deploy) would build without ${ignored.length === 1 ? "it" : "them"}`, "un-ignore them in .gitignore — the directory form `.ksor/` cannot be negated, so use `.ksor/*` plus `!.ksor/governance.yaml` and `!.ksor/takedowns.yaml` — then commit them (`ksor migrate` offers that edit)");
|
|
11348
|
-
if (parsed.strict && facts.dirty) return refuse$1(io, "ksor-build-dirty", facts.repository ? "an input (knowledge/, instance.md, .ksor/governance.yaml, .ksor/takedowns.yaml) differs from its last commit, and --strict stamps only committed content" : "the record is not in a git repository, so no input is committed", "commit the inputs and rebuild, or drop --strict to stamp a dirty build (the lock says `dirty: true`)");
|
|
11600
|
+
if (ignored.length > 0) return refuse$1(io, "ksor-governance-ignored", `git ignores ${ignored.join(" and ")}, so ${ignored.length === 1 ? "it is" : "they are"} in no commit — the policy and the takedown ledger ARE the record, and a clone (your CI, your deploy) would build without ${ignored.length === 1 ? "it" : "them"}`, "un-ignore them in .gitignore — the directory form `.ksor/` cannot be negated, so use `.ksor/*` plus `!.ksor/governance.yaml`, `!.ksor/people.yaml` and `!.ksor/takedowns.yaml` — then commit them (`ksor migrate` offers that edit)");
|
|
11601
|
+
if (parsed.strict && facts.dirty) return refuse$1(io, "ksor-build-dirty", facts.repository ? "an input (knowledge/, instance.md, .ksor/governance.yaml, .ksor/people.yaml, .ksor/takedowns.yaml) differs from its last commit, and --strict stamps only committed content" : "the record is not in a git repository, so no input is committed", "commit the inputs and rebuild, or drop --strict to stamp a dirty build (the lock says `dirty: true`)");
|
|
11349
11602
|
const baselines = [];
|
|
11350
11603
|
if (facts.historicLedger !== null) baselines.push({
|
|
11351
11604
|
source: "git history",
|
|
@@ -11386,6 +11639,7 @@ function runBuild(args, cwd, io, options) {
|
|
|
11386
11639
|
drafts: options.drafts,
|
|
11387
11640
|
instanceText: record.files.get("instance.md") ?? "",
|
|
11388
11641
|
policyText: record.files.get(".ksor/governance.yaml") ?? "",
|
|
11642
|
+
peopleText: record.files.get(".ksor/people.yaml") ?? null,
|
|
11389
11643
|
ledgerText,
|
|
11390
11644
|
ledgerEntries: result.ledgerEntries,
|
|
11391
11645
|
audiences: result.policy?.audiences ?? [],
|
|
@@ -13072,11 +13326,13 @@ const BARE_DOTKSOR_PATTERNS = /* @__PURE__ */ new Set([
|
|
|
13072
13326
|
]);
|
|
13073
13327
|
const GOVERNANCE_IGNORE_BLOCK = [
|
|
13074
13328
|
"# ksor's working directory — build output and scratch, never the record.",
|
|
13075
|
-
"# The
|
|
13076
|
-
"#
|
|
13077
|
-
"# cannot be negated, so the
|
|
13329
|
+
"# The governance files inside it ARE the record (the policy, the takedown",
|
|
13330
|
+
"# ledger, and the phone book the site publishes names from) and are",
|
|
13331
|
+
"# un-ignored by name: the directory form `.ksor/` cannot be negated, so the",
|
|
13332
|
+
"# glob is `.ksor/*`.",
|
|
13078
13333
|
".ksor/*",
|
|
13079
13334
|
"!.ksor/governance.yaml",
|
|
13335
|
+
"!.ksor/people.yaml",
|
|
13080
13336
|
"!.ksor/takedowns.yaml"
|
|
13081
13337
|
];
|
|
13082
13338
|
/**
|
|
@@ -13350,7 +13606,8 @@ function isEnvironmentError(value) {
|
|
|
13350
13606
|
const EMITTED_NAMES = /* @__PURE__ */ new Map([
|
|
13351
13607
|
["gitignore", ".gitignore"],
|
|
13352
13608
|
["env.example", ".env.example"],
|
|
13353
|
-
["dockerignore", ".dockerignore"]
|
|
13609
|
+
["dockerignore", ".dockerignore"],
|
|
13610
|
+
["mcp.json", ".mcp.json"]
|
|
13354
13611
|
]);
|
|
13355
13612
|
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
13356
13613
|
".md",
|
|
@@ -13712,7 +13969,9 @@ serves nothing. Runs in this process and holds it; SIGTERM/SIGINT drains.
|
|
|
13712
13969
|
Configured by environment — .env beside the record is read automatically:
|
|
13713
13970
|
|
|
13714
13971
|
<database.dsn_env> the Postgres DSN, under the NAME instance.md gives
|
|
13715
|
-
|
|
13972
|
+
<provider key> iff the instance's embedding provider needs one:
|
|
13973
|
+
GEMINI_API_KEY for gemini, OPENAI_API_KEY for openai.
|
|
13974
|
+
The refusal names the variable your record needs
|
|
13716
13975
|
KSOR_AUTH disabled-local (loopback dev) | disabled-public.
|
|
13717
13976
|
Serve REFUSES to boot with neither this nor a
|
|
13718
13977
|
configured SSO door — never open by accident
|