@panaversity/ksor 0.0.4 → 0.0.5
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 +32 -0
- package/dist/cli.mjs +35 -8
- package/package.json +1 -1
- package/templates/scaffold/AGENTS.md +70 -32
- package/templates/scaffold/README.md +19 -11
- package/templates/scaffold/env.example +23 -0
- package/templates/scaffold/gitignore +1 -0
- package/templates/scaffold/package.json +1 -0
- package/templates/scaffold/vercel.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @panaversity/ksor
|
|
2
2
|
|
|
3
|
+
## 0.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 995f002: feat: scaffolded projects ship a commented `.env.example` naming every
|
|
8
|
+
variable the agent surface needs — the DSN variable, the provider key, and
|
|
9
|
+
`KSOR_AUTH_DISABLED=1`, which a local run requires because `ksor serve` refuses
|
|
10
|
+
to boot unauthenticated. Copy it to `.env` and it is read automatically.
|
|
11
|
+
|
|
12
|
+
feat: standing up the agent surface is one command and one config block.
|
|
13
|
+
`ksor` now reads `./.env` automatically (Node-native, no dependency; a real
|
|
14
|
+
environment variable still wins), scaffolded projects get `pnpm up` —
|
|
15
|
+
schema → grant → ingest → serve — and `ksor schema --apply` is re-runnable
|
|
16
|
+
instead of failing on an already-provisioned database, so the whole sequence
|
|
17
|
+
is safe to repeat and doubles as the refresh after editing `knowledge/`.
|
|
18
|
+
|
|
19
|
+
fix: a scaffolded project deploys on the first try. The shipped `vercel.json`
|
|
20
|
+
pinned `--frozen-lockfile`, so an adopter's first Vercel import failed with
|
|
21
|
+
`ERR_PNPM_OUTDATED_LOCKFILE` — the scaffold declares a root dependency whose
|
|
22
|
+
stamped version the committed lockfile cannot record.
|
|
23
|
+
|
|
24
|
+
fix: the serve runbook no longer tells first-timers to declare
|
|
25
|
+
`retrieval.vector_floor: uncalibrated` before serving, which made every request
|
|
26
|
+
refuse until a floor was measured. Configuring the record needs one `database:`
|
|
27
|
+
block; the abstention gate is turned on deliberately, after it serves.
|
|
28
|
+
|
|
29
|
+
- 4e84cdf: fix: `ksor serve` reports its real version to MCP clients. In 0.0.4 every
|
|
30
|
+
client saw `serverInfo.version` of `0.0.0`: the gateway read the version from
|
|
31
|
+
an environment variable at module scope, and the CLI's static import evaluated
|
|
32
|
+
that module before the CLI could set the variable. The version now travels as
|
|
33
|
+
an argument, and a test drives the bundled binary to assert it.
|
|
34
|
+
|
|
3
35
|
## 0.0.4
|
|
4
36
|
|
|
5
37
|
### Patch Changes
|
package/dist/cli.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
16
16
|
import { parseArgs } from "node:util";
|
|
17
17
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
18
18
|
import { spawnSync } from "node:child_process";
|
|
19
|
-
//#region ../content-gateway/dist/main-
|
|
19
|
+
//#region ../content-gateway/dist/main-MwJTMEjf.mjs
|
|
20
20
|
/**
|
|
21
21
|
* The pool checkout timed out — never retried: under saturation a retry is
|
|
22
22
|
* a thundering herd aimed at the component already drowning.
|
|
@@ -2907,10 +2907,17 @@ async function runHttp(composition) {
|
|
|
2907
2907
|
* released `ksor serve` announce 0.0.0 to every client (verified live by
|
|
2908
2908
|
* probe; review 2026-08-20).
|
|
2909
2909
|
*/
|
|
2910
|
-
|
|
2911
|
-
|
|
2910
|
+
/**
|
|
2911
|
+
* Fallback only. The bundling CLI knows the PUBLISHED version and passes it to
|
|
2912
|
+
* `main(version)`; a module-level env read cannot work here, because this
|
|
2913
|
+
* module is evaluated by the CLI's static import BEFORE the CLI's own main()
|
|
2914
|
+
* body could set an env var (ESM evaluation order — the reason the previous
|
|
2915
|
+
* attempt at this shipped inert in 0.0.4).
|
|
2916
|
+
*/
|
|
2917
|
+
const GATEWAY_VERSION = "0.0.0";
|
|
2918
|
+
async function main$1(version = GATEWAY_VERSION) {
|
|
2912
2919
|
try {
|
|
2913
|
-
await runHttp(await compose(path.resolve(process.env["KSOR_INSTANCE"] ?? "instance.md"),
|
|
2920
|
+
await runHttp(await compose(path.resolve(process.env["KSOR_INSTANCE"] ?? "instance.md"), version));
|
|
2914
2921
|
} catch (error) {
|
|
2915
2922
|
const message = error instanceof Error ? error.message : String(error);
|
|
2916
2923
|
console.error(`error: ${message}`);
|
|
@@ -3043,7 +3050,7 @@ async function runScopedIn(pool, gucs, op, options = {}) {
|
|
|
3043
3050
|
throw lastError;
|
|
3044
3051
|
}
|
|
3045
3052
|
//#endregion
|
|
3046
|
-
//#region ../content/dist/commands-
|
|
3053
|
+
//#region ../content/dist/commands-D8yo1t8o.mjs
|
|
3047
3054
|
/**
|
|
3048
3055
|
* EVAL-LOCKED constants, quarried verbatim from the oracle
|
|
3049
3056
|
* (sor-agentfactory @ b554f91, config.py) — changing any of these is a
|
|
@@ -6148,6 +6155,14 @@ async function schemaCommand(args) {
|
|
|
6148
6155
|
if (instance === null) return fail$1(REFUSED, "schema: --apply needs --instance (the instance names the DSN env var; --dim alone names no database)");
|
|
6149
6156
|
const dsn = resolveDsn(instance);
|
|
6150
6157
|
if (typeof dsn === "number") return dsn;
|
|
6158
|
+
const already = await withPool(dsn, async (pool) => {
|
|
6159
|
+
const r = await pool.query("SELECT schema_version FROM schema_meta LIMIT 1").catch(() => null);
|
|
6160
|
+
return r === null ? null : String(r.rows[0]?.schema_version ?? "");
|
|
6161
|
+
});
|
|
6162
|
+
if (already !== null) {
|
|
6163
|
+
process.stdout.write(`schema: already applied (schema_meta ${already}) — nothing to do; drop the database to start over\n`);
|
|
6164
|
+
return 0;
|
|
6165
|
+
}
|
|
6151
6166
|
await withPool(dsn, (pool) => applySchema(pool, dim));
|
|
6152
6167
|
process.stdout.write(`schema: applied at dim ${dim} (database named by ${instance.dsnEnv})\n`);
|
|
6153
6168
|
return 0;
|
|
@@ -6363,7 +6378,7 @@ function isEnvironmentError(value) {
|
|
|
6363
6378
|
}
|
|
6364
6379
|
//#endregion
|
|
6365
6380
|
//#region src/init/materialize.ts
|
|
6366
|
-
const EMITTED_NAMES = /* @__PURE__ */ new Map([["gitignore", ".gitignore"]]);
|
|
6381
|
+
const EMITTED_NAMES = /* @__PURE__ */ new Map([["gitignore", ".gitignore"], ["env.example", ".env.example"]]);
|
|
6367
6382
|
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6368
6383
|
".md",
|
|
6369
6384
|
".json",
|
|
@@ -6674,7 +6689,20 @@ Verbs (dev and build exit 2 until they ship; the rest are implemented):\n init
|
|
|
6674
6689
|
|
|
6675
6690
|
Exit codes: 1 refused · 2 designed but not implemented · 3 environment
|
|
6676
6691
|
Docs: node_modules/${pkg.name}/docs · ${pkg.homepage}\n`;
|
|
6692
|
+
/**
|
|
6693
|
+
* Load `./.env` when one exists, so the served rung's variables (the DSN the
|
|
6694
|
+
* instance names, the provider key) live in a file the adopter already
|
|
6695
|
+
* gitignores instead of being exported by hand into every shell. Node does
|
|
6696
|
+
* this natively — no dependency — and a REAL environment variable still wins
|
|
6697
|
+
* over the file, so CI and production overrides behave as they should.
|
|
6698
|
+
*/
|
|
6699
|
+
function loadDotEnv() {
|
|
6700
|
+
try {
|
|
6701
|
+
process.loadEnvFile();
|
|
6702
|
+
} catch {}
|
|
6703
|
+
}
|
|
6677
6704
|
async function main(args) {
|
|
6705
|
+
loadDotEnv();
|
|
6678
6706
|
if (args.includes("--help") || args.includes("-h")) {
|
|
6679
6707
|
process.stdout.write(usage);
|
|
6680
6708
|
return 0;
|
|
@@ -6706,8 +6734,7 @@ async function main(args) {
|
|
|
6706
6734
|
return exitCodes.refused;
|
|
6707
6735
|
}
|
|
6708
6736
|
if (instance !== void 0) process.env["KSOR_INSTANCE"] = instance;
|
|
6709
|
-
|
|
6710
|
-
await main$1();
|
|
6737
|
+
await main$1(pkg.version);
|
|
6711
6738
|
return 0;
|
|
6712
6739
|
}
|
|
6713
6740
|
if (verb === "ingest" || verb === "schema" || verb === "grant" || verb === "calibrate" || verb === "gc") return runContentCli(args.slice(args.indexOf(verb)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panaversity/ksor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Knowledge System of Record — the authoritative, governed source of knowledge that humans and AI agents operate from. Name reserved; implementation in progress.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abstention",
|
|
@@ -52,52 +52,90 @@ pnpm check # the format checker — run before handing off any knowledge c
|
|
|
52
52
|
with honest abstention. It is the climbed rung — not required for `pnpm dev`.
|
|
53
53
|
Stand it up in this order (each step's errors explain how to fix themselves):
|
|
54
54
|
|
|
55
|
-
1. **Configure `instance.md`.**
|
|
56
|
-
|
|
55
|
+
1. **Configure `instance.md`.** One block is required — the name of the
|
|
56
|
+
environment variable holding your DSN (never the DSN itself):
|
|
57
57
|
|
|
58
58
|
```yaml
|
|
59
59
|
database:
|
|
60
|
-
dsn_env: KSOR_DB_URL
|
|
61
|
-
embedding:
|
|
62
|
-
provider: gemini # default; the seam, not the vendor, is the contract
|
|
63
|
-
model: gemini-embedding-001
|
|
64
|
-
dim: 1536 # ≤ 2000 for the pgvector HNSW index
|
|
65
|
-
retrieval:
|
|
66
|
-
vector_floor: uncalibrated # see step 6; `uncalibrated` REFUSES every serve until you paste a number
|
|
60
|
+
dsn_env: KSOR_DB_URL
|
|
67
61
|
```
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
That is enough. `embedding:` is optional and already defaults to
|
|
64
|
+
`provider: gemini`, `model: gemini-embedding-001`, `dim: 1536`; write it out
|
|
65
|
+
only to pin the space explicitly or to change it — and note that model and
|
|
66
|
+
dim are the PERSISTED identity of the embedding space, so changing either
|
|
67
|
+
later means re-embedding the whole corpus. Keep `dim` at or below 2000: the
|
|
68
|
+
pgvector HNSW index refuses more, and `gemini-embedding-001` can emit 3072.
|
|
69
|
+
|
|
70
|
+
Leave `retrieval:` out for now — the gate is off and the server says so.
|
|
71
|
+
Turning it on is step 4, AFTER the record is serving.
|
|
72
|
+
|
|
73
|
+
2. **Copy `.env.example` to `.env`** and fill it in — `ksor` reads it
|
|
74
|
+
automatically, so nothing needs exporting, and `.env` is already gitignored.
|
|
75
|
+
A real environment variable still wins over the file, so CI and production
|
|
76
|
+
overrides behave normally.
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
cp .env.example .env
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Three values matter:
|
|
83
|
+
|
|
84
|
+
- `KSOR_DB_URL` — the Postgres store named by `instance.md`'s `dsn_env`. It
|
|
85
|
+
needs the pgvector extension: `CREATE EXTENSION vector;`
|
|
86
|
+
- `GEMINI_API_KEY` — the embedding provider key.
|
|
87
|
+
- `KSOR_AUTH_DISABLED=1` — **required for a local run.** `ksor serve`
|
|
88
|
+
refuses to boot unauthenticated without it, deliberately, so a server is
|
|
89
|
+
never left open by accident. It binds loopback, where auth off is the
|
|
90
|
+
intended dev shape. A PUBLIC deployment configures the SSO door instead —
|
|
91
|
+
see the comments in `.env.example` and "Serving safely" below.
|
|
92
|
+
|
|
93
|
+
3. **Bring it up — one command:**
|
|
72
94
|
|
|
73
95
|
```sh
|
|
74
|
-
|
|
75
|
-
export GEMINI_API_KEY='…' # the embedding provider key
|
|
96
|
+
pnpm up # schema → grant → ingest → serve
|
|
76
97
|
```
|
|
77
98
|
|
|
78
|
-
|
|
79
|
-
|
|
99
|
+
Every step is re-runnable, so this is also how you **refresh after editing
|
|
100
|
+
`knowledge/`**: an applied schema reports "already applied", an existing
|
|
101
|
+
grant reports "already granted", and ingest builds a fresh generation.
|
|
80
102
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
**`pnpm up` ingests every time — but it re-EMBEDS nothing that has not
|
|
104
|
+
changed.** Chunks carry forward by content hash, so a rerun on an untouched
|
|
105
|
+
corpus makes zero provider calls (`embedded 0, carried N` in the output).
|
|
106
|
+
What it does spend is a generation: each run creates and activates a new one,
|
|
107
|
+
and they accumulate. So:
|
|
84
108
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
| You want to | Run |
|
|
110
|
+
| -------------------------------- | ------------------------------------------ |
|
|
111
|
+
| set up, or refresh after an edit | `pnpm up` |
|
|
112
|
+
| just restart the server | `pnpm serve` — no new generation |
|
|
113
|
+
| reap superseded generations | `pnpm exec ksor gc --instance instance.md` |
|
|
90
114
|
|
|
91
|
-
|
|
92
|
-
|
|
115
|
+
Run the steps individually (`pnpm schema`, `pnpm grant`, `pnpm ingest`)
|
|
116
|
+
when the acts belong to different people — a DBA holding the credentials
|
|
117
|
+
that authorize ingest, for instance.
|
|
93
118
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
119
|
+
4. **Turn the abstention gate on — deliberately, once it serves.** This is the
|
|
120
|
+
step that makes "not in this corpus" a real answer, and it is measured, never
|
|
121
|
+
guessed:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
pnpm exec ksor calibrate --instance instance.md
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
It prints a recommended `vector_floor` for THIS corpus in THIS embedding
|
|
128
|
+
space. Paste the number in and restart:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
retrieval:
|
|
132
|
+
vector_floor: 0.55 # measured by ksor calibrate on <date>
|
|
133
|
+
```
|
|
99
134
|
|
|
100
|
-
|
|
135
|
+
Never copy a floor from another corpus — recalibrate, and record the
|
|
136
|
+
measurement beside the number. Writing `vector_floor: uncalibrated` declares
|
|
137
|
+
the intent to gate WITHOUT a measurement, and every serve refuses until a
|
|
138
|
+
number replaces it; that is the fail-closed posture, not a starting point.
|
|
101
139
|
|
|
102
140
|
```sh
|
|
103
141
|
pnpm schema # apply the DDL (once)
|
|
@@ -30,17 +30,24 @@ Postgres store (with pgvector) and an embedding provider key, so it is not
|
|
|
30
30
|
part of `pnpm dev`. The ordered path is:
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
pnpm schema # apply the database schema (once)
|
|
36
|
-
pnpm grant # authorize ingest for this corpus (once)
|
|
37
|
-
pnpm ingest # embed knowledge/ into a generation and activate it
|
|
38
|
-
pnpm serve # run the MCP server over the record
|
|
33
|
+
cp .env.example .env # fill in KSOR_DB_URL, GEMINI_API_KEY, KSOR_AUTH_DISABLED=1
|
|
34
|
+
pnpm up # schema → grant → ingest → serve
|
|
39
35
|
```
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
`ksor` reads `.env` automatically — nothing to export. `KSOR_AUTH_DISABLED=1`
|
|
38
|
+
is required for a local run: serve refuses to boot unauthenticated on purpose,
|
|
39
|
+
so a server is never open by accident.
|
|
40
|
+
|
|
41
|
+
Add one block to `instance.md` first — `database: { dsn_env: KSOR_DB_URL }`,
|
|
42
|
+
the NAME of the variable, never the DSN. That is the whole required config:
|
|
43
|
+
`embedding:` already defaults to Gemini at 1536 dimensions, and leaving
|
|
44
|
+
`retrieval:` out starts you with the abstention gate off and honest about it
|
|
45
|
+
(turn it on afterwards with `ksor calibrate`, once the record is serving).
|
|
46
|
+
|
|
47
|
+
`pnpm up` is re-runnable — it is also how you refresh after editing
|
|
48
|
+
`knowledge/`. It re-ingests each time but re-embeds only what changed, so an
|
|
49
|
+
untouched corpus costs no provider calls; to simply restart the server without
|
|
50
|
+
building a new generation, run `pnpm serve` on its own. `AGENTS.md` → "Serving to agents" is the
|
|
44
51
|
full runbook; your coding agent reads it first. `pnpm serve` binds loopback
|
|
45
52
|
with auth off for local use; a public bind fails closed unless auth is
|
|
46
53
|
configured. Any other operation is `pnpm exec ksor <verb>`.
|
|
@@ -67,8 +74,9 @@ different coding agent's way of finding the same working contract.
|
|
|
67
74
|
| `.gemini/settings.json` | points Gemini CLI at `AGENTS.md`; Gemini does not read that filename on its own. |
|
|
68
75
|
| `.github/workflows/validate.yml` | your CI: runs the same checker on every pull request and push to main. |
|
|
69
76
|
| `.gitattributes` | markdown is checked out byte-stable on every platform, so the same commit hashes the same everywhere. |
|
|
70
|
-
| `.
|
|
71
|
-
| `
|
|
77
|
+
| `.env.example` | the variables the served rung needs; copy to `.env` (gitignored) and fill in. |
|
|
78
|
+
| `.gitignore` | keeps build output, `node_modules/`, and `.env` out of the record's history. |
|
|
79
|
+
| `package.json` | the `pnpm dev` / `pnpm build` / `pnpm check` commands and the served rung's `pnpm up` (schema → grant → ingest → serve, or run them separately), the pinned `@panaversity/ksor` tool, and the pnpm version this project pins. |
|
|
72
80
|
| `pnpm-workspace.yaml` | where the workspace looks for code (`system/site`, plus reserved `system/gateways/*` and `system/packages/*`), and the supply-chain policy for installs. |
|
|
73
81
|
| `pnpm-lock.yaml` | the exact dependency versions — the reason two machines build the same site. |
|
|
74
82
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Copy to .env — ksor reads it automatically, and .env is gitignored.
|
|
2
|
+
# A real environment variable always wins over this file, so CI and production
|
|
3
|
+
# keep their own values.
|
|
4
|
+
|
|
5
|
+
# The Postgres store, named by instance.md's database.dsn_env.
|
|
6
|
+
# Needs the pgvector extension: CREATE EXTENSION vector;
|
|
7
|
+
KSOR_DB_URL=postgresql://user:password@host:5432/dbname
|
|
8
|
+
|
|
9
|
+
# The embedding provider key. instance.md defaults to gemini-embedding-001.
|
|
10
|
+
GEMINI_API_KEY=
|
|
11
|
+
|
|
12
|
+
# Local development posture. `ksor serve` REFUSES to boot unauthenticated
|
|
13
|
+
# without this — deliberately, so a server is never open by accident. It binds
|
|
14
|
+
# loopback, where auth off is the intended dev shape.
|
|
15
|
+
#
|
|
16
|
+
# For a PUBLIC deployment, delete this line and configure the SSO door instead:
|
|
17
|
+
# KSOR_SSO_URL=https://your-sso.example.com
|
|
18
|
+
# KSOR_MCP_RESOURCE_URL=https://your-host.example.com/mcp
|
|
19
|
+
# KSOR_JWT_ALLOWED_AUDIENCES=https://your-host.example.com/mcp
|
|
20
|
+
# Serving a public bind with auth off additionally requires
|
|
21
|
+
# KSOR_ALLOW_PUBLIC_UNAUTHENTICATED=1, which serves your whole record to anyone
|
|
22
|
+
# who can reach the port.
|
|
23
|
+
KSOR_AUTH_DISABLED=1
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"dev": "pnpm -C system/site dev",
|
|
8
8
|
"build": "pnpm -C system/site build",
|
|
9
9
|
"check": "node .agents/skills/format-checker/check.mjs",
|
|
10
|
+
"up": "pnpm schema && pnpm grant && pnpm ingest && pnpm serve",
|
|
10
11
|
"schema": "ksor schema --instance instance.md --apply",
|
|
11
12
|
"grant": "ksor grant --instance instance.md",
|
|
12
13
|
"ingest": "ksor ingest --instance instance.md --knowledge knowledge --flip",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://openapi.vercel.sh/vercel.json",
|
|
3
3
|
"framework": null,
|
|
4
|
-
"installCommand": "pnpm install --frozen-lockfile",
|
|
4
|
+
"installCommand": "pnpm install --no-frozen-lockfile",
|
|
5
5
|
"buildCommand": "pnpm build",
|
|
6
6
|
"outputDirectory": "system/site/out",
|
|
7
7
|
"trailingSlash": true
|