@panaversity/ksor 0.0.39 → 0.0.41

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.
Files changed (108) hide show
  1. package/CHANGELOG.md +965 -0
  2. package/README.md +29 -9
  3. package/dist/checker/check-main.mjs +14049 -0
  4. package/dist/cli.mjs +11528 -5213
  5. package/dist/gateway-api-CF4ED9_g-BQusM_dK.mjs +10895 -0
  6. package/dist/gateway.d.mts +52 -13
  7. package/dist/gateway.mjs +2 -2
  8. package/dist/index.d.mts +1 -1
  9. package/dist/index.mjs +1 -1
  10. package/dist/{src-pl4aOpVs.mjs → src-dqpI-p1a.mjs} +1 -0
  11. package/docs/authorization.md +8 -6
  12. package/docs/deploying.md +36 -25
  13. package/docs/index.md +26 -13
  14. package/docs/ingesting.md +70 -22
  15. package/docs/tool-surface.md +69 -16
  16. package/package.json +4 -3
  17. package/schema/migrations/2.4-2.5__okf-profile.sql +114 -0
  18. package/schema/schema.sql +77 -14
  19. package/templates/scaffold/.agents/skills/add-sources/SKILL.md +63 -18
  20. package/templates/scaffold/.agents/skills/format-checker/SKILL.md +42 -33
  21. package/templates/scaffold/.agents/skills/format-checker/check.mjs +13827 -1314
  22. package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +65 -27
  23. package/templates/scaffold/.agents/skills/make-slides/SKILL.md +7 -5
  24. package/templates/scaffold/.agents/skills/make-summary/SKILL.md +13 -6
  25. package/templates/scaffold/.claude/skills/add-sources/SKILL.md +63 -18
  26. package/templates/scaffold/.claude/skills/format-checker/SKILL.md +42 -33
  27. package/templates/scaffold/.claude/skills/format-checker/check.mjs +13827 -1314
  28. package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +65 -27
  29. package/templates/scaffold/.claude/skills/make-slides/SKILL.md +7 -5
  30. package/templates/scaffold/.claude/skills/make-summary/SKILL.md +13 -6
  31. package/templates/scaffold/.github/workflows/validate.yml +9 -1
  32. package/templates/scaffold/.ksor/governance.yaml +17 -0
  33. package/templates/scaffold/AGENTS.md +293 -119
  34. package/templates/scaffold/Dockerfile +5 -1
  35. package/templates/scaffold/README.md +160 -42
  36. package/templates/scaffold/env.example +37 -6
  37. package/templates/scaffold/gitignore +14 -6
  38. package/templates/scaffold/instance.md +21 -17
  39. package/templates/scaffold/knowledge/governance-ladder.md +11 -2
  40. package/templates/scaffold/knowledge/index.md +9 -0
  41. package/templates/scaffold/knowledge/surfaces/for-agents.md +13 -6
  42. package/templates/scaffold/knowledge/surfaces/for-people.md +7 -6
  43. package/templates/scaffold/knowledge/surfaces/index.md +4 -20
  44. package/templates/scaffold/knowledge/surfaces/overview.md +25 -0
  45. package/templates/scaffold/knowledge/what-is-a-ksor.md +6 -5
  46. package/templates/scaffold/knowledge/what-is-a-ksor.summary.md +4 -0
  47. package/templates/scaffold/package.json +3 -4
  48. package/templates/scaffold/pnpm-lock.yaml +3 -0
  49. package/templates/scaffold/system/gateways/content.ts +13 -0
  50. package/templates/scaffold/system/site/app/(home)/page.tsx +2 -2
  51. package/templates/scaffold/system/site/app/.well-known/mcp/server.json/route.ts +10 -0
  52. package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +134 -91
  53. package/templates/scaffold/system/site/app/global.css +198 -6
  54. package/templates/scaffold/system/site/app/layout.tsx +8 -3
  55. package/templates/scaffold/system/site/app/llms-full.txt/route.ts +13 -7
  56. package/templates/scaffold/system/site/app/llms.txt/route.ts +12 -7
  57. package/templates/scaffold/system/site/app/md/[[...slug]]/route.ts +26 -25
  58. package/templates/scaffold/system/site/components/code-block.tsx +87 -0
  59. package/templates/scaffold/system/site/components/embed.tsx +276 -0
  60. package/templates/scaffold/system/site/components/footer-mark.tsx +3 -2
  61. package/templates/scaffold/system/site/components/governance.tsx +219 -87
  62. package/templates/scaffold/system/site/components/mdx.tsx +10 -2
  63. package/templates/scaffold/system/site/components/record-index.tsx +5 -5
  64. package/templates/scaffold/system/site/components/record-stack.tsx +10 -9
  65. package/templates/scaffold/system/site/components/record-views.tsx +6 -17
  66. package/templates/scaffold/system/site/components/sidebar-status.tsx +19 -18
  67. package/templates/scaffold/system/site/lib/alert-rule.ts +214 -0
  68. package/templates/scaffold/system/site/lib/attachment-rule.ts +6 -1
  69. package/templates/scaffold/system/site/lib/attachments.ts +0 -28
  70. package/templates/scaffold/system/site/lib/audience-rule.ts +15 -21
  71. package/templates/scaffold/system/site/lib/audience.ts +42 -146
  72. package/templates/scaffold/system/site/lib/embed-rule.ts +255 -0
  73. package/templates/scaffold/system/site/lib/governance.ts +339 -225
  74. package/templates/scaffold/system/site/lib/index-routes.ts +125 -0
  75. package/templates/scaffold/system/site/lib/lifecycle-rule.ts +52 -0
  76. package/templates/scaffold/system/site/lib/lock.ts +282 -0
  77. package/templates/scaffold/system/site/lib/order-rule.ts +37 -0
  78. package/templates/scaffold/system/site/lib/record-href.ts +68 -0
  79. package/templates/scaffold/system/site/lib/record-link.tsx +26 -0
  80. package/templates/scaffold/system/site/lib/rules-version.ts +11 -0
  81. package/templates/scaffold/system/site/lib/shared.ts +67 -104
  82. package/templates/scaffold/system/site/lib/sim-rule.ts +49 -0
  83. package/templates/scaffold/system/site/lib/source.ts +256 -186
  84. package/templates/scaffold/system/site/lib/stage-knowledge.ts +638 -467
  85. package/templates/scaffold/system/site/lib/stage-manifest.ts +128 -0
  86. package/templates/scaffold/system/site/package.json +1 -0
  87. package/templates/scaffold/system/site/record/actor.ts +23 -0
  88. package/templates/scaffold/system/site/record/check.ts +571 -0
  89. package/templates/scaffold/system/site/record/citations.ts +312 -0
  90. package/templates/scaffold/system/site/record/frontmatter.ts +134 -0
  91. package/templates/scaffold/system/site/record/git-ledger.ts +171 -0
  92. package/templates/scaffold/system/site/record/hygiene.ts +320 -0
  93. package/templates/scaffold/system/site/record/index-file.ts +150 -0
  94. package/templates/scaffold/system/site/record/index.ts +103 -0
  95. package/templates/scaffold/system/site/record/instance.ts +257 -0
  96. package/templates/scaffold/system/site/record/instant.ts +43 -0
  97. package/templates/scaffold/system/site/record/ledger.ts +694 -0
  98. package/templates/scaffold/system/site/record/load.ts +129 -0
  99. package/templates/scaffold/system/site/record/lock.ts +306 -0
  100. package/templates/scaffold/system/site/record/near-miss.ts +37 -0
  101. package/templates/scaffold/system/site/record/policy.ts +414 -0
  102. package/templates/scaffold/system/site/record/profile.ts +535 -0
  103. package/templates/scaffold/system/site/record/refusal.ts +106 -0
  104. package/templates/scaffold/system/site/record/yaml-file.ts +103 -0
  105. package/templates/scaffold/system/site/source.config.ts +109 -23
  106. package/dist/gateway-api-CmIthmJS-IUA9qS-T.mjs +0 -3225
  107. package/templates/scaffold/system/site/lib/denial-rule.ts +0 -220
  108. package/templates/scaffold/system/site/lib/page-order.ts +0 -93
@@ -26,6 +26,46 @@ The first `pnpm install` also fetches the
26
26
  `ksor` tool (pinned in `package.json`) and writes it into your lockfile —
27
27
  commit the updated lockfile.
28
28
 
29
+ **The five starter documents publish on the first build.** They ship
30
+ `status: stable`, so `pnpm dev` and `pnpm build` both give you a working
31
+ record straight away — pages, a sidebar, a `/llms.txt` an agent can read —
32
+ instead of an empty shelf. They are approved by `ksor-starter/KSOR-STAMP-VERSION`:
33
+ the tool that wrote them, named as a producer rather than as a person, because
34
+ no person reviewed a word of it. That is what the trust tier _unverified_ on
35
+ every one of those pages says, and it is true.
36
+
37
+ **So your first act here is replacing them.** They describe KSoR, not your
38
+ organisation, and a record that describes the wrong thing describes it on every
39
+ surface. Delete each one as your own knowledge arrives — and when the last is
40
+ gone, delete `ksor-starter/KSOR-STAMP-VERSION` from `approval_authorities` in
41
+ `.ksor/governance.yaml` too. Nothing of yours should be approved by a tool. Ask
42
+ your coding agent to run the intake interview: it replaces the `human:you`
43
+ placeholder in that file with your real handle and writes `instance.md` with
44
+ you.
45
+
46
+ **What you write starts unpublished.** A new document is `status: draft`, and
47
+ `pnpm build` admits a draft to no surface at all: no page, no sidebar row, no
48
+ `/llms.txt` entry, nothing for an agent to read. `pnpm dev` shows it, marked —
49
+ the preview is where drafts live.
50
+
51
+ Publishing one adds two keys beside `status: stable` — what produced the text,
52
+ and who approved it. Both, or `pnpm check` refuses the document:
53
+
54
+ ```yaml
55
+ status: stable
56
+ generated: { by: "human:you", at: 2026-01-31T09:00:00Z }
57
+ ksor:
58
+ audience: [public] # already there — every document carries it, drafts too
59
+ approval: { by: "human:you", at: 2026-01-31T09:00:00Z }
60
+ ```
61
+
62
+ `generated` is provenance: it names whatever produced the text — a person, or
63
+ the agent that drafted it — and nothing has to authorise it. `approval.by` is
64
+ authority, so it must name an actor `.ksor/governance.yaml` lists, and its `at`
65
+ may not be earlier than `generated.at` — the text that was approved has to be
66
+ the text that was written. That act is yours, so the record never claims
67
+ authority nobody granted.
68
+
29
69
  ### Presenting a document
30
70
 
31
71
  Ask your coding agent for slides and it writes them, from the document, into
@@ -77,26 +117,44 @@ opening, and the skill will say so rather than write one.
77
117
  The record's other surface is an MCP server for AI agents — the same
78
118
  knowledge, cited, with honest abstention. It is the climbed rung: it needs a
79
119
  Postgres store (with pgvector) and an embedding provider key, so it is not
80
- part of `pnpm dev`. The ordered path is:
120
+ part of `pnpm dev`. Three steps, and the order is load-bearing: the command
121
+ block is last because it needs both of the things above it. Skip ahead to it
122
+ and `pnpm provision` refuses, naming the config step 1 writes.
81
123
 
82
- ```sh
83
- cp .env.example .env # fill in KSOR_DB_URL, GEMINI_API_KEY, KSOR_AUTH=disabled-local
84
- pnpm provision # once: apply the schema, authorize ingest
85
- pnpm refresh # ingest the record, collect retired generations
86
- pnpm serve # the MCP server
87
- ```
124
+ **1. Uncomment the `database:` block already in `instance.md`.** It names the
125
+ VARIABLE holding your DSN, never the DSN itself:
88
126
 
89
- `ksor` reads `.env` automatically — nothing to export. `KSOR_AUTH=disabled-local`
90
- is required for a local run: serve refuses to boot unauthenticated on purpose,
91
- so a server is never open by accident.
127
+ ```yaml
128
+ database:
129
+ dsn_env: KSOR_DB_URL
130
+ ```
92
131
 
93
- Uncomment the `database:` block already in `instance.md`it names the
94
- VARIABLE holding your DSN, never the DSN itself. That is the whole required
95
- config:
96
- `embedding:` already defaults to Gemini at 1536 dimensions, and leaving
132
+ Nothing below works until it is there`pnpm provision` refuses with
133
+ `instance.md declares no database: block`. That is also the whole required
134
+ config: `embedding:` already defaults to Gemini at 1536 dimensions, and leaving
97
135
  `retrieval:` out starts you with the abstention gate off and honest about it
98
136
  (turn it on afterwards with `ksor calibrate`, once the record is serving).
99
137
 
138
+ **2. Copy the environment file**, then fill in `KSOR_DB_URL`, `GEMINI_API_KEY`
139
+ and `KSOR_AUTH=disabled-local`:
140
+
141
+ ```sh
142
+ cp .env.example .env
143
+ ```
144
+
145
+ `ksor` reads `.env` automatically — there is nothing to export, and where a
146
+ refusal tells you to _export_ that variable, putting it in `.env` is the same
147
+ thing. `KSOR_AUTH=disabled-local` is required for a local run: serve refuses to
148
+ boot unauthenticated on purpose, so a server is never open by accident.
149
+
150
+ **3. Bring it up.**
151
+
152
+ ```sh
153
+ pnpm provision # once: apply the schema, authorize ingest
154
+ pnpm refresh # build, ingest the record, collect retired generations
155
+ pnpm serve # the MCP server
156
+ ```
157
+
100
158
  `pnpm provision` runs once — it applies the schema (or migrates it forward) and
101
159
  authorizes ingest, the two privileged acts that should not happen on every
102
160
  boot. After that: `pnpm refresh` publishes what you have edited, and `pnpm serve`
@@ -104,10 +162,9 @@ runs the server. They are separate because publishing is an act, not a side
104
162
  effect of starting a process. A rerun on an unchanged record
105
163
  costs nothing: no new generation, no embedding, no rows. Edit a document and
106
164
  the next run picks up exactly that change. `AGENTS.md` → "Serving to agents" is the
107
- full runbook; your coding agent reads it first. `pnpm serve` refuses to boot
108
- unauthenticated: a local run declares `KSOR_AUTH=disabled-local` (already in
109
- `.env.example`) and binds loopback, so a server is never left open by accident;
110
- a public bind needs a configured SSO door instead. Any other operation is
165
+ full runbook; your coding agent reads it first. A public bind needs a
166
+ configured SSO door rather than `disabled-local` see step 2 and
167
+ "The agent surface deploys separately" below. Any other operation is
111
168
  `pnpm exec ksor <verb>`.
112
169
 
113
170
  ### Test the agent surface with an actual agent
@@ -194,6 +251,40 @@ equivalent (its default refusal of dependency install scripts covers the
194
251
  OTHER half of that posture), and this sentence is the disclosure.
195
252
  <!-- /ksor:pm -->
196
253
 
254
+ ### A note on `audit`
255
+
256
+ An audit of this scaffold reports vulnerabilities in `next`, and will keep
257
+ doing so: a framework that large always has open advisories against whatever
258
+ version you have pinned.
259
+ <!-- ksor:pm npm -->
260
+ `npm install` prints the count at the end of every install, so you meet it
261
+ before you have run anything, next to an invitation to run
262
+ `npm audit fix --force`.
263
+ <!-- /ksor:pm -->
264
+ <!-- ksor:pm pnpm -->
265
+ pnpm reports it only when you run `pnpm audit`.
266
+ <!-- /ksor:pm -->
267
+ <!-- ksor:pm bun -->
268
+ bun reports it only when you run `bun audit`.
269
+ <!-- /ksor:pm -->
270
+
271
+ **Never let an audit tool raise the pin for you.** It moves off the version
272
+ this scaffold was built and tested against, and that pin is the whole reason
273
+ two machines produce the same site. Bump it deliberately instead — take the
274
+ newer pin a newer `ksor init` emits, or raise it yourself and re-run
275
+ `pnpm build` and the deploy check above.
276
+
277
+ It also reads worse than it is, for one structural reason worth knowing:
278
+ **this site is a static export.** `pnpm build` writes HTML, JS and CSS to
279
+ `system/site/out/`, and no framework server ever runs in front of your
280
+ readers — no middleware, no server actions, no rewrites, no image optimizer.
281
+ Most framework advisories describe exactly those request paths, so they have
282
+ nothing here to reach. Two things that argument does NOT cover, and you should
283
+ treat as real: an advisory in the **build** toolchain, which does run, on your
284
+ machine and in your CI; and any advisory at all if you later add a served route
285
+ and stop exporting. Read what an advisory affects before deciding it is inert —
286
+ the static export is a reason, not a blanket.
287
+
197
288
  ## The files, explained
198
289
 
199
290
  Nothing here is decoration, and the dotfiles are not ceremony — each one is a
@@ -203,7 +294,12 @@ different coding agent's way of finding the same working contract.
203
294
  | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
204
295
  | `knowledge/` | **the record** — your governed markdown. The product; everything else serves it. |
205
296
  | `system/` | the code that serves the record: the site today, more as you need it. |
206
- | `instance.md` | what this record is authoritative for; its `name:` is the identity every surface publishes (read at server/build start — restart `pnpm dev` after renaming). This prose IS the agent surface's system prompt — `ksor serve` wires it into the MCP server's instructions. |
297
+ | `instance.md` | what this record is authoritative for; its `name:` is the identity every surface publishes and its `title:` the display title every page leads with (both read at server/build start — restart `pnpm dev` after changing either). Its BODY is the agent surface's system prompt — `ksor serve` wires it into the MCP server's instructions. |
298
+ | `.ksor/governance.yaml` | **the root of authority** — which audiences exist, who may approve a document, who may take one down. Committed; every governance act is checked against it. |
299
+ | `.ksor/takedowns.yaml` | the takedown ledger: every withdrawal and every lift, append-only and committed, so the site honours a takedown with no database in the loop. It appears at your first `ksor takedown` — an empty ledger would assert an act nobody performed. |
300
+ | `build.lock.json` | what the last `ksor build` published — the corpus, the commit, the toolchain — and what every machine surface stamps. Committed; written by `ksor build`, never by hand. |
301
+ | `Dockerfile`, `.dockerignore` | how the agent surface reaches a host. The Dockerfile names no host; `vercel.json` points at it rather than replacing it, so moving hosts is a redeploy. |
302
+ | `vercel.json` | one domain, two services — the static site and the MCP door — for the host this scaffold answers the setup interview for. Delete it if you deploy elsewhere. |
207
303
  | `AGENTS.md` | the working contract every coding agent reads first — the rules for writing knowledge here. |
208
304
  | `CLAUDE.md` | one line, pointing at `AGENTS.md`. Claude Code looks for this filename, not that one. |
209
305
  | `.agents/skills/` | the agent kit: `intake-interview` (define the record with you), `add-sources` (turn source material into governed documents), `make-slides` (generate a presentation from a document and attach it), `make-summary` (write a document's summary and attach it), `format-checker` (the rules, as a program). |
@@ -212,7 +308,7 @@ different coding agent's way of finding the same working contract.
212
308
  | `.github/workflows/validate.yml` | your CI: runs the same checker on every pull request and push to main. |
213
309
  | `.gitattributes` | markdown is checked out byte-stable on every platform, so the same commit hashes the same everywhere. |
214
310
  | `.env.example` | the variables the served rung needs; copy to `.env` (gitignored) and fill in. |
215
- | `.gitignore` | keeps build output, `node_modules/`, and `.env` out of the record's history. |
311
+ | `.gitignore` | keeps build output, `node_modules/`, and `.env` out of the record's history — and negates two paths inside `.ksor/`, because the policy and the ledger ARE the record. |
216
312
  | `package.json` | the surface commands — `pnpm dev` (the site) and `pnpm provision` / `pnpm refresh` / `pnpm serve` (the agent surface: set up once, publish, then serve) — plus `pnpm build` / `pnpm check`, the pinned `@panaversity/ksor` tool and the workspace layout the manifest declares. |
217
313
  <!-- ksor:pm pnpm -->
218
314
  | `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. |
@@ -250,27 +346,37 @@ and anything that can serve files can serve it.
250
346
  If the build image's pnpm predates the `packageManager` pin, set the
251
347
  `ENABLE_EXPERIMENTAL_COREPACK=1` build environment variable.
252
348
  <!-- /ksor:pm -->
253
- **Once `instance.md` declares a `database:`, the BUILD needs the DSN too.**
254
- `pnpm build` first runs `pnpm export-denylist`, which asks the record's
255
- database what has been withdrawn (`ksor takedown --export`) and writes
256
- `.ksor-denylist.json` for the site to read. Without it the build stops:
257
-
258
- ```
259
- KSOR_DB_URL is unset, and instance.md declares a database
260
- why: a takedown lives in that database. Without it this build cannot tell
261
- 'nothing is denied' from 'nobody asked'
262
- ```
263
-
264
- That is deliberate — a site built without asking would publish a document you
265
- withdrew. Give the build environment the same `KSOR_DB_URL` your server uses
266
- (read access is enough), or keep the record database-free, where the export
267
- writes "nothing denied" and exits 0.
349
+ **`pnpm build` runs `ksor build` first.** It generates every `index.md`,
350
+ runs the record checker, and writes `build.lock.json` the committed record
351
+ of what was published, from which commit, with which toolchain — and only
352
+ then builds the site. A checker refusal stops the build before anything is
353
+ written. Takedowns reach the site through `.ksor/takedowns.yaml`, the
354
+ committed ledger — a file in this repository, so the site build needs no
355
+ database access at all.
356
+
357
+ That is deliberate. The act that withdraws a document is one merged commit,
358
+ and both surfaces read it: the door refuses immediately, the site at its next
359
+ build. Merge the ledger entry, rebuild, redeploy.
360
+
361
+ **A withdrawal that arrives on a clock works the same way, and that one has to
362
+ be scheduled.** `stale_after` and `ksor.effective_from` are evaluated once per
363
+ build, at the instant that build ran, and the answer is written into
364
+ `system/site/out/` — static files cannot re-decide themselves. So a document
365
+ whose `stale_after` passes after your last build keeps appearing in `/llms.txt`
366
+ and in its markdown twin, while `ksor serve` — a process, evaluating per
367
+ request — already refuses it. `ksor build` prints the next instant at which
368
+ this happens. Nothing here rebuilds for you: `validate.yml` runs on pull
369
+ requests and `vercel.json` declares no cron. If this record uses either key,
370
+ add a scheduled rebuild and redeploy.
268
371
 
269
372
  - **GitHub Pages, nginx, S3, anything static** — run `pnpm build` and
270
373
  upload `system/site/out/`. Hosted under a sub-path (like
271
374
  `user.github.io/repo`)? Build with `KSOR_BASE_PATH=/repo pnpm build`.
272
- - **Verify any deploy** the same way: the home page, one document page,
273
- and `/llms.txt` all load; nothing else is required.
375
+ - **Verify any deploy** the same way: the home page, one document page and
376
+ `/llms.txt` load, and each names the documents this record has approved. On a
377
+ record whose documents are all still drafts, the home page and `/llms.txt`
378
+ come up empty and there is no document page at all — which is the correct
379
+ answer, not a broken deploy. Approve a document and rebuild to see it change.
274
380
 
275
381
  ### The agent surface deploys separately
276
382
 
@@ -280,9 +386,20 @@ Cloud Run, Fly, Render, ECS, Kubernetes or a VPS:
280
386
 
281
387
  ```sh
282
388
  docker build -t my-record .
283
- docker run --rm -p 8080:80 --env-file .env my-record
389
+ docker run --rm -p 8080:80 --env-file .env \
390
+ -e KSOR_AUTH=disabled-public my-record
284
391
  ```
285
392
 
393
+ **That last flag is not boilerplate, and it is not a workaround.** The image
394
+ sets `$PORT`, so the door binds `0.0.0.0` — a PUBLIC bind — and the
395
+ `KSOR_AUTH=disabled-local` your `.env` carries refuses there by design, saying
396
+ so in as many words. Your laptop is not the exception: a container really is
397
+ reachable from outside itself, and `disabled-public` is you saying you know
398
+ that. It goes on the command rather than into `.env` so your ordinary
399
+ `pnpm serve` keeps the loopback posture — and a real deployment sets it (or,
400
+ better, the SSO variables) in the host's environment, since `.dockerignore`
401
+ keeps `.env` out of the image entirely.
402
+
286
403
  One thing surprises people: **deploying does not publish.** The door serves
287
404
  whatever generation is already in the database, so a first deploy with no
288
405
  `pnpm refresh` serves an empty record. Publishing is a step you run — from your
@@ -290,10 +407,11 @@ machine or from CI — and it is deliberately not something a booting container
290
407
  does. The full walkthrough, including what a cold start costs and where ingest
291
408
  belongs, is in `node_modules/@panaversity/ksor/docs/deploying.md`.
292
409
 
293
- If `instance.md` declares `audiences:`, what you deploy is a **tier**.
294
- Plain `pnpm build` always builds the public tier — safe for any host.
295
- `KSOR_AUDIENCE=<audience> pnpm build` builds a wider tier for that
296
- audience's own deployment, and that build carries an
410
+ If `.ksor/governance.yaml` registers audiences, what you deploy is a
411
+ **viewer**. Plain `pnpm build` builds for `[public]` — safe for any host.
412
+ `KSOR_AUDIENCE=public,<audience> pnpm build` a comma list that must always
413
+ include `public` — builds for a wider viewer's own deployment, and that build
414
+ carries an
297
415
  "— not for publication" label because it must never reach a public host:
298
416
  put it behind access control you already trust (VPN, SSO proxy,
299
417
  authenticated host). The tiers govern what a build contains; where each
@@ -26,7 +26,10 @@ GEMINI_API_KEY=
26
26
  # a deploy go green.
27
27
  #
28
28
  # A container sets $PORT, so the door binds 0.0.0.0 — that is a public bind, and
29
- # `disabled-local` will refuse there. That refusal is the point.
29
+ # `disabled-local` will refuse there. That refusal is the point. To smoke-test
30
+ # the image locally, acknowledge it on the command instead of changing the line
31
+ # below, so a plain `ksor serve` keeps the loopback posture:
32
+ # docker run --rm -p 8080:80 --env-file .env -e KSOR_AUTH=disabled-public my-record
30
33
  #
31
34
  # For a real deployment, delete this line and configure the SSO door instead:
32
35
  # KSOR_SSO_URL=https://your-sso.example.com
@@ -48,7 +51,7 @@ KSOR_AUTH=disabled-local
48
51
  # "Keeping people out of the site".
49
52
  #
50
53
  # All three are required together, and are inlined at BUILD time — set them
51
- # before `pnpm build`, not on the running site. Leave them unset and the
54
+ # before the site build, not on the running site. Leave them unset and the
52
55
  # sign-in control does not render at all.
53
56
  #
54
57
  # The issuer is the same authorization server the door names in KSOR_SSO_URL.
@@ -76,10 +79,38 @@ KSOR_AUTH=disabled-local
76
79
  # validating after it, even with one instance.
77
80
  # KSOR_SNAPSHOT_KEYS=v1=<32+ random bytes>
78
81
 
79
- # Which audience tier this server serves, when instance.md declares
80
- # `audiences:`. Unset = the least-privileged tier. Same variable the site's
81
- # per-audience build reads, so both surfaces mean the same thing.
82
- # KSOR_AUDIENCE=public
82
+ # WHO this surface answers as: a comma list of audience identifiers, validated
83
+ # against the registry in .ksor/governance.yaml. It must always contain
84
+ # `public`, and unset means exactly `[public]`. A document is visible when its
85
+ # `ksor.audience` list and this one OVERLAP — there is no ranking, so
86
+ # `public,internal` is the reader who may see both, not a level.
87
+ # The same variable the site's per-audience build reads, so both surfaces mean
88
+ # the same thing. A build for anything wider than `public` belongs behind that
89
+ # audience's own access control, never on a public host.
90
+ # KSOR_AUDIENCE=public,internal
91
+
92
+ # The LOWEST trust tier this door will answer from: unverified (the default),
93
+ # machine-confirmed, or human-reviewed. unverified is the honest state of a
94
+ # record nobody has reviewed, and it is not a defect.
95
+ # Know what the tier IS before you raise the floor onto it. The tier itself is
96
+ # derived and may not be authored (`trust_tier:` in frontmatter is refused),
97
+ # but its input is: a document's own `verified:` entries. Nothing in
98
+ # `.ksor/governance.yaml` gates who may appear there — the policy authorises
99
+ # `ksor.approval` and takedown, and has no verification family — so what stands
100
+ # behind `human-reviewed` is review of the pull request that added the entry,
101
+ # and nothing else. A document that declares a review of itself gets the tier.
102
+ # A caller can raise this per call with the `min_trust_tier` argument; it can
103
+ # never be lowered below what is set here. Setting `human-reviewed` narrows the
104
+ # door to the documents that CLAIM a human review: on a record with no reviews
105
+ # that is nothing at all, and on a record where one document declares its own
106
+ # it is that document ALONE — a floor can invert if you raise it without
107
+ # reading what it admits.
108
+ # KSOR_MIN_TRUST_TIER=unverified
109
+
110
+ # `ksor build` records drafts as hidden from every surface (the default) or
111
+ # shown on HUMAN surfaces only — a review build, marked noindex. Recorded in
112
+ # build.lock.json and in build_id; never set it for a build you deploy.
113
+ # KSOR_DRAFTS=show
83
114
 
84
115
  # Bind. PORT is honoured too, for hosts that inject it.
85
116
  # KSOR_MCP_HOST=127.0.0.1
@@ -1,5 +1,10 @@
1
- # scratch space for ksor verbs everything transient lives under one roof
2
- .ksor/
1
+ # ksor's working directory build output and scratch, never the record.
2
+ # The two governance files inside it ARE the record (the policy and the
3
+ # takedown ledger) and are un-ignored by name: the directory form `.ksor/`
4
+ # cannot be negated, so the glob is `.ksor/*`.
5
+ .ksor/*
6
+ !.ksor/governance.yaml
7
+ !.ksor/takedowns.yaml
3
8
 
4
9
  node_modules/
5
10
  system/site/.next/
@@ -8,9 +13,16 @@ system/site/out/
8
13
  # the per-audience copy of the record a build stages — a filtered derivative,
9
14
  # never a second record; committing it would publish what a build excluded
10
15
  system/site/.staged-knowledge/
16
+ # and its manifest, which names this build's viewer and every concept it
17
+ # staged — committing it publishes the restricted viewer's page list
18
+ system/site/.staged-knowledge.json
11
19
  # and the lock that keeps one evaluation of a build staging at a time; it only
12
20
  # outlives a build that was killed mid-stage, and the next build clears it
13
21
  system/site/.staged-knowledge.lock
22
+ # where this build SERVES the sims it published — copied out of the staged
23
+ # record, so it holds one viewer's sims and the next build prunes the rest;
24
+ # the record owns the sim, this is only the copy that gets a url
25
+ system/site/public/sims/
14
26
  *.tsbuildinfo
15
27
 
16
28
  # secrets never enter the record — system/ is their future home (serve)
@@ -18,7 +30,3 @@ system/site/.staged-knowledge.lock
18
30
  !.env.example
19
31
 
20
32
  .DS_Store
21
-
22
- # The takedown manifest the site build reads — exported from the database,
23
- # never hand-edited, and re-exported on every build.
24
- .ksor-denylist.json
@@ -1,7 +1,9 @@
1
1
  ---
2
- format: 1
2
+ format: 2
3
3
  name: KSOR-STAMP-NAME
4
- ksor:
4
+ title: KSoR
5
+ description: What a Knowledge System of Record is, how a project climbs the governance ladder, and which surfaces publish the same governed knowledge.
6
+ toolchain:
5
7
  requires: ">=KSOR-STAMP-VERSION"
6
8
  scaffolded: "KSOR-STAMP-VERSION"
7
9
  # The served MCP rung needs ONE required block: the NAME of the environment
@@ -23,8 +25,6 @@ ksor:
23
25
  # version: 0.1.0
24
26
  ---
25
27
 
26
- # KSoR
27
-
28
28
  This record is authoritative for what a Knowledge System of Record is, how a
29
29
  project climbs the governance ladder, and which surfaces the same governed
30
30
  knowledge is published through. It does not cover the CLI's release history or
@@ -37,23 +37,27 @@ record wins.
37
37
  ## This is a starter, and it is yours to replace
38
38
 
39
39
  Everything above describes KSoR itself. It ships filled in so that a fresh
40
- project has a real governed corpus on the first `pnpm dev` — statuses, owners,
41
- provenance, a folder and a draft instead of an empty shelf and a placeholder.
40
+ project has a real governed corpus on the first `pnpm dev` — five approved
41
+ documents, three of them inside a folder, and one carrying all four study
42
+ attachments (a summary, slides, flashcards and a quiz) — instead of an empty
43
+ shelf and a placeholder.
42
44
  The documents live in `knowledge/`; delete them as your own knowledge arrives.
43
45
 
44
46
  Be deliberate about replacing it, because a starter that describes the wrong
45
- thing describes it _everywhere_. Two lines here are read by every surface:
46
-
47
- - **The heading** is the display title — the human name every page leads with.
48
- The machine identity stays `KSOR-STAMP-NAME` in the frontmatter, and that is
49
- what citations and `llms.txt` use.
50
- - **The first paragraph** is this record's scope. The site publishes it, and
51
- `ksor serve` hands it to a connecting agent as the MCP server's instructions.
52
- A record published with this paragraph unchanged will tell an agent quite
53
- accurately, and quite uselessly for you that it is authoritative for what a
54
- Knowledge System of Record is.
47
+ thing describes it _everywhere_. Three things here are read by every surface:
48
+
49
+ - **`title:`** is the display title — the human name every page leads with and
50
+ the heading of the record's root index. The machine identity stays
51
+ `KSOR-STAMP-NAME` in `name:`, and that is what citations and `llms.txt` use.
52
+ - **`description:`** is one sentence that seeds `llms.txt` and the MCP
53
+ discovery document.
54
+ - **This body** is the MCP server's instructions, handed in full to every
55
+ connecting agent; its first paragraph is this record's scope, which the site
56
+ publishes. A record published with it unchanged will tell an agent — quite
57
+ accurately, and quite uselessly for you — that it is authoritative for what
58
+ a Knowledge System of Record is.
55
59
 
56
60
  Ask your coding agent to run the **intake interview** (it knows how — see
57
61
  `.agents/skills/intake-interview/`), answer its questions, and let it write
58
- this document with you. Replace those two lines and every surface follows,
62
+ this document with you. Replace those three and every surface follows,
59
63
  because every surface reads them from here.
@@ -1,9 +1,13 @@
1
1
  ---
2
+ type: Document
2
3
  title: The governance ladder
3
4
  description: Level 0 works immediately; a project climbs only as far as its domain needs.
4
- status: draft
5
- owner: Product
5
+ status: stable
6
6
  order: 3
7
+ generated: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
8
+ ksor:
9
+ audience: [public]
10
+ approval: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
7
11
  ---
8
12
 
9
13
  Governance here is a ladder, not a gate. Demanding the top rung of a project on
@@ -18,6 +22,11 @@ record of such documents is already publishable, searchable and citable.
18
22
 
19
23
  Each rung is worth climbing when the domain asks for it, and not before.
20
24
 
25
+ > [!TIP]
26
+ > Climb one rung at a time, and only when something has gone wrong without it.
27
+ > A record that adds owners before anyone disputes a document spends review
28
+ > effort buying nothing.
29
+
21
30
  ### Owners and sources
22
31
 
23
32
  An owner names who stands behind a document. Provenance names where its claims
@@ -0,0 +1,9 @@
1
+ ---
2
+ okf_version: "0.2"
3
+ ---
4
+
5
+ # KSoR
6
+
7
+ * [What a Knowledge System of Record is](what-is-a-ksor.md) - The authoritative, governed knowledge layer humans, agents and software operate from.
8
+ * [The governance ladder](governance-ladder.md) - Level 0 works immediately; a project climbs only as far as its domain needs.
9
+ * [Surfaces](surfaces/)
@@ -1,12 +1,13 @@
1
1
  ---
2
+ type: Document
2
3
  title: The agent surface
3
4
  description: MCP for retrieval with citations, and machine-readable files beside it.
4
- status: approved
5
- owner: Product
6
- order: 2
7
- effective: 2026-08-22
8
- provenance:
9
- - KSoR README, "an agent interface through MCP for search, retrieval, citation, reasoning, and action"
5
+ status: stable
6
+ order: 6
7
+ generated: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
8
+ ksor:
9
+ audience: [public]
10
+ approval: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
10
11
  ---
11
12
 
12
13
  Agents reach the record through MCP — an open standard, so one corpus answers in
@@ -22,6 +23,12 @@ be checked against the document that carries it rather than taken on trust.
22
23
  "Not in this record" is a correct answer. It is never an error, and never a
23
24
  licence to fall back on what a model happens to remember.
24
25
 
26
+ > [!WARNING]
27
+ >
28
+ > An agent that fills a gap from its own memory has not used this record — it
29
+ > has used it as an opening paragraph. Nothing in the answer says which half
30
+ > came from where.
31
+
25
32
  ## Files beside the interface
26
33
 
27
34
  The build publishes the same knowledge as plain files an agent can fetch without
@@ -1,12 +1,13 @@
1
1
  ---
2
+ type: Document
2
3
  title: The human surface
3
4
  description: Pages for reading, reviewing and sharing the record.
4
- status: approved
5
- owner: Product
6
- order: 1
7
- effective: 2026-08-22
8
- provenance:
9
- - KSoR README, "a human experience for reading, learning, reviewing, and sharing"
5
+ status: stable
6
+ order: 5
7
+ generated: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
8
+ ksor:
9
+ audience: [public]
10
+ approval: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
10
11
  ---
11
12
 
12
13
  A static site renders every governed document at a route derived from its path,
@@ -1,21 +1,5 @@
1
- ---
2
- title: Surfaces
3
- description: One source, published through several synchronized projections.
4
- status: approved
5
- owner: Product
6
- order: 2
7
- ---
1
+ # Surfaces
8
2
 
9
- The record is written once and published through surfaces that cannot disagree,
10
- because each derives from the same source rather than from a copy of it.
11
-
12
- ## Why they cannot drift
13
-
14
- A surface is generated from the record at build time. Adding one never means
15
- editing the knowledge, and a document changed in one place changes everywhere
16
- the next time the record is published.
17
-
18
- ## The two audiences
19
-
20
- A person reads pages. An agent reads bytes. Both are served from the same
21
- governed markdown, which is what makes them answer alike.
3
+ * [Surfaces](overview.md) - One source, published through several synchronized projections.
4
+ * [The human surface](for-people.md) - Pages for reading, reviewing and sharing the record.
5
+ * [The agent surface](for-agents.md) - MCP for retrieval with citations, and machine-readable files beside it.
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: Document
3
+ title: Surfaces
4
+ description: One source, published through several synchronized projections.
5
+ status: stable
6
+ order: 4
7
+ generated: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
8
+ ksor:
9
+ audience: [public]
10
+ approval: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
11
+ ---
12
+
13
+ The record is written once and published through surfaces that cannot disagree,
14
+ because each derives from the same source rather than from a copy of it.
15
+
16
+ ## Why they cannot drift
17
+
18
+ A surface is generated from the record at build time. Adding one never means
19
+ editing the knowledge, and a document changed in one place changes everywhere
20
+ the next time the record is published.
21
+
22
+ ## The two audiences
23
+
24
+ A person reads pages. An agent reads bytes. Both are served from the same
25
+ governed markdown, which is what makes them answer alike.
@@ -1,12 +1,13 @@
1
1
  ---
2
+ type: Document
2
3
  title: What a Knowledge System of Record is
3
4
  description: The authoritative, governed knowledge layer humans, agents and software operate from.
4
- status: approved
5
- owner: Product
5
+ status: stable
6
6
  order: 1
7
- effective: 2026-08-22
8
- provenance:
9
- - KSoR README, "What Is a Knowledge System of Record?"
7
+ generated: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
8
+ ksor:
9
+ audience: [public]
10
+ approval: { by: "ksor-starter/KSOR-STAMP-VERSION", at: 2026-08-25T00:00:00Z }
10
11
  ---
11
12
 
12
13
  A traditional system of record establishes what is true about the current state
@@ -1,3 +1,7 @@
1
+ ---
2
+ type: Summary
3
+ ---
4
+
1
5
  A Knowledge System of Record settles **which copy governs**. When a wiki page, a
2
6
  slide or a model's memory disagrees with it, this record wins.
3
7
 
@@ -5,15 +5,14 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "pnpm -C system/site dev",
8
- "build": "pnpm export-denylist && pnpm -C system/site build",
9
- "export-denylist": "ksor takedown --instance instance.md --export .ksor-denylist.json",
8
+ "build": "ksor build && pnpm -C system/site build",
10
9
  "check": "node .agents/skills/format-checker/check.mjs",
11
10
  "provision": "pnpm schema && pnpm grant",
12
11
  "serve": "ksor serve",
13
- "refresh": "pnpm ingest && pnpm gc",
12
+ "refresh": "ksor build && pnpm ingest && pnpm gc",
14
13
  "schema": "ksor schema --instance instance.md --apply",
15
14
  "grant": "ksor grant --instance instance.md",
16
- "ingest": "ksor ingest --instance instance.md --knowledge knowledge --flip",
15
+ "ingest": "ksor ingest --instance instance.md --flip",
17
16
  "gc": "ksor gc --instance instance.md"
18
17
  },
19
18
  "dependencies": {