@panaversity/ksor 0.0.9 → 0.0.11
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 +266 -0
- package/dist/cli.mjs +583 -91
- package/docs/index.md +3 -2
- package/package.json +1 -1
- package/templates/scaffold/AGENTS.md +36 -26
- package/templates/scaffold/README.md +1 -1
- package/templates/scaffold/env.example +6 -5
- package/templates/scaffold/instance.md +11 -1
- package/templates/scaffold/system/site/app/.well-known/mcp/server.json/route.ts +5 -2
- package/templates/scaffold/system/site/lib/order-rule.ts +107 -0
- package/templates/scaffold/system/site/lib/page-order.ts +93 -0
- package/templates/scaffold/system/site/lib/shared.ts +49 -0
- package/templates/scaffold/system/site/lib/source.ts +9 -48
- package/templates/scaffold/system/site/lib/stage-knowledge.ts +19 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,271 @@
|
|
|
1
1
|
# @panaversity/ksor
|
|
2
2
|
|
|
3
|
+
## 0.0.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0a0dd27: A record describes itself on the surface agents discover it through
|
|
8
|
+
|
|
9
|
+
`/.well-known/mcp/server.json` carried one hard-coded sentence — "The <name>
|
|
10
|
+
Knowledge System of Record: governed markdown served with citations and honest
|
|
11
|
+
abstention." — byte-identical in every ksor record ever scaffolded. An agent
|
|
12
|
+
choosing between records in a registry learned nothing from any of them, which
|
|
13
|
+
is the opposite of what that document exists for.
|
|
14
|
+
|
|
15
|
+
The description now comes from the record's own prose: its display title and the
|
|
16
|
+
first real sentence of `instance.md`, which is what the intake interview writes.
|
|
17
|
+
A record whose owner has not described it yet SAYS so rather than borrowing a
|
|
18
|
+
confident sentence it has not earned — the same answer the MCP door already
|
|
19
|
+
gives an agent that connects, so the two surfaces do not disagree about whether
|
|
20
|
+
this record knows what it is.
|
|
21
|
+
|
|
22
|
+
The scaffold's opening paragraphs are authoring guidance, not scope, so the
|
|
23
|
+
template is detected across the whole body rather than paragraph by paragraph:
|
|
24
|
+
publishing instructions-to-the-author as a description would be worse than
|
|
25
|
+
admitting there is none.
|
|
26
|
+
|
|
27
|
+
- 0fe759d: Three defects found by auditing 0.0.10 against a live record
|
|
28
|
+
|
|
29
|
+
**A repeated `sslmode` was read the wrong end.** `pinnedTlsDsn` took the FIRST
|
|
30
|
+
value of a repeated parameter; `pg` takes the LAST. So on
|
|
31
|
+
`?sslmode=require&sslmode=disable` — whose effective mode is `disable` — the pin
|
|
32
|
+
saw a weak mode, collapsed the duplicates into one `verify-full`, turned TLS on,
|
|
33
|
+
and printed "TLS verified" at an operator whose DSN ended in `disable`. The
|
|
34
|
+
direction was safe; silently overruling an explicit opt-out and then misreporting
|
|
35
|
+
it was not. All three TLS functions now read the mode the driver will use.
|
|
36
|
+
|
|
37
|
+
The same sweep asserted the larger worry the pin creates — that re-serializing a
|
|
38
|
+
connection string could alter a credential. Seventeen DSNs with the passwords
|
|
39
|
+
people actually paste (raw `@`, spaces, `%`, `+`, brackets, non-ASCII,
|
|
40
|
+
percent-encoded separators) are now checked field by field against `pg`'s own
|
|
41
|
+
resolved view: everything the driver derives is byte-identical, and so is the
|
|
42
|
+
TLS decision.
|
|
43
|
+
|
|
44
|
+
**The outline's `position` disclosed documents an audience may not see.** It was
|
|
45
|
+
the rank in the whole record, so a public caller received 1, 3, 4 — a gap exactly
|
|
46
|
+
where an internal sibling sat, telling them something exists and roughly where.
|
|
47
|
+
The same row's `child_count` was already computed over visible children only, so
|
|
48
|
+
one response object disagreed with itself. `position` is now the rank among the
|
|
49
|
+
siblings the caller can see, computed as a window over the filtered set so it
|
|
50
|
+
stays correct across pages and at every depth, and both it and `depth` say what
|
|
51
|
+
they are in the tool schema.
|
|
52
|
+
|
|
53
|
+
**`ksor serve` now says when the record has no identity yet.** The MCP door
|
|
54
|
+
already refused to pass an unedited `instance.md` to agents as instructions —
|
|
55
|
+
it substitutes a plain statement that the scope is unstated — but the operator
|
|
56
|
+
starting the server was told nothing, so a record serving with no declared
|
|
57
|
+
identity looked exactly like one that had been described. It is a boot line now,
|
|
58
|
+
beside the abstention posture: both answer "how much should I trust this".
|
|
59
|
+
|
|
60
|
+
- f5cd885: The bearer door's key line joins the boot block instead of interrupting it
|
|
61
|
+
|
|
62
|
+
In bearer mode the line naming where the signing keys were discovered printed
|
|
63
|
+
before the aligned posture block and in a different shape, so it read as a stray
|
|
64
|
+
log line rather than as part of what the server was telling you about itself. It
|
|
65
|
+
is a `keys` row in the block now, under `auth`, resolved at boot exactly as
|
|
66
|
+
before.
|
|
67
|
+
|
|
68
|
+
- 5f30b5f: The site build no longer fails when two evaluations of the record staging overlap
|
|
69
|
+
|
|
70
|
+
The scaffold stages a per-audience copy of the record before the site build
|
|
71
|
+
reads it, removing the previous stage first. `rmSync(..., { force: true })`
|
|
72
|
+
suppresses ENOENT but retries nothing: Node retries EBUSY / EMFILE / ENFILE /
|
|
73
|
+
ENOTEMPTY / EPERM only when `maxRetries` is set, and it defaults to zero. The
|
|
74
|
+
bundler evaluates the source config more than once when it wants it in more than
|
|
75
|
+
one place, so one run could remove the stage while another was still copying
|
|
76
|
+
into it — surfacing as `ENOTEMPTY` and failing the entire site build (seen once
|
|
77
|
+
in CI, 2026-08-21).
|
|
78
|
+
|
|
79
|
+
The removal now asks for those retries. Losing that race is safe: the stage is a
|
|
80
|
+
deterministic function of the record and the denylist, so redoing it produces
|
|
81
|
+
the same bytes.
|
|
82
|
+
|
|
83
|
+
Three claims in the scaffold's `AGENTS.md` that recent releases made false are
|
|
84
|
+
also corrected: `--actor` no longer "defaults to the operating user" (it is
|
|
85
|
+
required, and there is no default by design); the signing keys are discovered
|
|
86
|
+
from the SSO's own metadata rather than fetched from Better Auth's path; and the
|
|
87
|
+
`order:` key now drives the MCP `outline` tool alongside the sidebar and
|
|
88
|
+
`llms.txt`, which is what "one order drives every surface" was always supposed
|
|
89
|
+
to mean.
|
|
90
|
+
|
|
91
|
+
- 4a1c154: The shrink guard guards `ksor ingest --flip` again — it had stopped
|
|
92
|
+
|
|
93
|
+
`.env.example` documents `KSOR_MAX_SHRINK` as "a corpus that shrinks by more
|
|
94
|
+
than this FRACTION refuses to flip". In 0.0.10 it did not. Deleting eight of ten
|
|
95
|
+
documents and running `ksor ingest --flip` published the two that were left,
|
|
96
|
+
silently, exit 0.
|
|
97
|
+
|
|
98
|
+
The cause was the fix that stopped a refused ingest from publishing. That moved
|
|
99
|
+
the flip out of `buildGeneration` and into the command, so the governance gate
|
|
100
|
+
could run against the new generation BEFORE it became the active one — and the
|
|
101
|
+
shrink check, which lived inside the build's flip branch, was stepped straight
|
|
102
|
+
over. The library test that covers the guard stayed green throughout, because it
|
|
103
|
+
drives `buildGeneration` directly with `flip: true`, which is no longer the path
|
|
104
|
+
the CLI takes.
|
|
105
|
+
|
|
106
|
+
There is now one answer to "may this generation be activated" — `flipRefusal` —
|
|
107
|
+
and both flip paths ask it, in the same transaction as the flip itself. The new
|
|
108
|
+
test drives the command rather than the library, so a guard that only one of two
|
|
109
|
+
paths performs fails the tier that proves it.
|
|
110
|
+
|
|
111
|
+
Verified against a live record: a 10 → 2 node build now names all eight removed
|
|
112
|
+
documents, refuses with exit 1, and leaves the previous generation serving.
|
|
113
|
+
|
|
114
|
+
## 0.0.10
|
|
115
|
+
|
|
116
|
+
### Patch Changes
|
|
117
|
+
|
|
118
|
+
- c07a5db: `ksor calibrate` states what its measurement is worth, not just its result
|
|
119
|
+
|
|
120
|
+
The default door synthesizes in-corpus questions by asking a model to write one
|
|
121
|
+
FROM each sampled passage, then scores those questions against the corpus that
|
|
122
|
+
contains the passage. They share vocabulary a reader's question will not, so the
|
|
123
|
+
in-corpus distribution sits higher than real traffic will and the separation the
|
|
124
|
+
run reports is an UPPER BOUND. The `--queries-file` door carried a caveat about
|
|
125
|
+
its own distribution; the default door — the biased one — carried none.
|
|
126
|
+
|
|
127
|
+
Found live: a real record calibrated this way reported min in-corpus 0.682
|
|
128
|
+
against max out-of-corpus 0.580 and recommended `vector_floor: 0.631`. Questions
|
|
129
|
+
the record demonstrably answers then scored 0.530 to 0.606 — every one below the
|
|
130
|
+
recommended floor. Pasting it would have made the record abstain on questions
|
|
131
|
+
whose answers it had just cited, which is the failure abstention exists to
|
|
132
|
+
prevent, arrived at from the other side.
|
|
133
|
+
|
|
134
|
+
The block now carries that caveat, and prints the one number it always left the
|
|
135
|
+
reader to work out: the separation margin, with the probe counts behind it. A
|
|
136
|
+
margin of 0.054 over six in-corpus and four out-of-corpus probes is a different
|
|
137
|
+
claim from the same margin over sixty, and both figures were already on the
|
|
138
|
+
report without ever reaching the page. The mathematics and the recommended value
|
|
139
|
+
are unchanged.
|
|
140
|
+
|
|
141
|
+
It also names the generation it measured. With nothing pinned — the ordinary
|
|
142
|
+
case, calibrating what is being served — the report carried no generation at
|
|
143
|
+
all, so the provenance comment beside a pasted floor read `on generation unknown
|
|
144
|
+
(no generation pinned)`. A floor is a threshold inside ONE generation's embedding
|
|
145
|
+
space, and the query that counts the chunks had already resolved which one.
|
|
146
|
+
|
|
147
|
+
`runCalibration` had no test of any kind; it has one now, against real Postgres.
|
|
148
|
+
|
|
149
|
+
- d00f3a2: The signing keys are discovered, not guessed — any standards-compliant SSO now works.
|
|
150
|
+
|
|
151
|
+
`KSOR_SSO_URL` is documented as "the AS base", and the verifier appended one
|
|
152
|
+
vendor's layout to it (`/api/auth/jwks`, Better Auth's). Auth0, Okta, Entra,
|
|
153
|
+
Keycloak, Cognito and Google all publish elsewhere, so every one of them failed
|
|
154
|
+
the key fetch — which is classified transient, so the door booted clean and
|
|
155
|
+
returned 503 to every request with nothing naming the cause. The only posture an
|
|
156
|
+
operator could actually reach was `KSOR_ALLOW_PUBLIC_UNAUTHENTICATED=1`: the one
|
|
157
|
+
key we handed people was the one that props the door open.
|
|
158
|
+
|
|
159
|
+
`jwks_uri` is now read from the SSO's own metadata document — RFC 8414 first,
|
|
160
|
+
then OpenID Discovery — with `KSOR_JWKS_URL` kept as an explicit override, and
|
|
161
|
+
the vendor path kept as a last resort that reports itself as a guess. Where the
|
|
162
|
+
keys came from is stated on the boot line.
|
|
163
|
+
|
|
164
|
+
Verified against three real providers: Google (RFC 8414, cross-origin
|
|
165
|
+
`jwks_uri`), GitHub Actions OIDC, and Entra — whose issuer carries a path,
|
|
166
|
+
the case a naive `${sso}/.well-known/…` gets wrong.
|
|
167
|
+
|
|
168
|
+
Discovery never refuses to boot: an unreachable AS falls back and says so.
|
|
169
|
+
|
|
170
|
+
- 9062088: A loopback authorization server's keys are reachable again.
|
|
171
|
+
|
|
172
|
+
JWKS discovery refused a cleartext `jwks_uri`, which is right for a network AS
|
|
173
|
+
and wrong for a local one: a dev authorization server on loopback advertises
|
|
174
|
+
`http://127.0.0.1:…/jwks`, the resolver refused it, the vendor guess was used
|
|
175
|
+
instead, and every request returned 503. `assertHttpUrl` already exempts
|
|
176
|
+
loopback for the SSO base for exactly this reason; the resolver now does too.
|
|
177
|
+
Cleartext to any non-loopback host is still refused.
|
|
178
|
+
|
|
179
|
+
Found by writing the first test that boots the gateway in bearer mode.
|
|
180
|
+
|
|
181
|
+
- 995ec48: A governance act names its actor; the tool no longer guesses one.
|
|
182
|
+
|
|
183
|
+
`ksor takedown --actor` fell back to `$USER` / `$USERNAME` / `"operator"`, so a
|
|
184
|
+
ledger row read `runner` under CI and `root` in a container — a self-asserted
|
|
185
|
+
string wearing a schema, indistinguishable from a person who was never there.
|
|
186
|
+
`retrieval_log.actor` is `NOT NULL` with the comment "NO default: unset errors
|
|
187
|
+
loudly", and the fallback is precisely what stopped it erroring.
|
|
188
|
+
|
|
189
|
+
Denying or revoking now REFUSES without `--actor`, before the DSN is resolved:
|
|
190
|
+
a missing actor is an argument error (exit 1), not an environment one. The
|
|
191
|
+
read-only modes — `--list`, `--ledger`, `--export` — write no ledger row and
|
|
192
|
+
need nothing.
|
|
193
|
+
|
|
194
|
+
- 41b0c38: Reading order is one rule again: the MCP door now reads `order:`
|
|
195
|
+
|
|
196
|
+
`order:` is the only ordering key a record may declare — it is in the governed
|
|
197
|
+
frontmatter set the format checker closes, and the checker's own remedy for a
|
|
198
|
+
stray `meta.json` says so. The MCP door never read it. The tree adapter was
|
|
199
|
+
converted from the predecessor, whose ordering keys were Docusaurus's
|
|
200
|
+
`position:` / `sidebar_position:`, neither of which a compliant record may
|
|
201
|
+
declare — so `outline` reported the record's structure in filename order and
|
|
202
|
+
called it the reading order, while the website honoured `order:`. On a
|
|
203
|
+
curriculum, where reading order IS the content, an agent asking "what do I read
|
|
204
|
+
first" got a different answer from the two surfaces.
|
|
205
|
+
|
|
206
|
+
Four smaller disagreements went with it, each now a row in a shared decision
|
|
207
|
+
table: unordered documents sorted at 10 000 rather than after everything;
|
|
208
|
+
fractional orders were truncated; ties compared `example.md` against
|
|
209
|
+
`example-two.md`, where `.` sorts after `-`, reversing ordinary pairs; and one
|
|
210
|
+
side folded case while the other did not.
|
|
211
|
+
|
|
212
|
+
The rule now lives in one file, copied into the scaffold and asserted
|
|
213
|
+
byte-identical, with `ORDER_CASES` run against BOTH surfaces — so a surface that
|
|
214
|
+
drifts fails on the row it broke.
|
|
215
|
+
|
|
216
|
+
- 41b0c38: `ksor serve` reports its own posture instead of forwarding other people's warnings
|
|
217
|
+
|
|
218
|
+
Booting printed four alarming paragraphs at an operator who had done nothing
|
|
219
|
+
wrong: the driver's multi-line `SECURITY WARNING` about `sslmode` aliases, ksor's
|
|
220
|
+
own three-line restatement of the same thing, and the MCP SDK's note about a
|
|
221
|
+
`responseMode` ksor chose deliberately.
|
|
222
|
+
|
|
223
|
+
The driver's warning is correct and its remedy is one word, so ksor now applies
|
|
224
|
+
it: a remote `sslmode=require|prefer|verify-ca` is rewritten to `verify-full`
|
|
225
|
+
before the connection is made. The connection is unchanged today — pg 8 was
|
|
226
|
+
already resolving all three to full verification, which is the entire content of
|
|
227
|
+
its warning — and it can no longer be silently downgraded by a driver upgrade.
|
|
228
|
+
The SDK's note describes a recorded decision, not a defect, and is suppressed by
|
|
229
|
+
exact message so that anything else it says still reaches the operator.
|
|
230
|
+
|
|
231
|
+
What is left is the record's posture, aligned and in ksor's own voice, with the
|
|
232
|
+
two lines that decide whether to trust what happens next saying what they mean:
|
|
233
|
+
auth `DISABLED` now names the bind it is survivable on, and an absent abstention
|
|
234
|
+
floor says out-of-corpus questions will be answered rather than refused.
|
|
235
|
+
|
|
236
|
+
- bea7d80: `read` names every section it will accept, not just the top-level ones
|
|
237
|
+
|
|
238
|
+
`read` resolves a `heading` three ways: a full heading path, any prefix of one,
|
|
239
|
+
and a section's last segment when that segment is unique in the document. The
|
|
240
|
+
error for a section it could not find listed only the TOP-LEVEL segments — a
|
|
241
|
+
strict subset of its own vocabulary — so it reported real, reachable sections as
|
|
242
|
+
absent. Found live: a nested section was refused by name and served on the next
|
|
243
|
+
call under the same name.
|
|
244
|
+
|
|
245
|
+
The error now lists the full heading paths (the form that always resolves and
|
|
246
|
+
never collides), states the unique-last-segment shorthand rather than doubling
|
|
247
|
+
the list to enumerate it, and counts the tail past twenty instead of printing an
|
|
248
|
+
unbounded list. The `heading` input and the `sections` output now describe the
|
|
249
|
+
same vocabulary in the tool schema, where an agent reads it before making the
|
|
250
|
+
call rather than after failing one.
|
|
251
|
+
|
|
252
|
+
- 4631268: Correct every remaining document that said `pnpm serve` publishes.
|
|
253
|
+
|
|
254
|
+
`serve` was `pnpm schema && pnpm grant && pnpm ingest && ksor serve` and is now
|
|
255
|
+
`ksor serve` alone. Three adopter-facing places still described the old chain:
|
|
256
|
+
the scaffolded `instance.md`'s own comment ("copy .env.example to .env, then run
|
|
257
|
+
`pnpm serve`"), the scaffold README's file table ("the agent surface: schema →
|
|
258
|
+
grant → ingest → serve"), and AGENTS.md's runbook ("`pnpm serve` is the only
|
|
259
|
+
command this rung needs"). Following any of them serves an empty record.
|
|
260
|
+
|
|
261
|
+
All three now say the same thing the CLI does: `pnpm provision` once,
|
|
262
|
+
`pnpm refresh` to publish, `pnpm serve` to serve — and why publishing is
|
|
263
|
+
separate, since a restart or an autoscaling event must not republish a record.
|
|
264
|
+
|
|
265
|
+
A test now asserts the CLAIM rather than the command. The existing guard could
|
|
266
|
+
not catch this: it checks that a named command exists, and `pnpm serve` does
|
|
267
|
+
exist — what was wrong was the sentence attached to it.
|
|
268
|
+
|
|
3
269
|
## 0.0.9
|
|
4
270
|
|
|
5
271
|
### Patch Changes
|