@panaversity/ksor 0.0.7 → 0.0.9

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