@panaversity/ksor 0.0.6 → 0.0.8
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 +273 -0
- package/dist/cli.mjs +3573 -1354
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-CpDIVudJ.mjs → src-pl4aOpVs.mjs} +1 -0
- package/docs/index.md +8 -4
- package/package.json +2 -2
- package/schema/migrations/2.1-2.2__governance-on-the-node-row.sql +55 -0
- package/schema/migrations/2.2-2.3__takedown-writes-and-a-readable-ledger.sql +70 -0
- package/schema/migrations/2.3-2.4__a-generation-remembers-its-schema.sql +22 -0
- package/schema/schema.sql +61 -8
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +24 -2
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +24 -2
- package/templates/scaffold/AGENTS.md +103 -9
- package/templates/scaffold/README.md +47 -9
- package/templates/scaffold/env.example +82 -2
- package/templates/scaffold/gitignore +4 -0
- package/templates/scaffold/instance.md +14 -0
- package/templates/scaffold/package.json +7 -3
- package/templates/scaffold/system/site/app/.well-known/mcp/server.json/route.ts +45 -0
- package/templates/scaffold/system/site/lib/audience-rule.ts +44 -0
- package/templates/scaffold/system/site/lib/audience.ts +4 -14
- package/templates/scaffold/system/site/lib/denial-rule.ts +212 -0
- package/templates/scaffold/system/site/lib/shared.ts +48 -0
- package/templates/scaffold/system/site/lib/stage-knowledge.ts +186 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,278 @@
|
|
|
1
1
|
# @panaversity/ksor
|
|
2
2
|
|
|
3
|
+
## 0.0.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- bfacb31: Correct the documented serving posture, and name the agent surface at `init`.
|
|
8
|
+
|
|
9
|
+
The docs said `ksor serve` "binds loopback with auth off by default". It never
|
|
10
|
+
did: `buildAuth` refuses to boot unless SSO is configured or
|
|
11
|
+
`KSOR_AUTH_DISABLED=1` is explicit — loopback included. An adopter who followed
|
|
12
|
+
the prose instead of `.env.example` exported only the DSN and the provider key
|
|
13
|
+
and hit a boot refusal they had been told would not happen, and the sentence
|
|
14
|
+
advertised a weaker posture than the product actually ships. Both READMEs and
|
|
15
|
+
the scaffold's `AGENTS.md` now say what the code does; the scaffold's own
|
|
16
|
+
`.env.example` and setup steps were already correct and are unchanged.
|
|
17
|
+
|
|
18
|
+
`ksor init`'s closing handoff now names `pnpm serve` alongside `pnpm dev`, so
|
|
19
|
+
the MCP surface is visible at the moment the adopter is reading the screen
|
|
20
|
+
rather than only in the runbook.
|
|
21
|
+
|
|
22
|
+
- 082df27: Governance now lives on the record, and databases can move forward.
|
|
23
|
+
|
|
24
|
+
**`visibility:` is enforced on the MCP surface.** It used to be enforced only by
|
|
25
|
+
the site's build-time staging step, because ingest dropped the key and the agent
|
|
26
|
+
door had nothing to filter on — a document marked `visibility: internal` was
|
|
27
|
+
hidden from the website and served in full to every agent. Schema 2.2 carries
|
|
28
|
+
`visibility`, `doc_status`, `owner`, `provenance`, `superseded_by` and
|
|
29
|
+
`corpus_id` on `content_nodes`; one seam (`lib/audience.ts`) binds the filter
|
|
30
|
+
into search, read and outline. A server that cannot establish who is asking
|
|
31
|
+
serves the least-privileged tier; an unknown tier refuses rather than widening;
|
|
32
|
+
a record that declares no `audiences:` is unfiltered exactly as before.
|
|
33
|
+
|
|
34
|
+
**A takedown now reaches BOTH surfaces, and has a door.** `ksor takedown
|
|
35
|
+
<stable-id> --reason …` imposes one (`--subtree`, `--list`, `--revoke`), through
|
|
36
|
+
the ingest role rather than a superuser psql prompt, writing the §7 row that
|
|
37
|
+
records who did it in the same transaction as the denial. `--export` writes the
|
|
38
|
+
manifest the site build reads, so a withdrawn document stops being published on
|
|
39
|
+
the human surface — `llms.txt` included. Schema 2.3 adds the write policy this
|
|
40
|
+
needs, and a `sor_content_auditor` role: `retrieval_log` had FORCE row-level
|
|
41
|
+
security, an INSERT policy, and no SELECT policy or grant, so the provenance
|
|
42
|
+
ledger the governance story rests on was write-only under every credential ksor
|
|
43
|
+
ships.
|
|
44
|
+
|
|
45
|
+
**The calibrator no longer hands out a floor it just measured as leaking.** When
|
|
46
|
+
a measurement does not separate in-corpus from out-of-corpus, the report says so
|
|
47
|
+
and names the fail-closed state (`vector_floor: uncalibrated`) instead of
|
|
48
|
+
printing a paste-ready number — the intended operator is a coding agent, and it
|
|
49
|
+
will paste what it is given. Two reporting bugs replicated from the Python
|
|
50
|
+
predecessor are also fixed: a missing generation printed Python's `None` literal
|
|
51
|
+
into the provenance comment, and the alternate-floor line always claimed
|
|
52
|
+
0.95-precision whatever precision was actually measured. Byte-fidelity to the
|
|
53
|
+
oracle is for algorithms, never for reports. The paste line now carries the
|
|
54
|
+
measurement DATE, which the invariant asked for and it never had.
|
|
55
|
+
|
|
56
|
+
**Forward migrations.** `schema/migrations/<from>-<to>__<slug>.sql` with a runner
|
|
57
|
+
that walks the chain rather than sorting it, so a missing step refuses instead of
|
|
58
|
+
being skipped. `ksor schema --apply` now compares versions instead of checking
|
|
59
|
+
presence, and migrates an existing database forward — replacing "drop and
|
|
60
|
+
recreate", which destroyed `retrieval_log` and `takedown_denylist`, the only two
|
|
61
|
+
tables that cannot be rebuilt from markdown.
|
|
62
|
+
|
|
63
|
+
**A wake-from-suspend is retried instead of failing the request.**
|
|
64
|
+
`connectionTimeoutMillis` bounds two different failures and Postgres reports
|
|
65
|
+
both with the same text: waiting for a slot in a saturated pool, and failing to
|
|
66
|
+
establish a connection at all. ksor treated both as saturation, which is never
|
|
67
|
+
retried — so on a serverless endpoint the first request after an idle period,
|
|
68
|
+
the one most likely to hit a cold start, was the one request that got no
|
|
69
|
+
retries. Measured against a black-holed endpoint: one attempt, 10s, with five
|
|
70
|
+
retries and a 30s budget unused. The two are now told apart by the pool's own
|
|
71
|
+
state and only saturation sheds.
|
|
72
|
+
|
|
73
|
+
**A dropped connection no longer kills `ksor serve`.** pg-pool removes a
|
|
74
|
+
client's error listener for the duration of a checkout, so a connection dying
|
|
75
|
+
mid-query became an uncaught exception and exited the process — the failure mode
|
|
76
|
+
of every serverless endpoint that suspends its compute. Checkouts are now
|
|
77
|
+
guarded and broken connections are discarded rather than reused.
|
|
78
|
+
|
|
79
|
+
**Search is no longer O(corpus).** The vector arm ranked with a window function,
|
|
80
|
+
which no HNSW index scan can satisfy, so every search computed the distance for
|
|
81
|
+
every chunk and sorted. Measured on PostgreSQL 17.7 / pgvector 0.8.2 at 20k rows:
|
|
82
|
+
452 ms → 39 ms, with the index actually used.
|
|
83
|
+
|
|
84
|
+
**`ksor serve` refuses where `pnpm build` refuses.** Two states had the site
|
|
85
|
+
stopping by name while the agent door came up clean and served the restricted
|
|
86
|
+
half. A database migrated to 2.2 carries the governance columns but no VALUES —
|
|
87
|
+
a migration cannot read frontmatter — and a NULL visibility reads as
|
|
88
|
+
`default_visibility`, the widest tier, so an adopter who migrated without
|
|
89
|
+
re-ingesting served every restricted document to every agent with the schema
|
|
90
|
+
check green. Schema 2.4 stamps each generation with the schema it was built
|
|
91
|
+
against, and serve refuses a generation older than the governance columns,
|
|
92
|
+
naming `ksor ingest` as the fix. A document declaring `visibility:` in a record
|
|
93
|
+
with no `audiences:` block is refused too, matching the site's
|
|
94
|
+
`ksor-visibility-without-audiences`.
|
|
95
|
+
|
|
96
|
+
**A `--subtree` takedown now reaches documents added after it.** The exported
|
|
97
|
+
manifest could only name what the active generation contained, and the site
|
|
98
|
+
builds from disk — so a document written under a withdrawn section and not yet
|
|
99
|
+
ingested was published to `/docs` and `llms.txt` with no warning anywhere. The
|
|
100
|
+
manifest now carries the DIRECTORIES a subtree denial governs, derived from its
|
|
101
|
+
descendants' recorded file paths. The site also checks the manifest belongs to
|
|
102
|
+
this record: one exported for a different instance used to pass every gate and
|
|
103
|
+
apply the wrong denial set.
|
|
104
|
+
|
|
105
|
+
**The readiness probe answers, and means something.** `/ready` reports NOT ready
|
|
106
|
+
while the schema is unverified, instead of green on an instance where every tool
|
|
107
|
+
call would fail on a missing column; the boot check is retried like a serving
|
|
108
|
+
read rather than treated as permanently unknown after one cold start; the whole
|
|
109
|
+
readiness chain shares one wall-clock budget (measured: 10.25s → 8.07s against
|
|
110
|
+
an unreachable endpoint); and concurrent probes share one in-flight check
|
|
111
|
+
however slow it is, instead of stacking a connection each.
|
|
112
|
+
|
|
113
|
+
**An embedding outage is no longer reported as "not in the record".** On a
|
|
114
|
+
record with a cosine floor, an unreachable provider means the floor cannot be
|
|
115
|
+
evaluated, so nothing may be served past it — but the abstention envelope tells
|
|
116
|
+
an agent the record does not cover the query and to say so without falling
|
|
117
|
+
back. For the whole outage the agent would assert the record lacks something it
|
|
118
|
+
contains. Searches now return a third outcome, `reason: "unavailable"` with
|
|
119
|
+
`abstained: false`, described in the tool text and the output schema alongside
|
|
120
|
+
`degraded_reason` (which had no description at all, and named a keyword search
|
|
121
|
+
that never ran).
|
|
122
|
+
|
|
123
|
+
**A new user is told how to start, and the instructions work.** The README's
|
|
124
|
+
only description of how to reach the agent surface was `pnpm serve # schema →
|
|
125
|
+
grant → ingest → serve` — a chain that no longer exists, so following it
|
|
126
|
+
literally serves an empty record. It is now the three deliberate steps
|
|
127
|
+
(`provision`, `refresh`, `serve`) with the reason they are separate, and
|
|
128
|
+
`ksor init`'s own handoff names the publish step it was skipping. The README
|
|
129
|
+
opens with a Start here section that gets you to a running site and then says
|
|
130
|
+
what to do next — open the project in your coding agent, which is the interface.
|
|
131
|
+
|
|
132
|
+
**`KSOR_DB_CONNECT_PER_REQUEST=1` closes each connection when its call
|
|
133
|
+
finishes.** Off by default, because the default measures better: a quiet server
|
|
134
|
+
already holds zero connections, and inside a burst the handshake is paid once
|
|
135
|
+
(2.58ms/call per-request against 0.13ms pooled on loopback; a remote TLS
|
|
136
|
+
endpoint widens it). The option is for the deployment where a pool is a fiction
|
|
137
|
+
— an external pooler sidecar, or a runtime that reuses no process between
|
|
138
|
+
invocations.
|
|
139
|
+
|
|
140
|
+
**Retrieval stems in the record's language.** `to_tsvector('english', …)` was
|
|
141
|
+
hardcoded in a STORED generated column and at four query sites, against the
|
|
142
|
+
claim that the owner writes "in any language they write in" — and on an
|
|
143
|
+
uncalibrated record the keyword arm is the only arm that gates.
|
|
144
|
+
`retrieval.text_search_config` is declared in `instance.md`, rendered into the
|
|
145
|
+
DDL the way the embedding dimension is, and parameterised (`$n::regconfig`) on
|
|
146
|
+
the query side. Because the column is STORED, changing it after a corpus exists
|
|
147
|
+
restems nothing, so a mismatch between the declared value and the one the
|
|
148
|
+
database was built with refuses at boot.
|
|
149
|
+
|
|
150
|
+
**The TLS posture is chosen, not inherited.** pg 8 resolves
|
|
151
|
+
`sslmode=require|prefer|verify-ca` to full verification, and the driver warns
|
|
152
|
+
that those adopt libpq semantics — no certificate verification — in pg 9. The
|
|
153
|
+
option is now passed explicitly for remote endpoints, so a dependency bump
|
|
154
|
+
cannot silently downgrade a deployment. Behaviour on pg 8 is unchanged; the
|
|
155
|
+
point is that it stays unchanged.
|
|
156
|
+
|
|
157
|
+
**`outline` carries `permalink`.** It was fetched by every retrieval query,
|
|
158
|
+
width-guarded, then dropped before the wire — so no citation could resolve to a
|
|
159
|
+
page a person can open.
|
|
160
|
+
|
|
161
|
+
**`read` takes `snapshot_token`, not `snapshot`.** `search` returns `snapshot`
|
|
162
|
+
as an object and `read` accepted `snapshot` as a string, so an agent copying the
|
|
163
|
+
field of that name from one into the field of that name in the other got an
|
|
164
|
+
input-validation error instead of a pinned read. Declaring the output schemas
|
|
165
|
+
turned an informal ambiguity into a validated contract that contradicted itself.
|
|
166
|
+
|
|
167
|
+
**A database that lost its `schema_meta` row is refused, not blamed on the
|
|
168
|
+
network.** The remedy was passed to an error whose constructor takes a class
|
|
169
|
+
name, so a multi-line fix printed inside "content store temporarily unavailable
|
|
170
|
+
(…)" and exited 3 — telling the operator to chase connectivity for a data
|
|
171
|
+
problem that will never fix itself.
|
|
172
|
+
|
|
173
|
+
**`ksor takedown --ledger` shows THIS record's acts.** It filtered by tenant
|
|
174
|
+
only while every governance write records the corpus, so a tenant serving two
|
|
175
|
+
records saw one audit trail polluted with the other's.
|
|
176
|
+
|
|
177
|
+
**`outline` frames its text as untrusted, like the other two tools.** Titles and
|
|
178
|
+
heading paths are corpus-authored and reach the agent exactly as passage content
|
|
179
|
+
does; `search` and `read` both said so and flagged directive-shaped payloads,
|
|
180
|
+
and `outline` did neither.
|
|
181
|
+
|
|
182
|
+
**`pnpm setup` never ran your setup.** The scaffold shipped a script named
|
|
183
|
+
`setup` and three documents told the adopter to run it — but `pnpm setup` is
|
|
184
|
+
pnpm's own installer, and it wins. The documented step printed "No changes to
|
|
185
|
+
the environment were made", exited 0, applied no DDL, and the next command
|
|
186
|
+
failed with `relation "corpora" does not exist`, blaming the database for a step
|
|
187
|
+
that never ran. The script is now `pnpm provision`, and a test rejects any
|
|
188
|
+
scaffold script named after a pnpm command.
|
|
189
|
+
|
|
190
|
+
**A takedown that the site cannot honour says so.** A scaffold is adopter-owned,
|
|
191
|
+
so upgrading the CLI does not touch their `system/site` or their `package.json`.
|
|
192
|
+
A project scaffolded before the denylist manifest existed has neither the build
|
|
193
|
+
step that exports it nor the staging code that reads it — so a takedown was
|
|
194
|
+
imposed, the CLI's own remedy was followed exactly, the site rebuilt, and the
|
|
195
|
+
withdrawn document was still published while the MCP door refused it. `--export`
|
|
196
|
+
now detects both halves and prints the exact edit for each.
|
|
197
|
+
|
|
198
|
+
**A record with nothing published no longer answers "not in the record".**
|
|
199
|
+
Following `ksor init`'s printed next-steps reaches a provisioned but never
|
|
200
|
+
ingested record, where every question got `abstained` — an assertion about
|
|
201
|
+
coverage for a record that is simply empty. That is now
|
|
202
|
+
`reason: "unpublished"`.
|
|
203
|
+
|
|
204
|
+
**A door whose boot checks have not passed refuses requests.** Reporting
|
|
205
|
+
not-ready keeps a platform from routing traffic; it does not stop anything that
|
|
206
|
+
reaches the port. A gateway that started against an unreachable database and
|
|
207
|
+
recovered moments later answered `{"ready":false}` and still served a
|
|
208
|
+
`visibility: internal` document to a direct request. The schema and governance
|
|
209
|
+
checks are one deferred set, retried together, and they gate every request with
|
|
210
|
+
a 503 that names the remedy.
|
|
211
|
+
|
|
212
|
+
**`ksor ingest` refuses to publish what `ksor serve` cannot serve.** It exited 0
|
|
213
|
+
on a generation the door then refused to boot on, so the deploy step was green
|
|
214
|
+
and the container crash-looped.
|
|
215
|
+
|
|
216
|
+
**The MCP discovery document is valid.** `/.well-known/mcp/server.json` failed
|
|
217
|
+
the published schema on four counts at once — no `version` (required), a `name`
|
|
218
|
+
without the required `namespace/identifier` shape, no `$schema`, and a
|
|
219
|
+
`capabilities` field the schema does not define. `instance.md` gains `version:`
|
|
220
|
+
alongside `mcp_url:` to feed it.
|
|
221
|
+
|
|
222
|
+
**`outline` pages.** It truncated at `limit` with no way to continue and did not
|
|
223
|
+
mention `limit` or `has_more` in its description, so an agent read a partial
|
|
224
|
+
list as the complete record. It now takes `offset` and returns `next_offset`.
|
|
225
|
+
|
|
226
|
+
**A cold burst is no longer mistaken for an overloaded pool.** pg-pool counts a
|
|
227
|
+
socket that is still completing its handshake as a full slot, so a burst of
|
|
228
|
+
requests arriving at a waking database looked like saturation and was shed
|
|
229
|
+
permanently — with identical requests getting opposite verdicts depending on
|
|
230
|
+
arrival order. Saturation is now measured by connections that actually
|
|
231
|
+
connected.
|
|
232
|
+
|
|
233
|
+
Also: every envelope now reports the abstention `gate` and the measured
|
|
234
|
+
`top_cosine`, so `ok=true` from an uncalibrated record can no longer be read as
|
|
235
|
+
coverage; the MCP server states four framework rules in its instructions instead
|
|
236
|
+
of serving the unedited scaffold placeholder; `ingest` records the git commit it
|
|
237
|
+
ingested instead of the literal string `unspecified`; `pnpm provision`
|
|
238
|
+
separates applying DDL and granting ingest from starting a server, and
|
|
239
|
+
`pnpm refresh` (ingest then gc) collects retired generations; the scaffold ships the `database:` block its own
|
|
240
|
+
runbook requires, and `env.example` documents the production variables the code
|
|
241
|
+
actually reads; shutdown logs and has a deadline; pool sizing and the TLS posture
|
|
242
|
+
are chosen rather than inherited; `ksor takedown --export` reads through the
|
|
243
|
+
runtime role rather than the ingest role, so a site build host no longer needs
|
|
244
|
+
write access to the record; and `KSOR_DRAIN_TIMEOUT_MS` is read when the server
|
|
245
|
+
starts rather than when the module loads, which is what made it inert in `.env`; `gate: "uncalibrated"` is gone from the
|
|
246
|
+
tool description and output schema, because that state throws rather than
|
|
247
|
+
reaching the wire; and the docs name every verb the binary has, with a drift
|
|
248
|
+
test that fails when they stop matching.
|
|
249
|
+
|
|
250
|
+
- bfacb31: `ksor init` now names `pnpm serve` in its next-steps output, and the docs stop
|
|
251
|
+
describing an auth-off default that never existed.
|
|
252
|
+
|
|
253
|
+
The handoff printed after scaffolding listed `pnpm install` and `pnpm dev`, so
|
|
254
|
+
the agent projection — the core surface of every KSoR — went unnamed at the one
|
|
255
|
+
moment the adopter is actually reading the screen.
|
|
256
|
+
|
|
257
|
+
Separately, decision 7's serving clause and the three docs that copied it said a
|
|
258
|
+
local `serve` "binds loopback with auth off". `buildAuth` has never had that
|
|
259
|
+
default: it refuses to boot unless SSO is configured or `KSOR_AUTH_DISABLED=1`
|
|
260
|
+
is set explicitly, loopback included. The real posture is stronger than the
|
|
261
|
+
sentence claimed, but the docs were telling adopters a local `serve` would come
|
|
262
|
+
up without the flag it requires.
|
|
263
|
+
|
|
264
|
+
## 0.0.7
|
|
265
|
+
|
|
266
|
+
### Patch Changes
|
|
267
|
+
|
|
268
|
+
- fcd44db: feat: restarting an unedited record is free. `ksor serve` runs ingest on every
|
|
269
|
+
start, and ingest now compares the corpus it just read against the generation
|
|
270
|
+
already serving — identical content at the same source commit consumes no
|
|
271
|
+
generation, writes no rows, and embeds nothing ("unchanged — generation N
|
|
272
|
+
already serves this corpus"). Editing a document still builds a generation and
|
|
273
|
+
re-embeds only what changed, and a new source commit over identical bytes still
|
|
274
|
+
records one, because that is a build fact provenance must keep.
|
|
275
|
+
|
|
3
276
|
## 0.0.6
|
|
4
277
|
|
|
5
278
|
### Patch Changes
|