@panaversity/ksor 0.0.50 → 0.0.52

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.
@@ -22,22 +22,27 @@ numbers are exact for every record:
22
22
 
23
23
  | | chars | ~tokens | |
24
24
  | ------------------------------ | ------ | ------- | ------------------- |
25
- | all three, as transmitted | 16,734 | 4,184 | **always resident** |
26
- | `search` alone | 7,932 | 1,983 | always resident |
27
- | `outline` alone | 3,332 | 833 | always resident |
28
- | `read` alone | 5,466 | 1,367 | always resident |
29
- | `outline` + `read`, if deleted | 8,798 | 2,200 | the saving below |
25
+ | all three, as transmitted | 17,394 | 4,349 | **always resident** |
26
+ | `search` alone | 8,152 | 2,038 | always resident |
27
+ | `outline` alone | 3,552 | 888 | always resident |
28
+ | `read` alone | 5,686 | 1,422 | always resident |
29
+ | `outline` + `read`, if deleted | 9,238 | 2,310 | the saving below |
30
30
 
31
31
  **Two measurements, so read the first row apart from the rest.** Each tool's
32
32
  row is the JSON of that tool's own object; the first row is the JSON of the
33
33
  whole `tools` array, which carries four characters no tool's row does — its
34
34
  two brackets and the two separators between three tools. So the three tools
35
- sum to **16,730** and the array is **16,734**. Deleting a tool saves that
35
+ sum to **17,390** and the array is **17,394**. Deleting a tool saves that
36
36
  tool's own row, not a share of the total.
37
37
 
38
- They grew: `search` was 5,383 chars before the trust floor and the governance
39
- each hit now carries, and `read` 3,396 before it carried the same governance
40
- block beside the frontmatter. That is the price of an agent being able to tell
38
+ They grew, twice, and each rise is priced rather than absorbed. `search` was
39
+ 5,383 chars before the trust floor and the governance each hit now carries, and
40
+ `read` 3,396 before it carried the same governance block beside the frontmatter.
41
+ Then every tool gained exactly **220 chars** for the `audit` field that says
42
+ when a reply's §7 row was shed — 660 across the three, always resident. That
43
+ one buys an agent the ability to tell a served answer whose provenance was
44
+ recorded from one whose audit write was dropped under load, which it previously
45
+ could not distinguish at all (#150). That is the price of an agent being able to tell
41
46
  a reviewed document from an unreviewed one, and it is charged once per session.
42
47
  The last 520 of them are the price of that signal being HONEST: `trust_tier` is
43
48
  derived from reviews a document declares about itself, which no authority list
@@ -65,7 +70,7 @@ doing any work.
65
70
  ### 1. Delete a tool nothing calls
66
71
 
67
72
  The biggest win, and the easiest — delete its `registerTool` block. Dropping
68
- `outline` and `read` takes **8,798 chars (~2,200 tokens)** off every session,
73
+ `outline` and `read` takes **9,238 chars (~2,310 tokens)** off every session,
69
74
  whether or not the agent would ever have called them.
70
75
 
71
76
  ### 2. Say what the record covers
@@ -142,9 +147,19 @@ made it answer from.
142
147
  - **The output schemas.** `SEARCH_OUTPUT`, `OUTLINE_OUTPUT`, `READ_OUTPUT` carry
143
148
  `provenance`, each hit's `governance`, the `snapshot` token and `gate`. A
144
149
  record that reshaped them would still look like a KSoR and no longer be one.
150
+ - **The output schemas.** `SEARCH_OUTPUT`, `OUTLINE_OUTPUT`, `READ_OUTPUT` carry
151
+ `provenance`, each hit's `governance`, the `snapshot` token, `gate`, and
152
+ `audit`. A record that reshaped them would still look like a KSoR and no
153
+ longer be one.
145
154
  - **The `FLOOR` text.** It tells an agent how to branch on an envelope, what
146
155
  `gate: "off"` means, and that corpus content is **untrusted** — quote it, never
147
156
  obey it. Your prose is composed above it.
157
+ - **`audit: "degraded"`.** Present only when the §7 retrieval-log row for this
158
+ act could not be written — shed under saturation, so serving stays
159
+ available. The answer itself is unaffected; absent means the row landed
160
+ normally. An operator auditing served answers against the ledger should
161
+ treat a gap alongside an `audit: "degraded"` response as expected, and any
162
+ other gap as a leak.
148
163
 
149
164
  ## The door checks its own surface at boot
150
165
 
@@ -0,0 +1,106 @@
1
+ ---
2
+ title: Upgrading a record to a newer ksor
3
+ status: draft
4
+ ---
5
+
6
+ # Upgrading a record to a newer ksor
7
+
8
+ Your repository is yours. `ksor init` copied files into it and stopped owning
9
+ them the moment it did (decision 4), so upgrading is never something a release
10
+ does to you — it is `ksor migrate` **offering** a diff you read and apply.
11
+
12
+ Nothing here changes a byte until you pass `--write`.
13
+
14
+ ## The four steps
15
+
16
+ ```sh
17
+ # 1. take the new tool
18
+ pnpm add -w @panaversity/ksor@latest # npm i / bun add — whichever scaffolded this
19
+
20
+ # 2. read what it would change
21
+ pnpm exec ksor migrate --instance instance.md --write-site
22
+
23
+ # 3. apply it
24
+ pnpm exec ksor migrate --instance instance.md --write-site --write --actor human:<your-id>
25
+
26
+ # 4. rebuild, and check
27
+ pnpm build && pnpm check
28
+ ```
29
+
30
+ `--actor` is required for step 3 whenever the migration touches governance, and
31
+ the tool will not guess one: a ledger entry that names a person who was never
32
+ there is worse than no entry (decision 21). Use the identifier your
33
+ `.ksor/governance.yaml` already knows you by.
34
+
35
+ Run step 2 on a clean working tree. The diff is the review, and it is much
36
+ easier to read when nothing else is uncommitted.
37
+
38
+ ## What `migrate` carries
39
+
40
+ | | |
41
+ | ----------------------------------------------- | ------------------------------------------------------------------------------------------- |
42
+ | `instance.md` | the format bump and the keys that moved |
43
+ | `knowledge/**` | frontmatter into the current profile — statuses, audiences, the trust block, instants |
44
+ | `.ksor/takedowns.yaml`, `.ksor/governance.yaml` | the ledger and the policy, including denials that lived only in a database |
45
+ | `.gitignore` | the entries a new release needs negated |
46
+ | `.agents/` and `.claude/` format-checker | the emitted checker, so your own `pnpm check` and your CI agree with the tool |
47
+ | root `package.json` **scripts** | scripts a release broke — a removed flag, a step that now needs `ksor build` in front of it |
48
+ | `system/site/**` | **only with `--write-site`** — every file of the site this release emits |
49
+
50
+ `--write-site` is the one to remember, because it is the only path by which a
51
+ security bump reaches an existing project: the site's `package.json` is where
52
+ `next`, `react` and the Fumadocs pins live, and nothing else updates them.
53
+
54
+ It is an **update, never a creation**. A record with no `system/site` of its own
55
+ is not given one.
56
+
57
+ ### The site manifest is merged, not replaced
58
+
59
+ Every other file under `system/site` is reissued whole. `system/site/package.json`
60
+ is not, because it is a register with two authors: ksor owns the entries it
61
+ ships, you own everything else. So a dependency or script you added survives the
62
+ upgrade, and the pins ksor ships move to the new versions. An entry ksor no
63
+ longer ships is left alone rather than deleted — the tool cannot tell one it
64
+ retired from one you added.
65
+
66
+ ## What `migrate` does not carry
67
+
68
+ These are yours, and no release touches them. Diff them against a fresh
69
+ `ksor init` in a scratch directory when a release note says they changed:
70
+
71
+ - `Dockerfile` and `.dockerignore`
72
+ - `vercel.json` (or whatever your host reads)
73
+ - `.github/workflows/validate.yml`
74
+ - `.env.example`
75
+ - `README.md`, `AGENTS.md`, `CLAUDE.md` at the repository root
76
+ - `pnpm-workspace.yaml` / `.npmrc` and any lockfile
77
+
78
+ ```sh
79
+ npx @panaversity/ksor@latest init /tmp/fresh
80
+ diff -ru /tmp/fresh/vercel.json ./vercel.json
81
+ ```
82
+
83
+ ## What refuses, and why that is the point
84
+
85
+ `migrate` stops rather than inventing. `ksor-migrate-underivable` names the one
86
+ thing it cannot know — a title, a description, a `generated.at`, or the actor
87
+ behind a takedown it found in a database — and tells you the flag or the edit
88
+ that supplies it. Two you will meet often:
89
+
90
+ - **A record that declares `database:`** refuses until `KSOR_DB_URL` is
91
+ exported, because denials living only in that database would be republished by
92
+ a migration that never read them. Export it, or remove `database:` if the
93
+ record no longer has one.
94
+ - **`approved` documents become `draft`** unless `--approve-by <actor>` says who
95
+ approves them in the same act. Approval is a human act and a migration is not
96
+ a human.
97
+
98
+ ## After it applies
99
+
100
+ `pnpm build` regenerates every index and rewrites `build.lock.json`; `pnpm check`
101
+ runs the record checker that shipped with the new tool. If you serve the record,
102
+ `ksor schema --apply` and then a full `pnpm refresh` publish a generation the new
103
+ door can read — and a calibrated `vector_floor` measured under an older serving
104
+ predicate must be re-measured with `ksor calibrate`, because a floor measured
105
+ against a different predicate is a declared-but-uncalibrated floor and the door
106
+ will refuse every search until it is replaced.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panaversity/ksor",
3
- "version": "0.0.50",
3
+ "version": "0.0.52",
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",
@@ -72,8 +72,8 @@
72
72
  "tsdown": "0.22.14",
73
73
  "typescript": "7.0.2",
74
74
  "vitest": "^4.1.10",
75
- "@panaversity/ksor-content": "0.0.0",
76
- "@panaversity/ksor-content-gateway": "0.0.0"
75
+ "@panaversity/ksor-content-gateway": "0.0.0",
76
+ "@panaversity/ksor-content": "0.0.0"
77
77
  },
78
78
  "engines": {
79
79
  "node": ">=24"
@@ -49,6 +49,12 @@ the question — that is what made the last version unanswerable.
49
49
  **3 of 3 — Who signs off on a document, and who can take one down?**
50
50
 
51
51
  > Names or handles, not job titles. If it is just you, say so.
52
+ >
53
+ > Ask for BOTH: the handle the record will store (`human:bashiraziz`) and the
54
+ > natural name to print ("Bashir Aziz"). They are not derivable from each other
55
+ > — `human:ciso` and `human:mjs` are not anybody's squashed full name — so the
56
+ > handle goes into the frontmatter and the policy, and the pair goes into
57
+ > `.ksor/people.yaml` for the site to print.
52
58
 
53
59
  ---
54
60
 
@@ -120,7 +126,7 @@ never an email address.
120
126
  climb, not part of this interview). The strictness answer from question 5
121
127
  is the intent behind the `retrieval.vector_floor` on that climb, measured
122
128
  by `ksor calibrate` — capture it in the prose now so it is ready.
123
- - Write `.ksor/governance.yaml` from questions 6 and 7: `version: "0.1"`,
129
+ - Write `.ksor/governance.yaml` from question 3: `version: "0.1"`,
124
130
  the `audiences:` registry if there is one, and the two authority sets with
125
131
  real actors. That file is the root of authority — every approval, every
126
132
  deprecation and every ledger entry is checked against it. **Keep
@@ -128,6 +134,15 @@ never an email address.
128
134
  document is still in `knowledge/`.** Those five are approved by it, so a
129
135
  policy rewritten without it refuses the next build by name
130
136
  (`ksor-approver-unauthorised`). It leaves when the last sample does.
137
+ - Write `.ksor/people.yaml` from question 3: `version: "0.1"` and a `people:`
138
+ MAP from each actor to its natural name — `"human:bashiraziz": Bashir Aziz`.
139
+ Keyed by the actor exactly as the record stores it, quoted because it
140
+ contains a colon. Nothing else — the site looks the actor up at render time,
141
+ so pages read "Owner · Bashir Aziz" instead of "Owner · human:bashiraziz". Every skill that records a governance
142
+ act (this one, add-sources for `verified:` entries, `ksor takedown` for
143
+ withdrawals) asks the owner for a natural name whenever it is about to write
144
+ an actor that isn't in `people.yaml` yet — the owner is the only source of a
145
+ display name, never a convention-based guess.
131
146
  - **Offer to start replacing the starter documents — they are already
132
147
  published.** All five ship `status: stable`, approved by
133
148
  `ksor-starter/KSOR-STAMP-VERSION`, so the site and `llms.txt` carry them from
@@ -49,6 +49,12 @@ the question — that is what made the last version unanswerable.
49
49
  **3 of 3 — Who signs off on a document, and who can take one down?**
50
50
 
51
51
  > Names or handles, not job titles. If it is just you, say so.
52
+ >
53
+ > Ask for BOTH: the handle the record will store (`human:bashiraziz`) and the
54
+ > natural name to print ("Bashir Aziz"). They are not derivable from each other
55
+ > — `human:ciso` and `human:mjs` are not anybody's squashed full name — so the
56
+ > handle goes into the frontmatter and the policy, and the pair goes into
57
+ > `.ksor/people.yaml` for the site to print.
52
58
 
53
59
  ---
54
60
 
@@ -120,7 +126,7 @@ never an email address.
120
126
  climb, not part of this interview). The strictness answer from question 5
121
127
  is the intent behind the `retrieval.vector_floor` on that climb, measured
122
128
  by `ksor calibrate` — capture it in the prose now so it is ready.
123
- - Write `.ksor/governance.yaml` from questions 6 and 7: `version: "0.1"`,
129
+ - Write `.ksor/governance.yaml` from question 3: `version: "0.1"`,
124
130
  the `audiences:` registry if there is one, and the two authority sets with
125
131
  real actors. That file is the root of authority — every approval, every
126
132
  deprecation and every ledger entry is checked against it. **Keep
@@ -128,6 +134,15 @@ never an email address.
128
134
  document is still in `knowledge/`.** Those five are approved by it, so a
129
135
  policy rewritten without it refuses the next build by name
130
136
  (`ksor-approver-unauthorised`). It leaves when the last sample does.
137
+ - Write `.ksor/people.yaml` from question 3: `version: "0.1"` and a `people:`
138
+ MAP from each actor to its natural name — `"human:bashiraziz": Bashir Aziz`.
139
+ Keyed by the actor exactly as the record stores it, quoted because it
140
+ contains a colon. Nothing else — the site looks the actor up at render time,
141
+ so pages read "Owner · Bashir Aziz" instead of "Owner · human:bashiraziz". Every skill that records a governance
142
+ act (this one, add-sources for `verified:` entries, `ksor takedown` for
143
+ withdrawals) asks the owner for a natural name whenever it is about to write
144
+ an actor that isn't in `people.yaml` yet — the owner is the only source of a
145
+ display name, never a convention-based guess.
131
146
  - **Offer to start replacing the starter documents — they are already
132
147
  published.** All five ship `status: stable`, approved by
133
148
  `ksor-starter/KSOR-STAMP-VERSION`, so the site and `llms.txt` carry them from
@@ -0,0 +1,32 @@
1
+ # Natural names for the actors this record cites. The site looks an actor up
2
+ # here at render time and prints the name in its place — so a page reads
3
+ # "Owner · Bashir Aziz" instead of "Owner · human:bashiraziz".
4
+ #
5
+ # A MAP, keyed by the actor exactly as the record stores it. Not a derivation:
6
+ # there is no rule that turns "Bashir Aziz" into `mjs`, `ciso` or `audit-lead`,
7
+ # and most handles in a real record are not somebody's squashed full name. The
8
+ # key is the identifier, the value is what to print, and nothing is guessed.
9
+ #
10
+ # people:
11
+ # "human:bashiraziz": Bashir Aziz
12
+ # "human:ciso": Ayesha Khan
13
+ # "team:legal-ops": Legal Operations
14
+ #
15
+ # ONE-WAY. The identifier is what the record STORES, cites, and checks against
16
+ # `.ksor/governance.yaml`; the name here is only what a page prints. Nothing
17
+ # reads this file back into an actor, and no authority follows from appearing
18
+ # in it (which is why it is not part of the policy).
19
+ #
20
+ # NOT the same list as `.ksor/governance.yaml`. That file says who MAY approve
21
+ # or withdraw; this one says what to print for anyone the record cites. An
22
+ # approver is usually in both, for two different reasons; a document's owner and
23
+ # a `verified:` entry's actor are only ever here, because neither carries
24
+ # authority. Nothing cross-checks the two, on purpose: someone who leaves the
25
+ # authority list is still the recorded approver of what they approved, and their
26
+ # name has to keep rendering on those acts.
27
+ #
28
+ # Optional. An actor with no entry renders exactly as stored, as it did before
29
+ # this file existed. The intake and add-sources skills offer to add one; the
30
+ # owner can also edit this file by hand — it is theirs.
31
+ version: "0.1"
32
+ people: {}
@@ -33,6 +33,36 @@ written by `ksor takedown` — every entry's actor is checked against the
33
33
  policy, so a line appended by hand is refused exactly as the verb would refuse
34
34
  it, and a deleted line is refused by `ksor build` against the file's history.
35
35
 
36
+ Display names for those actors live in `.ksor/people.yaml`, a small phone book
37
+ mapping each actor to the name a page should print — `"human:bashiraziz": Bashir
38
+ Aziz` — so a page reads "Owner · Bashir Aziz" instead of "Owner ·
39
+ human:bashiraziz". The two are not derivable from each other (`human:ciso` is
40
+ nobody's squashed full name), which is why both are written down.
41
+
42
+ It is a SEPARATE file from `.ksor/governance.yaml` deliberately: the policy is
43
+ the root of authority, its key set is closed so that nothing sits there without
44
+ being enforced, and its digest is hashed into `build.lock.json` — so a display
45
+ name living there would mean correcting the spelling of somebody's name refused
46
+ your next build. Appearing in `people.yaml` confers no authority whatsoever; it
47
+ only changes what is printed.
48
+
49
+ An actor with no entry renders exactly as stored, and the governance skills
50
+ offer to add one when they are about to write an actor the file does not know.
51
+
52
+ **The two files list different things, and neither is derived from the other.**
53
+ `governance.yaml` names who MAY act — an actor must be in `approval_authorities`
54
+ to approve a document, or in `takedown_authorities` to withdraw one.
55
+ `people.yaml` names what to PRINT for any actor the record cites. An approver
56
+ usually appears in both, for two different reasons. A document's `ksor.owner`
57
+ and a `verified:` entry's actor appear only in `people.yaml`, because neither
58
+ carries authority — the policy has no verification family at all. And an
59
+ authority whose handle already reads fine needs no entry at all.
60
+
61
+ Nothing cross-checks them, deliberately: a person who leaves the authority list
62
+ is still the recorded approver of everything they approved, and their name must
63
+ keep rendering on those acts. A governance act is history; the policy is
64
+ current state.
65
+
36
66
  ## Critical rules
37
67
 
38
68
  1. **The site never contains authored content.** Knowledge goes in
@@ -215,6 +245,22 @@ Stand it up in this order (each step's errors explain how to fix themselves):
215
245
  the intent to gate WITHOUT a measurement, and every serve refuses until a
216
246
  number replaces it; that is the fail-closed posture, not a starting point.
217
247
 
248
+ **The same applies across TIME, not only across corpora.** A floor measured
249
+ against 5 documents is a copied constant once the record holds 200, and it
250
+ weakens in silence: questions that used to be out-of-corpus start scoring
251
+ above a fixed number, so the record answers what it used to refuse.
252
+
253
+ ```sh
254
+ pnpm exec ksor calibrate --instance instance.md --check
255
+ ```
256
+
257
+ reads the record's own logged searches and reports how the declared floor is
258
+ holding — no provider key, no LLM, one query, and it always exits 0. Run it
259
+ on a schedule; when it says WATCH, re-run `ksor calibrate` to get a new
260
+ number. It is a monitor, not a measurement: it can say the floor has gone
261
+ permissive against real traffic, never that it is too strict for questions
262
+ nobody asked.
263
+
218
264
  ```sh
219
265
  pnpm schema # apply the DDL (once)
220
266
  pnpm grant # authorize ingest for this corpus (once)
@@ -333,7 +379,7 @@ governance block every hit now carries:
333
379
 
334
380
  | | |
335
381
  | -------------------------------- | ------------------------------ |
336
- | all three tool definitions | ~4,054 tokens, always resident |
382
+ | all three tool definitions | ~4,349 tokens, always resident |
337
383
  | one `search` at `k=10` (default) | ~4,196 tokens per call |
338
384
  | one `search` at `k=5` | ~2,330 tokens per call |
339
385
 
@@ -4,6 +4,7 @@
4
4
  # cannot be negated, so the glob is `.ksor/*`.
5
5
  .ksor/*
6
6
  !.ksor/governance.yaml
7
+ !.ksor/people.yaml
7
8
  !.ksor/takedowns.yaml
8
9
 
9
10
  node_modules/