@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 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-DxfT2qs3.mjs
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
- const GATEWAY_VERSION = process.env["KSOR_GATEWAY_VERSION"] || "0.0.0";
2911
- async function main$1() {
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"), GATEWAY_VERSION));
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-3cajARmF.mjs
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
- process.env["KSOR_GATEWAY_VERSION"] = pkg.version;
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.4",
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`.** Add the serve blocks to the frontmatter
56
- (`pnpm check` accepts them; the kernel validates their values):
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 # the NAME of the env var holding the DSN — never the DSN itself
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
- 2. **Provision Postgres** with the `vector` extension (`CREATE EXTENSION vector`),
70
- e.g. a Neon database. Export the DSN under the name `dsn_env` chose, plus the
71
- provider key:
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
- export KSOR_DB_URL='postgresql://…' # the var instance.md names
75
- export GEMINI_API_KEY='…' # the embedding provider key
96
+ pnpm up # schema grant → ingest → serve
76
97
  ```
77
98
 
78
- 3. **Apply the schema:** `pnpm schema` (creates tables, indexes, and the
79
- ingest role).
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
- 4. **Authorize ingest:** `pnpm grant`writes the one row row-level security
82
- requires before any write to this corpus is allowed. Idempotent, and
83
- `pnpm exec ksor grant --instance instance.md --revoke` withdraws it.
103
+ **`pnpm up` ingests every timebut 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
- This is a separate, named act on purpose: applying the schema and
86
- authorizing writes are different decisions, and a schema step that granted
87
- itself write access would make the tool its own authorizer. Apply the schema
88
- and ingest as the SAME Postgres login (the ingest role is granted to whoever
89
- applied the DDL).
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
- 5. **Ingest:** `pnpm ingest` embeds `knowledge/` into a fresh generation and
92
- activates it (`--flip`). Safe to re-run (see the generation model below).
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
- 6. **Calibrate the abstention floor** (only if `vector_floor: uncalibrated`):
95
- `pnpm exec ksor calibrate --instance instance.md` prints a recommended
96
- `vector_floor` measurement; paste the number into `instance.md`'s `retrieval:`
97
- block and re-run. A corpus that declares no `retrieval:` block serves with the
98
- gate OFF (honest: it will not refuse out-of-corpus questions).
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
- 7. **Serve:** `pnpm serve`.
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
- export KSOR_DB_URL='postgresql://…' # the DSN var your instance.md names
34
- export GEMINI_API_KEY='…' # the embedding provider key
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
- One setup step comes before this adding the `database:`/`embedding:` blocks
42
- to `instance.md` and there is more to know about the generation model and the
43
- fail-closed security posture. `AGENTS.md` "Serving to agents" is the
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
- | `.gitignore` | keeps build output, `node_modules/`, and `.env*` out of the record's history. |
71
- | `package.json` | the `pnpm dev` / `pnpm build` / `pnpm check` / `pnpm schema` / `pnpm grant` / `pnpm ingest` / `pnpm serve` commands, the pinned `@panaversity/ksor` tool, and the pnpm version this project pins. |
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
@@ -12,5 +12,6 @@ system/site/.staged-knowledge/
12
12
 
13
13
  # secrets never enter the record — system/ is their future home (serve)
14
14
  .env*
15
+ !.env.example
15
16
 
16
17
  .DS_Store
@@ -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