@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.
- package/CHANGELOG.md +965 -0
- package/README.md +29 -9
- package/dist/checker/check-main.mjs +14049 -0
- package/dist/cli.mjs +11528 -5213
- package/dist/gateway-api-CF4ED9_g-BQusM_dK.mjs +10895 -0
- package/dist/gateway.d.mts +52 -13
- package/dist/gateway.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-pl4aOpVs.mjs → src-dqpI-p1a.mjs} +1 -0
- package/docs/authorization.md +8 -6
- package/docs/deploying.md +36 -25
- package/docs/index.md +26 -13
- package/docs/ingesting.md +70 -22
- package/docs/tool-surface.md +69 -16
- package/package.json +4 -3
- package/schema/migrations/2.4-2.5__okf-profile.sql +114 -0
- package/schema/schema.sql +77 -14
- package/templates/scaffold/.agents/skills/add-sources/SKILL.md +63 -18
- package/templates/scaffold/.agents/skills/format-checker/SKILL.md +42 -33
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +13827 -1314
- package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +65 -27
- package/templates/scaffold/.agents/skills/make-slides/SKILL.md +7 -5
- package/templates/scaffold/.agents/skills/make-summary/SKILL.md +13 -6
- package/templates/scaffold/.claude/skills/add-sources/SKILL.md +63 -18
- package/templates/scaffold/.claude/skills/format-checker/SKILL.md +42 -33
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +13827 -1314
- package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +65 -27
- package/templates/scaffold/.claude/skills/make-slides/SKILL.md +7 -5
- package/templates/scaffold/.claude/skills/make-summary/SKILL.md +13 -6
- package/templates/scaffold/.github/workflows/validate.yml +9 -1
- package/templates/scaffold/.ksor/governance.yaml +17 -0
- package/templates/scaffold/AGENTS.md +293 -119
- package/templates/scaffold/Dockerfile +5 -1
- package/templates/scaffold/README.md +160 -42
- package/templates/scaffold/env.example +37 -6
- package/templates/scaffold/gitignore +14 -6
- package/templates/scaffold/instance.md +21 -17
- package/templates/scaffold/knowledge/governance-ladder.md +11 -2
- package/templates/scaffold/knowledge/index.md +9 -0
- package/templates/scaffold/knowledge/surfaces/for-agents.md +13 -6
- package/templates/scaffold/knowledge/surfaces/for-people.md +7 -6
- package/templates/scaffold/knowledge/surfaces/index.md +4 -20
- package/templates/scaffold/knowledge/surfaces/overview.md +25 -0
- package/templates/scaffold/knowledge/what-is-a-ksor.md +6 -5
- package/templates/scaffold/knowledge/what-is-a-ksor.summary.md +4 -0
- package/templates/scaffold/package.json +3 -4
- package/templates/scaffold/pnpm-lock.yaml +3 -0
- package/templates/scaffold/system/gateways/content.ts +13 -0
- package/templates/scaffold/system/site/app/(home)/page.tsx +2 -2
- package/templates/scaffold/system/site/app/.well-known/mcp/server.json/route.ts +10 -0
- package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +134 -91
- package/templates/scaffold/system/site/app/global.css +198 -6
- package/templates/scaffold/system/site/app/layout.tsx +8 -3
- package/templates/scaffold/system/site/app/llms-full.txt/route.ts +13 -7
- package/templates/scaffold/system/site/app/llms.txt/route.ts +12 -7
- package/templates/scaffold/system/site/app/md/[[...slug]]/route.ts +26 -25
- package/templates/scaffold/system/site/components/code-block.tsx +87 -0
- package/templates/scaffold/system/site/components/embed.tsx +276 -0
- package/templates/scaffold/system/site/components/footer-mark.tsx +3 -2
- package/templates/scaffold/system/site/components/governance.tsx +219 -87
- package/templates/scaffold/system/site/components/mdx.tsx +10 -2
- package/templates/scaffold/system/site/components/record-index.tsx +5 -5
- package/templates/scaffold/system/site/components/record-stack.tsx +10 -9
- package/templates/scaffold/system/site/components/record-views.tsx +6 -17
- package/templates/scaffold/system/site/components/sidebar-status.tsx +19 -18
- package/templates/scaffold/system/site/lib/alert-rule.ts +214 -0
- package/templates/scaffold/system/site/lib/attachment-rule.ts +6 -1
- package/templates/scaffold/system/site/lib/attachments.ts +0 -28
- package/templates/scaffold/system/site/lib/audience-rule.ts +15 -21
- package/templates/scaffold/system/site/lib/audience.ts +42 -146
- package/templates/scaffold/system/site/lib/embed-rule.ts +255 -0
- package/templates/scaffold/system/site/lib/governance.ts +339 -225
- package/templates/scaffold/system/site/lib/index-routes.ts +125 -0
- package/templates/scaffold/system/site/lib/lifecycle-rule.ts +52 -0
- package/templates/scaffold/system/site/lib/lock.ts +282 -0
- package/templates/scaffold/system/site/lib/order-rule.ts +37 -0
- package/templates/scaffold/system/site/lib/record-href.ts +68 -0
- package/templates/scaffold/system/site/lib/record-link.tsx +26 -0
- package/templates/scaffold/system/site/lib/rules-version.ts +11 -0
- package/templates/scaffold/system/site/lib/shared.ts +67 -104
- package/templates/scaffold/system/site/lib/sim-rule.ts +49 -0
- package/templates/scaffold/system/site/lib/source.ts +256 -186
- package/templates/scaffold/system/site/lib/stage-knowledge.ts +638 -467
- package/templates/scaffold/system/site/lib/stage-manifest.ts +128 -0
- package/templates/scaffold/system/site/package.json +1 -0
- package/templates/scaffold/system/site/record/actor.ts +23 -0
- package/templates/scaffold/system/site/record/check.ts +571 -0
- package/templates/scaffold/system/site/record/citations.ts +312 -0
- package/templates/scaffold/system/site/record/frontmatter.ts +134 -0
- package/templates/scaffold/system/site/record/git-ledger.ts +171 -0
- package/templates/scaffold/system/site/record/hygiene.ts +320 -0
- package/templates/scaffold/system/site/record/index-file.ts +150 -0
- package/templates/scaffold/system/site/record/index.ts +103 -0
- package/templates/scaffold/system/site/record/instance.ts +257 -0
- package/templates/scaffold/system/site/record/instant.ts +43 -0
- package/templates/scaffold/system/site/record/ledger.ts +694 -0
- package/templates/scaffold/system/site/record/load.ts +129 -0
- package/templates/scaffold/system/site/record/lock.ts +306 -0
- package/templates/scaffold/system/site/record/near-miss.ts +37 -0
- package/templates/scaffold/system/site/record/policy.ts +414 -0
- package/templates/scaffold/system/site/record/profile.ts +535 -0
- package/templates/scaffold/system/site/record/refusal.ts +106 -0
- package/templates/scaffold/system/site/record/yaml-file.ts +103 -0
- package/templates/scaffold/system/site/source.config.ts +109 -23
- package/dist/gateway-api-CmIthmJS-IUA9qS-T.mjs +0 -3225
- package/templates/scaffold/system/site/lib/denial-rule.ts +0 -220
- package/templates/scaffold/system/site/lib/page-order.ts +0 -93
|
@@ -5,31 +5,33 @@ here; every coding agent reads this file first.
|
|
|
5
5
|
|
|
6
6
|
## The two worlds
|
|
7
7
|
|
|
8
|
-
| Path | What it is
|
|
9
|
-
| ------------- |
|
|
10
|
-
| `knowledge/` | **the record** — governed markdown, the owner's world, the product
|
|
11
|
-
| `system/` | **the system** — all code that serves the record
|
|
12
|
-
| `instance.md` | what this SoR is authoritative for; its prose IS the agent surface's system prompt (`ksor serve` wires the body into the MCP server's instructions). Its `name:` is the machine identity (llms.txt, citations) and its
|
|
8
|
+
| Path | What it is |
|
|
9
|
+
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
10
|
+
| `knowledge/` | **the record** — governed markdown, the owner's world, the product |
|
|
11
|
+
| `system/` | **the system** — all code that serves the record |
|
|
12
|
+
| `instance.md` | what this SoR is authoritative for; its prose IS the agent surface's system prompt (`ksor serve` wires the body into the MCP server's instructions). Its `name:` is the machine identity (llms.txt, citations) and its `title:` is the DISPLAY TITLE every page leads with — both are frontmatter keys, both read when the server or build STARTS, so restart `pnpm dev` after changing either (found live 2026-08-18) |
|
|
13
13
|
|
|
14
14
|
The record survives the system: `knowledge/` must stay readable and complete
|
|
15
15
|
even if `system/` is deleted. Dependency flows one way — the system reads the
|
|
16
16
|
record; the record never references the system.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`instance.md` carries a closed key set — `format`, `name`, `ksor`, `site`,
|
|
25
|
-
the optional pair `audiences` + `default_visibility` (the record's reader
|
|
26
|
-
audiences, ordered least- to most-restricted with `public` first, and the one
|
|
27
|
-
a document takes when it names none — declared together or not at all), and
|
|
28
|
-
the four serve-config blocks `database` / `embedding` / `retrieval` / `budgets`
|
|
18
|
+
`instance.md` (`format: 2`) carries a closed key set — `name` (the machine
|
|
19
|
+
identity), `title` (the display title and the root index's heading),
|
|
20
|
+
`description` (one sentence for `llms.txt` and the MCP discovery document),
|
|
21
|
+
`toolchain` (the upgrade stamp), `site`, `mcp_url`, `version`, and the four
|
|
22
|
+
serve-config blocks `database` / `embedding` / `retrieval` / `budgets`
|
|
29
23
|
(present only once you climb to the served MCP rung — see "Serving to agents"
|
|
30
|
-
below; a `pnpm dev`-only project declares none).
|
|
31
|
-
|
|
32
|
-
other key rather
|
|
24
|
+
below; a `pnpm dev`-only project declares none). Who may read what lives in
|
|
25
|
+
`.ksor/governance.yaml`, never here. The body below the frontmatter is the
|
|
26
|
+
MCP server's instructions in full; `pnpm check` names any other key rather
|
|
27
|
+
than ignoring it, and refuses a key repeated inside a group.
|
|
28
|
+
|
|
29
|
+
Authority lives in `.ksor/governance.yaml`: the audiences a document may name
|
|
30
|
+
(`public` is implicit), who may approve a document for `stable`, and who may
|
|
31
|
+
take one down. Takedowns are `.ksor/takedowns.yaml`, an append-only ledger
|
|
32
|
+
written by `ksor takedown` — every entry's actor is checked against the
|
|
33
|
+
policy, so a line appended by hand is refused exactly as the verb would refuse
|
|
34
|
+
it, and a deleted line is refused by `ksor build` against the file's history.
|
|
33
35
|
|
|
34
36
|
## Critical rules
|
|
35
37
|
|
|
@@ -105,9 +107,9 @@ Stand it up in this order (each step's errors explain how to fix themselves):
|
|
|
105
107
|
3. **Bring it up.** Once, then every time:
|
|
106
108
|
|
|
107
109
|
```sh
|
|
108
|
-
pnpm provision
|
|
109
|
-
pnpm refresh
|
|
110
|
-
pnpm serve
|
|
110
|
+
pnpm provision # schema (or migrate) + grant — the privileged acts, run once
|
|
111
|
+
pnpm refresh # build, ingest the record, collect retired generations
|
|
112
|
+
pnpm serve # the MCP server (one supervised process)
|
|
111
113
|
```
|
|
112
114
|
|
|
113
115
|
`provision` is separate on purpose: applying DDL and granting ingest are acts
|
|
@@ -195,14 +197,19 @@ Stand it up in this order (each step's errors explain how to fix themselves):
|
|
|
195
197
|
pnpm exec ksor calibrate --instance instance.md
|
|
196
198
|
```
|
|
197
199
|
|
|
198
|
-
It
|
|
199
|
-
|
|
200
|
+
It ends with a `retrieval:` block for THIS corpus in THIS embedding space,
|
|
201
|
+
ready to paste into instance.md's frontmatter exactly as printed. Paste it
|
|
202
|
+
and restart:
|
|
200
203
|
|
|
201
204
|
```yaml
|
|
202
205
|
retrieval:
|
|
203
|
-
vector_floor: 0.55 #
|
|
206
|
+
vector_floor: 0.55 # calibrated <date> on generation 3, model <model>/d1536, door: synthesized
|
|
207
|
+
floor_digest: 8bfb07d0e6f5
|
|
204
208
|
```
|
|
205
209
|
|
|
210
|
+
If instance.md already has a `retrieval:` block, merge the keys into it — a
|
|
211
|
+
second `retrieval:` is a duplicate key and is refused.
|
|
212
|
+
|
|
206
213
|
Never copy a floor from another corpus — recalibrate, and record the
|
|
207
214
|
measurement beside the number. Writing `vector_floor: uncalibrated` declares
|
|
208
215
|
the intent to gate WITHOUT a measurement, and every serve refuses until a
|
|
@@ -319,27 +326,40 @@ ordinary zod. It is yours, and it is **deletable**: without it the door serves
|
|
|
319
326
|
the same defaults.
|
|
320
327
|
|
|
321
328
|
Edit it because an agent pays for this surface out of its context window, twice.
|
|
322
|
-
|
|
329
|
+
The definitions depend only on the code, so they are exact for every record
|
|
330
|
+
(re-measured 2026-08-25 from the served `tools/list`); the per-call figures are
|
|
331
|
+
the 2026-08-23 measurement against a live 81-document record, plus the
|
|
332
|
+
governance block every hit now carries:
|
|
323
333
|
|
|
324
334
|
| | |
|
|
325
335
|
| -------------------------------- | ------------------------------ |
|
|
326
|
-
| all three tool definitions | ~
|
|
327
|
-
| one `search` at `k=10` (default) | ~
|
|
328
|
-
| one `search` at `k=5` | ~2,
|
|
336
|
+
| all three tool definitions | ~4,054 tokens, always resident |
|
|
337
|
+
| one `search` at `k=10` (default) | ~4,196 tokens per call |
|
|
338
|
+
| one `search` at `k=5` | ~2,330 tokens per call |
|
|
329
339
|
|
|
330
340
|
Three edits pay for themselves:
|
|
331
341
|
|
|
332
342
|
- **Delete a tool nothing calls.** Removing `outline` and `read` gives back
|
|
333
|
-
~
|
|
343
|
+
~2,152 tokens for the whole session.
|
|
334
344
|
- **Say what this record covers**, above `FLOOR.search`. It is how an agent with
|
|
335
345
|
several records attached picks yours; name the subject AND the boundary.
|
|
336
346
|
- **Set `k`** in the input schema — it is the lever on reply size.
|
|
337
347
|
|
|
338
348
|
```ts
|
|
339
349
|
description: `Leave, benefits, conduct. Not product docs.\n\n${FLOOR.search}`,
|
|
340
|
-
inputSchema: z.object({
|
|
350
|
+
inputSchema: z.object({
|
|
351
|
+
query: z.string(),
|
|
352
|
+
k: z.number().int().default(5),
|
|
353
|
+
min_trust_tier: z.enum(TRUST_TIERS).optional(),
|
|
354
|
+
}),
|
|
341
355
|
```
|
|
342
356
|
|
|
357
|
+
Keep `min_trust_tier`. Dropping it does not weaken the record — the handler
|
|
358
|
+
still applies `unverified` and this deployment's own floor — but it takes away
|
|
359
|
+
the only way a caller can ask to be answered ONLY from what a human reviewed,
|
|
360
|
+
and the door says so at boot with a notice naming the tool and the line to
|
|
361
|
+
paste, rather than failing quietly.
|
|
362
|
+
|
|
343
363
|
You can add your own tools with `registerTool` too — but be clear-eyed: ksor
|
|
344
364
|
makes no provenance claim about a tool it did not hand you a handler for.
|
|
345
365
|
|
|
@@ -356,49 +376,94 @@ surface at boot and refuses to start if a guarantee is gone:
|
|
|
356
376
|
|
|
357
377
|
## Withdrawing a document — `ksor takedown`
|
|
358
378
|
|
|
359
|
-
A takedown is the one governance act that must reach EVERY surface at once.
|
|
360
|
-
|
|
361
|
-
|
|
379
|
+
A takedown is the one governance act that must reach EVERY surface at once. It
|
|
380
|
+
is written to the committed ledger `.ksor/takedowns.yaml` FIRST and to the
|
|
381
|
+
database denylist second, in one act — so a record with no database can
|
|
382
|
+
withdraw a document, and the site reads the withdrawal from the repository
|
|
383
|
+
rather than from anything exported.
|
|
362
384
|
|
|
363
385
|
```sh
|
|
364
|
-
pnpm exec ksor takedown --instance instance.md <stable-id> --reason "legal request 2026-08"
|
|
365
|
-
pnpm exec ksor takedown --instance instance.md <stable-id> --reason "..." --subtree
|
|
386
|
+
pnpm exec ksor takedown --instance instance.md --actor human:you <stable-id> --reason "legal request 2026-08"
|
|
387
|
+
pnpm exec ksor takedown --instance instance.md --actor human:you <stable-id> --reason "..." --scope subtree
|
|
366
388
|
pnpm exec ksor takedown --instance instance.md --list # what is currently denied
|
|
367
389
|
pnpm exec ksor takedown --instance instance.md --ledger # who denied what, when
|
|
368
|
-
pnpm exec ksor takedown --instance instance.md --revoke <
|
|
390
|
+
pnpm exec ksor takedown --instance instance.md --actor human:you --revoke <entry-id> --reason "..."
|
|
369
391
|
```
|
|
370
392
|
|
|
371
|
-
The stable id is what a search result reports as `provenance.stable_id
|
|
372
|
-
|
|
373
|
-
section and everything beneath it, including
|
|
374
|
-
|
|
375
|
-
|
|
393
|
+
The stable id is what a search result reports as `provenance.stable_id`, and it
|
|
394
|
+
is `knowledge/<path-without-.md>` — always, since path is identity.
|
|
395
|
+
`--scope subtree` withdraws a section and everything beneath it, including
|
|
396
|
+
documents added later. `--revoke` takes the id of a LEDGER ENTRY, not a stable
|
|
397
|
+
id: the ledger is append-only, so a lift is a new entry rather than a deleted
|
|
398
|
+
line. The id is printed by the denial that created it, listed by `--ledger`,
|
|
399
|
+
and written in `.ksor/takedowns.yaml` — three ways to the same string, none of
|
|
400
|
+
which needs a database. `--removed <entry-id>` records that what a denial
|
|
401
|
+
names was deleted — a document, or at `--scope subtree` the directory — and
|
|
402
|
+
`--apply` writes the rows for entries that reached the database late. Record
|
|
403
|
+
the deletion; do not revoke instead: a revocation says the hold was LIFTED,
|
|
404
|
+
which is a different act, and it stops covering the path if it ever returns.
|
|
405
|
+
|
|
406
|
+
`--actor` names who performed the act, and a denial, a revocation or an
|
|
407
|
+
amendment is REFUSED without it. There is no default: a name taken from the
|
|
376
408
|
environment reads like a person and is whatever the shell happened to be
|
|
377
409
|
(`runner` under CI, `root` in a container), which is worse than no name at all
|
|
378
|
-
in the one row that exists to record who did this.
|
|
379
|
-
(
|
|
410
|
+
in the one row that exists to record who did this. It must be a well-formed
|
|
411
|
+
actor (`human:<handle>` or `process:<id>` — a bare name is refused) and
|
|
412
|
+
`takedown_authorities` in `.ksor/governance.yaml` must name it. The same check
|
|
413
|
+
runs over every entry in the ledger at `pnpm check`, `ksor build` and ingest, so
|
|
414
|
+
a line appended by hand in a pull request is refused exactly as the verb would
|
|
415
|
+
refuse it. The read-only modes
|
|
416
|
+
(`--list`, `--ledger`) need no actor — nobody is performing an act by looking.
|
|
417
|
+
They do not need a database either: on a record that declares none they read
|
|
418
|
+
the committed `.ksor/takedowns.yaml`, which is the whole record of the act
|
|
419
|
+
anyway.
|
|
380
420
|
|
|
381
421
|
**The MCP door stops serving it immediately. The SITE stops at its next
|
|
382
|
-
build** — the site reads
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
surface already refuses.
|
|
422
|
+
build** — the site reads the committed ledger (`.ksor/takedowns.yaml`), not
|
|
423
|
+
the database, and so needs no database access to honour a takedown. After a
|
|
424
|
+
takedown, merge the ledger entry, rebuild and redeploy the site, or the human
|
|
425
|
+
surface keeps publishing what the agent surface already refuses.
|
|
386
426
|
|
|
387
427
|
## Publishing
|
|
388
428
|
|
|
389
429
|
`pnpm build` emits a fully static site (`system/site/out/`) deployable to
|
|
390
430
|
any host — Vercel reads the shipped `vercel.json` (deploy from the repo
|
|
391
431
|
ROOT, never `system/site/`), and every other host just serves the folder.
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
`
|
|
397
|
-
`
|
|
398
|
-
`
|
|
432
|
+
`pnpm build` runs `ksor build` first — every `index.md` regenerated, the
|
|
433
|
+
record checked, `build.lock.json` written (commit it: it is the provenance
|
|
434
|
+
every machine artefact stamps) — and a refusal stops the build before a byte
|
|
435
|
+
is written. `ksor build --strict` additionally refuses an uncommitted input
|
|
436
|
+
(`ksor-build-dirty`), which is the posture for a release.
|
|
437
|
+
`KSOR_BASE_PATH=/repo pnpm build` targets sub-path hosting. With audiences
|
|
438
|
+
registered in `.ksor/governance.yaml`, plain `pnpm build` is the `public`
|
|
439
|
+
viewer; `KSOR_AUDIENCE=public,<audience> pnpm build` — a comma list, always
|
|
440
|
+
including `public` — builds for a wider viewer, and that build belongs
|
|
399
441
|
behind that audience's own access control, never on a public host.
|
|
400
442
|
Details in README → Deploying.
|
|
401
443
|
|
|
444
|
+
**The starter publishes; what the owner writes does not, until they approve
|
|
445
|
+
it.** The five starter documents ship `status: stable`, approved by
|
|
446
|
+
`ksor-starter/KSOR-STAMP-VERSION` — a PRODUCER actor, not a person, and
|
|
447
|
+
`.ksor/governance.yaml` authorises it so the first `pnpm build` emits a record
|
|
448
|
+
with something in it. Two things follow, and both are your job. The samples
|
|
449
|
+
describe KSoR rather than this organisation, so replacing them is the owner's
|
|
450
|
+
first act on this record; and when the last one goes, the producer goes with it
|
|
451
|
+
— delete `ksor-starter/KSOR-STAMP-VERSION` from `approval_authorities`, because
|
|
452
|
+
nothing the owner wrote should be approved by a tool.
|
|
453
|
+
|
|
454
|
+
Everything written after that starts at `status: draft`, and §2.5 admits a
|
|
455
|
+
draft to NO surface of a build — not the page, not the sidebar, not the search
|
|
456
|
+
index, not `llms.txt`. A build of a record whose documents are all drafts emits
|
|
457
|
+
a site with zero document pages and an empty `## Documents` section, reports
|
|
458
|
+
`0 admitted to a machine surface`, and is working correctly. `pnpm dev` shows
|
|
459
|
+
the drafts, which is what the preview is for. Never approve on the owner's
|
|
460
|
+
behalf to make a build look fuller: approving is `status: stable` plus a
|
|
461
|
+
`ksor.approval: { by, at }` naming an actor `.ksor/governance.yaml` authorises,
|
|
462
|
+
and it is the owner's act. Ask them, then write down what they said
|
|
463
|
+
(`.agents/skills/intake-interview/`). `KSOR_DRAFTS=show pnpm build` publishes
|
|
464
|
+
drafts to the HUMAN surface only, marks the build `noindex` and records itself
|
|
465
|
+
in `build.lock.json` — a review link, never a way to ship.
|
|
466
|
+
|
|
402
467
|
### The MCP door is a container
|
|
403
468
|
|
|
404
469
|
The other surface is a live process, so it ships as one. `Dockerfile` and
|
|
@@ -406,9 +471,17 @@ The other surface is a live process, so it ships as one. `Dockerfile` and
|
|
|
406
471
|
|
|
407
472
|
```sh
|
|
408
473
|
docker build -t my-record .
|
|
409
|
-
docker run --rm -p 8080:80 --env-file .env
|
|
474
|
+
docker run --rm -p 8080:80 --env-file .env \
|
|
475
|
+
-e KSOR_AUTH=disabled-public my-record
|
|
410
476
|
```
|
|
411
477
|
|
|
478
|
+
The `-e` is required and must not be dropped: the image sets `$PORT`, so the
|
|
479
|
+
door binds `0.0.0.0` — a PUBLIC bind — and the `KSOR_AUTH=disabled-local` in
|
|
480
|
+
`.env` refuses there, correctly. Never "fix" that refusal by editing `.env`;
|
|
481
|
+
put the deliberate value on the command, so the owner's ordinary `pnpm serve`
|
|
482
|
+
keeps its loopback posture. A real deployment sets it — or the SSO variables —
|
|
483
|
+
in the host's environment, since `.dockerignore` keeps `.env` out of the image.
|
|
484
|
+
|
|
412
485
|
That image runs on Cloud Run, Fly, Render, ECS, Kubernetes or a VPS unchanged.
|
|
413
486
|
`vercel.json` declares BOTH surfaces — a `site` service built from
|
|
414
487
|
`system/site/out/` and a `door` service pointing at that same `Dockerfile` —
|
|
@@ -433,39 +506,82 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
433
506
|
- One document per file under `knowledge/`; the path is the document's
|
|
434
507
|
identity and its URL — ascii lowercase, digits, hyphens; no spaces or
|
|
435
508
|
special characters; no two files differing only in case; never both
|
|
436
|
-
`foo.md` and `foo
|
|
437
|
-
name in any language — the filename is the address, not the name.
|
|
509
|
+
`foo.md` and a folder `foo/`. The `title:` carries the document's real
|
|
510
|
+
name in any language — the filename is the address, not the name. Every
|
|
511
|
+
folder's `index.md` is GENERATED by `ksor build` (an OKF §8 index of the
|
|
512
|
+
folder) and committed — never edit one, never put prose in one; a folder's
|
|
513
|
+
own introduction is a named document inside it, such as `overview.md`.
|
|
514
|
+
`log.md` and `README.md` are reserved names.
|
|
438
515
|
- The frontmatter `title` IS the rendered page heading — never repeat it as
|
|
439
516
|
an `# h1` in the body, and quote any value containing a colon
|
|
440
517
|
(`title: "Note: quoting"`).
|
|
441
|
-
- Frontmatter
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
`
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
`
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
518
|
+
- Frontmatter is the KSoR Profile of OKF (the record spec's §2). Required on
|
|
519
|
+
every document: `type` (`Document` unless the knowledge is a `Policy`,
|
|
520
|
+
`Procedure`, `Control`, `Standard`, `Definition`, `Decision Record`,
|
|
521
|
+
`Example` or `Attested Computation` — those reserved types also require
|
|
522
|
+
`sources` and `ksor.owner`), `title`, `description` (one sentence), `status`
|
|
523
|
+
(`draft | stable | deprecated`) and `ksor.audience` (a list — `[public]`, or
|
|
524
|
+
audiences registered in `.ksor/governance.yaml`; never omitted, never
|
|
525
|
+
inferred). A `stable` document carries `generated: { by, at }` and
|
|
526
|
+
`ksor.approval: { by, at }` by an actor the policy authorises, with
|
|
527
|
+
`generated.at` no later than the approval; a `deprecated` one carries
|
|
528
|
+
`ksor.deprecated: { by, at }` and usually `ksor.superseded_by: <id>`
|
|
529
|
+
(a stable document every reader of this one may read). Optional: `order`
|
|
530
|
+
(reading position), `sources` (`{ id, resource, title }`, cited in the body
|
|
531
|
+
as GFM footnotes `[^id]`), `verified` (`[{ by, at }]` — sets the trust
|
|
532
|
+
tier: none → unverified, machine actors → machine-confirmed, any `human:`
|
|
533
|
+
→ human-reviewed; unlike `ksor.approval`, the policy does not gate WHO may
|
|
534
|
+
appear here — it has no verification family — so a `verified` entry is a
|
|
535
|
+
claim the document makes about itself and the pull request that adds it is
|
|
536
|
+
the only thing standing behind it), `stale_after`, `ksor.effective_from`. Actors are
|
|
537
|
+
`human:<id>`, `process:<id>` or `<producer>/<version>` in `verified`,
|
|
538
|
+
`generated`, `ksor.approval` and `ksor.deprecated` — anything else there is
|
|
539
|
+
refused. `ksor.owner` is not checked for its shape: write an actor or
|
|
540
|
+
`team:<id>` by convention, but it is free text, so a bare word passes and can
|
|
541
|
+
then never be the `ksor.deprecated.by` that deprecates the document. Every
|
|
542
|
+
timestamp is an ISO 8601 instant with an offset (`2026-08-25T09:00:00Z`) —
|
|
543
|
+
never a bare date. Unknown keys are preserved, unless
|
|
544
|
+
the name is one edit from a profile key — `stale_afer:` is refused rather
|
|
545
|
+
than kept, because a preserved near miss is the key it meant, failing open.
|
|
546
|
+
The `ksor:` block's own key set is closed. The pre-profile keys
|
|
547
|
+
`visibility`, `owner`, `provenance`, `effective`, `superseded` and
|
|
548
|
+
`superseded_by` are refused by name and `ksor migrate` moves them; `id` and
|
|
549
|
+
`name` it deletes (the path is the identity); `sor_id` it REFUSES rather
|
|
550
|
+
than drops, because retiring it changes the document's stable id and any
|
|
551
|
+
takedown keyed on the old one must be re-denied against the new one first.
|
|
552
|
+
- **Each page says how long it takes to read**, counted from the document's own
|
|
553
|
+
words when the site is built. Fenced code and frontmatter do not count toward
|
|
554
|
+
it, so a short page carrying a long example is not reported as a long read.
|
|
555
|
+
Nothing to author — it is derived. It sits with the owner and the effective
|
|
556
|
+
date, in the row under the title; a document with a SUMMARY moves it onto
|
|
557
|
+
that view's own strip instead, where it reports the view you are reading
|
|
558
|
+
rather than the document. Each tab names its own figure to a screen reader,
|
|
559
|
+
so the cost of each view is available without switching to it.
|
|
458
560
|
- **The governance keys are rendered, so they are worth filling in.** Each
|
|
459
561
|
page shows its owner and effective date under the title, lists every
|
|
460
|
-
`
|
|
562
|
+
`sources` entry separately at the foot, and — for a deprecated document —
|
|
461
563
|
carries a notice above the title naming its successor and linking to it. A
|
|
462
564
|
key you leave off renders nothing at all: the site never invents a value, so
|
|
463
565
|
a missing owner reads as missing rather than as unowned.
|
|
464
|
-
- **The agent surface carries them too
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
566
|
+
- **The agent surface carries them too — by EXCLUDING what it must not
|
|
567
|
+
hand over.** `llms.txt` and `llms-full.txt` list only what the §2.5 table
|
|
568
|
+
admits to a machine surface: stable, effective, unexpired, undenied. A
|
|
569
|
+
draft, a deprecated document, and one whose `stale_after` had passed **when
|
|
570
|
+
the build ran**, are not entries at all, so an agent is never handed a
|
|
571
|
+
withdrawn document as plain prose. `llms-full.txt` serves each document's own
|
|
572
|
+
frontmatter intact, plus the derived `trust_tier` and this build's stamps —
|
|
573
|
+
so what an agent reads carries the same governance a reader sees on the page.
|
|
574
|
+
- **Those files are a SNAPSHOT, and keeping them true is yours.** A build
|
|
575
|
+
decides admission ONCE, at its own instant, and writes the answer into files;
|
|
576
|
+
static output cannot re-decide itself. So a document whose `stale_after`
|
|
577
|
+
passes AFTER a build keeps appearing in `llms.txt` and in its markdown twin
|
|
578
|
+
until the next build, while `ksor serve` — which evaluates the same rule per
|
|
579
|
+
request — already refuses it. `ksor build` prints the next instant at which
|
|
580
|
+
this goes out of date, and prints which documents it held back and why.
|
|
581
|
+
Nothing here rebuilds on a schedule: the shipped `validate.yml` runs the
|
|
582
|
+
checker on pull requests and `vercel.json` declares no cron. If this record
|
|
583
|
+
uses `stale_after` or `ksor.effective_from`, add a scheduled rebuild and
|
|
584
|
+
redeploy, or accept that the static half is as current as the last build.
|
|
469
585
|
- **Don't want any of it on the published pages?** Set `governance: false`
|
|
470
586
|
under `site:` in `instance.md`. The record keeps every key — the agent
|
|
471
587
|
surface and your audit trail still read them, and `llms.txt`/`llms-full.txt`
|
|
@@ -473,38 +589,38 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
473
589
|
knowing: the home page shows the agent surface VERBATIM in its panel, so the
|
|
474
590
|
keys stay visible there even with this off. That panel's whole claim is that
|
|
475
591
|
it is the bytes an agent is served, and editing them to match a page setting
|
|
476
|
-
would make it lie. Remove the panel if you need the front page silent too.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
`
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
- `
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
592
|
+
would make it lie. Remove the panel if you need the front page silent too.
|
|
593
|
+
What it does not hide is a CAVEAT: the supersession notice stays, and so does
|
|
594
|
+
the chip on a document the record has not brought into force yet or has not
|
|
595
|
+
reviewed since its `stale_after` — a reader handed one of those with no word
|
|
596
|
+
of it has been misled, and the sidebar, the listings and the search results
|
|
597
|
+
say it whatever this key is set to.
|
|
598
|
+
- **`status` is shown only when it is a caveat.** `deprecated` appears as a
|
|
599
|
+
small label; `stable` shows nothing, because a reader already assumes a
|
|
600
|
+
document in the record is current — so the label stays rare enough to be
|
|
601
|
+
noticed on the pages where it matters. `draft` carries the same label, but
|
|
602
|
+
only under `pnpm dev`: a BUILT site has no draft page to label it on.
|
|
603
|
+
- `ksor.audience` lists who may read a document; a viewer holds a list that
|
|
604
|
+
always includes `public`, and the document is visible when the two overlap.
|
|
605
|
+
Every identifier but `public` must be registered in
|
|
606
|
+
`.ksor/governance.yaml` — an unregistered one is refused, because a typo
|
|
607
|
+
reads as a restriction. `pnpm check` refuses any link, `ksor.superseded_by`
|
|
608
|
+
pointer or summary that reaches a document not every reader of the source
|
|
609
|
+
may read (`ksor-link-widens`) — the leak no single build can catch, because
|
|
610
|
+
the build that publishes the link has already dropped its target.
|
|
494
611
|
|
|
495
612
|
**Publication, not authorship: anyone who can clone the repository reads
|
|
496
613
|
every document regardless of frontmatter; if someone must not read a
|
|
497
614
|
document and can clone, the answer is a second repository.**
|
|
498
615
|
|
|
499
|
-
- A replaced document is marked `status:
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
`
|
|
506
|
-
|
|
507
|
-
refused.
|
|
616
|
+
- A replaced document is marked `status: deprecated` with
|
|
617
|
+
`ksor.deprecated: { by, at }` (a takedown authority, or the owner an
|
|
618
|
+
`ownership:` rule in `.ksor/governance.yaml` resolves — `ksor.owner` on the
|
|
619
|
+
document is not that owner, and this record's policy declares no rule yet) and
|
|
620
|
+
`ksor.superseded_by:` naming its successor by id (`policies/refunds-v2`) —
|
|
621
|
+
deprecated documents are never deleted. The successor must exist, be
|
|
622
|
+
`stable`, and be readable by every reader of this document, or the pointer
|
|
623
|
+
strands them (`ksor-supersession-strands`).
|
|
508
624
|
- Images and assets live in `knowledge/` beside the document that uses them,
|
|
509
625
|
referenced by relative links. A relative link must never leave `knowledge/`.
|
|
510
626
|
- **Study attachments.** A document may carry four optional companions named
|
|
@@ -520,8 +636,9 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
520
636
|
|
|
521
637
|
An attachment is **part of its document**, not a document. It has no URL of
|
|
522
638
|
its own, no sidebar row, no line in `llms.txt`, and no identity an agent can
|
|
523
|
-
cite — so
|
|
524
|
-
|
|
639
|
+
cite — so a summary's frontmatter is exactly `type: Summary` and nothing
|
|
640
|
+
else (the checker refuses any other key), and every attachment takes its
|
|
641
|
+
audience and any takedown from its parent. Restrict the
|
|
525
642
|
document and its summary and deck go with it; there is no way to publish a
|
|
526
643
|
summary more widely than the document it summarises. An attachment whose
|
|
527
644
|
document is missing is refused, by `pnpm check` and by `pnpm build` alike.
|
|
@@ -582,9 +699,9 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
582
699
|
from at random and offers another round; a smaller one is simply asked in
|
|
583
700
|
full.
|
|
584
701
|
|
|
585
|
-
**`pnpm
|
|
586
|
-
|
|
587
|
-
it is easy to trip by accident:
|
|
702
|
+
**`pnpm build` refuses a quiz a reader could pass without reading** (the
|
|
703
|
+
audit runs in the site build, not in `pnpm check`), and this is worth knowing
|
|
704
|
+
before you write one, because it is easy to trip by accident:
|
|
588
705
|
|
|
589
706
|
| Refusal | Means |
|
|
590
707
|
| -------------------------- | ----------------------------------------------------- |
|
|
@@ -664,15 +781,71 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
664
781
|
renders in the site's accent. The list lives in `system/site/app/global.css`
|
|
665
782
|
and is yours — adding a tool is one colour and one mark.
|
|
666
783
|
|
|
784
|
+
- **A passage the reader must not miss, as a CALLOUT.** GitHub's alert syntax —
|
|
785
|
+
a blockquote whose first line is the kind in brackets:
|
|
786
|
+
|
|
787
|
+
```markdown
|
|
788
|
+
> [!WARNING]
|
|
789
|
+
> This threshold changed in March. The figure below is the old one.
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
Five kinds: `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`. The site tints
|
|
793
|
+
the panel in that kind's colour and rules its left edge; every other reader
|
|
794
|
+
of the record — GitHub, a plain editor, `/md/`, `llms-full.txt` — shows an
|
|
795
|
+
ordinary blockquote carrying a visible label, so nothing is lost and nothing
|
|
796
|
+
becomes punctuation.
|
|
797
|
+
|
|
798
|
+
**Not `:::warning`.** That form is a dialect: a record written in one renders
|
|
799
|
+
as literal colons in every reader except the one site that understands it.
|
|
800
|
+
|
|
801
|
+
- **A long line is the reader's to unwrap.** Nothing to author — a fenced block
|
|
802
|
+
wider than the column gets a button beside its copy button that wraps it, and
|
|
803
|
+
a block that fits gets no button at all.
|
|
804
|
+
- **Something running, as an embed.** A document that wants to show a page in
|
|
805
|
+
motion — a simulation, a player, a dashboard — links to it and gives the link
|
|
806
|
+
the title `embed`:
|
|
807
|
+
|
|
808
|
+
```markdown
|
|
809
|
+
[Play run-until-done](goal-loop.sim.html "embed")
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
Still CommonMark: a link title is a tooltip everywhere else, so GitHub, a
|
|
813
|
+
plain editor, `/md/` and `llms-full.txt` all show the author's link. Nothing
|
|
814
|
+
loads until a reader clicks, which is what keeps a built page free of
|
|
815
|
+
external requests — and the panel names what it is about to reach, so the
|
|
816
|
+
click is informed.
|
|
817
|
+
|
|
818
|
+
**You do not state a height.** A page carried in the record is measured, so
|
|
819
|
+
the frame is exactly as tall as what it holds — on this record's own seven,
|
|
820
|
+
to the pixel. A number written into a document would be a number some other
|
|
821
|
+
measure makes wrong.
|
|
822
|
+
|
|
823
|
+
**Carry the page in where you can.** A file named `<name>.sim.html`, sitting
|
|
824
|
+
beside its document exactly like a figure, is published by the build and
|
|
825
|
+
served from this site — so it works offline, tells nobody outside what
|
|
826
|
+
someone is reading, and is versioned with the document instead of changing
|
|
827
|
+
under it. It is an ASSET, not an attachment: named freely, as many per
|
|
828
|
+
document as the prose needs.
|
|
829
|
+
|
|
830
|
+
An `https:` link works too, for a page you cannot carry. It is the weaker
|
|
831
|
+
option for a reason worth knowing: many hosts send `X-Frame-Options:
|
|
832
|
+
SAMEORIGIN`, which forbids any other site from framing them, and a browser
|
|
833
|
+
enforces that whatever this record does. Check before you rely on one.
|
|
834
|
+
|
|
667
835
|
- Copy load-bearing values (numbers, thresholds, dates) exactly from their
|
|
668
|
-
source,
|
|
836
|
+
source, name the source in `sources`, and cite it from the claim with a GFM
|
|
837
|
+
footnote whose label is that source's `id`.
|
|
669
838
|
|
|
670
839
|
### Structuring the record
|
|
671
840
|
|
|
672
|
-
- A folder per topic
|
|
673
|
-
|
|
841
|
+
- A folder per topic. Its `index.md` is GENERATED by `ksor build` — an OKF §8
|
|
842
|
+
map of the folder — so a folder's own introduction is a named document
|
|
843
|
+
inside it, such as `overview.md`, and the folder sorts where its
|
|
844
|
+
lowest-`order:` document does.
|
|
674
845
|
- Sidebar position is the governed `order:` key: documents that declare it come
|
|
675
|
-
first, ascending; the rest follow in name order.
|
|
846
|
+
first, ascending; the rest follow in name order. Folders sit in that SAME
|
|
847
|
+
list, not after it — so a folder can sit between two documents, and where it
|
|
848
|
+
sits is decided by the `order:` of the documents inside it.
|
|
676
849
|
- One order drives every surface — the sidebar, `llms.txt`, the home page's
|
|
677
850
|
first-document link, and the MCP `outline` tool an agent reads to decide what
|
|
678
851
|
to read first. Set it once and they agree. The door picks up a reorder at the
|
|
@@ -687,8 +860,9 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
687
860
|
- `.agents/skills/add-sources/` — turn source material (documents, pages,
|
|
688
861
|
notes) into governed knowledge.
|
|
689
862
|
- `.agents/skills/make-slides/` — generate a presentation from one document
|
|
690
|
-
- `.agents/skills/make-summary/` — write a document's summary and attach it
|
|
691
863
|
and attach it, so it renders on that document's page.
|
|
864
|
+
- `.agents/skills/make-summary/` — write a document's summary and attach it,
|
|
865
|
+
so it renders as a second tab on that document's page.
|
|
692
866
|
- `.agents/skills/format-checker/` — the rules above, as a program;
|
|
693
867
|
`pnpm check` runs it and its errors explain how to fix themselves.
|
|
694
868
|
|
|
@@ -696,7 +870,7 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
696
870
|
|
|
697
871
|
You own `system/site/` outright — these are the seams, cheapest first:
|
|
698
872
|
|
|
699
|
-
- **Display title** — `instance.md`'s
|
|
873
|
+
- **Display title** — `instance.md`'s `title:` key (the intake interview
|
|
700
874
|
writes it). Headline, navbar, and browser title follow on restart.
|
|
701
875
|
- **Accent color** — the one brand pair in `system/site/app/global.css`
|
|
702
876
|
(`--primary` and `--primary-foreground`, light and dark); every accented
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
#
|
|
9
9
|
# Build and run it anywhere:
|
|
10
10
|
# docker build -t my-record .
|
|
11
|
-
# docker run --rm -p 8080:80 --env-file .env my-record
|
|
11
|
+
# docker run --rm -p 8080:80 --env-file .env -e KSOR_AUTH=disabled-public my-record
|
|
12
|
+
#
|
|
13
|
+
# The -e is not optional. ENV PORT below makes this a PUBLIC bind (0.0.0.0), and
|
|
14
|
+
# the KSOR_AUTH=disabled-local a local .env carries refuses there on purpose —
|
|
15
|
+
# see the CMD comment. Keep the deliberate value on the command, not in .env.
|
|
12
16
|
#
|
|
13
17
|
# This image serves; it does not publish. `ksor ingest` is a write plane that
|
|
14
18
|
# runs from CI or your machine against the same database — see the deployment
|