@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 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