@panaversity/ksor 0.0.44 → 0.0.46
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 +108 -0
- package/dist/cli.mjs +1 -0
- package/docs/authorization.md +55 -2
- package/docs/deploying.md +28 -0
- package/package.json +1 -1
- package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +69 -43
- package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +69 -43
- package/templates/scaffold/README.md +512 -287
- package/templates/scaffold/package.json +1 -0
- package/templates/scaffold/system/site/preview.mjs +93 -0
|
@@ -3,15 +3,40 @@
|
|
|
3
3
|
A **Knowledge System of Record**: the governed source of knowledge this
|
|
4
4
|
project's people and AI agents operate from.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Three things live here, and they are not the same kind of thing:
|
|
7
7
|
|
|
8
8
|
- **`knowledge/` — the record.** Plain governed markdown (plus the optional
|
|
9
9
|
study attachments a document may carry). Yours forever, readable anywhere,
|
|
10
10
|
portable without this repository's code.
|
|
11
|
-
- **`system/` — the system.** The site
|
|
11
|
+
- **`system/` — the system.** The site, and later the services, that serve the
|
|
12
12
|
record. Replaceable machinery.
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
- **`.ksor/governance.yaml` — the authority.** Which audiences exist, who may
|
|
14
|
+
approve a document, who may take one down. Every governance act is checked
|
|
15
|
+
against it, so the record never claims authority nobody granted.
|
|
16
|
+
|
|
17
|
+
The point of all this is the **agent interface**: an MCP door that answers from
|
|
18
|
+
the record with citations, and declines — in as many words — when the record
|
|
19
|
+
does not cover the question. That refusal is the feature; an agent that
|
|
20
|
+
improvises over a gap is the thing a system of record exists to prevent.
|
|
21
|
+
Beside it, the **human interface** is a site your readers browse. Same record,
|
|
22
|
+
same governance, different machinery: the site is files, the door is a process,
|
|
23
|
+
and they deploy separately.
|
|
24
|
+
|
|
25
|
+
**Contents**
|
|
26
|
+
|
|
27
|
+
- [Quick start](#quick-start)
|
|
28
|
+
- [Explore the human interface](#explore-the-human-interface)
|
|
29
|
+
- [Serving to agents](#serving-to-agents)
|
|
30
|
+
- [Quick deployment](#quick-deployment)
|
|
31
|
+
- [Make the record yours: replace the starters](#make-the-record-yours-replace-the-starters)
|
|
32
|
+
- [Writing knowledge](#writing-knowledge)
|
|
33
|
+
- [Deploying](#deploying)
|
|
34
|
+
- [Reference](#reference) — [commands](#commands) · [the files](#the-files-explained) · [when something refuses you](#when-something-refuses-you) · [dependencies](#dependencies-and-advisories)
|
|
35
|
+
- [Ownership](#ownership)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick start
|
|
15
40
|
|
|
16
41
|
```sh
|
|
17
42
|
pnpm install
|
|
@@ -19,155 +44,181 @@ pnpm dev # browse the knowledge at http://localhost:3000
|
|
|
19
44
|
```
|
|
20
45
|
|
|
21
46
|
<!-- ksor:pm pnpm -->
|
|
47
|
+
|
|
22
48
|
No pnpm? Run `npm install -g pnpm` — or `corepack enable pnpm` on Node
|
|
23
49
|
versions that bundle corepack.
|
|
50
|
+
|
|
24
51
|
<!-- /ksor:pm -->
|
|
25
|
-
The first `pnpm install` also fetches the
|
|
26
|
-
`ksor` tool (pinned in `package.json`) and writes it into your lockfile —
|
|
27
|
-
commit the updated lockfile.
|
|
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
52
|
|
|
54
|
-
|
|
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
|
-
```
|
|
53
|
+
**Commit the lockfile your first install writes.**
|
|
61
54
|
|
|
62
|
-
|
|
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.
|
|
55
|
+
<!-- ksor:pm pnpm -->
|
|
68
56
|
|
|
69
|
-
|
|
57
|
+
The committed `pnpm-lock.yaml` covers the site but not `@panaversity/ksor`
|
|
58
|
+
itself: the version pinned in `package.json` was stamped by the CLI that
|
|
59
|
+
scaffolded this project and could not be resolved before that happened. Your
|
|
60
|
+
first `pnpm install` writes it in. Commit the result before you push — pnpm
|
|
61
|
+
turns on `--frozen-lockfile` automatically whenever `CI` is set, so an
|
|
62
|
+
uncommitted pin fails your CI rather than warning you. (`vercel.json` already
|
|
63
|
+
installs with `--no-frozen-lockfile`, and the shipped `validate.yml` runs no
|
|
64
|
+
install.)
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
<!-- /ksor:pm -->
|
|
67
|
+
<!-- ksor:pm npm -->
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
No lockfile ships with this scaffold: npm keeps ONE lock for the whole
|
|
70
|
+
workspace, and the `@panaversity/ksor` version pinned in `package.json` was
|
|
71
|
+
stamped by the CLI that scaffolded this project — it could not be resolved into
|
|
72
|
+
a lock before it existed. Your FIRST `npm install` writes `package-lock.json`;
|
|
73
|
+
run it before you push, and COMMIT the result — that lock is why two machines
|
|
74
|
+
build the same site.
|
|
77
75
|
|
|
78
|
-
|
|
79
|
-
`
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
introduction: click through it inline, or **Present** for fullscreen.
|
|
84
|
-
Presenter notes stay off the screen.
|
|
85
|
-
|
|
86
|
-
The slides live in the record, so they are reviewed in the same pull request
|
|
87
|
-
as the document, versioned with it, and withdrawn when it is withdrawn. There
|
|
88
|
-
is no third party and no link to rot. If you already keep a deck in Google
|
|
89
|
-
Slides, Canva or SlideShare you can point at it instead — `slides.url:` rather
|
|
90
|
-
than `deck:` — and the page will offer it as a link with a frame the reader
|
|
91
|
-
loads on click, so nothing is requested from the host until somebody asks.
|
|
76
|
+
One honest difference from the pnpm scaffold: pnpm quarantines newly published
|
|
77
|
+
dependency versions for 48 hours (`minimumReleaseAge`), so a routine install
|
|
78
|
+
never picks up a day-zero compromised release. npm has no equivalent — the
|
|
79
|
+
`.npmrc` here carries the install-script denial half of that posture, and this
|
|
80
|
+
sentence is the disclosure of the half it cannot.
|
|
92
81
|
|
|
93
|
-
|
|
82
|
+
<!-- /ksor:pm -->
|
|
83
|
+
<!-- ksor:pm bun -->
|
|
94
84
|
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
No lockfile ships with this scaffold: the `@panaversity/ksor` version pinned in
|
|
86
|
+
`package.json` was stamped by the CLI that scaffolded this project — it could
|
|
87
|
+
not be resolved into a lock before it existed. Your FIRST `bun install` writes
|
|
88
|
+
`bun.lock`; run it before you push, and COMMIT the result — that lock is why two
|
|
89
|
+
machines build the same site.
|
|
97
90
|
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
One honest difference from the pnpm scaffold: pnpm quarantines newly published
|
|
92
|
+
dependency versions for 48 hours (`minimumReleaseAge`), so a routine install
|
|
93
|
+
never picks up a day-zero compromised release. bun has no equivalent — its
|
|
94
|
+
default refusal of dependency install scripts covers the OTHER half of that
|
|
95
|
+
posture, and this sentence is the disclosure.
|
|
96
|
+
|
|
97
|
+
<!-- /ksor:pm -->
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Explore the human interface
|
|
102
|
+
|
|
103
|
+
Before changing anything, get a feel for how the record behaves. With
|
|
104
|
+
`pnpm dev` running:
|
|
105
|
+
|
|
106
|
+
- **Edit a starter document.** Change its body, save, watch the page update.
|
|
107
|
+
- **Add a new document.** It appears on the dev site, marked — and it would be
|
|
108
|
+
on no built surface at all, because a new document is `status: draft`. No
|
|
109
|
+
page, no sidebar row, no `/llms.txt` entry. That is the system working, not a
|
|
110
|
+
broken build.
|
|
111
|
+
- **Ask your coding agent to do the same.** `AGENTS.md` carries the working
|
|
112
|
+
rules; read it before you change how documents are written here. The kit in
|
|
113
|
+
`.agents/skills/` already knows this project: `intake-interview` (define the
|
|
114
|
+
record with you), `add-sources` (turn source material into governed
|
|
115
|
+
documents), `make-slides`, `make-summary`, and `format-checker` (the rules,
|
|
116
|
+
as a program — also what `pnpm check` runs).
|
|
117
|
+
|
|
118
|
+
**Treat the starters as scratch paper.** They ship approved by
|
|
119
|
+
`ksor-starter/KSOR-STAMP-VERSION` — a tool, not a person. Edit the body and that
|
|
120
|
+
approval stays stamped on text nobody reviewed, and `pnpm check` will not catch
|
|
121
|
+
it, because the frontmatter is still internally consistent. So explore in them
|
|
122
|
+
freely, but start nothing you intend to keep in one of them. Replacing them
|
|
123
|
+
properly is [below](#make-the-record-yours-replace-the-starters).
|
|
124
|
+
|
|
125
|
+
To see what a build actually produces:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
pnpm build # the static site, into system/site/out/
|
|
129
|
+
pnpm preview # serve exactly those bytes
|
|
100
130
|
```
|
|
101
131
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
used it believes they have the whole document. It reports what it left out
|
|
107
|
-
because the document did not support it.
|
|
132
|
+
There is no `start` script, and that is not an omission: the site is a static
|
|
133
|
+
export, so nothing serves it at runtime. `pnpm preview` is `node:http` and
|
|
134
|
+
nothing else — no dependency, no network fetch — so it works offline and behind
|
|
135
|
+
a firewall, like the build itself.
|
|
108
136
|
|
|
109
|
-
|
|
110
|
-
sidebar row, no line in `llms.txt`, and it takes its governance from its
|
|
111
|
-
parent. Ask for one only where there is something to compress — under about two
|
|
112
|
-
screens, a summary that restates the page teaches readers the tab is not worth
|
|
113
|
-
opening, and the skill will say so rather than write one.
|
|
137
|
+
---
|
|
114
138
|
|
|
115
|
-
|
|
139
|
+
## Serving to agents
|
|
116
140
|
|
|
117
|
-
The record's other surface is an MCP
|
|
118
|
-
knowledge, cited, with
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
and `pnpm provision` refuses, naming the config step 1 writes.
|
|
141
|
+
**This is the point of the whole thing.** The record's other surface is an MCP
|
|
142
|
+
server: the same knowledge, cited, with a measured floor under which it
|
|
143
|
+
declines. An agent connected to it answers from your governed documents or says
|
|
144
|
+
the record does not cover the question — and never quietly fills the gap from
|
|
145
|
+
its training.
|
|
123
146
|
|
|
124
|
-
|
|
125
|
-
|
|
147
|
+
It asks for two things `pnpm dev` does not: a Postgres store with pgvector, and
|
|
148
|
+
an embedding provider key. Three steps, and the order is load-bearing — the
|
|
149
|
+
command block is last because it needs both of the things above it.
|
|
150
|
+
|
|
151
|
+
### 1. Nothing to configure
|
|
152
|
+
|
|
153
|
+
`instance.md` already names the VARIABLE holding your DSN — never the DSN
|
|
154
|
+
itself:
|
|
126
155
|
|
|
127
156
|
```yaml
|
|
128
157
|
database:
|
|
129
158
|
dsn_env: KSOR_DB_URL
|
|
130
159
|
```
|
|
131
160
|
|
|
132
|
-
|
|
133
|
-
`
|
|
134
|
-
|
|
135
|
-
`retrieval:` out starts you with the abstention gate off and honest about it
|
|
136
|
-
(turn it on afterwards with `ksor calibrate`, once the record is serving).
|
|
161
|
+
That is the whole required config. `embedding:` defaults to Gemini at 1536
|
|
162
|
+
dimensions, and `retrieval:` is written for you by step 3's `calibrate`. Change
|
|
163
|
+
the variable name here only if you want a different one.
|
|
137
164
|
|
|
138
|
-
|
|
139
|
-
and `KSOR_AUTH=disabled-local`:
|
|
165
|
+
### 2. Fill in the environment
|
|
140
166
|
|
|
141
167
|
```sh
|
|
142
168
|
cp .env.example .env
|
|
143
169
|
```
|
|
144
170
|
|
|
171
|
+
Then set `KSOR_DB_URL`, `GEMINI_API_KEY` and `KSOR_AUTH=disabled-local`.
|
|
172
|
+
|
|
173
|
+
**Point `KSOR_DB_URL` at a hosted Postgres now if you intend to deploy** — the
|
|
174
|
+
same one your host will use. [Neon](https://neon.com) is what this project's own
|
|
175
|
+
docs are measured against; pgvector is on every plan including the free one, and
|
|
176
|
+
you enable it once per database with `CREATE EXTENSION IF NOT EXISTS vector;`.
|
|
177
|
+
Anything with pgvector works. [Quick deployment](#quick-deployment) explains why
|
|
178
|
+
one shared database saves you a step.
|
|
179
|
+
|
|
145
180
|
`ksor` reads `.env` automatically — there is nothing to export, and where a
|
|
146
|
-
refusal tells you to _export_
|
|
181
|
+
refusal tells you to _export_ a variable, putting it in `.env` is the same
|
|
147
182
|
thing. `KSOR_AUTH=disabled-local` is required for a local run: serve refuses to
|
|
148
183
|
boot unauthenticated on purpose, so a server is never open by accident.
|
|
149
184
|
|
|
150
|
-
|
|
185
|
+
### 3. Bring it up
|
|
151
186
|
|
|
152
187
|
```sh
|
|
153
188
|
pnpm provision # once: apply the schema, authorize ingest
|
|
154
189
|
pnpm refresh # build, ingest the record, collect retired generations
|
|
155
|
-
pnpm
|
|
190
|
+
pnpm exec ksor calibrate --instance instance.md # measure the floor; paste the printed block into instance.md
|
|
191
|
+
pnpm serve # the MCP server, with the gate already on
|
|
156
192
|
```
|
|
157
193
|
|
|
158
194
|
`pnpm provision` runs once — it applies the schema (or migrates it forward) and
|
|
159
|
-
authorizes ingest, the two privileged acts that should not happen on every
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
195
|
+
authorizes ingest, the two privileged acts that should not happen on every boot.
|
|
196
|
+
After that, `pnpm refresh` publishes what you have edited and `pnpm serve` runs
|
|
197
|
+
the server. They are separate because **publishing is an act, not a side effect
|
|
198
|
+
of starting a process.**
|
|
199
|
+
|
|
200
|
+
**`calibrate` is what makes "not in this corpus" a real answer**, and it belongs
|
|
201
|
+
here rather than later: it needs an ingested corpus but no running server, and
|
|
202
|
+
the floor is read when the door boots — so measuring before `pnpm serve` means
|
|
203
|
+
the door comes up gated the first time. It prints a `retrieval:` block for THIS
|
|
204
|
+
corpus in THIS embedding space; paste it into `instance.md` exactly as printed.
|
|
205
|
+
Never copy a floor from another corpus.
|
|
206
|
+
|
|
207
|
+
It also prints its own caveat, and it is worth reading: the probes it writes are
|
|
208
|
+
derived from your passages, so they share vocabulary a real question will not.
|
|
209
|
+
The floor it reports is an upper bound on separation until you check it against
|
|
210
|
+
questions the corpus did not write (`--queries-file`).
|
|
211
|
+
|
|
212
|
+
A rerun on an unchanged record costs nothing: no new generation, no embedding,
|
|
213
|
+
no rows. Edit a document and the next run picks up exactly that change.
|
|
214
|
+
|
|
215
|
+
`AGENTS.md` → "Serving to agents" is the full runbook; your coding agent reads
|
|
216
|
+
it first. A public bind needs a configured SSO door rather than
|
|
217
|
+
`disabled-local` — see [The agent surface deploys
|
|
218
|
+
separately](#the-agent-surface-deploys-separately). Any other operation is
|
|
168
219
|
`pnpm exec ksor <verb>`.
|
|
169
220
|
|
|
170
|
-
### Test the
|
|
221
|
+
### Test the door with an actual agent
|
|
171
222
|
|
|
172
223
|
The MCP door is meant to be read by agents, so check it with one rather than
|
|
173
224
|
with `curl`. With `pnpm serve` running, write `.mcp.json` at the repo root:
|
|
@@ -183,6 +234,12 @@ with `curl`. With `pnpm serve` running, write `.mcp.json` at the repo root:
|
|
|
183
234
|
}
|
|
184
235
|
```
|
|
185
236
|
|
|
237
|
+
**If you skipped `calibrate`, expect answers where this test wants refusals** —
|
|
238
|
+
the gate is off until a floor is measured, which the server says at boot
|
|
239
|
+
(`abstain OFF`) and in every search envelope (`gate: "off"`). That is honest,
|
|
240
|
+
not broken: a floor nobody measured would be a number pretending to be a
|
|
241
|
+
guarantee.
|
|
242
|
+
|
|
186
243
|
Open a new session of your coding agent, confirm it lists the server, then ask
|
|
187
244
|
it three questions **in this order** — the order is the test:
|
|
188
245
|
|
|
@@ -191,198 +248,215 @@ it three questions **in this order** — the order is the test:
|
|
|
191
248
|
arrive with a citation.
|
|
192
249
|
2. Something **adjacent but not covered** — your record's own subject area, a
|
|
193
250
|
question it genuinely does not answer. It should decline.
|
|
194
|
-
3. Something far outside the record. It should decline, and must not answer
|
|
195
|
-
|
|
251
|
+
3. Something far outside the record. It should decline, and must not answer from
|
|
252
|
+
its own knowledge.
|
|
196
253
|
|
|
197
254
|
Question 2 is the one that matters. Anything can answer questions it has the
|
|
198
255
|
text for; refusing a plausible near-miss is the property that makes a system of
|
|
199
256
|
record worth trusting, and it is the one that breaks quietly.
|
|
200
257
|
|
|
201
|
-
|
|
202
|
-
The abstention gate is off until you measure a floor for this corpus, which the
|
|
203
|
-
server says out loud at boot (`abstain OFF`) and in every search envelope
|
|
204
|
-
(`gate: "off"`). Run `pnpm exec ksor calibrate --instance instance.md` first if
|
|
205
|
-
you want to test refusal. Delete `.mcp.json`, or keep it — it holds no secret.
|
|
258
|
+
Delete `.mcp.json`, or keep it — it holds no secret.
|
|
206
259
|
|
|
207
|
-
|
|
208
|
-
the agent kit in `.agents/skills/` knows how to interview you
|
|
209
|
-
(`intake-interview`), convert your source material (`add-sources`), and keep
|
|
210
|
-
the record well-formed (`format-checker`, also `pnpm check`).
|
|
260
|
+
---
|
|
211
261
|
|
|
212
|
-
|
|
262
|
+
## Quick deployment
|
|
213
263
|
|
|
214
|
-
|
|
215
|
-
The committed `pnpm-lock.yaml` covers the site. It cannot cover
|
|
216
|
-
`@panaversity/ksor` itself, because the version pinned in `package.json` is
|
|
217
|
-
stamped by the CLI that scaffolded this project and could not be resolved before
|
|
218
|
-
that happened. So your FIRST `pnpm install` writes it — run it before you push,
|
|
219
|
-
and commit the result.
|
|
220
|
-
|
|
221
|
-
The deploy config already accounts for this (`vercel.json` installs with
|
|
222
|
-
`--no-frozen-lockfile`), and the shipped `validate.yml` runs no install. If you
|
|
223
|
-
add CI of your own, note that pnpm turns on `--frozen-lockfile` automatically
|
|
224
|
-
whenever `CI` is set.
|
|
225
|
-
<!-- /ksor:pm -->
|
|
226
|
-
<!-- ksor:pm npm -->
|
|
227
|
-
No lockfile ships with this scaffold: npm keeps ONE lock for the whole
|
|
228
|
-
workspace, and the `@panaversity/ksor` version pinned in `package.json` was
|
|
229
|
-
stamped by the CLI that scaffolded this project — it could not be resolved
|
|
230
|
-
into a lock before it existed. Your FIRST `npm install` writes
|
|
231
|
-
`package-lock.json`; run it before you push, and COMMIT the result — that
|
|
232
|
-
lock is why two machines build the same site.
|
|
233
|
-
|
|
234
|
-
One honest difference from the pnpm scaffold: pnpm quarantines newly
|
|
235
|
-
published dependency versions for 48 hours (`minimumReleaseAge`), so a
|
|
236
|
-
routine install never picks up a day-zero compromised release. npm has no
|
|
237
|
-
equivalent — `.npmrc` here carries the install-script denial half of that
|
|
238
|
-
posture, and this sentence is the disclosure of the half it cannot.
|
|
239
|
-
<!-- /ksor:pm -->
|
|
240
|
-
<!-- ksor:pm bun -->
|
|
241
|
-
No lockfile ships with this scaffold: the `@panaversity/ksor` version pinned
|
|
242
|
-
in `package.json` was stamped by the CLI that scaffolded this project — it
|
|
243
|
-
could not be resolved into a lock before it existed. Your FIRST
|
|
244
|
-
`bun install` writes `bun.lock`; run it before you push, and COMMIT the
|
|
245
|
-
result — that lock is why two machines build the same site.
|
|
246
|
-
|
|
247
|
-
One honest difference from the pnpm scaffold: pnpm quarantines newly
|
|
248
|
-
published dependency versions for 48 hours (`minimumReleaseAge`), so a
|
|
249
|
-
routine install never picks up a day-zero compromised release. bun has no
|
|
250
|
-
equivalent (its default refusal of dependency install scripts covers the
|
|
251
|
-
OTHER half of that posture), and this sentence is the disclosure.
|
|
252
|
-
<!-- /ksor:pm -->
|
|
264
|
+
Both surfaces on one domain, in about ten minutes:
|
|
253
265
|
|
|
254
|
-
|
|
266
|
+
1. **Push the repository to GitHub.**
|
|
267
|
+
2. **Import it in Vercel**, then **set Root Directory to `./`.** Vercel
|
|
268
|
+
auto-fills it with `system/site`, because that is where it finds a framework
|
|
269
|
+
— and the build then reads `system/site/vercel.json`, which does not exist,
|
|
270
|
+
and fails with `Project framework is set to "services", but no services are
|
|
271
|
+
declared`. The services ARE declared, in `vercel.json` at the repo root,
|
|
272
|
+
which is the only place they can be: one builds the site, the other builds a
|
|
273
|
+
container from the root `Dockerfile`.
|
|
274
|
+
3. **Set three environment variables** in Vercel: `KSOR_DB_URL`,
|
|
275
|
+
`GEMINI_API_KEY`, and `KSOR_AUTH=disabled-public`.
|
|
255
276
|
|
|
256
|
-
|
|
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 -->
|
|
277
|
+
Two things catch people here, and both are the system being deliberate:
|
|
270
278
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
`pnpm
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
`
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
288
|
-
## The files, explained
|
|
279
|
+
- **`disabled-local` will not deploy.** The container sets `$PORT`, so the door
|
|
280
|
+
binds `0.0.0.0` — a PUBLIC bind — and refuses that value by design, saying so
|
|
281
|
+
in as many words. `disabled-public` is you saying you know the door is
|
|
282
|
+
reachable from outside itself. It belongs in Vercel's environment, not in your
|
|
283
|
+
`.env`, so your local `pnpm serve` keeps its loopback posture. It is a
|
|
284
|
+
starting posture, not a destination: [secure it
|
|
285
|
+
properly](#audiences-decide-what-a-build-contains) with the SSO variables once
|
|
286
|
+
the thing is up.
|
|
287
|
+
- **Point `KSOR_DB_URL` at a Postgres your host can reach.** Deploying does not
|
|
288
|
+
publish — the door serves whatever generation is already in the database. If
|
|
289
|
+
your DSN pointed at a database on your laptop, the site comes up fine and the
|
|
290
|
+
door comes up empty. Use one hosted Postgres for both and the `pnpm refresh`
|
|
291
|
+
you already ran is the generation Vercel serves.
|
|
289
292
|
|
|
290
|
-
|
|
291
|
-
|
|
293
|
+
If you only want readers served, the site is a static export with no database in
|
|
294
|
+
the loop: deploy with no environment variables at all and add the door later.
|
|
292
295
|
|
|
293
|
-
|
|
294
|
-
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
295
|
-
| `knowledge/` | **the record** — your governed markdown. The product; everything else serves it. |
|
|
296
|
-
| `system/` | the code that serves the record: the site today, more as you need it. |
|
|
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. |
|
|
303
|
-
| `AGENTS.md` | the working contract every coding agent reads first — the rules for writing knowledge here. |
|
|
304
|
-
| `CLAUDE.md` | one line, pointing at `AGENTS.md`. Claude Code looks for this filename, not that one. |
|
|
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). |
|
|
306
|
-
| `.claude/skills/` | byte-identical copies of the kit — Claude Code discovers skills only here. The checker enforces the mirror, so the two cannot drift. |
|
|
307
|
-
| `.gemini/settings.json` | points Gemini CLI at `AGENTS.md`; Gemini does not read that filename on its own. |
|
|
308
|
-
| `.github/workflows/validate.yml` | your CI: runs the same checker on every pull request and push to main. |
|
|
309
|
-
| `.gitattributes` | markdown is checked out byte-stable on every platform, so the same commit hashes the same everywhere. |
|
|
310
|
-
| `.env.example` | the variables the served rung needs; copy to `.env` (gitignored) and fill in. |
|
|
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. |
|
|
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. |
|
|
313
|
-
<!-- ksor:pm pnpm -->
|
|
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. |
|
|
315
|
-
| `pnpm-lock.yaml` | the exact dependency versions — the reason two machines build the same site. |
|
|
316
|
-
<!-- /ksor:pm -->
|
|
317
|
-
<!-- ksor:pm npm -->
|
|
318
|
-
| `.npmrc` | dependency install scripts are denied; the comment inside discloses the one protection this scaffold lacks (a 48-hour quarantine on new releases). |
|
|
319
|
-
| `package-lock.json` | the exact dependency versions — written by your FIRST install; commit it, it is the reason two machines build the same site. |
|
|
320
|
-
<!-- /ksor:pm -->
|
|
321
|
-
<!-- ksor:pm bun -->
|
|
322
|
-
| `bun.lock` | the exact dependency versions — written by your FIRST install; commit it, it is the reason two machines build the same site. |
|
|
323
|
-
<!-- /ksor:pm -->
|
|
296
|
+
---
|
|
324
297
|
|
|
325
|
-
|
|
326
|
-
prose: rules that are only written down cannot refuse anything. `pnpm check`
|
|
327
|
-
runs it, and every failure it reports says what is wrong, why the rule exists,
|
|
328
|
-
and how to fix it.
|
|
298
|
+
## Make the record yours: replace the starters
|
|
329
299
|
|
|
330
|
-
|
|
331
|
-
|
|
300
|
+
**Five starter documents publish on the first build.** They ship
|
|
301
|
+
`status: stable`, so `pnpm dev` and `pnpm build` both give you a working record
|
|
302
|
+
straight away — pages, a sidebar, a `/llms.txt` an agent can read — instead of
|
|
303
|
+
an empty shelf. They are approved by `ksor-starter/KSOR-STAMP-VERSION`: the tool
|
|
304
|
+
that wrote them, named as a producer rather than as a person, because no person
|
|
305
|
+
reviewed a word of it. That is what the trust tier _unverified_ on every one of
|
|
306
|
+
those pages says, and it is true.
|
|
307
|
+
|
|
308
|
+
They also describe KSoR, not your organisation — and a record that describes the
|
|
309
|
+
wrong thing describes it on every surface. So replace them, in this order:
|
|
310
|
+
|
|
311
|
+
1. **Run the intake interview.** Ask your coding agent for it. Three questions —
|
|
312
|
+
what this record is the final word on, what sits just outside it, and who may
|
|
313
|
+
approve or withdraw a document — then it writes `instance.md` with you and
|
|
314
|
+
replaces the `human:you` placeholder in `.ksor/governance.yaml` with your
|
|
315
|
+
real handle.
|
|
316
|
+
2. **Delete each starter document** as your own knowledge arrives.
|
|
317
|
+
3. **When the last one is gone, delete `ksor-starter/KSOR-STAMP-VERSION` from
|
|
318
|
+
`approval_authorities` in `.ksor/governance.yaml`.** Nothing of yours should
|
|
319
|
+
be approved by a tool.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Writing knowledge
|
|
324
|
+
|
|
325
|
+
### A new document publishes nothing
|
|
326
|
+
|
|
327
|
+
A new document is `status: draft`, and `pnpm build` admits a draft to no surface
|
|
328
|
+
at all: no page, no sidebar row, no `/llms.txt` entry, nothing for an agent to
|
|
329
|
+
read. `pnpm dev` shows it, marked — the preview is where drafts live.
|
|
330
|
+
|
|
331
|
+
### Publishing adds two keys
|
|
332
|
+
|
|
333
|
+
Beside `status: stable`, name what produced the text and who approved it. Both,
|
|
334
|
+
or `pnpm check` refuses the document:
|
|
335
|
+
|
|
336
|
+
```yaml
|
|
337
|
+
status: stable
|
|
338
|
+
generated: { by: "human:you", at: 2026-01-31T09:00:00Z }
|
|
339
|
+
ksor:
|
|
340
|
+
audience: [public] # already there — every document carries it, drafts too
|
|
341
|
+
approval: { by: "human:you", at: 2026-01-31T09:00:00Z }
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`generated` is **provenance**: it names whatever produced the text — a person,
|
|
345
|
+
or the agent that drafted it — and nothing has to authorise it.
|
|
346
|
+
|
|
347
|
+
`approval.by` is **authority**, so it must name an actor `.ksor/governance.yaml`
|
|
348
|
+
lists, and its `at` may not be earlier than `generated.at` — the text that was
|
|
349
|
+
approved has to be the text that was written. That act is yours.
|
|
350
|
+
|
|
351
|
+
Run `pnpm check` before you commit. It runs the rules as a program, and every
|
|
352
|
+
failure it reports says what is wrong, why the rule exists, and how to fix it.
|
|
353
|
+
|
|
354
|
+
### Presenting a document
|
|
355
|
+
|
|
356
|
+
Ask your coding agent for slides and it writes them, from the document, into the
|
|
357
|
+
record:
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
make slides for knowledge/expenses/approvals.md
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
The `make-slides` skill reads the document whole, writes the deck into
|
|
364
|
+
`knowledge/expenses/approvals.slides.yaml`, checks every claim and every number
|
|
365
|
+
back against the document, and tells you what it left out because the document
|
|
366
|
+
did not support it — which is usually how you find out a document has a gap. The
|
|
367
|
+
deck then renders on that document's page, straight after its introduction:
|
|
368
|
+
click through it inline, or **Present** for fullscreen. Presenter notes stay off
|
|
369
|
+
the screen.
|
|
370
|
+
|
|
371
|
+
The slides live in the record, so they are reviewed in the same pull request as
|
|
372
|
+
the document, versioned with it, and withdrawn when it is withdrawn. There is no
|
|
373
|
+
third party and no link to rot. If you already keep a deck in Google Slides,
|
|
374
|
+
Canva or SlideShare you can point at it instead — `slides.url:` rather than
|
|
375
|
+
`deck:` — and the page will offer it as a link with a frame the reader loads on
|
|
376
|
+
click, so nothing is requested from the host until somebody asks.
|
|
377
|
+
|
|
378
|
+
### Summarising a document
|
|
379
|
+
|
|
380
|
+
Long documents get a **Summary** tab beside their own words, written the same
|
|
381
|
+
way:
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
summarise knowledge/expenses/approvals.md
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The `make-summary` skill reads the document whole, writes
|
|
388
|
+
`knowledge/expenses/approvals.summary.md`, and checks every line back against
|
|
389
|
+
the document — every number, every rule, and every `##` section, because a
|
|
390
|
+
summary that covers the opening and trails off is worse than none: a reader who
|
|
391
|
+
used it believes they have the whole document. It reports what it left out
|
|
392
|
+
because the document did not support it.
|
|
393
|
+
|
|
394
|
+
The summary is part of its document, not a document of its own: no route, no
|
|
395
|
+
sidebar row, no line in `llms.txt`, and it takes its governance from its parent.
|
|
396
|
+
Ask for one only where there is something to compress — under about two screens,
|
|
397
|
+
a summary that restates the page teaches readers the tab is not worth opening,
|
|
398
|
+
and the skill will say so rather than write one.
|
|
399
|
+
|
|
400
|
+
---
|
|
332
401
|
|
|
333
402
|
## Deploying
|
|
334
403
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
and anything that can serve files can serve it.
|
|
404
|
+
[Quick deployment](#quick-deployment) covers the common path. This is the rest
|
|
405
|
+
of it.
|
|
338
406
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
407
|
+
### The site
|
|
408
|
+
|
|
409
|
+
`pnpm build` writes HTML, JS and CSS to `system/site/out/` — about 2 MB with
|
|
410
|
+
zero host-specific dependencies. Anything that can serve files can serve it, and
|
|
411
|
+
`pnpm preview` serves exactly those bytes locally.
|
|
412
|
+
|
|
413
|
+
**`pnpm build` runs `ksor build` first.** It generates every `index.md`, runs
|
|
414
|
+
the record checker, and writes `build.lock.json` — the committed record of what
|
|
415
|
+
was published, from which commit, with which toolchain — and only then builds
|
|
416
|
+
the site. A checker refusal stops the build before anything is written.
|
|
417
|
+
|
|
418
|
+
- **Vercel** — the shipped `vercel.json` deploys from the repo root, builds with
|
|
419
|
+
`pnpm build`, and serves `system/site/out/`. It also declares the MCP **door**
|
|
420
|
+
as a second service built from the shipped `Dockerfile`, so `/mcp` and the
|
|
421
|
+
site share one domain.
|
|
422
|
+
<!-- ksor:pm pnpm -->
|
|
346
423
|
If the build image's pnpm predates the `packageManager` pin, set the
|
|
347
424
|
`ENABLE_EXPERIMENTAL_COREPACK=1` build environment variable.
|
|
348
|
-
<!-- /ksor:pm -->
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
425
|
+
<!-- /ksor:pm -->
|
|
426
|
+
- **GitHub Pages, nginx, S3, anything static** — run `pnpm build` and upload
|
|
427
|
+
`system/site/out/`. Hosted under a sub-path (like `user.github.io/repo`)?
|
|
428
|
+
Build with `KSOR_BASE_PATH=/repo pnpm build`.
|
|
429
|
+
|
|
430
|
+
**Verify any deploy** the same way: the home page, one document page and
|
|
431
|
+
`/llms.txt` load, and each names the documents this record has approved. On a
|
|
432
|
+
record whose documents are all still drafts, the home page and `/llms.txt` come
|
|
433
|
+
up empty and there is no document page at all — which is the correct answer, not
|
|
434
|
+
a broken deploy. Approve a document and rebuild to see it change.
|
|
435
|
+
|
|
436
|
+
### Takedowns reach the site through a committed file
|
|
437
|
+
|
|
438
|
+
Takedowns reach the site through `.ksor/takedowns.yaml`, the committed ledger —
|
|
439
|
+
a file in this repository, so the site build needs no database access at all.
|
|
440
|
+
|
|
441
|
+
That is deliberate. The act that withdraws a document is one merged commit, and
|
|
442
|
+
both surfaces read it: the door refuses immediately, the site at its next build.
|
|
443
|
+
Merge the ledger entry, rebuild, redeploy.
|
|
360
444
|
|
|
361
445
|
**A withdrawal that arrives on a clock works the same way, and that one has to
|
|
362
446
|
be scheduled.** `stale_after` and `ksor.effective_from` are evaluated once per
|
|
363
447
|
build, at the instant that build ran, and the answer is written into
|
|
364
448
|
`system/site/out/` — static files cannot re-decide themselves. So a document
|
|
365
449
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
- **GitHub Pages, nginx, S3, anything static** — run `pnpm build` and
|
|
373
|
-
upload `system/site/out/`. Hosted under a sub-path (like
|
|
374
|
-
`user.github.io/repo`)? Build with `KSOR_BASE_PATH=/repo pnpm build`.
|
|
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.
|
|
450
|
+
and in its markdown twin, while `ksor serve` — a process, evaluating per request
|
|
451
|
+
— already refuses it. `ksor build` prints the next instant at which this
|
|
452
|
+
happens. Nothing here rebuilds for you: `validate.yml` runs on pull requests and
|
|
453
|
+
`vercel.json` declares no cron. **If this record uses either key, add a scheduled
|
|
454
|
+
rebuild and redeploy.**
|
|
380
455
|
|
|
381
456
|
### The agent surface deploys separately
|
|
382
457
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
Cloud Run, Fly, Render, ECS, Kubernetes or a VPS:
|
|
458
|
+
`Dockerfile` and `.dockerignore` at the repo root build it, and they name no
|
|
459
|
+
host — the same image runs on Cloud Run, Fly, Render, ECS, Kubernetes or a VPS:
|
|
386
460
|
|
|
387
461
|
```sh
|
|
388
462
|
docker build -t my-record .
|
|
@@ -392,13 +466,13 @@ docker run --rm -p 8080:80 --env-file .env \
|
|
|
392
466
|
|
|
393
467
|
**That last flag is not boilerplate, and it is not a workaround.** The image
|
|
394
468
|
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
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
469
|
+
`KSOR_AUTH=disabled-local` your `.env` carries refuses there by design. Your
|
|
470
|
+
laptop is not the exception: a container really is reachable from outside
|
|
471
|
+
itself, and `disabled-public` is you saying you know that. It goes on the
|
|
472
|
+
command rather than into `.env` so your ordinary `pnpm serve` keeps the loopback
|
|
473
|
+
posture — and a real deployment sets it (or, better, the SSO variables) in the
|
|
474
|
+
host's environment, since `.dockerignore` keeps `.env` out of the image
|
|
475
|
+
entirely.
|
|
402
476
|
|
|
403
477
|
One thing surprises people: **deploying does not publish.** The door serves
|
|
404
478
|
whatever generation is already in the database, so a first deploy with no
|
|
@@ -407,15 +481,16 @@ machine or from CI — and it is deliberately not something a booting container
|
|
|
407
481
|
does. The full walkthrough, including what a cold start costs and where ingest
|
|
408
482
|
belongs, is in `node_modules/@panaversity/ksor/docs/deploying.md`.
|
|
409
483
|
|
|
410
|
-
|
|
411
|
-
|
|
484
|
+
### Audiences decide what a build contains
|
|
485
|
+
|
|
486
|
+
If `.ksor/governance.yaml` registers audiences, what you deploy is a **viewer**.
|
|
487
|
+
Plain `pnpm build` builds for `[public]` — safe for any host.
|
|
412
488
|
`KSOR_AUDIENCE=public,<audience> pnpm build` — a comma list that must always
|
|
413
|
-
include `public` — builds for a wider viewer
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
build may be served is yours to enforce.
|
|
489
|
+
include `public` — builds for a wider viewer, and that build carries an "— not
|
|
490
|
+
for publication" label because it must never reach a public host: put it behind
|
|
491
|
+
access control you already trust (VPN, SSO proxy, authenticated host). The tiers
|
|
492
|
+
govern what a build contains; where each build may be served is yours to
|
|
493
|
+
enforce.
|
|
419
494
|
|
|
420
495
|
The site can also show a **sign-in control** that names the reader in the
|
|
421
496
|
navbar. It is off until you set three variables (see `.env.example`), and it
|
|
@@ -424,6 +499,156 @@ so it is worth having on a record already behind one of the answers above, and
|
|
|
424
499
|
is not a substitute for them. Setup and the honest limits:
|
|
425
500
|
`node_modules/@panaversity/ksor/docs/deploying.md`.
|
|
426
501
|
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Reference
|
|
505
|
+
|
|
506
|
+
### Commands
|
|
507
|
+
|
|
508
|
+
| Command | What it does | When |
|
|
509
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
510
|
+
| `pnpm install` | fetches dependencies and the pinned `ksor` tool | first — then commit the lockfile |
|
|
511
|
+
| `pnpm dev` | the site at `:3000`, drafts visible and marked | while you write |
|
|
512
|
+
| `pnpm check` | runs the record checker as a program | before every commit |
|
|
513
|
+
| `pnpm build` | `ksor build` (index pages, checker, `build.lock.json`), then the static site to `system/site/out/` | before a deploy |
|
|
514
|
+
| `pnpm preview` | serves `system/site/out/` — there is no `start`, the site is a static export | to check a build |
|
|
515
|
+
| `pnpm provision` | applies the schema, authorizes ingest | once, for the agent surface |
|
|
516
|
+
| `pnpm refresh` | builds, ingests the record, collects retired generations | every time you publish |
|
|
517
|
+
| `pnpm serve` | the MCP server on `:8080` | to run the door locally |
|
|
518
|
+
| `pnpm exec ksor <verb>` | everything else — `calibrate`, `takedown`, … | as needed |
|
|
519
|
+
|
|
520
|
+
### The files, explained
|
|
521
|
+
|
|
522
|
+
Nothing here is decoration, and the dotfiles are not ceremony — each one is a
|
|
523
|
+
different coding agent's way of finding the same working contract.
|
|
524
|
+
|
|
525
|
+
**The record and its authority**
|
|
526
|
+
|
|
527
|
+
| Entry | What it is |
|
|
528
|
+
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
529
|
+
| `knowledge/` | **the record** — your governed markdown. The product; everything else serves it. |
|
|
530
|
+
| `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. |
|
|
531
|
+
| `.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. |
|
|
532
|
+
| `.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. |
|
|
533
|
+
| `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. |
|
|
534
|
+
|
|
535
|
+
**The system that serves it**
|
|
536
|
+
|
|
537
|
+
| Entry | What it is |
|
|
538
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
539
|
+
| `system/` | the code that serves the record: the site today, more as you need it. |
|
|
540
|
+
| `package.json` | the surface commands — `pnpm dev` (the site) and `pnpm provision` / `pnpm refresh` / `pnpm serve` (the agent surface) — plus `pnpm build` / `pnpm preview` / `pnpm check`, the pinned `@panaversity/ksor` tool and the workspace layout. |
|
|
541
|
+
| `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. |
|
|
542
|
+
| `vercel.json` | one domain, two services — the static site and the MCP door. Delete it if you deploy elsewhere. |
|
|
543
|
+
| `.env.example` | the variables the served rung needs; copy to `.env` (gitignored) and fill in. |
|
|
544
|
+
|
|
545
|
+
<!-- ksor:pm pnpm -->
|
|
546
|
+
|
|
547
|
+
| Entry | What it is |
|
|
548
|
+
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
549
|
+
| `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. |
|
|
550
|
+
| `pnpm-lock.yaml` | the exact dependency versions — the reason two machines build the same site. |
|
|
551
|
+
|
|
552
|
+
<!-- /ksor:pm -->
|
|
553
|
+
<!-- ksor:pm npm -->
|
|
554
|
+
|
|
555
|
+
| Entry | What it is |
|
|
556
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
557
|
+
| `.npmrc` | dependency install scripts are denied; the comment inside discloses the one protection this scaffold lacks (a 48-hour quarantine on new releases). |
|
|
558
|
+
| `package-lock.json` | the exact dependency versions — written by your FIRST install; commit it, it is the reason two machines build the same site. |
|
|
559
|
+
|
|
560
|
+
<!-- /ksor:pm -->
|
|
561
|
+
<!-- ksor:pm bun -->
|
|
562
|
+
|
|
563
|
+
| Entry | What it is |
|
|
564
|
+
| ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
565
|
+
| `bun.lock` | the exact dependency versions — written by your FIRST install; commit it, it is the reason two machines build the same site. |
|
|
566
|
+
|
|
567
|
+
<!-- /ksor:pm -->
|
|
568
|
+
|
|
569
|
+
**The working contract**
|
|
570
|
+
|
|
571
|
+
| Entry | What it is |
|
|
572
|
+
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
573
|
+
| `AGENTS.md` | the working contract every coding agent reads first — the rules for writing knowledge here. |
|
|
574
|
+
| `CLAUDE.md` | one line, pointing at `AGENTS.md`. Claude Code looks for this filename, not that one. |
|
|
575
|
+
| `.agents/skills/` | the agent kit: `intake-interview` (define the record with you), `add-sources` (turn source material into governed documents), `make-slides`, `make-summary`, `format-checker` (the rules, as a program). |
|
|
576
|
+
| `.claude/skills/` | byte-identical copies of the kit — Claude Code discovers skills only here. The checker enforces the mirror, so the two cannot drift. |
|
|
577
|
+
| `.gemini/settings.json` | points Gemini CLI at `AGENTS.md`; Gemini does not read that filename on its own. |
|
|
578
|
+
| `.github/workflows/validate.yml` | your CI: runs the same checker on every pull request and push to main. |
|
|
579
|
+
| `.gitattributes` | markdown is checked out byte-stable on every platform, so the same commit hashes the same everywhere. |
|
|
580
|
+
| `.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. |
|
|
581
|
+
|
|
582
|
+
`format-checker` deliberately contains a program, `check.mjs`, and not only
|
|
583
|
+
prose: rules that are only written down cannot refuse anything.
|
|
584
|
+
|
|
585
|
+
Everything here is yours to change. The kit exists so that any coding agent can
|
|
586
|
+
operate this project without being taught it first.
|
|
587
|
+
|
|
588
|
+
### When something refuses you
|
|
589
|
+
|
|
590
|
+
This project refuses loudly and on purpose. Most of what looks like a failure is
|
|
591
|
+
a rule doing its job — and every refusal names its own fix, so this table is a
|
|
592
|
+
map rather than a substitute.
|
|
593
|
+
|
|
594
|
+
| What you see | What it means | What to do |
|
|
595
|
+
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
596
|
+
| `pnpm check` refuses a document | `status: stable` without both `generated` and `ksor.approval`, or an approval earlier than the text it approves | add both keys; approval cannot precede what it approves |
|
|
597
|
+
| `start` — missing script | there is none: the site is a static export, so nothing serves it at runtime | `pnpm preview`, or upload the folder |
|
|
598
|
+
| `pnpm preview` exits `3` | there is no `system/site/out/` yet | run the build first |
|
|
599
|
+
| `pnpm serve` refuses to boot | it will not run unauthenticated by accident | `KSOR_AUTH=disabled-local` in `.env` for a loopback run |
|
|
600
|
+
| the deployed or containerised door refuses with `disabled-local` | it binds `0.0.0.0` — a public bind | `KSOR_AUTH=disabled-public` in the host environment, or configure the SSO variables |
|
|
601
|
+
| the agent answers questions 2 and 3 instead of declining | no floor is measured, so the gate is off (`abstain OFF`, `gate: "off"`) — step 3's `calibrate` was skipped | `pnpm exec ksor calibrate --instance instance.md`, paste the block, restart |
|
|
602
|
+
| a deployed door serves an empty record | deploying does not publish — and a laptop DSN is unreachable from the host | point both at one hosted Postgres, then `pnpm refresh` |
|
|
603
|
+
| the home page and `/llms.txt` are empty | every document is still a draft — correct, not broken | approve one and rebuild |
|
|
604
|
+
| a new document never appears on the built site | drafts reach no built surface at all | publish it — `status: stable` plus both governance keys |
|
|
605
|
+
| an expired document still shows on the site but not through the door | the static build evaluated `stale_after` at build time | rebuild and redeploy; schedule a rebuild if you use it |
|
|
606
|
+
| Vercel: `no services are declared` | Root Directory was auto-filled with `system/site` | set it to `./` |
|
|
607
|
+
|
|
608
|
+
### Dependencies and advisories
|
|
609
|
+
|
|
610
|
+
An audit of this scaffold reports vulnerabilities in `next`, and will keep doing
|
|
611
|
+
so: a framework that large always has open advisories against whatever version
|
|
612
|
+
you have pinned.
|
|
613
|
+
|
|
614
|
+
<!-- ksor:pm npm -->
|
|
615
|
+
|
|
616
|
+
`npm install` prints the count at the end of every install, so you meet it
|
|
617
|
+
before you have run anything, next to an invitation to run
|
|
618
|
+
`npm audit fix --force`.
|
|
619
|
+
|
|
620
|
+
<!-- /ksor:pm -->
|
|
621
|
+
<!-- ksor:pm pnpm -->
|
|
622
|
+
|
|
623
|
+
pnpm reports it only when you run `pnpm audit`.
|
|
624
|
+
|
|
625
|
+
<!-- /ksor:pm -->
|
|
626
|
+
<!-- ksor:pm bun -->
|
|
627
|
+
|
|
628
|
+
bun reports it only when you run `bun audit`.
|
|
629
|
+
|
|
630
|
+
<!-- /ksor:pm -->
|
|
631
|
+
|
|
632
|
+
**Never let an audit tool raise the pin for you.** It moves off the version this
|
|
633
|
+
scaffold was built and tested against, and that pin is the whole reason two
|
|
634
|
+
machines produce the same site. Bump it deliberately instead — take the newer
|
|
635
|
+
pin a newer `ksor init` emits, or raise it yourself and re-run `pnpm build`.
|
|
636
|
+
|
|
637
|
+
It also reads worse than it is, for one structural reason worth knowing: **this
|
|
638
|
+
site is a static export.** `pnpm build` writes HTML, JS and CSS to
|
|
639
|
+
`system/site/out/`, and no framework server ever runs in front of your readers —
|
|
640
|
+
no middleware, no server actions, no rewrites, no image optimizer. Most
|
|
641
|
+
framework advisories describe exactly those request paths, so they have nothing
|
|
642
|
+
here to reach.
|
|
643
|
+
|
|
644
|
+
Two things that argument does NOT cover, and you should treat as real: an
|
|
645
|
+
advisory in the **build** toolchain, which does run, on your machine and in your
|
|
646
|
+
CI; and any advisory at all if you later add a served route and stop exporting.
|
|
647
|
+
Read what an advisory affects before deciding it is inert — the static export is
|
|
648
|
+
a reason, not a blanket.
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
427
652
|
## Ownership
|
|
428
653
|
|
|
429
654
|
Everything here is yours. The scaffold was generated by
|